fix(onboarding): skip the achievements tour stop when the module is off

The dashboard tour launcher now derives includeAchievements from the
per-user module map (modules?.achievements !== false), so an account
that has the achievements module turned off no longer walks the user to
a page it has hidden.

Mention the module on/off concept in the integrations tour stop, where
the tour introduces Settings and customization, so a first-time user
learns whole areas can be turned on or off.
This commit is contained in:
Marc-André Bombeck committed 2026-06-16 02:04:20 +02:00
1 parent f72d0b0ef3
commit ca196f27a6
3 files changed
+43 -1

No files matched your search

+1 -1
View File
@@ -3687,7 +3687,7 @@
},
"integrations": {
"title": "Connect your devices",
"body": "Open Settings to link Withings (auto-sync weight, BP and pulse), moodLog, or your Insights provider. Everything stays optional and encrypted."
"body": "Open Settings to link Withings (auto-sync weight, BP and pulse), moodLog, or your Insights provider. Everything stays optional and encrypted — and under Settings → Modules you can turn whole areas like sleep, mood or achievements on or off to keep HealthLog focused on what you track."
},
"achievements": {
"title": "Earn streaks and badges",
@@ -21,6 +21,7 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
setTourForceLaunch,
shouldAutoLaunchTour,
tourIncludesAchievements,
TOUR_AUTOLAUNCH_DELAY_MS,
tourForceLaunchKey,
} from "../tour-launcher";
@@ -132,6 +133,27 @@ describe("shouldAutoLaunchTour() — v1.4.47 W5 chain-gate", () => {
});
});
describe("tourIncludesAchievements() — v1.18.0 B5 module gate", () => {
it("includes the achievements stop by default (no module map)", () => {
expect(tourIncludesAchievements(undefined)).toBe(true);
expect(tourIncludesAchievements({})).toBe(true);
});
it("includes the achievements stop when the module is explicitly on", () => {
expect(tourIncludesAchievements({ achievements: true })).toBe(true);
});
it("drops the achievements stop when the module is explicitly off", () => {
expect(tourIncludesAchievements({ achievements: false })).toBe(false);
});
it("ignores other disabled modules — only the achievements key matters", () => {
expect(
tourIncludesAchievements({ sleep: false, mood: false }),
).toBe(true);
});
});
describe("setTourForceLaunch() — v1.4.47 W5 manual replay bypass", () => {
// (c) of the W5 acceptance: the manual "launch tour" trigger still
// works. The launcher's render-phase gate is exercised by
@@ -101,6 +101,19 @@ export function shouldAutoLaunchTour(args: {
return args.nowMs - completedMs >= TOUR_AUTOLAUNCH_DELAY_MS;
}
/**
* v1.18.0 B5 — whether the achievements tour stop should be included.
* Reads the per-user module map with the standard client default-on
* convention (`modules?.achievements !== false`): only an explicit
* `false` drops the stop. Pure + exported so the rule is unit-pinned
* without rendering the launcher.
*/
export function tourIncludesAchievements(
modules: Partial<Record<string, boolean>> | undefined,
): boolean {
return modules?.achievements !== false;
}
// v1.4.15 H4 — sessionStorage keys are now scoped by user id so an
// admin impersonating a second user does not inherit the first user's
// "tour dismissed for this session" state. The previous global keys
@@ -348,8 +361,15 @@ export function TourLauncher({ ready }: TourLauncherProps) {
if (showTour !== true) return null;
// v1.18.0 B5 — drop the achievements tour stop when the account has the
// achievements module turned off. Reads the per-user module map the same
// way every other client gate does (`modules?.<key> !== false`,
// default-on).
const includeAchievements = tourIncludesAchievements(user?.modules);
return (
<OnboardingTour
includeAchievements={includeAchievements}
onClose={async (outcome) => {
// Optimistic: hide the overlay immediately, persist in the
// background. The flag is idempotent so re-fires are safe;