diff --git a/src/components/onboarding/__tests__/done-screen.test.tsx b/src/components/onboarding/__tests__/done-screen.test.tsx index f3010a24c..c03b2d545 100644 --- a/src/components/onboarding/__tests__/done-screen.test.tsx +++ b/src/components/onboarding/__tests__/done-screen.test.tsx @@ -262,6 +262,74 @@ describe(" shared-provider offer", () => { expect(html).not.toContain('data-slot="onboarding-ai-unavailable"'); }); + /** + * The whole decision table, in one place. + * + * Three arms the screen can be in — the offer is available, the offer is + * withheld for a reason that is not health (the assistant surfaces switched + * off, a managed profile), a receipt is already on file — crossed with the + * three health states. The point of the cross is that HEALTH decides the + * claim in every arm: the consent arm used to promise "insights work for + * you right now" without reading health at all, which told somebody who had + * consented that a dead provider was working. That is the same unmeasured + * claim as the `unknown` case at the other end of the panel. + */ + const TABLE = [ + { arm: "offerable", consent: false, health: "healthy", state: "offer" }, + { + arm: "offerable", + consent: false, + health: "unhealthy", + state: "unavailable", + }, + { arm: "offerable", consent: false, health: "unknown", state: "neutral" }, + { arm: "withheld", consent: false, health: "healthy", state: "neutral" }, + { + arm: "withheld", + consent: false, + health: "unhealthy", + state: "unavailable", + }, + { arm: "withheld", consent: false, health: "unknown", state: "neutral" }, + { arm: "consented", consent: true, health: "healthy", state: "consent" }, + { + arm: "consented", + consent: true, + health: "unhealthy", + state: "unavailable", + }, + { arm: "consented", consent: true, health: "unknown", state: "neutral" }, + ] as const; + + it.each(TABLE)( + "settles on $state when the offer is $arm and the provider is $health", + ({ arm, consent, health, state }) => { + aiProviderState.data = { + managedBy: "server", + serverProviderHealth: health, + // The server only ever offers on `healthy`, and never with a receipt + // on file; the withheld arm is the flags-off / managed-profile case. + serverProviderOffer: arm === "offerable" && health === "healthy", + serverProviderConsent: consent, + }; + const html = render(); + expect(html).toContain(`data-ai-state="${state}"`); + // The positive sentence appears in exactly one of the nine cells. + expect(html.includes('data-slot="onboarding-ai-shared-key"')).toBe( + state === "consent", + ); + expect(html.includes('data-slot="onboarding-ai-unavailable"')).toBe( + state === "unavailable", + ); + expect(html.includes('data-slot="onboarding-ai-offer-grant"')).toBe( + state === "offer", + ); + // The rest of the panel is untouched in every cell. + expect(html).toContain('data-slot="onboarding-ai-keyless"'); + expect(html).toContain('href="/settings/ai"'); + }, + ); + it("grants through the affirmative web intent — the only path that may lift a revocation", async () => { mutations.length = 0; apiFetchRaw.mockReset(); diff --git a/src/components/onboarding/done-screen.tsx b/src/components/onboarding/done-screen.tsx index 6bbdad6ad..af4636436 100644 --- a/src/components/onboarding/done-screen.tsx +++ b/src/components/onboarding/done-screen.tsx @@ -169,12 +169,22 @@ export function DoneScreen({ state }: { state: OnboardingStateDto }) { }); const operatorProvides = aiProvider?.managedBy === "server"; + const sharedHealth = aiProvider?.serverProviderHealth; const consentOnFile = justGranted || aiProvider?.serverProviderConsent === true; const offerShared = operatorProvides && !consentOnFile && aiProvider?.serverProviderOffer === true; + // A receipt on file is a fact about the ACCOUNT; it says nothing about + // whether the provider works. This line used to promise "insights work for + // you right now" on the strength of the receipt alone, so somebody who had + // just consented was told a dead provider was serving them — the same + // unmeasured claim as the offer's, made at the other end of the panel. The + // positive sentence is now earned by health as well, and a consented + // account on a broken provider reads the same honest note as anybody else. + const sharedKeyNote = + operatorProvides && consentOnFile && sharedHealth === "healthy"; // Only a health verdict somebody EARNED paints the "not answering" card — // `unhealthy`, which means the provider was tried and the last thing that // happened was a failure. `unknown` is not that verdict: it is an empty @@ -187,9 +197,9 @@ export function DoneScreen({ state }: { state: OnboardingStateDto }) { // nobody measured. const sharedUnavailable = operatorProvides && - !consentOnFile && + !sharedKeyNote && !offerShared && - aiProvider?.serverProviderHealth === "unhealthy"; + sharedHealth === "unhealthy"; return (
)} - {consentOnFile && operatorProvides ? ( + {sharedKeyNote ? (