fix(onboarding): confirm names the modules leaving the nav

The screen listed what the answers switch on and said everything else
stays one click away under Settings, Modules. That is true of
reachability and false of the navigation entry: a module the answers do
not ask for is written off by the completion's derivation.

It now names those modules, read through the registry's own merge rule
and intersected with the map the navigation shows today, so nothing is
claimed about a module the account payload says nothing about.
This commit is contained in:
Marc-André Bombeck committed 2026-09-11 09:24:36 +02:00
1 parent 77a09a5080
commit e9e4a4b44d
11 files changed
+201 -2

No files matched your search

@@ -0,0 +1,86 @@
/**
* v1.39 (Wave C) — what the confirm screen is allowed to claim.
*
* C4 (research I6): the screen listed what the answers switch ON and said
* everything else "stays one click away under Settings, Modules" — true of
* reachability, false of the navigation entry. A module the answers do not
* ask for leaves the navigation, and nothing said so.
*
* Rendered server-side, this repo's component-test convention.
*/
import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, it, vi } from "vitest";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { I18nProvider } from "@/lib/i18n/context";
import type { AuthUser } from "@/hooks/use-auth";
import {
defaultOnboardingSteps,
emptyOnboardingNeeds,
type OnboardingStateDto,
} from "@/lib/onboarding/needs";
let currentUser: AuthUser | null = null;
vi.mock("next/navigation", () => ({
useRouter: () => ({ push: vi.fn(), replace: vi.fn() }),
}));
vi.mock("@/hooks/use-auth", () => ({
useAuth: () => ({ user: currentUser }),
useAccountOnceMounted: () => currentUser,
}));
const { ConfirmScreen } = await import("../confirm-screen");
function state(
overrides: Partial<OnboardingStateDto> = {},
needs: Partial<OnboardingStateDto["needs"]> = {},
): OnboardingStateDto {
return {
steps: defaultOnboardingSteps(),
needs: { ...emptyOnboardingNeeds(), recordTarget: "me", ...needs },
completedAt: null,
firstResult: null,
...overrides,
};
}
function render(
flow: OnboardingStateDto,
modules: Partial<Record<string, boolean>> = {},
): string {
currentUser = {
id: "u1",
heightCm: null,
dateOfBirth: null,
gender: null,
timezone: "Europe/Berlin",
unitPreference: "metric",
modules,
} as unknown as AuthUser;
return renderToStaticMarkup(
<QueryClientProvider client={new QueryClient()}>
<I18nProvider initialLocale="en">
<ConfirmScreen state={flow} />
</I18nProvider>
</QueryClientProvider>,
);
}
describe("<ConfirmScreen> — modules leaving the navigation (C4)", () => {
it("names them when the answers do not ask for what the record shows", () => {
const html = render(state({}, { areas: ["sleep"], medication: "no" }), {
medications: true,
});
expect(html).toContain('data-slot="onboarding-confirm-modules-off"');
expect(html).toContain("Medications");
});
it("says nothing when the answers take nothing away", () => {
const html = render(state({}, { areas: ["sleep"], medication: "yes" }), {
medications: true,
});
expect(html).not.toContain('data-slot="onboarding-confirm-modules-off"');
});
});
+13 -1
View File
@@ -112,7 +112,12 @@ export function ConfirmScreen({ state }: { state: OnboardingStateDto }) {
const needs = state.needs;
const forSomeoneElse = needs.recordTarget === "someone-else";
const { chosen, alwaysOn } = confirmedModules(needs);
// v1.39 (Wave C, C4) — `user.modules` is the map the navigation reads, so
// the screen can only name a module the person actually has today.
const { chosen, alwaysOn, wouldSwitchOff } = confirmedModules(
needs,
user?.modules ?? {},
);
const back = previousScreen(state, "confirm");
const glucoseUnit = needs.units.glucoseUnit ?? user?.glucoseUnit ?? null;
@@ -182,6 +187,13 @@ export function ConfirmScreen({ state }: { state: OnboardingStateDto }) {
})
: t("onboarding.flow.confirm.nothingExtra")}
</p>
{wouldSwitchOff.length > 0 ? (
<p className="text-sm" data-slot="onboarding-confirm-modules-off">
{t("onboarding.flow.confirm.switchesOff", {
modules: moduleNames(wouldSwitchOff),
})}
</p>
) : null}
<p className="text-muted-foreground text-sm">
{t("onboarding.flow.confirm.alwaysOn", {
modules: moduleNames(alwaysOn),