ui/phase-10: in-game shell with view-replacement skeleton

Wraps every in-game route under `/games/:id/*` in a responsive shell
with a header (race / turn placeholders, view-menu dropdown or mobile
hamburger, account menu), a three-tab sidebar (Calculator, Inspector,
Order), an active-view slot, and a mobile-only bottom-tabs row
`[Map, Calc, Order, More]`. Every view in the IA section
(`map`, `table/:entity`, `report`, `battle/:battleId?`, `mail`,
`designer/{ship-class,science}/:id?`) ships as a thin SvelteKit route
that mounts a `lib/active-view/<name>.svelte` stub rendering a
localised `coming soon` body. The lobby's `gotoGame` path now actually
lands on a rendered shell instead of a 404.

The "view router" mentioned in the plan is implemented as the file
system plus two-line route wrappers — no separate dispatch component.
Sidebar tab state lives as a `$state` rune inside `sidebar.svelte`,
which sits in the layout that SvelteKit keeps mounted across child
route swaps, so tab choice survives every active-view navigation for
free. A `?sidebar=calc|inspector|order` URL param seeds the initial
tab on first mount; the mobile bottom-tabs use a layout-owned
`mobileTool` rune with a URL-gated `effectiveTool` derivation so the
Calc / Order tool overlay only applies on `/map` and naturally drops
when the user navigates elsewhere.

Tablet ships with a click-toggle drawer for the sidebar rather than
the IA section's swipe-from-right gesture; the structural breakpoint
satisfies Phase 10's acceptance criterion and Phase 35 polish lands
the swipe. The mobile More drawer mirrors the header view-menu
content; the IA's narrower More list (Mail, Battle, Tables, History,
Settings, Logout) is also a Phase 35 polish target once History
exists.

Topic doc `ui/docs/navigation.md` captures the active-view model, the
sidebar state-preservation rule, the `?sidebar=` and `mobileTool`
conventions, and the transient map-overlay back-stack concept (with
the implementation deferred to Phase 34 alongside its first user).
i18n catalogues for `en` and `ru` add the full `game.shell.*`,
`game.view.*`, `game.sidebar.*`, `game.bottom_tabs.*` namespaces.

Tests: Vitest covers the header view-menu (every IA destination
including the Tables sub-list), the account-menu Logout / Language
wiring, the sidebar default tab / switching / `?sidebar=` seed /
close button, and every active-view stub. Playwright e2e boots an
authenticated session via `__galaxyDebug.setDeviceSessionId` (no
gateway calls — the shell makes none in Phase 10), exercises every
view through both the desktop dropdown and the mobile More drawer,
verifies sidebar tab survival across navigation, and uses
`setViewportSize` to validate the breakpoint switches at 768 px and
1024 px.

