diff --git a/src/app/auth/register/__tests__/page.test.tsx b/src/app/auth/register/__tests__/page.test.tsx index 47272db5a..1ef5d0564 100644 --- a/src/app/auth/register/__tests__/page.test.tsx +++ b/src/app/auth/register/__tests__/page.test.tsx @@ -11,9 +11,8 @@ * the page rendered. The sign-out click itself is driven in * `e2e/invite-registration.spec.ts`. * - * Mutation check, run: drop the `isAuthenticated` branch → the two signed-in - * cases go red naming the form inputs they found, the anonymous case stays - * green. + * Mutation check, run: drop the signed-in branch → the two signed-in cases go + * red naming the form inputs they found, the anonymous case stays green. */ import { describe, expect, it, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; @@ -112,4 +111,18 @@ describe("register page — an invitation cannot be accepted from a live session const markup = render(); expect(markup).not.toContain('data-testid="register-already-signed-in"'); }); + + it("renders the form when the auth probe failed and only a stale marker says otherwise", () => { + // `useAuth` deliberately holds the LAST-KNOWN state when `/api/auth/me` + // fails at the transport level or with a 5xx, so the shell does not sign + // people out on a blip. There is no account payload on that path, so the + // panel would name nobody ("You are signed in as .") and offer a sign-out + // that posts to the endpoint the same failure is affecting. An invited + // household member on a flaky connection could not register at all. + authRef.value = { user: null, isAuthenticated: true, isLoading: false }; + inviteRef.value = TOKEN; + const markup = render(); + expect(markup).not.toContain('data-testid="register-already-signed-in"'); + expect(markup).toContain('id="username"'); + }); }); diff --git a/src/app/auth/register/page.tsx b/src/app/auth/register/page.tsx index b47fe23ea..8b1bc9c34 100644 --- a/src/app/auth/register/page.tsx +++ b/src/app/auth/register/page.tsx @@ -27,11 +27,19 @@ export default function RegisterPage() { // v1.38.19 — an invitation creates a NEW account, so it cannot be accepted // from inside a live session; `POST /api/auth/register` refuses one with // 409 `already_authenticated`. Rendering the form anyway would let the - // visitor discover that by filling it in. `isLoading` holds the panel back - // until the session question is actually answered — failing closed the - // other way round would flash "you are already signed in" at somebody who - // is not. - const { user, isAuthenticated, isLoading: authLoading } = useAuth(); + // visitor discover that by filling it in. + // + // The gate is the resolved account payload, not `isAuthenticated`. That + // flag deliberately holds the LAST-KNOWN state off a localStorage marker + // whenever `/api/auth/me` fails at the transport level or with a 5xx, so + // the shell does not sign people out on a blip — and on that path there is + // no `user`. Gating on it would show an invited household member behind a + // 502 a panel that names nobody and offers a sign-out posting to the very + // endpoint that is failing, with no way to register. A payload in hand is + // the only positive evidence of a session, and it is also absent while the + // probe is still in flight, so the panel cannot flash at somebody who is + // not signed in. + const { user } = useAuth(); const [signingOut, setSigningOut] = useState(false); const [email, setEmail] = useState(""); const [username, setUsername] = useState(""); @@ -104,7 +112,7 @@ export default function RegisterPage() { } } - if (isAuthenticated && !authLoading) { + if (user) { return (