feat(ui): lobby site-style sidebar + profile screen (#47)

- Wrap lobby and profile in a shared `lobby-shell.svelte` chrome:
  page-list sidebar (Overview/Profile) and a top "Player-xxxx"
  identity strip mirroring the project site's monospace look.
- Strip the legacy `lobby.title`, device-session-id `<code>`, and
  `lobby.greeting` paragraph; the identity strip both names the user
  and opens the profile editor.
- Add a top-level `profile` AppScreen with a three-field form
  (`display_name`, `preferred_language`, `time_zone`) backed by a new
  `src/api/account.ts` wrapper around `user.account.get`,
  `user.profile.update`, and `user.settings.update`. Saving switches
  the active i18n locale in-place when the new preferred language is
  one the UI ships translations for.
- Update e2e fixture + auth-flow / lobby-flow specs to use the new
  `lobby-account-name` testid and wait for the loaded identity before
  releasing pending `SubscribeEvents` (webkit revocation race). New
  `profile-screen.spec.ts` covers navigation, edit-save, and cancel.
- Sync `ui/docs/lobby.md` and `ui/docs/navigation.md` to the new
  layout.

Closes #47
This commit is contained in:
Ilia Denisov
2026-05-26 13:42:10 +02:00
parent b03993fcb1
commit 2ecdecad1e
15 changed files with 1122 additions and 132 deletions
+11 -7
View File
@@ -18,9 +18,9 @@ for the whole session. The only other routes are the dev/test-only
rune singletons in `src/lib/app-nav.svelte.ts`:
- **`appScreen`** — the top-level screen
(`login` / `lobby` / `lobby-create` / `game`) plus the active
`gameId`. It replaces the old `goto`-based redirects and the `[id]`
route param.
(`login` / `lobby` / `lobby-create` / `profile` / `game`) plus the
active `gameId`. It replaces the old `goto`-based redirects and the
`[id]` route param.
- **`activeView`** — the in-game view (`map` / `table` / `report` /
`battle` / `mail` / `designer-science`) plus the sub-parameters the
old route segments carried (`tableEntity`, `battleId`, `turn`,
@@ -31,8 +31,11 @@ render: it gates on `session.status` (anonymous → login, authenticated
→ the `appScreen.screen`), and for the authenticated tree mounts the
matching screen component from `src/lib/screens/`
(`login-screen.svelte`, `lobby-screen.svelte`,
`lobby-create-screen.svelte`) or, for `screen === "game"`, the in-game
shell `src/lib/game/game-shell.svelte`. The game shell in turn renders
`lobby-create-screen.svelte`, `profile-screen.svelte`) or, for
`screen === "game"`, the in-game shell
`src/lib/game/game-shell.svelte`. Lobby and profile share a
post-login chrome (sidebar + identity strip) implemented in
`lib/screens/lobby-shell.svelte`; see [`lobby.md`](lobby.md). The game shell in turn renders
the active view from `activeView` (see below). Navigation is
`appScreen.go(screen, { gameId })` and `activeView.select(view,
params)` — never `goto`.
@@ -73,8 +76,9 @@ Browser **Back/Forward move between screens**, not views, and they do
so without ever changing the URL. The shell layers screen history on
top of `appScreen` via SvelteKit shallow routing: `appScreen.go(...)`
calls `pushState("", { screen, gameId })` for the overlay screens
(`game`, `lobby-create`) and `replaceState(...)` for `lobby` / `login`,
so browser **Back from a game returns to the lobby** beneath it. On
(`game`, `lobby-create`, `profile`) and `replaceState(...)` for
`lobby` / `login`, so browser **Back from a game (or profile) returns
to the lobby** beneath it. On
the first authenticated render the dispatcher stamps the restored
overlay on top of the load entry, then mirrors `page.state` back into
the store on every popstate through `appScreen.syncFromHistory(...)`.