From a6dedffdb8eb3dae904b494652c2f372f9b1258e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 11 Sep 2026 09:25:28 +0200 Subject: [PATCH] fix(onboarding): no dead settings links before completion Both Settings links on the confirm screen are page routes, and the onboarding cookie that sends every page route back into the flow is cleared by the completion this very screen calls. On a first run they could not go anywhere. They now render as plain text until the flow has been completed once. The words stay, because where a setting lives is true either way. --- .../__tests__/confirm-screen.test.tsx | 23 +++++++++ src/components/onboarding/confirm-screen.tsx | 48 ++++++++++++++----- 2 files changed, 59 insertions(+), 12 deletions(-) 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 ? (