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 (