Compare commits

...

11 Commits

Author SHA1 Message Date
funman300 da3e5423dc feat(replay): add full-screen tableau dim layer for mini-tableau preview
Spawn a `ReplayTableauDimLayer` UI node (100% × 100%, 50% opacity black)
at z=54 (Z_REPLAY_OVERLAY − 1) whenever a replay starts. The dim layer
darkens the entire card world so the replay chrome (banner at z=55,
move-log panel at z=55) reads clearly against the scene without
obscuring card positions — matching the mockup's "Game Peek Band at
50% opacity" spec. Bevy's UI/world compositor means no changes to
card_plugin are needed: UI nodes always render above world-space sprites
regardless of Transform.z values.

The dim layer carries no Interaction component (purely visual; pointer
events pass through). Despawned alongside the banner and move-log panel
in `react_to_state_change` when the replay ends.

Adds Z_REPLAY_DIM (= 54) and TABLEAU_DIM_ALPHA (= 0.5) constants plus
two new tests: lifecycle (spawn/despawn mirrors floating chip pattern)
and z-ordering invariant (Z_REPLAY_DIM < Z_REPLAY_OVERLAY pinned).

1275 tests pass / 0 failing. Closes the last major B-2 sub-piece.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 18:01:22 -07:00
funman300 a1864271de docs(handoff): refresh post-v0.21.6 — anchor to new tag, reset menu state
Fold the six post-v0.21.5 commit narratives into CHANGELOG §
[0.21.6] (now the source of truth for that release's scope).
Reset the Since-cut log to "no threads in flight." Update
status (HEAD f63db76, tags through v0.21.6, tests 1273
passing). Resume prompt now anchors at v0.21.6.

The post-cut menu's main item is now the mini-tableau preview
— the only major B-2 sub-piece left after Move Log panel
shipped. Architectural change (touches card_plugin rendering),
best tackled in a fresh session.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 17:48:51 -07:00
funman300 f63db769ae docs: cut v0.21.6 — Move Log panel + scrub-UX polish
Patch release rolling up six post-v0.21.5 commits under the
through-line "Move Log panel + scrub-UX polish":

- d3cb1a5: HC-mode coverage for scrub track + notches
- 2e25476: continuous scrub on key-held ← / → at 100ms cadence
- d6f32d3: Move Log panel + active row (header + format helpers)
- 140251b: 2 prev rows above active
- e7345ae: active-row highlight with ACCENT_PRIMARY background
- 4437a1a: 2 next rows below active

The Move Log panel is the first replay-overlay surface that
isn't attached to the banner — it lives at a separate screen
anchor (bottom: 0) with its own spawn/despawn lifecycle.
Establishes the multi-anchor replay UI pattern that the
remaining B-2 sub-piece (mini-tableau preview) will inherit.

Panel grows 56 → 84 → 112 px across the four move-log commits.
HighContrastBackground primitive lifted to ui_theme parallel
to HighContrastBorder; settings_plugin gains
update_high_contrast_backgrounds for the BackgroundColor
repaint cycle. Continuous scrub uses a per-key accumulator
resource (ReplayScrubKeyHold) gated on SCRUB_REPEAT_INTERVAL_SECS
(0.1s).

Tests: 1250 → 1273 (+23 net new). Clippy clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 17:46:24 -07:00
funman300 4437a1aaf9 feat(replay): add 2 next rows below active row in Move Log panel
Symmetric to the prev-rows commit. Adds 2 about-to-apply move
rows below the active row so the panel now shows a full 5-row
window: prev offset 2 → prev offset 1 → active → next offset 1
→ next offset 2. Panel grows from 84 → 112 px to fit the
additional rows.

Format helper `format_kth_next_row(state, k)` returns the kth
about-to-apply move's text:
- k=1 → moves[cursor], displayed as "{cursor + 1} │ {body}"
- k=2 → moves[cursor + 1], displayed as "{cursor + 2} │ ..."
- Returns empty when cursor + k - 1 >= moves.len() (under-fill
  late in the replay) or k=0 (degenerate).

Symmetric implementation:
- New `ReplayOverlayMoveLogNextRow { offset: u8 }` component
- Spawn loop iterates 1..=MOVE_LOG_NEXT_ROWS in order so offset
  1 sits directly below active, offset 2 below that
- Per-frame `update_move_log_next_rows` system mirrors the
  prev-rows updater
- TEXT_SECONDARY (matching prev rows) keeps the active row's
  highlight as the focal point

For post-game replays the next rows aren't spoilers (the game
is already won). If a future use case reuses the panel during
live play, the preview-shape would need rethinking.

4 new tests:
- format_kth_next_row: k=1, 2 in-range cases + k beyond
  moves.len() out-of-range + k=0 degenerate.
- move_log_next_rows_spawn_with_panel: cardinality matches
  MOVE_LOG_NEXT_ROWS.
- move_log_next_rows_paint_helper_strings_at_spawn: text
  matches helper output per offset.
- move_log_next_rows_underfill_at_replay_end: offset 1
  populates at cursor=9/10, offset 2 stays empty.

