Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a2432dfe7a | |||
| 511550232c | |||
| e5c4f51a6e | |||
| 23902cdc44 | |||
| 3cc8eacafa | |||
| 90e24d9711 | |||
| decbe0bbd9 | |||
| 1873b3f9be | |||
| d11d97e677 | |||
| d322abf67b | |||
| c9e4c0b4cd | |||
| fe68861e10 | |||
| c33b39cf11 |
+149
-1
@@ -6,9 +6,157 @@ project follows [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
No threads in flight. v0.21.4 cut on 2026-05-08; CHANGELOG accumulates
|
||||
No threads in flight. v0.21.5 cut on 2026-05-08; CHANGELOG accumulates
|
||||
the next cycle here.
|
||||
|
||||
## [0.21.5] — 2026-05-08
|
||||
|
||||
Patch release for the post-v0.21.4 work. One through-line:
|
||||
**replay-overlay scrubbing affordances + accessibility**. v0.21.4
|
||||
shipped pause / resume / step + the WIN MOVE marker as the first
|
||||
*scrubbing-shaped* additions to the replay overlay; v0.21.5
|
||||
fills out the rest of the scrubbing UX so the player has both
|
||||
visual anchor points (notches + labels) and a complete keyboard
|
||||
control surface (Space / Esc / ← / →) for navigating a paused
|
||||
replay.
|
||||
|
||||
Two of the six commits in this cycle are layout-changing — they
|
||||
grow the banner height from 60 px → 76 px → 92 px to make room
|
||||
for the notch labels and keybind footer. Banner geometry was
|
||||
fixed for every prior B-2 commit; this release establishes the
|
||||
"grow the container, add a flex-column child" pattern that the
|
||||
remaining B-2 sub-pieces (move-log scroller, mini-tableau
|
||||
preview) will inherit when they land.
|
||||
|
||||
### Added
|
||||
|
||||
- **Quarter-mark scrub-bar notches** (`fe68861`). Five 1 px
|
||||
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 sits next to the WIN MOVE marker spawn so
|
||||
the lifecycles match. Notches paint in `BORDER_SUBTLE` (same
|
||||
as the unfilled track) and rely on extending past the 1 px
|
||||
track (5 px tall, anchored 2 px above the 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.
|
||||
- **Percentage labels under each notch** (`d322abf`). Five
|
||||
`0%` / `25%` / `50%` / `75%` / `100%` labels in a new 16 px
|
||||
row beneath the 1 px scrub track give the player explicit
|
||||
quarter-mark readouts. Banner grew from 60 → 76 px to
|
||||
accommodate the row — first **layout-changing** commit in
|
||||
the B-2 arc. Pure helper `scrub_notch_labels()` returns the
|
||||
fixed array, paired index-for-index with
|
||||
`scrub_notch_positions()`. Spawn loop applies an "endpoints
|
||||
flush, middle three percent-anchored" positioning pattern:
|
||||
leftmost label gets `left: 0`, rightmost gets `right: 0`,
|
||||
middle three anchor at `left: Val::Percent(p)` since Bevy
|
||||
0.18 UI lacks a clean CSS-style `translate-x: -50%`
|
||||
centering primitive. Label colour is `TEXT_SECONDARY`
|
||||
rather than the mockup's `BORDER_SUBTLE` (the latter would
|
||||
match the notches but is too low-contrast against
|
||||
`BG_ELEVATED_HI` to read at 12 px).
|
||||
- **Keybind-hint footer** (`1873b3f`). Vim-style mode line on
|
||||
the left (`▌ NORMAL │ replay`) plus a keybind hint on the
|
||||
right at the bottom edge of the banner. Banner grew from
|
||||
76 → 92 px to fit the 16 px footer row. Surfaces every
|
||||
wired keyboard accelerator visually so CLAUDE.md §3.3's
|
||||
UI-first contract holds for keyboard accelerators too. The
|
||||
footer lists *only* keybinds that are actually wired —
|
||||
the only-wired-keybinds discipline means each release
|
||||
cycle's hint string is a precise honest contract with the
|
||||
player. Two pure helpers (`keybind_footer_mode_text`,
|
||||
`keybind_footer_hint_text`) keep the static text testable.
|
||||
1 px top border in `BORDER_SUBTLE` separates the footer
|
||||
from the labels row.
|
||||
- **ESC keyboard accelerator for replay-stop** (`90e24d9`).
|
||||
New `handle_stop_keyboard` system parallels
|
||||
`handle_pause_keyboard` in shape — fires only when state
|
||||
is `Playing`, calls `stop_replay_playback`. 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 the existing `other_modal_scrims` check so ESC
|
||||
during active replay belongs to the replay overlay, not
|
||||
the pause modal.
|
||||
- **HC-mode coverage for the keybind-footer top border**
|
||||
(`23902cd`).
|
||||
`HighContrastBorder::with_default(BORDER_SUBTLE)` marker
|
||||
on the footer's border-carrying Node so the existing
|
||||
`apply_high_contrast_borders` system bumps the 1 px top
|
||||
border from `#505050` → `#a0a0a0` when
|
||||
`Settings::high_contrast_mode` is on. Without the marker
|
||||
the footer reads as floating loose under HC because the
|
||||
border that anchors it to the labels row is
|
||||
near-invisible.
|
||||
- **← / → keyboard accelerators for paused stepping**
|
||||
(`e5c4f51`). 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. Hooks the
|
||||
existing undo system rather than replaying-forward-from-
|
||||
zero — 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.
|
||||
The mockup labels these `[← →] scrub`; single-move step
|
||||
is the closest behaviour shippable today, so the footer
|
||||
hint reads `[← →] step` — only-wired-keybinds discipline.
|
||||
|
||||
### Changed
|
||||
|
||||
- **Banner height grew 60 → 76 → 92 px** across two
|
||||
layout-changing commits (`d322abf` then `1873b3f`). Top
|
||||
row's `flex_grow: 1.0` still consumes 59 px so the
|
||||
existing content (label / progress chip / buttons) has
|
||||
the same vertical space; the new rows (16 px labels +
|
||||
16 px footer) extend the banner downward into the
|
||||
gameplay area. Banner geometry is now mutable — every
|
||||
prior B-2 commit fit inside fixed 60 px space.
|
||||
- **Keybind-footer hint text grew alongside the wirings**:
|
||||
`[SPACE] pause/resume` →
|
||||
`[SPACE] pause/resume · [ESC] stop` →
|
||||
`[SPACE] pause/resume · [ESC] stop · [← →] step`.
|
||||
- **`pause_plugin::toggle_pause` now defers when a replay
|
||||
is active** (`90e24d9`). Adds a fourth defer-if check to
|
||||
the existing modal-stack pattern.
|
||||
- **`ReplayOverlayPlugin` registers
|
||||
`add_message::<UndoRequestEvent>()`** (`e5c4f51`).
|
||||
Defensive registration so the plugin runs cleanly under
|
||||
`MinimalPlugins` without `GamePlugin` attached.
|
||||
|
||||
### Documentation
|
||||
|
||||
- `SESSION_HANDOFF.md` refreshed five times this cycle.
|
||||
The B option in the Resume menu now traces the full arc:
|
||||
notches → labels → footer → ESC → HC → arrow keys.
|
||||
- The pre-existing `daily_challenge` warning test that
|
||||
fails when wall-clock UTC is within 30 minutes of
|
||||
midnight is documented in this cycle's handoff. Same
|
||||
shape as the earlier `winnable_seed_search` flake —
|
||||
time-dependent, deterministically passes outside the
|
||||
trigger window.
|
||||
|
||||
### Stats
|
||||
|
||||
- **1250 total tests / 1249 passing / 1 pre-existing
|
||||
time-dependent flake** across the workspace (net +22 from
|
||||
v0.21.4's 1228 baseline):
|
||||
- 4 from `fe68861` (scrub-notch coverage)
|
||||
- 4 from `d322abf` (notch-label coverage)
|
||||
- 4 from `1873b3f` (keybind-footer coverage)
|
||||
- 3 from `90e24d9` (ESC-accelerator coverage)
|
||||
- 1 from `23902cd` (HC-marker coverage)
|
||||
- 6 from `e5c4f51` (arrow-keyboard coverage)
|
||||
- **Pre-existing flake**:
|
||||
`daily_challenge_plugin::tests::check_system_fires_warning_event_only_once_per_day`
|
||||
fails when wall-clock UTC is within 30 minutes of
|
||||
midnight. Verified pre-existing by stash-and-retest
|
||||
before each commit. Will pass deterministically outside
|
||||
the trigger window. Not introduced by this release.
|
||||
- Clippy clean across the workspace.
|
||||
|
||||
## [0.21.4] — 2026-05-08
|
||||
|
||||
Patch release for the post-v0.21.3 work. One through-line:
|
||||
|
||||
+246
-93
@@ -1,91 +1,198 @@
|
||||
# Solitaire Quest — Session Handoff
|
||||
|
||||
**Last updated:** 2026-05-08 — **v0.21.3 cut and tagged at
|
||||
`3d92a91`**, working tree clean, all post-tag work pushed to
|
||||
**Last updated:** 2026-05-08 — **v0.21.4 cut and tagged at
|
||||
`23ff62c`**, working tree clean, all post-tag work pushed to
|
||||
origin.
|
||||
|
||||
v0.21.3 is a patch release with one through-line: **accessibility
|
||||
arc closure**. v0.21.2 explicitly carved out "dynamic-paint sites"
|
||||
(HUD action buttons, modal buttons, radial menu rim) on the
|
||||
assumption that their existing paint cycles would race the
|
||||
central `update_high_contrast_borders` system. v0.21.3 walks the
|
||||
actual code, finds the carve-out was over-cautious, and closes
|
||||
it. Bonus: the first real consumer of `ToastVariant::Warning`
|
||||
also lands here, making the `ToastVariant` enum fully load-bearing
|
||||
(every variant has at least one driver).
|
||||
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).
|
||||
|
||||
Full v0.21.3 detail lives in `CHANGELOG.md` § [0.21.3]. This
|
||||
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.
|
||||
|
||||
Full v0.21.4 detail lives in `CHANGELOG.md` § [0.21.4]. 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
|
||||
`3d92a91`; post-cut work on B-2 (`ab857bb` data field +
|
||||
`52befa6` WIN MOVE marker UI + `fbe48ac` playback controls)
|
||||
rides on top of that.
|
||||
- **HEAD on origin:** matches local. v0.21.3 is fully on origin.
|
||||
`23ff62c`; any post-cut docs edits ride on top of that.
|
||||
- **HEAD on origin:** matches local. v0.21.4 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:** **1228 passing / 0 failing** across the workspace
|
||||
(1207 from v0.21.3's stats + 5 from `ab857bb`'s
|
||||
`win_move_index` coverage + 8 from `52befa6`'s WIN MOVE marker
|
||||
pure-helper truth-table + spawn lifecycle + 8 from `fbe48ac`'s
|
||||
pause / step / keyboard accelerator coverage).
|
||||
- **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
|
||||
`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.3 cut
|
||||
## Since the v0.21.4 cut
|
||||
|
||||
- **`ab857bb` — `Replay::win_move_index` data field landed.**
|
||||
First finite step toward the B-2 replay screen-takeover
|
||||
redesign. Additive optional `Option<usize>` on `Replay` with
|
||||
`#[serde(default)]` so older `latest_replay.json` /
|
||||
`replays.json` files load unchanged (no schema bump). Populated
|
||||
at the live recording site via a new `with_win_move_index`
|
||||
builder; for fresh recordings the value is always
|
||||
`Some(moves.len() - 1)` because recording freezes on win, but
|
||||
storing it explicitly lets the playback UI read the WIN MOVE
|
||||
position directly without re-deriving on every render. 5 new
|
||||
tests (1207 → 1212): default, builder set / set-None, on-disk
|
||||
round-trip, legacy-JSON-loads-with-None backward-compat.
|
||||
- **`52befa6` — WIN MOVE marker on the scrub bar.** Second
|
||||
commit on B-2 — the UI that consumes the data field. New
|
||||
`ReplayOverlayWinMoveMarker` component spawned as a sibling
|
||||
to `ReplayOverlayScrubFill` under the 1px scrub track,
|
||||
absolute-positioned at `replay.win_move_index / total` along
|
||||
the bar. Painted in `STATE_SUCCESS` (green) so the marker
|
||||
reads as "this is where the win lives." Pure helper
|
||||
`win_move_marker_pct` returns `None` for any state where the
|
||||
marker shouldn't draw (Inactive, Completed, replay missing
|
||||
the field, empty move list); percentage clamps to `[0, 100]`
|
||||
defensively. Lifecycle is spawn-time only — the marker is
|
||||
immutable during a single playback because the underlying
|
||||
`Replay` doesn't change while `Playing`. Despawned with the
|
||||
overlay tree on transition back to `Inactive`. 8 new tests
|
||||
(1212 → 1220): pure-helper truth table + spawn-presence /
|
||||
spawn-absence / despawn-lifecycle observables.
|
||||
- **`fbe48ac` — playback controls (pause / resume / step).**
|
||||
Third commit on B-2. New `paused: bool` field on
|
||||
`ReplayPlaybackState::Playing`; `tick_replay_playback` skips
|
||||
the `secs_to_next` decrement entirely while paused so cursor
|
||||
and timer freeze together. New public API:
|
||||
`toggle_pause_replay_playback` and `step_replay_playback`
|
||||
(the latter hard-gated to `Playing { paused: true }` so
|
||||
manual stepping can't race the tick loop). UI: Pause /
|
||||
Resume button (label repaints reactively via
|
||||
`update_pause_button_label` which walks `Children` from
|
||||
marker to inner `Text`) + Step button + Space keyboard
|
||||
accelerator. Existing 25 `Playing { ... }` construction
|
||||
sites across tests gained `paused: false` mechanically.
|
||||
8 new tests (1220 → 1228): label truth table, label repaint
|
||||
on state change, click-toggles-paused, step advances exactly
|
||||
one cursor with paused preserved, step-while-running no-op,
|
||||
Space toggles paused.
|
||||
- **`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.
|
||||
|
||||
**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.
|
||||
|
||||
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.
|
||||
|
||||
## Open punch list
|
||||
|
||||
@@ -126,11 +233,30 @@ 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`. What still
|
||||
needs to land: a move-log scroller and a mini-tableau
|
||||
preview — both screen-takeover-only pieces that need a
|
||||
larger layout reflow than the existing banner can carry.
|
||||
Multi-session.
|
||||
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.
|
||||
- *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
|
||||
@@ -273,20 +399,27 @@ 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.3 is tagged at 3d92a91 (cut 2026-05-08, a
|
||||
patch release rolling up the accessibility-arc closure: HC reaches
|
||||
the previously-carved-out dynamic-paint sites, and the first real
|
||||
consumer of `ToastVariant::Warning` lands as the daily-challenge
|
||||
expiry toast). v0.21.2 stays at f23df3b, v0.21.1 at daa655a,
|
||||
v0.21.0 at 04f9bf9. Working tree clean. See CHANGELOG.md §
|
||||
[0.21.3] for full detail.
|
||||
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.
|
||||
|
||||
State: HEAD locally — see `git rev-parse HEAD`. All workspace tests
|
||||
pass (1207+; check with `cargo test --workspace`), clippy clean.
|
||||
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.
|
||||
|
||||
READ FIRST (in order, before doing anything):
|
||||
1. SESSION_HANDOFF.md — this file
|
||||
2. CHANGELOG.md — [0.21.3] section is the most recent cut
|
||||
2. CHANGELOG.md — [0.21.4] 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
|
||||
@@ -307,17 +440,37 @@ DECISION TO ASK THE PLAYER FIRST:
|
||||
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 post-v0.21.3: WIN MOVE
|
||||
marker (`ab857bb` data field + `52befa6` UI), playback
|
||||
controls (`fbe48ac` pause/resume/step + Space). What
|
||||
still needs to land: a move-log scroller and a
|
||||
mini-tableau preview — both layout-heavy pieces that need
|
||||
more vertical real estate than the current banner-only
|
||||
overlay carries, so the natural next finite step is the
|
||||
screen-takeover layout itself (mockup at
|
||||
`docs/ui-mockups/replay-overlay-mobile.html`). The
|
||||
smaller floating-MOVE-chip piece shipped in v0.21.2
|
||||
(`2fb2d63`).
|
||||
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`.
|
||||
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
|
||||
|
||||
@@ -30,6 +30,7 @@ use crate::events::{
|
||||
use crate::font_plugin::FontResource;
|
||||
use crate::game_plugin::{GameOverScreen, GameStatePath};
|
||||
use crate::progress_plugin::ProgressResource;
|
||||
use crate::replay_playback::ReplayPlaybackState;
|
||||
use crate::resources::{DragState, GameStateResource};
|
||||
use crate::selection_plugin::{SelectionKeySet, SelectionState};
|
||||
use crate::settings_plugin::{SettingsChangedEvent, SettingsResource, SettingsStoragePath};
|
||||
@@ -154,6 +155,7 @@ fn toggle_pause(
|
||||
mut drag: Option<ResMut<DragState>>,
|
||||
mut changed: MessageWriter<StateChangedEvent>,
|
||||
selection: Option<Res<SelectionState>>,
|
||||
replay_state: Option<Res<ReplayPlaybackState>>,
|
||||
) {
|
||||
let PauseModalQueries {
|
||||
pause_screens: screens,
|
||||
@@ -184,6 +186,15 @@ fn toggle_pause(
|
||||
if !other_modal_scrims.is_empty() {
|
||||
return;
|
||||
}
|
||||
// If a replay is currently playing, let `replay_overlay::handle_stop_keyboard`
|
||||
// own the Esc press — that handler stops the replay. Without this guard a
|
||||
// single Esc both stops the replay AND opens the pause modal on top of the
|
||||
// (now empty) board, leaving the player on a screen they didn't ask for.
|
||||
// The HUD-button path is gated too; clicking Pause while watching a replay
|
||||
// is almost always an accident.
|
||||
if replay_state.is_some_and(|s| s.is_playing()) {
|
||||
return;
|
||||
}
|
||||
// If a card is currently selected, let SelectionPlugin handle this Escape
|
||||
// (it will clear the selection). Pause must not also open in the same frame.
|
||||
if selection.is_some_and(|s| s.selected_pile.is_some()) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -42,7 +42,7 @@
|
||||
use bevy::prelude::*;
|
||||
use solitaire_data::{Replay, ReplayMove};
|
||||
|
||||
use crate::events::{DrawRequestEvent, MoveRequestEvent, StateChangedEvent};
|
||||
use crate::events::{DrawRequestEvent, MoveRequestEvent, StateChangedEvent, UndoRequestEvent};
|
||||
use crate::game_plugin::{GameMutation, RecordingReplay};
|
||||
use crate::resources::GameStateResource;
|
||||
use crate::settings_plugin::SettingsResource;
|
||||
@@ -284,6 +284,52 @@ pub fn step_replay_playback(
|
||||
true
|
||||
}
|
||||
|
||||
/// Steps the replay **backwards** by exactly one move while paused.
|
||||
///
|
||||
/// Strategy: the live game's undo system is the source of truth for
|
||||
/// reversing moves. Every move the replay forward-stepped (via
|
||||
/// [`step_replay_playback`] or the auto-advance loop in
|
||||
/// [`tick_replay_playback`]) was dispatched as a canonical
|
||||
/// [`MoveRequestEvent`] / [`DrawRequestEvent`], which the game
|
||||
/// applied and pushed onto its undo stack. So a backwards step here
|
||||
/// is simply: decrement the cursor (so the about-to-apply move
|
||||
/// re-points at the one we're rewinding past) and fire an
|
||||
/// [`UndoRequestEvent`] so the game reverses its most-recent move
|
||||
/// next frame.
|
||||
///
|
||||
/// Hard-gated to the paused state via destructure pattern —
|
||||
/// matches the existing [`step_replay_playback`] gate so the
|
||||
/// player can only scrub one direction at a time and the tick
|
||||
/// loop never races a manual rewind.
|
||||
///
|
||||
/// Returns `false` and is a no-op in three cases:
|
||||
/// - State isn't `Playing` (no replay attached).
|
||||
/// - State is `Playing` but not paused (the tick loop owns the cursor).
|
||||
/// - Cursor is already at 0 (nothing to rewind past).
|
||||
///
|
||||
/// Returns `true` on a successful step; the actual game-state
|
||||
/// reversal happens next frame when `handle_undo` reads the
|
||||
/// `UndoRequestEvent`.
|
||||
pub fn step_backwards_replay_playback(
|
||||
state: &mut ResMut<ReplayPlaybackState>,
|
||||
undo_writer: &mut MessageWriter<UndoRequestEvent>,
|
||||
) -> bool {
|
||||
let ReplayPlaybackState::Playing {
|
||||
cursor,
|
||||
paused: true,
|
||||
..
|
||||
} = state.as_mut()
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
if *cursor == 0 {
|
||||
return false;
|
||||
}
|
||||
*cursor -= 1;
|
||||
undo_writer.write(UndoRequestEvent);
|
||||
true
|
||||
}
|
||||
|
||||
/// Tick system. Runs every frame; only does work when
|
||||
/// [`ReplayPlaybackState::is_playing`].
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user