diff --git a/src/components/onboarding/__tests__/confirm-screen.test.tsx b/src/components/onboarding/__tests__/confirm-screen.test.tsx index bef1dc2ae..274a05b74 100644 --- a/src/components/onboarding/__tests__/confirm-screen.test.tsx +++ b/src/components/onboarding/__tests__/confirm-screen.test.tsx @@ -1,6 +1,12 @@ /** * v1.39 (Wave C) — what the confirm screen is allowed to claim. * + * C5 (research M-l): both of the screen's own Settings links are page routes, + * and `hl_onboarding=pending` is still set while this screen is on — the + * completion is what clears it, and this screen is what calls the completion. + * The proxy sends both straight back to the flow, so on a first run they are + * dead links under a sentence that is not yet true. + * * 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 @@ -84,3 +90,20 @@ describe(" — modules leaving the navigation (C4)", () => { expect(html).not.toContain('data-slot="onboarding-confirm-modules-off"'); }); }); + +describe(" — Settings links on a first run (C5)", () => { + it("renders both as plain text while the completion has not run", () => { + const html = render(state()); + expect(html).not.toContain('href="/settings/modules"'); + expect(html).not.toContain('href="/settings/account"'); + // The words stay: they say where the thing lives, which is true. + expect(html).toContain("Settings, Modules"); + expect(html).toContain("Settings, Account"); + }); + + it("renders them as links once the flow has been completed once", () => { + const html = render(state({ completedAt: "2026-09-10T08:00:00.000Z" }), {}); + expect(html).toContain('href="/settings/modules"'); + expect(html).toContain('href="/settings/account"'); + }); +}); diff --git a/src/components/onboarding/confirm-screen.tsx b/src/components/onboarding/confirm-screen.tsx index afee75e5d..dae55b0c8 100644 --- a/src/components/onboarding/confirm-screen.tsx +++ b/src/components/onboarding/confirm-screen.tsx @@ -27,6 +27,7 @@ import { /** A step the confirm screen may pass on the ledger without an answer. */ type PassableStepId = (typeof ONBOARDING_SKIPPABLE_STEP_IDS)[number]; +import { isOnboardingSettled } from "@/lib/onboarding/needs"; import { firstResultApplies, nextScreen, @@ -111,6 +112,17 @@ export function ConfirmScreen({ state }: { state: OnboardingStateDto }) { const ledger = useRef(state); const needs = state.needs; + /** + * v1.39 (Wave C, C5) — is `hl_onboarding=pending` still set? (research M-l) + * + * Both of this screen's Settings links are page routes, and the cookie is + * cleared by the completion — which is what THIS screen calls. So on a + * first run the proxy 307s both straight back into the flow: two links + * that cannot go anywhere, under a sentence saying where things live. The + * words are true, the links are not, so only the links go. + */ + const settingsReachable = + isOnboardingSettled(state) || state.completedAt !== null; const forSomeoneElse = needs.recordTarget === "someone-else"; // 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. @@ -201,12 +213,18 @@ export function ConfirmScreen({ state }: { state: OnboardingStateDto }) {

{t("onboarding.flow.confirm.everythingElse")}{" "} - - {t("onboarding.flow.confirm.modulesLink")} - + {settingsReachable ? ( + + {t("onboarding.flow.confirm.modulesLink")} + + ) : ( + + {t("onboarding.flow.confirm.modulesLink")} + + )}

@@ -241,12 +259,18 @@ export function ConfirmScreen({ state }: { state: OnboardingStateDto }) { ) : null}

- - {t("onboarding.flow.confirm.changeInSettings")} - + {settingsReachable ? ( + + {t("onboarding.flow.confirm.changeInSettings")} + + ) : ( + + {t("onboarding.flow.confirm.changeInSettings")} + + )}

{forSomeoneElse ? (