From 7d3a6e695dd96c53c8a481eb1e78232b5ffbc4c8 Mon Sep 17 00:00:00 2001 From: Indiana Date: Wed, 29 Jul 2026 07:01:27 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20open=20the=20door=20=E2=80=94=20no=20si?= =?UTF-8?q?gn-in=20wall,=20ever?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Point and shoot. SeancePage no longer redirects a stranger to /enter; it silently provisions a wanderer (POST /auth/guest, a real account) and drops them straight into the séance. The landing CTA goes to /seance unconditionally. Registering is now what it should always have been: how you *keep* a codex and unlock device pairing — not the toll to get in. Details that matter: - One auto-attempt only, guarded by a ref: survives StrictMode's double effect, and a failure (rate limit, offline) doesn't retry forever. - The loading veil covers provisioning, so there's no flash of an empty séance while the account is created. - If provisioning genuinely fails, it falls back to /enter rather than a blank screen — the manual door still exists. - A returning visitor already holds a cookie, so this never fires for them. App.test's "sends anonymous visitors to /enter" assertion was inverted to assert the open door instead — that test encoded the wall we just removed. Verified live: POST /auth/guest returns wanderer-a6f1 and that cookie authenticates on /auth/me. 366 frontend tests pass; i18n parity holds. Co-Authored-By: Claude Opus 5 --- frontend/src/App.test.tsx | 7 +++++-- frontend/src/pages/LandingPage.tsx | 6 +++++- frontend/src/pages/SeancePage.tsx | 25 +++++++++++++++++++++++-- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index 1e3fea7..9026a81 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -52,13 +52,16 @@ describe('App', () => { ) }) - it('sends anonymous visitors to /enter from the séance CTA', async () => { + it('takes anonymous visitors straight to the séance, with no sign-in wall', async () => { + // The open door: a stranger must reach the séance itself. SeancePage + // provisions a wanderer for them on arrival; /enter is an upgrade path + // (keep your codex, pair a device), never a gate. render() await waitFor(() => expect(screen.getAllByText('QUANTUMANCY').length).toBeGreaterThan(0), ) const cta = screen.getByRole('button', { name: /begin the séance/i }) await userEvent.click(cta) - await waitFor(() => expect(window.location.pathname).toBe('/enter')) + await waitFor(() => expect(window.location.pathname).toBe('/seance')) }) }) diff --git a/frontend/src/pages/LandingPage.tsx b/frontend/src/pages/LandingPage.tsx index f4d7fea..7c3e874 100644 --- a/frontend/src/pages/LandingPage.tsx +++ b/frontend/src/pages/LandingPage.tsx @@ -274,7 +274,11 @@ export function LandingPage() { diff --git a/frontend/src/pages/SeancePage.tsx b/frontend/src/pages/SeancePage.tsx index dcda571..a7384df 100644 --- a/frontend/src/pages/SeancePage.tsx +++ b/frontend/src/pages/SeancePage.tsx @@ -69,10 +69,28 @@ const TOAST_TTL_MS = 6000 const SDR_SAMPLE_RATE_HZ = 2_048_000 export function SeancePage() { - const { user, checking } = useAuth() + const { user, checking, guest } = useAuth() const { t } = useTranslation() + const [guestFailed, setGuestFailed] = useState(false) + // One auto-attempt only. Guards React StrictMode's double-effect and + // stops a failed attempt (rate limit / offline) from retrying forever. + const guestAttempted = useRef(false) - if (checking) { + // The open door. There is no sign-in wall: if nobody is authenticated + // once the session check settles, silently provision a wanderer — a real + // guest account (POST /auth/guest) — and drop straight into the séance. + // Point and shoot. Registering later is how a seeker *keeps* what they + // find and unlocks device pairing; it was never meant to be the toll to + // get in. A returning visitor already has a cookie, so `user` is set and + // this never fires for them. + useEffect(() => { + if (checking || user || guestAttempted.current) return + guestAttempted.current = true + void guest().catch(() => setGuestFailed(true)) + }, [checking, user, guest]) + + // Still resolving a session, or provisioning the wanderer: hold the veil. + if (checking || (!user && !guestFailed)) { return (
@@ -82,6 +100,9 @@ export function SeancePage() {
) } + + // Auto-provision failed (the veil is genuinely crowded, or offline). Fall + // back to the manual door rather than a blank screen or a crash. if (!user) return return (