Tests: 1269 → 1273. Clippy clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 17:44:59 -07:00
funman300 e7345aed6c feat(replay): highlight active row in Move Log panel
Wraps the active-row Text in a Node with
BackgroundColor(ACCENT_PRIMARY) so the row reads as "current
focus" against the panel's elevated background. Inner Text
colour bumps from TEXT_PRIMARY (#d0d0d0) to TEXT_PRIMARY_HC
(#f5f5f5) for legible contrast against the brick-red highlight.

format_active_move_row now prefixes the row with `▶` (the focus
marker) so the visual hierarchy is reinforced even before the
background paints (HC mode, future palette tweaks). The empty
case still returns empty — cursor=0 doesn't paint a stray "▶ "
prefix on an otherwise-empty row.

Mirrors the mockup at docs/ui-mockups/replay-overlay-mobile.html
§ "Move Log Card" where the active row has bg-suit-red-cb
(brick-red equivalent) + dark text + the ▶ marker.

3 new tests:
- active_row_wrapper_carries_accent_primary_background: walks
  from the active-row Text to its parent Node and asserts the
  wrapper carries BackgroundColor(ACCENT_PRIMARY).
- active_row_text_uses_high_contrast_color_for_highlight: pins
  the TextColor as TEXT_PRIMARY_HC.
- active_row_format_includes_focus_prefix: pure-helper guard for
  the ▶ prefix + the cursor=0-stays-empty contract.

Plus 2 existing tests updated for the new prefixed format
(format_active_move_row_handles_cursor_zero_and_positive,
move_log_active_row_repaints_on_cursor_advance).

Tests: 1266 → 1269 (+3 net new, +2 updated). Clippy clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 17:41:14 -07:00
funman300 140251beae feat(replay): add 2 prev rows above active row in Move Log panel
Extends the Move Log panel's single active-row to a 3-row recent-
history window: 2 prev rows showing the moves applied just before
the active one, then the active row. Display order top-to-bottom:
header → prev offset 2 (oldest) → prev offset 1 → active.

Panel grows from 56 → 84 px to fit the additional rows. Active
row keeps TEXT_PRIMARY; prev rows render in TEXT_SECONDARY so
the active row stands out from context rows even without an
explicit highlight. (Active-row highlight is a follow-up commit.)

The format helper generalises:
- New `format_kth_recent_row(state, k)` returns the text for the
  kth-most-recently-applied move (k=1 is active, k=2 is row above,
  etc.). Returns empty when k > cursor (early-replay under-fill)
  or k = 0 (degenerate).
- `format_active_move_row` becomes a thin wrapper for k=1, kept
  at module scope so call sites stay readable.

New `ReplayOverlayMoveLogPrevRow { offset: u8 }` component carries
the row's offset (1 = just-before-active, 2 = before that). Spawn
loop iterates `MOVE_LOG_PREV_ROWS..=1` in reverse so the highest-
offset (oldest) row sits topmost in the panel's flex column.

Per-frame `update_move_log_prev_rows` system reads each row's
offset, computes k = offset + 1, and repaints via
format_kth_recent_row. Empty-when-out-of-range means panels gracefully
under-fill at cursor=1 (only active populated) and cursor=2
(active + offset 1, offset 2 empty).

4 new tests:
- format_kth_recent_row: k=1, 2, 3 in-range cases + k>cursor
  out-of-range + k=0 degenerate.
- move_log_prev_rows_spawn_with_panel: cardinality matches the
  MOVE_LOG_PREV_ROWS const.
- move_log_prev_rows_paint_helper_strings_at_spawn: text matches
  helper output per offset.
- move_log_prev_rows_repaint_on_cursor_advance: drives cursor=2
  → cursor=5 and asserts offset 1 / offset 2 texts follow.

Tests: 1262 → 1266. Clippy clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 17:35:07 -07:00
funman300 d6f32d3154 feat(replay): add Move Log panel with active-row readout
First slice of the move-log mockup at
docs/ui-mockups/replay-overlay-mobile.html § "Move Log Card".
Adds a separate root UI entity anchored to the viewport's bottom
edge (sibling-of-banner pattern, mirrors ReplayFloatingProgressChip
lifecycle) carrying a `▌ MOVE LOG · N/M` header plus a single row
showing the most-recently-applied move.

Subsequent commits in this multi-session arc add prev/next rows,
active-row highlight, and auto-scroll on cursor advance. Splitting
the work at "panel + active row only" lands the structural piece
(panel exists, lifecycle works, format helpers proven) before
tackling the harder questions about rendering un-applied future
moves and scrolling.

Position decision: bottom-of-viewport (matches mockup), separate
root entity from the 92 px top banner. Keeps the banner from
growing further into a top-heavy 170+ px strip; the
top-status + bottom-info paradigm reads as vim/IDE-style buffer
chrome that players intuitively scan.

Four pure helpers handle the formatting:
- format_pile(p) → lowercase, 1-indexed display string
  ("foundation 3" rather than enum's 0-indexed Foundation(2))
- format_move_body(m) → "{from} → {to}" or "stock cycle"
- format_move_log_header(state) → "▌ MOVE LOG · N/M",
  "▌ MOVE LOG · COMPLETE" for `Completed`, empty for `Inactive`
- format_active_move_row(state) → "{cursor} │ {body}" with
  1-based cursor for player display, empty at cursor=0

Two per-frame update systems (update_move_log_header,
update_move_log_active_row) repaint the texts on resource change
with the standard early-exit-on-no-change idiom.

Despawn handling: react_to_state_change gains a third query for
ReplayOverlayMoveLogPanel entities and despawns them on
Playing → Inactive alongside the banner root and floating chip.

Panel border carries HighContrastBorder so the 1 px top edge
bumps under HC mode — same pattern as the keybind footer.

8 new tests:
- format_pile pile-name + 1-index pinning
- format_move_body both-variant pinning
- format_move_log_header three-state coverage
- format_active_move_row cursor=0 vs cursor>0
- move_log_panel spawn cardinality (exactly one)
- move_log_panel header paints helper string at spawn
- move_log_active_row repaints on cursor advance
- move_log_panel despawn parity with overlay tree

Tests: 1254 → 1262. Clippy clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 17:29:37 -07:00
funman300 8fdc41f36f docs(handoff): record post-v0.21.5 polish; recommend notch-label centering
Two carve-outs land on top of v0.21.5:
- d3cb1a5: HC-mode coverage for scrub track + notches via new
  HighContrastBackground primitive in ui_theme + paint system
  in settings_plugin.
- 2e25476: continuous scrub on key-held ← / → at 100ms cadence;
  matches mockup's "[← →] scrub" terminology while keeping
  single-press = single-step semantics.

Update Since-cut log, status (1250 → 1254 tests passing,
flake cleared), and next-step menu. B-2 keyboard accelerator
coverage + accessibility + scrub UX are all complete; remaining
options are notch-label centering polish (smallest), the
move-log/mini-tableau multi-session arcs that close B-2, or
WIN MOVE marker HC bump (optional).

Recommended next-step: notch label centering (small).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 17:20:51 -07:00
funman300 2e25476d0a feat(replay): continuous scrub on key-held arrow keys
Holding ← or → now triggers continuous step at 100 ms cadence
(10 steps/sec) — matches the mockup's `[← →] scrub`
terminology while keeping single-press = single-step semantics.

Implementation: per-key accumulators in a new
`ReplayScrubKeyHold` resource. Each frame the key is held, the
corresponding accumulator absorbs `time.delta_secs()`; when it
exceeds `SCRUB_REPEAT_INTERVAL_SECS` (0.1s) the handler fires
another step and resets the accumulator. `just_pressed` events
bypass the accumulator entirely and fire immediately —
release resets to 0 so the next fresh press also fires
immediately rather than at half-interval.

Symmetric handling for ← (backwards step via undo) and →
(forward step). Both keys remain paused-only via the same
destructure-gate pattern in the underlying step helpers.

Footer text unchanged (`[← →] step`) — the only-wired-keybinds
discipline says "list what works"; held-key continuous scrub
is a discoverable enhancement to the same keybind, not a new
keybind.

`handle_arrow_keyboard` gains `Res<Time>` and
`ResMut<ReplayScrubKeyHold>` parameters. `Time` is provided by
MinimalPlugins's TimePlugin so headless tests already have it.

2 new tests (in addition to the 4 existing arrow scenarios):
- arrow_right_keyboard_repeats_while_held: drives time at
  exactly SCRUB_REPEAT_INTERVAL_SECS per tick and asserts that
  a second step fires after the just_pressed one.
- arrow_keyboard_release_resets_accumulator: verifies the
  release branch zeros the per-key accumulator.

Tests: 1252 → 1254. Clippy clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 17:19:46 -07:00
funman300 d3cb1a51d4 feat(replay): HC-mode coverage for scrub track + notches
The 1 px scrub track and 5 quarter-mark notch ticks paint their
shape via BackgroundColor (not BorderColor — they're tiny
full-bleed Nodes, not borders on wider containers), so the
existing HighContrastBorder marker doesn't apply to them.

Add a parallel primitive in ui_theme: HighContrastBackground
marker carrying default_color, mirroring HighContrastBorder's
shape exactly. Add update_high_contrast_backgrounds system in
settings_plugin alongside update_high_contrast_borders — same
on/off rule (off → marker.default_color, on → BORDER_SUBTLE_HC),
same change-suppression idiom (only mutate when different so
Bevy's change-detection doesn't trigger per-frame repaints).

Tag the scrub track Node and all five notch Nodes with
HighContrastBackground::with_default(BORDER_SUBTLE) so the
existing settings repaint cycle picks them up under HC mode.

The scrub fill (ACCENT_PRIMARY brick-red) and WIN MOVE marker
(STATE_SUCCESS lime-green) don't get the marker — accent and
state colours are already saturated and don't need an HC
luminance variant.

2 new tests: spawn-time marker presence on the track and
cardinality-matches-notch-count on the ticks.

Tests: 1250 → 1252. Clippy clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 17:14:03 -07:00
funman300 c8358f4275 docs(handoff): refresh post-v0.21.5 — anchor to new tag, reset menu state
Fold the six post-v0.21.4 commit narratives into CHANGELOG §
[0.21.5] (now the source of truth for that release's scope).
Reset the Since-cut log to "no threads in flight." Update
status (HEAD `a2432df`, tags through v0.21.5, tests still
1250/1249 passing pending the time-dependent flake clearing).
Resume prompt now anchors at v0.21.5 with the smaller post-cut
menu of next-finite-steps.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 17:08:56 -07:00
5 changed files with 1695 additions and 257 deletions
+113 -1
View File
@@ -6,9 +6,121 @@ project follows [Semantic Versioning](https://semver.org/).
## [Unreleased]
No threads in flight. v0.21.5 cut on 2026-05-08; CHANGELOG accumulates
No threads in flight. v0.21.6 cut on 2026-05-08; CHANGELOG accumulates
the next cycle here.
## [0.21.6] — 2026-05-08
Patch release for the post-v0.21.5 work. Through-line:
**Move Log panel + scrub-UX polish**. v0.21.5 closed out the
keyboard-accelerator surface (Space / Esc / ← / →) and the
keybind footer; v0.21.6 builds on that with two parallel
threads — accessibility + scrub-on-hold polish for the v0.21.5
surfaces, plus a brand-new Move Log panel anchored to the
viewport's bottom edge that gives players a 5-row recent-and-
upcoming move history alongside the existing top-edge banner.
The Move Log panel is the first replay-overlay surface that
*isn't* attached to the banner — it lives at a separate screen
anchor (bottom: 0) with its own spawn/despawn lifecycle.
Establishes the pattern for "multi-anchor replay UI" that the
remaining B-2 sub-piece (mini-tableau preview) will inherit.
### Added
- **HC-mode coverage for the scrub track + quarter-mark notch
ticks** (`d3cb1a5`). Adds parallel primitive
`HighContrastBackground` to `ui_theme` and a paint system
`update_high_contrast_backgrounds` in `settings_plugin` that
mirrors the existing border-marker pattern but targets
`BackgroundColor` instead of `BorderColor`. Tags the 1 px
scrub track Node and all five quarter-mark notch ticks so
they bump from `BORDER_SUBTLE` (`#505050`) →
`BORDER_SUBTLE_HC` (`#a0a0a0`) under HC mode. Scrub fill
(`ACCENT_PRIMARY`) and WIN MOVE marker (`STATE_SUCCESS`)
don't get the marker — accent and state colours are already
saturated and don't need an HC luminance variant.
- **Continuous scrub on key-held arrow keys** (`2e25476`).
Holding ← or → triggers continuous step at 100 ms cadence
(10 steps/sec) — matches the mockup's `[← →] scrub`
terminology while keeping single-press = single-step
semantics. Per-key accumulators in a new
`ReplayScrubKeyHold` resource; `just_pressed` events bypass
the accumulator and fire immediately. Release resets to 0
so the next fresh press fires immediately rather than at
half-interval.
- **Move Log panel** (`d6f32d3` + `140251b` + `e7345ae` +
`4437a1a`). New bottom-edge UI panel showing a 5-row window
onto recent + upcoming moves: 2 prev rows above the active
row + active row highlighted in `ACCENT_PRIMARY` + 2 next
rows below. Header reads `▌ MOVE LOG · N/M` (or
`▌ MOVE LOG · COMPLETE` when finished). Active row carries
a `▶` focus prefix and `TEXT_PRIMARY_HC` text colour for
legible contrast against the brick-red highlight. Prev /
next rows render in `TEXT_SECONDARY` so the active row
stays the focal point.
- Sibling-of-banner pattern (separate root entity anchored
at viewport bottom, not a banner child) — same
spawn/despawn lifecycle as `ReplayFloatingProgressChip`,
different screen anchor.
- Five pure helpers handle the formatting:
`format_pile`, `format_move_body`,
`format_move_log_header`, `format_kth_recent_row` (active
+ prev), `format_kth_next_row` (next). 1-indexed display
numbers throughout (`Foundation(2)` reads as "foundation
3" rather than the enum's 0-index).
- Panel grows from 56 → 84 → 112 px across the four
move-log commits. `MOVE_LOG_PREV_ROWS` and
`MOVE_LOG_NEXT_ROWS` constants (both = 2) parameterise
the row count; `format_kth_recent_row` and
`format_kth_next_row` return empty for out-of-range k so
panels gracefully under-fill at the start (cursor=1) and
end (cursor=N-1) of a replay.
- HC marker on the panel's top border so the 1 px edge
bumps under HC mode (same pattern as the keybind footer).
### Changed
- **`react_to_state_change` despawns the Move Log panel** on
`Playing → Inactive` alongside the banner root and floating
progress chip. Third query in the same defer-and-despawn
cycle.
- **Move Log panel height grew 56 → 84 → 112 px** across the
prev-rows and next-rows commits. The panel is sized to fit
the chosen row count + header + padding; tunable via the
`MOVE_LOG_PANEL_HEIGHT` const.
- **`format_active_move_row` now prefixes the `▶` focus
marker** (`e7345ae`). Wraps `format_kth_recent_row(state, 1)`
and prepends the prefix when the body is non-empty. Empty
case still returns empty — cursor=0 doesn't paint a stray
`▶` on an otherwise-empty row.
### Documentation
- `SESSION_HANDOFF.md` refreshed twice this cycle — once
recording the HC paint + continuous-scrub polish, then
again as the Move Log arc shipped commit-by-commit. The
Resume menu's B option now traces the full arc:
notches → labels → footer → ESC → HC → arrow keys →
HC paint → continuous scrub → move log.
### Stats
- **1273 passing tests / 0 failing** across the workspace
(net +23 from v0.21.5's 1250 baseline):
- 2 from `d3cb1a5` (HC marker on track + notches).
- 2 from `2e25476` (continuous-scrub repeat-while-held +
release-resets-accumulator).
- 8 from `d6f32d3` (move-log panel init + 5 helpers + 3
spawn / lifecycle scenarios).
- 4 from `140251b` (prev rows: helper k coverage + spawn
cardinality + spawn texts + repaint on cursor advance).
- 3 from `e7345ae` (active row highlight: wrapper bg +
text colour + focus prefix + cursor=0 stays empty).
- 4 from `4437a1a` (next rows: helper k coverage + spawn
cardinality + spawn texts + under-fill at replay end).
- Clippy clean across the workspace.
## [0.21.5] — 2026-05-08
Patch release for the post-v0.21.4 work. One through-line:
+104 -245
View File
@@ -1,198 +1,85 @@
# Solitaire Quest — Session Handoff
**Last updated:** 2026-05-08 — **v0.21.4 cut and tagged at
`23ff62c`**, working tree clean, all post-tag work pushed to
**Last updated:** 2026-05-08 — **v0.21.6 cut and tagged at
`f63db76`**, working tree clean, all post-tag work pushed to
origin.
v0.21.4 is a patch release with one through-line:
**replay-scrubbing accessibility**. The replay overlay used to be
pure-passive — start, watch, wait. v0.21.4 adds the scaffolding
for *navigating within* a replay: a WIN MOVE marker on the scrub
bar so the player can see at a glance where the winning move
sits, plus pause / resume / step controls (with a Space keyboard
accelerator) so they can stop on any move and inspect the board.
Also lands the additive `Replay::win_move_index: Option<usize>`
data field that makes the marker possible — serde-default so
older on-disk replays load with `None` and simply don't get a
marker (no schema bump).
v0.21.6 is a patch release with through-line:
**Move Log panel + scrub-UX polish**. v0.21.5 closed out the
keyboard-accelerator surface (Space / Esc / ← / →) and the
keybind footer; v0.21.6 builds on that with two parallel
threads — accessibility + scrub-on-hold polish for the v0.21.5
surfaces, plus a brand-new Move Log panel anchored to the
viewport's bottom edge that gives players a 5-row recent-and-
upcoming move history alongside the existing top-edge banner.
Three commits on the B-2 replay screen-takeover redesign arc
land here. The remaining sub-pieces (screen-takeover layout,
move-log scroller, mini-tableau preview) share a layout-reflow
prerequisite the banner can't carry, so they're deferred to a
future cycle as a single multi-session arc.
The Move Log panel is the first replay-overlay surface that
*isn't* attached to the banner — it lives at a separate screen
anchor (`bottom: 0`) with its own spawn/despawn lifecycle.
Establishes the "multi-anchor replay UI" pattern that the
remaining B-2 sub-piece (mini-tableau preview) will inherit.
Full v0.21.4 detail lives in `CHANGELOG.md` § [0.21.4]. This
Six commits on the B-2 replay screen-takeover redesign arc land
here, bringing the post-v0.21.4 total to 12. The remaining B-2
piece — mini-tableau preview that dims the gameplay tableau
during replay — is the only major sub-piece still open.
Full v0.21.6 detail lives in `CHANGELOG.md` § [0.21.6]. This
file from here on focuses on what's *open* post-cut and how to
resume.
## Status at pause
- **HEAD locally:** see `git rev-parse HEAD`. The cut commit is
`23ff62c`; any post-cut docs edits ride on top of that.
- **HEAD on origin:** matches local. v0.21.4 is fully on origin.
`f63db76`; any post-cut docs edits ride on top of that.
- **HEAD on origin:** matches local. v0.21.6 is fully on origin.
- **Working tree:** clean. No WIP outstanding.
- **`artwork/` directory:** still untracked. Intentional.
- **Build:** `cargo clippy --workspace --all-targets -- -D warnings`
clean.
- **Tests:** **1250 total / 1249 passing / 1 pre-existing
time-dependent flake** across the workspace
(1228 in v0.21.4 + 4 from `fe68861`'s scrub-notch tests + 4
from `d322abf`'s notch-label tests + 4 from `1873b3f`'s
keybind-footer tests + 3 from `90e24d9`'s ESC-accelerator
tests + 1 from `23902cd`'s HC-marker test + 6 from
`e5c4f51`'s arrow-keyboard tests). The flake is
`daily_challenge_plugin::tests::check_system_fires_warning_event_only_once_per_day`
— fails when wall-clock UTC is within 30 minutes of midnight
(the daily-expiry warning window the test asserts against).
Verified pre-existing. Detail in `CHANGELOG.md` § [0.21.4]
§ Stats; post-cut delta tracked here.
- **Tags on origin:** `v0.9.0` through `v0.21.4`. v0.21.4 is on
- **Tests:** **1273 passing / 0 failing** across the workspace.
Detail in `CHANGELOG.md` § [0.21.6] § Stats.
- **Tags on origin:** `v0.9.0` through `v0.21.6`. v0.21.6 is on
`f63db76`; v0.21.5 stays on `a2432df`; v0.21.4 stays on
`23ff62c`; v0.21.3 stays on `3d92a91`; v0.21.2 stays on
`f23df3b`; v0.21.1 stays on `daa655a`; v0.21.0 stays on
`04f9bf9`; v0.20.0 stays on `41a009a`.
- **Tags on origin:** `v0.9.0` through `v0.21.3`. v0.21.3 is on
`3d92a91`; v0.21.2 stays on `f23df3b`; v0.21.1 stays on
`daa655a`; v0.21.0 stays on `04f9bf9`; v0.20.0 stays on
`41a009a`.
## Since the v0.21.4 cut
## Since the v0.21.6 cut
- **`fe68861` — `feat(replay): add quarter-mark notches to scrub
bar`.** First finite step toward B-2's screen-takeover layout.
Five 1px vertical ticks at 0/25/50/75/100 % give the player
visual anchor points without needing to mentally bisect the
bar. Pure helper `scrub_notch_positions()` returns the fixed
array; spawn loop lives next to the WIN MOVE marker spawn so
the lifecycles match. Notches paint in `BORDER_SUBTLE`
(matches unfilled-track colour) and rely on extending past the
1px track (5px tall, anchored 2px above track top) for
visibility — same trick the WIN MOVE marker uses. Spawned
*after* the WIN MOVE marker so a notch and the marker landing
on the same percentage paint the marker on top. Mirrors the
notch ladder in `docs/ui-mockups/replay-overlay-mobile.html`.
4 new tests; 1228 → 1232.
- **`d322abf` — `feat(replay): add percentage labels under
scrub-bar notches`.** First **layout-changing** commit in B-2's
screen-takeover arc. Banner height grew from 60 → 76 px to make
room for a 16 px label row beneath the 1 px scrub track; the
top row's `flex_grow: 1.0` still consumes the same 59 px so no
ripples on existing content. Pure helper `scrub_notch_labels()`
returns the fixed `["0%", "25%", "50%", "75%", "100%"]` array,
paired index-for-index with `scrub_notch_positions()`. Spawn
loop applies an "endpoints flush, middle three percent-anchored"
positioning pattern (Bevy 0.18 UI has no clean
`translate-x: -50%` primitive, so endpoints flush against
banner edges and middle three accept slight right-of-notch
offset). Label colour is `TEXT_SECONDARY` (mockup's
`BORDER_SUBTLE` reads as too low-contrast at 12 px against
`BG_ELEVATED_HI`). 4 new tests; 1232 → 1236.
- **`1873b3f` — `feat(replay): add keybind-hint footer to
overlay banner`.** Second layout-changing commit in B-2's arc.
Banner grew from 76 → 92 px to fit a 16 px footer row at the
bottom edge with a vim-style mode line on the left
(`▌ NORMAL │ replay`) and a keybind-hint on the right
(`[SPACE] pause/resume`). Surfaces the existing Space
accelerator visually so CLAUDE.md §3.3's UI-first contract
holds for keyboard accelerators too. Footer lists *only
wired* keybinds — future commits that wire ESC for stop or
← / → for prev/next will extend the right-hand text in
lockstep. Two pure helpers (`keybind_footer_mode_text`,
`keybind_footer_hint_text`) keep the static text testable;
shared `font_handle_for_labels` clone covers both label and
footer text spawns. 1px top border in `BORDER_SUBTLE`
separates the footer from the labels row. 4 new tests;
1236 → 1240.
- **`90e24d9` — `feat(replay): wire ESC accelerator for stop,
gate pause modal`.** ESC during an active replay now stops it
(mirrors the Stop button click). New `handle_stop_keyboard`
system in `replay_overlay.rs` parallels `handle_pause_keyboard`
in shape. Cross-plugin coordination via `pause_plugin::toggle_pause`:
added a fourth defer-if check
(`replay_state.is_some_and(|s| s.is_playing())`) right after
`other_modal_scrims` and before `selection`. Symmetric to the
existing modal-stack defer pattern. Footer hint extended from
`[SPACE] pause/resume` → `[SPACE] pause/resume · [ESC] stop`
in lockstep with the wiring; the only-wired-keybinds
discipline holds. 3 new tests + 1 updated helper-pin test;
1240 → 1243.
- **`23902cd` — `feat(replay): HC-mode coverage for
keybind-footer top border`.** Tag the footer's border-carrying
Node with `HighContrastBorder::with_default(BORDER_SUBTLE)` so
the existing `apply_high_contrast_borders` system bumps the
1 px top border from `#505050` → `#a0a0a0` under HC mode.
Footer text colours don't need bumps —
`TEXT_SECONDARY` (`#a0a0a0`) is already at `BORDER_SUBTLE_HC`
luminance by design (no `TEXT_SECONDARY_HC` constant exists).
The 1 px scrub track, notch ticks, and WIN MOVE marker render
via `BackgroundColor` (not `BorderColor`) so the marker
doesn't apply — HC coverage for those would need a
settings-aware paint system (precedent: `radial_rim_outline`
in `radial_menu`) and is deferred. 1 new test; 1243 → 1244.
- **`e5c4f51` — `feat(replay): wire ← / → keyboard accelerators
for paused stepping`.** New `step_backwards_replay_playback`
in `replay_playback.rs` decrements the cursor and dispatches
`UndoRequestEvent`; the game's `handle_undo` reads it next
frame to reverse its most-recent move — hooking the existing
undo system rather than replaying forward from cursor 0
(every replay-applied move pushes to the undo stack the same
way a player move would, so undo is the right reversal
primitive). Both arrow keys are paused-only via the same
destructure-gate pattern the forward step uses. Footer hint
extended in lockstep:
`[SPACE] pause/resume · [ESC] stop · [← →] step`. Footer
reads "step" not the mockup's "scrub" — single-move step is
what's wired; continuous scrub would need a key-held event
source. `ReplayOverlayPlugin` gains
`add_message::<UndoRequestEvent>()` defensively. 6 new tests
(2 hint pins + 4 keyboard scenarios) + 1 updated helper-pin
test; 1244 → 1250 total tests, 1249 passing.
No threads in flight. Working tree clean as of 2026-05-08. New
work since the cut would land here as commit narratives; for
the v0.21.6 contents themselves, see `CHANGELOG.md` § [0.21.6].
**Pre-existing flake noted (verified):**
`daily_challenge_plugin::tests::
check_system_fires_warning_event_only_once_per_day` is
time-dependent — fails when wall-clock UTC is within 30
minutes of midnight (the daily-expiry warning window the test
asserts against). Verified pre-existing by stashing all
changes and re-running before commit — failure persisted. Same
shape as the `winnable_seed_search` flake from earlier in the
session. Will pass deterministically when UTC isn't in the
warning window. Not introduced by recent work.
Open next-step menu (Move Log + scrub-UX + keyboard accelerator
coverage + accessibility are all complete):
1. **Mini-tableau preview** — the only remaining major B-2
sub-piece. Mockup shows a 240 px-tall band at 50 % opacity
showing the gameplay tableau peeking through the replay
chrome. Implementation needs to add a settings-aware dim
overlay or alpha modulation on the tableau cards during
replay. Architectural — touches `card_plugin` rendering.
Multi-session.
2. **Move Log auto-scroll** — only relevant if the panel's
row count grows beyond the current 5-row fixed window.
Currently the prev-2 / active / next-2 layout fits all
visible content, so auto-scroll is unneeded. Becomes
relevant if a future commit expands the panel's row
capacity (e.g. 10-row scrolling list).
3. **Polish: notch label centering.** Bevy 0.18 lacks a
clean `translate-x: -50%` primitive so middle three
labels sit slightly right-of-notch. Could use a child
Text wrapper with computed left-margin compensation.
Tiny commit, requires visual review.
4. **Polish: WIN MOVE marker HC bump.** Currently uses
`STATE_SUCCESS` lime which stays visible under HC, but a
contrast bump under HC would make it even more legible
alongside the bumped notches. Optional.
Banner geometry is now mutable — every prior B-2 commit fit
inside fixed 60 px space, but the notch-labels commit
established the "grow the container, add a new flex-column
child" precedent and the keybind-footer commit applied it
again. The next sub-pieces need significantly more vertical
room and follow the same shape.
Next finite step on B-2: keyboard accelerator coverage is now
complete (`Space` / `Esc` / `` / ``). Remaining choices:
1. **HC-mode coverage for the scrub-track / notch ticks /
WIN MOVE marker.** These render via `BackgroundColor` (not
`BorderColor`) so `HighContrastBorder` doesn't apply.
Pattern would mirror `radial_menu::radial_rim_outline` —
per-frame paint reading `Settings::high_contrast_mode`.
Small commit, accessibility-progressing.
2. **Continuous scrub on key-held ← / →** instead of
single-move step. Needs a key-held event source (or
accumulator timer in the keyboard handler). Medium scope;
matches the mockup's `[← →] scrub` terminology.
3. **Move-log scroller / mini-tableau preview** — both need a
much larger banner-height grow (effectively the takeover
container itself). Bigger arcs; the natural place to land
the layout reflow that turns the banner into a takeover.
4. **Cut a v0.21.5 patch release** rolling up the four
post-cut commits (`fe68861`, `d322abf`, `1873b3f`,
`90e24d9`, `23902cd`, `e5c4f51`) under the through-line
"replay-overlay scrubbing affordances + accessibility."
Coherent narrative; six commits is a normal-sized patch
bundle for this project.
Recommended order: option 4 (cut release) is a clean next
boundary — six commits with a clear through-line is the right
size to bundle. Option 1 (HC paint for decorative pieces) is
the smallest next-feature commit if continuing past the cut.
Recommended order: option 1 (mini-tableau preview) is the
big remaining piece that closes B-2 — best tackled in a
fresh session because it crosses into `card_plugin`. Options
3 and 4 are visual polish that benefit from user review.
## Open punch list
@@ -233,30 +120,22 @@ palette refresh all shipped in v0.20.0 + v0.21.0. What stays open:
shipped in v0.21.2 (`2fb2d63`). The WIN MOVE scrub-bar marker
shipped post-v0.21.3 in `ab857bb` (data field) + `52befa6`
(UI). Playback controls (pause / resume / step + Space
accelerator) shipped post-v0.21.3 in `fbe48ac`. Quarter-mark
scrub notches (5 ticks at 0/25/50/75/100 %) shipped
post-v0.21.4 in `fe68861` — first decoration step toward the
takeover layout. Percentage labels under each notch shipped
post-v0.21.4 in `d322abf` — first **layout-changing** commit
(banner 60 → 76 px). Keybind-hint footer shipped in `1873b3f`
(banner 76 → 92 px — vim-style mode line + `[SPACE]
pause/resume`). ESC accelerator wiring (with cross-plugin
gate in `pause_plugin::toggle_pause`) shipped in `90e24d9`.
HC-mode coverage for the footer's top border shipped in
`23902cd`. ← / → keyboard accelerators for paused stepping
shipped in `e5c4f51` (hooks the existing undo system for
backwards step; footer extended to
`[SPACE] pause/resume · [ESC] stop · [← →] step`). Banner
geometry is mutable; keyboard accelerator coverage is
complete. What still needs to land: HC-mode coverage for
the scrub-track / notches / WIN MOVE marker (they render
via `BackgroundColor` so the `HighContrastBorder` marker
doesn't apply — needs a settings-aware paint), continuous
scrub on key-held ← / → (vs single-step), then the bigger
pieces — a move-log scroller and a mini-tableau preview —
both screen-takeover-only pieces that need a much larger
banner height grow (effectively the takeover container
itself). Multi-session.
accelerator) shipped post-v0.21.3 in `fbe48ac`. v0.21.5
bundled six more commits under "replay-overlay scrubbing
affordances + accessibility" (scrub notches + labels +
keybind footer + ESC and ← / → accelerators + HC border).
v0.21.6 bundled six more under "Move Log panel + scrub-UX
polish" — bottom-edge Move Log panel with prev/active/next
rows + active highlight, HC-mode coverage for the scrub
track + notches, continuous scrub on key-held arrows. Banner
height grew 60 → 76 → 92 px across two layout-changing
commits in v0.21.5; Move Log panel grew 56 → 84 → 112 px
across the v0.21.6 move-log commits. Per-commit detail in
`CHANGELOG.md` § [0.21.5] and § [0.21.6]. The only major
B-2 piece left is the mini-tableau preview — the mockup's
"Game Peek Band" at 50 % opacity. Architectural; touches
`card_plugin` rendering.
Multi-session.
- *Floating `MOVE N/M` chip above the focused card during
playback — closed 2026-05-08 by `2fb2d63`.* World-space
`Text2d` entity sibling to the banner overlay; uses the same
@@ -399,27 +278,23 @@ into a v0.21.1 / v0.22.0 cut.
```
You are a senior Rust + Bevy developer working on Solitaire Quest.
Working directory: <Rusty_Solitaire clone path on this machine>.
Branch: master. v0.21.4 is tagged at 23ff62c (cut 2026-05-08, a
patch release rolling up replay-scrubbing accessibility: WIN MOVE
marker on the scrub bar, pause / resume / step playback controls
with a Space keyboard accelerator, and the additive
`Replay::win_move_index: Option<usize>` data field that makes the
marker possible). v0.21.3 stays at 3d92a91, v0.21.2 at f23df3b,
v0.21.1 at daa655a, v0.21.0 at 04f9bf9. Working tree clean. See
CHANGELOG.md § [0.21.4] for full detail.
Branch: master. v0.21.6 is tagged at f63db76 (cut 2026-05-08, a
patch release rolling up Move Log panel + scrub-UX polish:
brand-new bottom-edge Move Log panel with prev / active / next
row context + active-row highlight, plus HC-mode coverage for
scrub track + notches and continuous scrub on key-held arrow
keys). v0.21.5 stays at a2432df, v0.21.4 at 23ff62c, v0.21.3
at 3d92a91, v0.21.2 at f23df3b, v0.21.1 at daa655a, v0.21.0 at
04f9bf9. Working tree clean. See CHANGELOG.md § [0.21.6] for
full detail.
State: HEAD locally — see `git rev-parse HEAD`. Post-cut HEAD is
`e5c4f51` (six carved-out commits on top of v0.21.4 — scrub-bar
notches `fe68861`, notch labels `d322abf`, keybind-hint footer
`1873b3f`, ESC accelerator + pause-modal gate `90e24d9`, HC
marker for footer border `23902cd`, ← / → keyboard accelerators
`e5c4f51`). Workspace tests: 1250 total / 1249 passing / 1
pre-existing time-dependent flake (clock-near-midnight; verified
not introduced by recent work). Clippy clean.
State: HEAD locally — see `git rev-parse HEAD`. The cut commit
is f63db76; any post-cut docs edits ride on top of that.
Workspace tests: 1273 passing / 0 failing. Clippy clean.
READ FIRST (in order, before doing anything):
1. SESSION_HANDOFF.md — this file
2. CHANGELOG.md — [0.21.4] section is the most recent cut
2. CHANGELOG.md — [0.21.6] section is the most recent cut
3. CLAUDE.md — unified-3.0 rule set
4. CLAUDE_SPEC.md — formal architecture spec
5. ARCHITECTURE.md — crate responsibilities + data flow
@@ -439,38 +314,22 @@ DECISION TO ASK THE PLAYER FIRST:
tests can't catch. Likely surfaces JNI ClipboardManager
and Android Keystore stubs that need real bridges. Larger
scope; needs an Android device or emulator running.
B. Replay-overlay screen-takeover redesign — multi-session
work. Three sub-pieces shipped in v0.21.4: WIN MOVE
marker (data field + UI) and pause / step / Space
playback controls. The smaller floating-MOVE-chip piece
shipped in v0.21.2 (`2fb2d63`). Post-v0.21.4: scrub
notches `fe68861`, notch labels `d322abf` (banner
60 → 76 px), keybind-hint footer `1873b3f` (banner
76 → 92 px), ESC accelerator + cross-plugin gate
`90e24d9`, HC-mode coverage for the footer top border
`23902cd`, and ← / → keyboard accelerators for paused
stepping `e5c4f51` (hooks the game's undo system for
backwards step; footer extended to
`[SPACE] pause/resume · [ESC] stop · [← →] step`).
Keyboard accelerator coverage is complete. Natural next
finite steps:
1. **Cut a v0.21.5 patch release** rolling up the six
post-cut commits under "replay-overlay scrubbing
affordances + accessibility." Coherent narrative;
clean release boundary.
2. **HC-mode coverage** for the scrub-track / notches /
WIN MOVE marker (render via `BackgroundColor` not
`BorderColor`, so `HighContrastBorder` doesn't apply
— needs a settings-aware paint, precedent
`radial_rim_outline`). Small commit.
3. **Continuous scrub on key-held ← / →** instead of
single-step. Needs a key-held event source. Matches
the mockup's `[← →] scrub` terminology.
4. **Move-log scroller / mini-tableau preview** — both
need a much larger banner-height grow (effectively
the takeover container itself). Multi-session arcs
that close B-2.
Mockup at `docs/ui-mockups/replay-overlay-mobile.html`.
B. Replay-overlay screen-takeover redesign — nearly complete
after 12 commits across v0.21.4-6. Scrub bar with notches
+ labels + WIN MOVE marker, pause / resume / step / stop
buttons, Space + Esc + ← / → keyboard accelerators with
continuous scrub on hold, keybind-hint footer, full HC-mode
coverage on the banner pieces, and a brand-new bottom-edge
Move Log panel with a 5-row prev/active/next window all
ship. The only remaining major B-2 sub-piece is the
**mini-tableau preview** — the mockup's "Game Peek Band"
at 50 % opacity showing the tableau through the replay
chrome. Implementation needs a settings-aware dim overlay
or alpha modulation on the tableau cards during replay.
Architectural — touches `card_plugin` rendering. Best
tackled in a fresh session because it crosses into a
plugin the recent B-2 work hasn't touched. Mockup at
`docs/ui-mockups/replay-overlay-mobile.html`.
C. Phase 8 (sync) — local storage scaffolding, self-hosted
Axum server, `SolitaireServerClient` impl, GPGS stub
wired into Settings. The biggest open arc by scope; rolls
File diff suppressed because it is too large Load Diff
+38 -1
View File
@@ -34,7 +34,8 @@ use crate::ui_modal::{
};
use crate::ui_tooltip::Tooltip;
use crate::ui_theme::{
BG_BASE, BG_ELEVATED, BG_ELEVATED_HI, BORDER_SUBTLE, BORDER_SUBTLE_HC, HighContrastBorder,
BG_BASE, BG_ELEVATED, BG_ELEVATED_HI, BORDER_SUBTLE, BORDER_SUBTLE_HC, HighContrastBackground,
HighContrastBorder,
RADIUS_SM, SPACE_2, STATE_SUCCESS, TEXT_PRIMARY, TEXT_SECONDARY, TYPE_BODY, TYPE_BODY_LG,
TYPE_CAPTION, VAL_SPACE_2, VAL_SPACE_3, Z_MODAL_PANEL,
};
@@ -365,6 +366,7 @@ impl Plugin for SettingsPlugin {
update_color_blind_text,
update_high_contrast_text,
update_high_contrast_borders,
update_high_contrast_backgrounds,
update_reduce_motion_text,
update_tooltip_delay_text,
update_time_bonus_multiplier_text,
@@ -674,6 +676,41 @@ fn update_high_contrast_borders(
}
}
/// Repaints `BackgroundColor` on every entity tagged with
/// [`HighContrastBackground`] based on `Settings::high_contrast_mode`.
/// Off → the marker's `default_color`; on → `BORDER_SUBTLE_HC`
/// (`#a0a0a0`). Compares against the current background and only
/// mutates when different so Bevy's change-detection doesn't trigger
/// repaints every frame.
///
/// Parallel to [`update_high_contrast_borders`]. Same on/off rule,
/// same change-suppression idiom, different colour channel —
/// `BackgroundColor` for tick marks, decorative strips, fine
/// separators that paint their shape directly rather than via a
/// `BorderColor` on a wider Node.
///
/// Tagged sites in v0.21.x: the replay overlay's 1 px scrub track
/// + 5 quarter-mark notch ticks (`replay_overlay::spawn_overlay`).
///
/// More sites can be tagged in follow-ups by adding
/// `HighContrastBackground::with_default(...)` to their spawn tuple.
pub(crate) fn update_high_contrast_backgrounds(
settings: Res<SettingsResource>,
mut backgrounds: Query<(&HighContrastBackground, &mut BackgroundColor)>,
) {
let high_contrast = settings.0.high_contrast_mode;
for (marker, mut bg) in backgrounds.iter_mut() {
let target = if high_contrast {
BORDER_SUBTLE_HC
} else {
marker.default_color
};
if bg.0 != target {
*bg = BackgroundColor(target);
}
}
}
fn update_reduce_motion_text(
settings: Res<SettingsResource>,
mut text_nodes: Query<&mut Text, With<ReduceMotionText>>,
+29
View File
@@ -252,6 +252,35 @@ impl HighContrastBorder {
}
}
/// Marker for entities whose [`BackgroundColor`] should swap to
/// [`BORDER_SUBTLE_HC`] when `Settings::high_contrast_mode` is on.
/// Parallel to [`HighContrastBorder`] but for sites that paint their
/// shape via `BackgroundColor` rather than `BorderColor` —
/// `bevy::ui` 1 px decorative strips, tick marks, fine separators
/// often render as tiny full-bleed `Node`s, not as borders, so the
/// border-marker pattern doesn't apply.
///
/// `default_color` records the off-state colour the entity was
/// spawned with so the system can revert when HC is toggled back
/// off. The accompanying paint system is
/// [`update_high_contrast_backgrounds`](crate::settings_plugin::update_high_contrast_backgrounds).
///
/// [`BackgroundColor`]: bevy::prelude::BackgroundColor
#[derive(bevy::prelude::Component, Debug, Clone, Copy)]
pub struct HighContrastBackground {
/// Background colour to use when high-contrast mode is *off* —
/// the site's normal idle / active-state colour.
pub default_color: bevy::prelude::Color,
}
impl HighContrastBackground {
/// Convenience constructor —
/// `HighContrastBackground::with_default(BORDER_SUBTLE)`.
pub const fn with_default(default_color: bevy::prelude::Color) -> Self {
Self { default_color }
}
}
/// Strong border — hover outline, focused button, active popover.
/// `outline` from the design system. `#505050`.
pub const BORDER_STRONG: Color = Color::srgba(0.314, 0.314, 0.314, 1.0);