feat(lobby): F8-04b hierarchical sidebar + paid-tier gate for create-game
Reshape the lobby UI from a single Overview into a two-level sidebar (games · profile · DEV synthetic-reports) with four games sub-panels (active-past · recruitment · invitations · private-games). Move the `create new game` button into the private-games panel, merge the applications section into recruitment cards as status chips, and add DEV-only synthetic-report loader as a top-level screen. Add a paid-tier gate at backend `lobby.game.create`: free callers get `403 forbidden` before the lobby service is invoked. The UI hides the private-games sub-panel + create button on free tier (DEV affordances flag overrides). Update every integration test that creates a game to use a new `testenv.PromoteToPaid` helper; add a new `TestLobbyFlow_FreeUserCreateGameForbidden`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+50
-11
@@ -17,10 +17,25 @@ for the whole session. The only other routes are the dev/test-only
|
||||
`/__debug/*` surfaces. What the URL used to encode now lives in two
|
||||
rune singletons in `src/lib/app-nav.svelte.ts`:
|
||||
|
||||
- **`appScreen`** — the top-level screen
|
||||
(`login` / `lobby` / `lobby-create` / `profile` / `game`) plus the
|
||||
active `gameId`. It replaces the old `goto`-based redirects and the
|
||||
`[id]` route param.
|
||||
- **`appScreen`** — the top-level screen plus the active `gameId`. The
|
||||
literal values are:
|
||||
- `login` — anonymous entry point
|
||||
- `lobby` — historical alias; the dispatcher renders a tiny resolver
|
||||
that immediately navigates to the first visible games sub-panel
|
||||
(kept for snapshots persisted before the F8-04b split)
|
||||
- `lobby-create` — create-game form
|
||||
- `profile` — profile editor
|
||||
- `game` — in-game shell (drives `activeView`, see below)
|
||||
- `games-active-past`, `games-recruitment`, `games-invitations`,
|
||||
`games-private-games` — the four lobby sub-panels (F8-04b)
|
||||
- `synthetic-reports` — DEV-only legacy-report loader, gated by
|
||||
`VITE_GALAXY_DEV_AFFORDANCES === "true"`
|
||||
|
||||
It replaces the old `goto`-based redirects and the `[id]` route
|
||||
param. Sanitize on session-restore allows every literal above, but
|
||||
the lobby shell's $effect re-routes a restored
|
||||
`games-private-games` (free tier) or `synthetic-reports` (prod
|
||||
bundle) to the first visible games sub-panel silently — no toast.
|
||||
- **`activeView`** — the in-game view (`map` / `table` / `report` /
|
||||
`battle` / `mail` / `designer-science`) plus the sub-parameters the
|
||||
old route segments carried (`tableEntity`, `battleId`, `turn`,
|
||||
@@ -30,16 +45,40 @@ A single-route dispatcher (`src/routes/+page.svelte`) chooses what to
|
||||
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`, `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
|
||||
(`login-screen.svelte`, `lobby-screen.svelte` resolver,
|
||||
`lobby-create-screen.svelte`, `profile-screen.svelte`,
|
||||
`games-active-past-screen.svelte`, `games-recruitment-screen.svelte`,
|
||||
`games-invitations-screen.svelte`,
|
||||
`games-private-games-screen.svelte`,
|
||||
`synthetic-reports-screen.svelte`) or, for `screen === "game"`, the
|
||||
in-game shell `src/lib/game/game-shell.svelte`. Every authenticated
|
||||
non-game screen wraps its body in
|
||||
`lib/screens/lobby-shell.svelte`, which renders the two-level sidebar
|
||||
+ identity strip; 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`.
|
||||
|
||||
### Lobby submenu
|
||||
|
||||
The lobby shell renders the `games` parent as an always-expanded
|
||||
submenu on desktop (>640px) whenever the active screen is one of the
|
||||
`games-*` literals. The submenu order is canonical
|
||||
(`active-past` → `recruitment` → `invitations` → `private-games`), and
|
||||
visibility is computed per-render from the
|
||||
`account.entitlement.is_paid` flag, `lobbyData.myGames.length`, and
|
||||
the build-time `VITE_GALAXY_DEV_AFFORDANCES` flag — see the
|
||||
[Games panels](lobby.md#games-panels) table for the rules.
|
||||
|
||||
On mobile (≤640px) the sidebar collapses to a horizontal strip
|
||||
(F8-04). The `games` entry then renders as a single
|
||||
`games · {active-sub} ▾` button; tapping it opens a popover
|
||||
(`role="listbox"`) of every visible sub-panel. Tapping a sub-panel
|
||||
selects it and closes the popover; tapping outside or pressing
|
||||
`Escape` closes it without changing the active page; re-tapping the
|
||||
active sub-panel inside the popover is a no-op (the same idiom as the
|
||||
F8-02 turn-navigator fix).
|
||||
|
||||
### Active-view dispatch
|
||||
|
||||
The client renders **one active view at a time**. The game shell
|
||||
|
||||
Reference in New Issue
Block a user