docs(ui): sync docs to the app-shell; fix stale nav comments
Tests · UI / test (push) Failing after 9m28s
Tests · UI / test (push) Failing after 9m28s
Rewrite ui/docs (navigation, order-composer, auth-flow, pwa-strategy, game-state + secondary topic docs) and ui/README for the single-URL app-shell (in-memory screens/views, Back→lobby via shallow routing, sessionStorage restore + validation, return-to-lobby). ui/PLAN.md gets a Phase-10 supersede note (implemented; standalone-compatible). Fix stale code comments (session-store auth gate, report-sections spec contract). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+29
-20
@@ -18,10 +18,15 @@ authoritative in [`docs/FUNCTIONAL.md` §1](../../docs/FUNCTIONAL.md).
|
||||
- `ui/frontend/src/lib/revocation-watcher.ts` — minimal
|
||||
`SubscribeEvents` watcher that triggers `signOut("revoked")` on
|
||||
any non-aborted stream termination.
|
||||
- `ui/frontend/src/routes/login/+page.svelte` — two-step form.
|
||||
- `ui/frontend/src/routes/lobby/+page.svelte` — placeholder lobby
|
||||
that issues the first authenticated `user.account.get`.
|
||||
- `ui/frontend/src/routes/+layout.svelte` — route guard plus the
|
||||
- `ui/frontend/src/lib/screens/login-screen.svelte` — two-step form.
|
||||
- `ui/frontend/src/lib/screens/lobby-screen.svelte` — lobby that
|
||||
issues the first authenticated `user.account.get`.
|
||||
- `ui/frontend/src/routes/+page.svelte` — the state-based auth gate /
|
||||
screen dispatcher (anonymous → login, authenticated → the
|
||||
`appScreen` screen). The single-URL app-shell has no per-screen
|
||||
routes; see [`navigation.md`](navigation.md).
|
||||
- `ui/frontend/src/routes/+layout.svelte` — boot-time session init,
|
||||
the `loading` / `unsupported` interception, and the
|
||||
browser-not-supported blocker.
|
||||
|
||||
## State machine (`SessionStatus`)
|
||||
@@ -50,8 +55,9 @@ authoritative in [`docs/FUNCTIONAL.md` §1](../../docs/FUNCTIONAL.md).
|
||||
```
|
||||
|
||||
`signOut("revoked")` shares the same observable end state as
|
||||
`signOut("user")`; the reason exists only for telemetry. Both
|
||||
trigger the layout effect's `anonymous → /login` redirect.
|
||||
`signOut("user")`; the reason exists only for telemetry. Both settle
|
||||
`status` to `anonymous`, which the dispatcher renders as the login
|
||||
screen — there is no URL redirect (the app-shell stays at `/game/`).
|
||||
|
||||
## UX states and error mapping
|
||||
|
||||
@@ -67,7 +73,7 @@ those branches.
|
||||
| 200 from `send-email-code` | advance to step `code`, focus the code input |
|
||||
| `invalid_request` from `send` | stay on step `email`, surface the gateway message |
|
||||
| `service_unavailable` from `send` | stay on step `email`, surface "service is temporarily unavailable" |
|
||||
| 200 from `confirm-email-code` | persist `device_session_id`, redirect to `/lobby` |
|
||||
| 200 from `confirm-email-code` | persist `device_session_id`, settle `status` to `authenticated` (dispatcher shows the lobby) |
|
||||
| `invalid_request` from `confirm` | bounce to step `email`, message: "code expired or already used" |
|
||||
| any other error from `confirm` | stay on step `code`, surface the gateway message |
|
||||
|
||||
@@ -89,8 +95,10 @@ After `confirm-email-code` succeeds, `session.signIn` writes the
|
||||
`device_session_id` into the IDB cache (`namespace=session`,
|
||||
`key=device-session-id`). On the next page load,
|
||||
`SessionStore.init` reads it back and settles `status` to
|
||||
`authenticated`, so the layout effect routes the user straight to
|
||||
`/lobby`.
|
||||
`authenticated`, so the dispatcher renders the authenticated screen
|
||||
straight away. Which authenticated screen it is comes from the
|
||||
restored `appScreen` snapshot (lobby by default; see
|
||||
[`navigation.md`](navigation.md)), not from the URL.
|
||||
|
||||
The keypair lives next to the id in the same database (object
|
||||
store `keypair`, key `device`). Clearing site data wipes both;
|
||||
@@ -102,21 +110,22 @@ again. This is the documented re-login path — there is no paired
|
||||
|
||||
The keystore relies on WebCrypto Ed25519, which currently lands in
|
||||
Chrome ≥ 137, Firefox ≥ 130, Safari ≥ 17.4 (see
|
||||
[`storage.md`](storage.md) for the rationale). On boot the layout
|
||||
runs a sanity probe (`crypto.subtle.generateKey` for `Ed25519`); if
|
||||
it rejects, the layout switches to a `browser not supported` page
|
||||
instead of rendering `/login`. The client deliberately does not ship a
|
||||
JavaScript Ed25519 fallback — the design decision is modern-browser
|
||||
baseline only.
|
||||
[`storage.md`](storage.md) for the rationale). On boot the root
|
||||
layout runs a sanity probe (`crypto.subtle.generateKey` for
|
||||
`Ed25519`); if it rejects, `status` settles to `unsupported` and the
|
||||
layout renders a `browser not supported` page instead of the login
|
||||
screen. The client deliberately does not ship a JavaScript Ed25519
|
||||
fallback — the design decision is modern-browser baseline only.
|
||||
|
||||
## Revocation
|
||||
|
||||
The lobby layout opens a long-running `SubscribeEvents` stream as
|
||||
The root layout opens a long-running `SubscribeEvents` stream as
|
||||
soon as `status` becomes `authenticated`. Its only contract is
|
||||
liveness: any non-aborted termination of the stream is treated as
|
||||
a server-side session revocation, the watcher calls
|
||||
`session.signOut("revoked")`, and the layout effect redirects to
|
||||
`/login`.
|
||||
`session.signOut("revoked")`, `status` settles to `anonymous`, and
|
||||
the dispatcher swaps to the login screen on the next render — the
|
||||
URL stays `/game/`.
|
||||
|
||||
Session revocation closes the active client within one second: the
|
||||
gateway closes the stream the moment it observes a
|
||||
@@ -126,8 +135,8 @@ reacts on the next event-loop tick.
|
||||
## Localisation
|
||||
|
||||
The login form, the root layout's blocker page, and the lobby
|
||||
placeholder go through the i18n primitive in `src/lib/i18n/`. The
|
||||
language picker on `/login` lists every entry in
|
||||
screen go through the i18n primitive in `src/lib/i18n/`. The
|
||||
language picker on the login screen lists every entry in
|
||||
`SUPPORTED_LOCALES` by its native name and is initialised from
|
||||
`navigator.languages` (web) with `en` as the fallback. Picking a
|
||||
different language re-renders the form in place and is forwarded
|
||||
|
||||
Reference in New Issue
Block a user