ui/phase-26: history mode (turn navigator + read-only banner)
Split GameStateStore into currentTurn (server's latest) and viewedTurn (displayed snapshot) so history excursions don't corrupt the resume bookmark or the live-turn bound. Add viewTurn / returnToCurrent / historyMode rune, plus a game-history cache namespace that stores past-turn reports for fast re-entry. OrderDraftStore.bindClient takes a getHistoryMode getter and short-circuits add / remove / move while the user is viewing a past turn; RenderedReportSource skips the order overlay in the same case. Header replaces the static "turn N" with a clickable triplet (TurnNavigator), the layout mounts HistoryBanner under the header, and visibility-refresh is a no-op while history is active. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+34
-5
@@ -75,17 +75,46 @@ end-to-end command flow) can set it on navigation.
|
||||
|
||||
The Order entry is hidden when the layout's `historyMode` flag is
|
||||
true. Phase 12 plumbs the flag end-to-end as a prop —
|
||||
`+layout.svelte` passes a constant `false` to `Sidebar`, which
|
||||
`+layout.svelte` forwards a derived value to `Sidebar`, which
|
||||
forwards `hideOrder` to its `TabBar`; the same flag goes to
|
||||
`BottomTabs` so the mobile `Order` button is also suppressed. A
|
||||
`?sidebar=order` URL seed that arrives while the flag is true falls
|
||||
back to `inspector`, and an `$effect` on the sidebar resets
|
||||
`activeTab` away from `order` if the flag flips on mid-session.
|
||||
Phase 26 introduces `lib/history-mode.ts` and replaces the constant
|
||||
with the live signal; the order draft survives the toggle because
|
||||
|
||||
Phase 26 wires the flag to the live history signal owned by
|
||||
`GameStateStore`. The derivation lives directly in `+layout.svelte`
|
||||
(`const historyMode = $derived(gameState.historyMode)`) — no
|
||||
separate `lib/history-mode.ts` module ships, because the layout is
|
||||
the single consumer and the project's compactness rule rejects a
|
||||
one-line indirection. The order draft survives the toggle because
|
||||
`OrderDraftStore` lives one level above the sidebar in the layout
|
||||
hierarchy. See [`order-composer.md`](order-composer.md) for the
|
||||
draft-store side of the flow.
|
||||
hierarchy; the same `historyMode` derivation is also fed into
|
||||
`OrderDraftStore.bindClient` so inspector-driven mutations
|
||||
(`add` / `remove` / `move`) become no-ops while the user is
|
||||
viewing a past turn. See [`order-composer.md`](order-composer.md)
|
||||
for the draft-store side of the flow and
|
||||
[`game-state.md`](game-state.md) for the rune split between
|
||||
`currentTurn` and `viewedTurn`.
|
||||
|
||||
## Header turn navigator and history banner
|
||||
|
||||
The header replaces the Phase 11 inline `turn N` text with a
|
||||
`← Turn N →` triplet (`lib/header/turn-navigator.svelte`). The
|
||||
arrows step `viewedTurn` by ±1 (disabled at boundaries `0` and
|
||||
`currentTurn`); clicking the middle button opens an absolute
|
||||
popover (desktop) or a fixed full-width drawer (mobile, ≤ 767.98
|
||||
px) listing every turn from `currentTurn` down to `0`. Selecting
|
||||
the current-turn row routes through `gameState.returnToCurrent()`;
|
||||
any other row calls `gameState.viewTurn(N)`. The popover reuses
|
||||
`view-menu.svelte`'s outside-click / Escape pattern.
|
||||
|
||||
`lib/header/history-banner.svelte` renders directly under the
|
||||
header whenever `gameState.historyMode === true`. It shows
|
||||
"Viewing turn {N} · read-only" with a "Return to current turn"
|
||||
button that delegates back to `gameState.returnToCurrent()`. Both
|
||||
the navigator and the banner read `gameState` through context, so
|
||||
the layout is the only place where the wiring lives.
|
||||
|
||||
## Layout breakpoints
|
||||
|
||||
|
||||
Reference in New Issue
Block a user