feat: open the door — no sign-in wall, ever
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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(<App />)
|
||||
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'))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -274,7 +274,11 @@ export function LandingPage() {
|
||||
<button
|
||||
type="button"
|
||||
className="cta-primary"
|
||||
onClick={() => navigate(user ? '/seance' : '/enter')}
|
||||
// Straight to the séance, always. A stranger is provisioned a
|
||||
// wanderer on arrival (see SeancePage) rather than being sent
|
||||
// to a sign-in wall first — registering is how you *keep* a
|
||||
// codex and unlock device pairing, not the toll to get in.
|
||||
onClick={() => navigate('/seance')}
|
||||
>
|
||||
{t('landing.cta')}
|
||||
</button>
|
||||
|
||||
@@ -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 (
|
||||
<div className="seance-loading" role="status">
|
||||
<div className="loading-sigil" aria-hidden>
|
||||
@@ -82,6 +100,9 @@ export function SeancePage() {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 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 <Navigate to="/enter" replace />
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user