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:
@@ -0,0 +1,127 @@
|
||||
# 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`](../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.
|
||||
Reference in New Issue
Block a user