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>
6.4 KiB
In-game shell — navigation model
This doc covers the chrome that wraps every in-game view: the
responsive layout shell, the active-view router built on SvelteKit's
file-system routes, the sidebar with three tools and its
state-preservation rule, and the mobile bottom-tabs. The user-facing
spec — view list, breakpoint diagrams, history-mode plans — lives in
../PLAN.md, section
Information Architecture and Navigation. This doc is the source of
truth for how those rules are implemented.
Active-view model
The client renders one active view at a time. Every active view is
a SvelteKit route under routes/games/[id]/; the route file is a
two-line wrapper that mounts the matching content component from
src/lib/active-view/<name>.svelte. The "view router" mentioned in
the plan is the file system plus those wrappers — there is no
separate dispatch component.
| URL | Active view component | Phase that fills it |
|---|---|---|
/games/:id/map |
lib/active-view/map.svelte |
Phase 11 |
/games/:id/table/:entity |
lib/active-view/table.svelte |
Phase 11 / 17 / 19 / 22 |
/games/:id/report |
lib/active-view/report.svelte |
Phase 23 |
/games/:id/battle/:battleId? |
lib/active-view/battle.svelte |
Phase 27 |
/games/:id/mail |
lib/active-view/mail.svelte |
Phase 28 |
/games/:id/designer/ship-class/:id? |
lib/active-view/designer-ship-class.svelte |
Phase 17 / 18 |
/games/:id/designer/science/:id? |
lib/active-view/designer-science.svelte |
Phase 21 |
/games/:id (no trailing view) redirects to /games/:id/map. The
optional :id? segments on the designer routes match SvelteKit's
[[id]] syntax — they accept both the new-draft and editing URLs;
later phases read the param when wiring real content.
The entity slug on the table route is kebab-case (planets,
ship-classes, ship-groups, fleets, sciences, races); the
table stub maps it to the matching game.view.table.<snake> i18n
key.
Sidebar tools and state preservation
The desktop sidebar hosts three tools:
| Tool | Component | Phase that fills it |
|---|---|---|
| Calculator | lib/sidebar/calculator-tab.svelte |
Phase 30 |
| Inspector | lib/sidebar/inspector-tab.svelte |
Phase 13 / 19 |
| Order | lib/sidebar/order-tab.svelte |
Phase 12 / 14 |
The sidebar's selected-tab state is a $state rune inside
lib/sidebar/sidebar.svelte. The component is mounted by the layout
at routes/games/[id]/+layout.svelte, and SvelteKit keeps that
layout instance alive while the user navigates between child routes
(/games/:id/map → /games/:id/report → …). The rune therefore
survives every active-view switch automatically, with no URL coupling
needed.
A ?sidebar=calc|calculator|inspector|order URL param is read once
on mount and seeds the initial tab. Later phases that want to land
the user on a particular tool (for example, Phase 14's first
end-to-end command flow) can set it on navigation.
Layout breakpoints
Three discrete CSS modes matched to the IA section diagrams:
- ≥ 1024 px (desktop) — the sidebar sits beside the active view and is always rendered. The header view-menu trigger uses the dropdown icon (▾). Bottom-tabs and the tablet sidebar-toggle are CSS-hidden.
- 768–1024 px (tablet) — the sidebar collapses behind a click toggle in the header right corner. Tapping the toggle slides the sidebar in as a fixed overlay above the active view; a close button on the sidebar dismisses it. The full swipe-from-right gesture in the IA section is deferred to Phase 35 polish — the click toggle satisfies the "layout switches at 768 px" acceptance criterion on Phase 10.
- < 768 px (mobile) — the sidebar is hidden entirely and the bottom-tabs row appears at the bottom of the viewport. The view-menu trigger swaps to a hamburger icon (☰) that opens the drop-down as a full-width drawer below the header.
Inspector is intentionally unreachable on mobile in Phase 10. Per the IA section the mobile inspector is a bottom-sheet raised by tapping a map object, and that mechanism waits for Phase 13.
Mobile bottom-tabs and tool overlay
The bottom-tabs row is [Map, Calc, Order, More]. Map navigates to
/games/:id/map and clears any tool overlay. Calc and Order navigate
to /games/:id/map too — but they also flip the layout's
mobileTool state to calc / order, which the layout uses to
swap the active-view slot for the Calculator / Order tool component.
The tool overlay only applies when the URL is /map. Navigating to
any other view through the More drawer or the header view-menu makes
the layout's derived effectiveTool collapse back to map, so the
user always sees the URL's active view rather than a stale overlay.
The next time the user taps a Calc or Order bottom-tab, the
navigation re-routes them to /map and re-applies the overlay.
The More button opens a drawer that mirrors the header view-menu
content. The IA section's narrower "More" list (Mail, Battle log,
Tables, History, Settings, Logout) is the polish target for Phase 35
— Phase 10 keeps a single source of truth for destinations.
Transient map overlays
Some views can push a transient overlay onto /map with a back
affordance — for example, the ship-class designer pushes a
range-preview overlay onto the map. The transient overlay clears
when the user navigates to any other view via the header or the
bottom-tabs.
Phase 10 documents this concept but does not implement the back-stack mechanism. Phase 34 lands the back-stack alongside its first user (multi-turn projection, range circles in the ship-class designer).
Auth gate
The root +layout.svelte redirects anonymous → /login for any
non-/__debug/ path; the in-game shell inherits that gate without
any extra check. When a session is revoked while the user is in the
shell, the same redirect fires through the existing
revocation watcher.