Phase 10 status stays `pending` in `ui/PLAN.md` until the local-ci
run lands green; flipping to `done` follows in the next commit per
the per-stage CI gate in `CLAUDE.md`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-08 20:15:49 +02:00
parent 0f8f8698bd
commit fc371c7fe1
36 changed files with 2337 additions and 29 deletions
+114 -29
View File
@@ -1032,44 +1032,129 @@ Goal: assemble the in-game layout shell (header, sidebar, main area)
with empty placeholder content for every view, so navigation works
end-to-end before any data is wired.
Artifacts:
Decisions taken with the project owner during implementation:
- `ui/frontend/src/routes/games/[id]/+layout.svelte` shell layout with
responsive breakpoints (desktop / tablet / mobile)
- `ui/frontend/src/lib/header/` header component: race name, turn
counter (static placeholder `turn ?`), view dropdown / hamburger,
account menu
- `ui/frontend/src/lib/sidebar/` sidebar with three tabs (Calculator,
Inspector, Order), each tab content stubbed to `coming soon`; mobile
bottom-tab bar `[Map, Calc, Order, More]` with corresponding stub
panels
- `ui/frontend/src/lib/active-view/` view router supporting
`/games/:id/{map,table/:entity,report,battle/:battleId,mail,
designer/...}` with stub content per view
- topic doc `ui/docs/navigation.md` documenting the active-view
model, the state-preservation rule, and the transient map-overlay
concept (the back-stack mechanism itself is implemented in Phase 34
when the first overlay user, ship-designer reach circles, ships)
1. **Routing — file-system based, no extra dispatcher.** The
"view router" called out in the original artifact list is
implemented as SvelteKit's file-system routes plus thin
`+page.svelte` wrappers that mount the matching
`lib/active-view/<name>.svelte` stub. No separate dispatch
component lives in the codebase; each route file is a two-line
wrapper.
2. **Optional designer ID segments.** Both designer URLs ship as
`[[id]]` optional segments
(`designer/ship-class/[[classId]]/`,
`designer/science/[[scienceId]]/`) so Phase 18 / 21 can read
the param without a routing migration. Phase 10 stubs ignore
the param.
3. **Battle URL — optional id.** `battle/[[battleId]]/` accepts
both the list URL (`/battle`) and a specific battle URL
(`/battle/<id>`). Phase 27 keeps the optional segment and
switches behaviour based on presence.
4. **Tablet sidebar — click toggle, not swipe.** The 7681024 px
tablet sidebar slides in from a header-button click rather
than the IA section's swipe-from-right gesture. The structural
breakpoint switch satisfies Phase 10's acceptance criterion;
Phase 35 polish lands the swipe gesture.
5. **Mobile tool overlay — `mobileTool` state, gated by URL.**
The mobile bottom-tabs Calc / Order navigate to `/map` and
set a layout-owned `mobileTool` rune. The layout's derived
`effectiveTool` only honours the rune when the URL is `/map`,
so navigating to any other view via the More drawer or the
header view-menu naturally drops the overlay. The desktop
sidebar separately accepts a `?sidebar=calc|inspector|order`
URL param that seeds the initial tab on first mount, used by
later phases that want to land directly on a particular tool.
6. **Sidebar tool filenames — `*-tab.svelte`.** Phase 12 / 13 / 30
each name their final implementation
(`order-tab.svelte`, `inspector-tab.svelte`,
`calculator-tab.svelte`). The Phase 10 stubs ship with those
names so later phases replace the content in place without
renaming.
7. **Race-name and turn-counter placeholders.** The header race
name is the static `race ?` string from i18n, mirroring the
spec's static `turn ?` placeholder. Phase 11 wires both from
`user.games.report` data through `lib/header/turn-counter.svelte`.
8. **Auth gate inherited.** The root `+layout.svelte` already
redirects `anonymous → /login`; the in-game shell needs no
extra guard. Phase 10 verified this by booting the e2e shell
spec via `__galaxyDebug.setDeviceSessionId` and observing the
post-`session.init` `authenticated` status.
9. **More drawer mirrors the view-menu.** The mobile bottom-tabs
"More" drawer renders the same seven destinations as the
header view-menu. The IA section's narrower More list (Mail,
Battle log, Tables, History, Settings, Logout) is the polish
target for Phase 35 once History exists; Phase 10 keeps a
single destination list to avoid drift.
Artifacts (delivered):
- `ui/frontend/src/routes/games/[id]/+layout.svelte` — chrome
layout (header, conditional sidebar, active-view slot, mobile
bottom-tabs, mobileTool gate, sidebarOpen toggle)
- `ui/frontend/src/routes/games/[id]/+layout.ts` —
`ssr=false; prerender=false;` mirroring the root SPA flags
- `ui/frontend/src/routes/games/[id]/+page.ts` — redirects
`/games/:id` → `/games/:id/map`
- `ui/frontend/src/routes/games/[id]/{map, table/[entity], report,
battle/[[battleId]], mail, designer/ship-class/[[classId]],
designer/science/[[scienceId]]}/+page.svelte` — thin route
wrappers that mount the matching active-view stub
- `ui/frontend/src/lib/header/{header, turn-counter, view-menu,
account-menu}.svelte` — header composition with race
placeholder, turn counter (static `?`), view-menu
(dropdown desktop / hamburger mobile), and account menu
(Settings / Sessions / Theme stub buttons; Language driven by
`i18n.setLocale`; Logout calls `session.signOut("user")`)
- `ui/frontend/src/lib/sidebar/{sidebar, tab-bar, calculator-tab,
inspector-tab, order-tab, bottom-tabs}.svelte` — three-tab
sidebar with `inspector` default and `?sidebar=` URL seed;
mobile-only bottom-tabs with `[Map, Calc, Order, More]` plus a
More drawer duplicating the view-menu destinations
- `ui/frontend/src/lib/sidebar/types.ts` — shared `SidebarTab`
and `MobileTool` types
- `ui/frontend/src/lib/active-view/{map, table, report, battle,
mail, designer-ship-class, designer-science}.svelte` — Phase 10
stubs rendering localised titles plus `coming soon` copy with
stable testids that later phases replace
- `ui/frontend/src/lib/i18n/locales/{en,ru}.ts` — full
`game.shell.*`, `game.view.*`, `game.sidebar.*`,
`game.bottom_tabs.*` catalogue
- Topic doc `ui/docs/navigation.md`
- Vitest: `tests/game-shell-{header,sidebar,stubs}.test.ts`
- Playwright: `tests/e2e/game-shell.spec.ts` (7 cases × 4 projects;
mobile-only and viewport-switch cases conditionally skipped on
non-matching projects)
Dependencies: Phase 8.
Acceptance criteria:
Acceptance criteria (met):
- entering `/games/:id/map` from the lobby renders the shell with all
navigation chrome;
- header dropdown switches to every other view; mobile hamburger does
the same;
- entering `/games/:id/map` from the lobby renders the shell with
all navigation chrome;
- header dropdown switches to every other view; mobile hamburger
does the same;
- sidebar tabs preserve their stub state across switches;
- the responsive layout matches the breakpoint diagrams in
`Information Architecture and Navigation`.
`Information Architecture and Navigation` (with the swipe
gesture deferred to Phase 35).
Targeted tests:
Targeted tests (delivered):
- Vitest component tests for header navigation actions;
- Playwright e2e: visit every view stub via header dropdown, assert
empty state copy renders;
- multi-viewport Playwright run validating layout switches at the 768
px and 1024 px breakpoints.
- Vitest component tests for the header (race / turn placeholders,
view-menu navigation to every IA destination, account-menu
Logout / Language wiring);
- Vitest component tests for the sidebar (default tab, switching,
empty-state copy, `?sidebar=` URL seed, close button);
- Vitest component tests for every active-view stub (title,
`coming soon` copy, table-entity prop, battle-id prop);
- Playwright e2e: visit every view stub via header dropdown and
via the mobile More drawer; sidebar tab choice survives
navigation across active views; mobile bottom-tabs toggle the
Calc / Order tool overlay;
- Playwright e2e: `setViewportSize`-driven viewport switch test
validates layout transitions at 768 px and 1024 px (sidebar
visibility, sidebar-toggle / bottom-tabs visibility).
## Phase 11. Map Wired to Live Game State