feat(ui): single-URL game app-shell (in-memory screens/views) #35

Merged
developer merged 8 commits from feature/ui-app-shell into development 2026-05-23 20:18:09 +00:00
Owner

Collapses the whole game UI into a single stable URL (/game/) with all
screen/view state held in memory — the address bar never changes.

What changed

  • One SvelteKit route (+page.svelte) acts as a screen dispatcher;
    login / lobby / lobby-create / game are in-memory screens, in-game
    views (map / table / report / battle / mail / designer-science) are
    in-memory views. The games/[id]/**, login/, lobby/ route trees
    are gone; their components moved to src/lib/screens/ & src/lib/game/.
  • State lives in src/lib/app-nav.svelte.ts (appScreen + activeView
    rune singletons), persisted to sessionStorage. The auth gate is now
    state-based (no goto redirects).
  • Screen-level Back via SvelteKit shallow routing (pushState /
    page.state): browser Back from a game → lobby, the URL stays /game/,
    in-game view switches add no history.
  • Restore on refresh from sessionStorage, with boot-time validation
    of the restored game (cancelled/removed → lobby + toast). Re-entering a
    game from the lobby starts at the map (only an in-place refresh restores).
  • Return-to-lobby control added in the in-game header.
  • Push-event deep-links (toast → view) are now in-memory activeView.select.

Mobile fix

.game-shell was min-height: 100vh, so sub-pixel overflow made the
document scrollable; on mobile that toggles the dynamic toolbar and
resizes the position: fixed overlays mid-gesture, breaking taps on the
bottom-tabs / map menu / planet sheet. Pinned with position: fixed; inset: 0 on the mobile breakpoint (desktop unchanged).

Compatibility

Standalone targets in ui/PLAN.md (Wails / Capacitor / gomobile) load a
single bundled index.html — the app-shell is the natural fit. PLAN.md
Phase 10 carries a supersede note.

Tests / docs

  • pnpm check clean; full e2e green on all four browser projects; mobile
    suite 82/82 after the fix.
  • Test migration: e2e seed-then-window.__galaxyNav.enterGame(...); unit
    tests mock $lib/app-nav.svelte.
  • Docs synced: ui/docs/{navigation,order-composer,auth-flow,pwa-strategy, game-state,...}, ui/README.md, ui/PLAN.md.

🤖 Generated with Claude Code

Collapses the whole game UI into a single stable URL (`/game/`) with all screen/view state held in memory — the address bar never changes. ## What changed - **One SvelteKit route** (`+page.svelte`) acts as a screen dispatcher; login / lobby / lobby-create / game are in-memory *screens*, in-game views (map / table / report / battle / mail / designer-science) are in-memory *views*. The `games/[id]/**`, `login/`, `lobby/` route trees are gone; their components moved to `src/lib/screens/` & `src/lib/game/`. - **State** lives in `src/lib/app-nav.svelte.ts` (`appScreen` + `activeView` rune singletons), persisted to `sessionStorage`. The auth gate is now state-based (no `goto` redirects). - **Screen-level Back** via SvelteKit shallow routing (`pushState` / `page.state`): browser Back from a game → lobby, the URL stays `/game/`, in-game view switches add no history. - **Restore on refresh** from `sessionStorage`, with boot-time validation of the restored game (cancelled/removed → lobby + toast). Re-entering a game from the lobby starts at the map (only an in-place refresh restores). - **Return-to-lobby** control added in the in-game header. - Push-event deep-links (toast → view) are now in-memory `activeView.select`. ## Mobile fix `.game-shell` was `min-height: 100vh`, so sub-pixel overflow made the document scrollable; on mobile that toggles the dynamic toolbar and resizes the `position: fixed` overlays mid-gesture, breaking taps on the bottom-tabs / map menu / planet sheet. Pinned with `position: fixed; inset: 0` on the mobile breakpoint (desktop unchanged). ## Compatibility Standalone targets in `ui/PLAN.md` (Wails / Capacitor / gomobile) load a single bundled `index.html` — the app-shell is the natural fit. PLAN.md Phase 10 carries a supersede note. ## Tests / docs - `pnpm check` clean; full e2e green on all four browser projects; mobile suite 82/82 after the fix. - Test migration: e2e seed-then-`window.__galaxyNav.enterGame(...)`; unit tests mock `$lib/app-nav.svelte`. - Docs synced: `ui/docs/{navigation,order-composer,auth-flow,pwa-strategy, game-state,...}`, `ui/README.md`, `ui/PLAN.md`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
developer added 8 commits 2026-05-23 20:14:32 +00:00
Add `appScreen` + `activeView` rune singletons with a shared sessionStorage
snapshot — the in-memory source of truth that replaces URL-based screen/view
routing for the single-URL app-shell. Not wired in yet (additive).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Collapse the game UI to one route (`/`): a screen dispatcher renders
login/lobby/lobby-create/game from `appScreen`/`activeView` state instead of
URL routes. Move screen components to lib/screens & lib/game; the game shell
reads the game id from `appScreen.gameId` and re-inits per-game stores via an
$effect; in-game views render from `activeView`. Flip ~23 goto/href nav sites
to store mutations; drop the `?sidebar=` URL coupling. Auth gate is now
state-based. WIP: browser-history (Back→lobby), restore-validation, the
return-to-lobby button, push deep-links, and the test migration are follow-ups
on this branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirror the screen into browser history via SvelteKit shallow routing
(pushState/replaceState with page.state) so Back/Forward move between
screens while the URL stays at /game/. Overlays (game, lobby-create) push;
lobby/login replace. A popstate→page.state effect syncs the store back
without re-pushing (no loop); the boot stamp puts a restored overlay above
the load entry so Back falls through to lobby. In-game view switches never
touch history.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- A restored game that no longer exists (cancelled/removed/revoked) drops to
  the lobby with a toast instead of the in-game error state: game-state
  exposes a `notFound` flag and the shell redirects via appScreen.go("lobby").
- Add a visible "return to lobby" control to the in-game header.
- Push/toast deep-links use activeView.select(...) (no URL); fix a latent
  visibility-listener double-install on in-place game switches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Unit: repoint moved screen imports (lib/screens, lib/game), mock
  $lib/app-nav (appScreen/activeView) instead of $app/navigation, drop the
  removed gameId props, assert screen/view selection.
- e2e: add a dev-only window.__galaxyNav affordance; specs enter a game via
  enterGame(...) instead of a /games/:id URL; URL assertions become content
  assertions (the URL stays /game/); reload uses waitUntil:"commit" (shallow
  routing) and mocks /rpc on game entry.
- Remove the obsolete report scroll-restore test (it relied on a SvelteKit
  route Snapshot that no longer exists); update the missing-membership test
  to the new lobby-redirect+toast behaviour. Fix a stale report.svelte
  docstring.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
docs(ui): sync docs to the app-shell; fix stale nav comments
Tests · UI / test (push) Failing after 9m28s
e31fb2c17a
Rewrite ui/docs (navigation, order-composer, auth-flow, pwa-strategy,
game-state + secondary topic docs) and ui/README for the single-URL
app-shell (in-memory screens/views, Back→lobby via shallow routing,
sessionStorage restore + validation, return-to-lobby). ui/PLAN.md gets a
Phase-10 supersede note (implemented; standalone-compatible). Fix stale
code comments (session-store auth gate, report-sections spec contract).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(ui): pin the mobile game shell to the viewport
Tests · UI / test (push) Successful in 2m49s
c1672224a6
The app-shell migration surfaced a mobile-only e2e failure: taps on the
bottom-tab bar, the map-toggles menu, and the planet sheet were
intercepted by sibling elements despite the targets being on top.

Root cause: `.game-shell` used `min-height: 100vh`, so sub-pixel content
overflowed the viewport and made the document scrollable. On mobile that
scroll toggles the browser's dynamic toolbar, which resizes the viewport
and every `position: fixed` overlay (their sizes derive from `100vh`)
mid-gesture — defeating Playwright's actionability hit-test, and making
the real controls jittery to tap.

Pin the shell with `position: fixed; inset: 0` on the mobile breakpoint
so it leaves document flow: the document can no longer scroll, the
toolbar stays put, the viewport and overlays stay stable, and the
active-view area remains the single internal scroll region. Desktop is
unchanged (the rule is scoped to max-width: 767.98px).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
docs(ui): clarify lobby re-enter starts at the map, only refresh restores
Tests · UI / test (pull_request) Successful in 2m32s
1dadf08672
The single-URL restore replays the saved screen/view on an in-place
refresh only. Re-entering a game from the lobby resets activeView to the
map (lobby calls activeView.reset() before appScreen.go("game")), and
browser Back / the return-to-lobby control exit to the lobby. Spell this
out so the refresh-restore is not mistaken for a per-re-enter restore.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
developer merged commit f0857243e2 into development 2026-05-23 20:18:09 +00:00
developer deleted branch feature/ui-app-shell 2026-05-23 20:18:09 +00:00
Sign in to join this conversation.