docs: cut v0.19.0 — punch-list close + Wayland + animation polish

Promotes [Unreleased] to [0.19.0]. The release closes v0.18.0's
punch list (async H-key hint, persistent replay share URLs),
expands desktop platform fit (Wayland session support +
monitor-aware default window size), polishes the win-celebration
and double-click animation paths, and clears two test-flake
contributors. The Rusty Pixel pixel-art card theme arc was
prototyped and reverted in the same window — the engine plumbing
(pixel_art ThemeMeta field, PNG manifest face support, second
embedded:// theme channel) was fully reverted and is not part of
this release.

SESSION_HANDOFF.md refreshed to reflect the v0.19.0 ship:
v0.18.0 punch-list items B and D marked shipped; new Open punch
list documents the Rusty Pixel arc as historical, calls out the
desktop-packaging follow-through (app icon next), the
pull_failure_sets_error_status flake (next-round candidate),
and a settings-UI item for the smart-default-size opt-out.
Resume prompt refreshed with the post-v0.19.0 A-D decision menu.

Build: cargo clippy --workspace --all-targets -- -D warnings clean.
Tests: 1170 passing / 0 failing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-05-06 20:06:21 -07:00
parent 6037596cc0
commit 04be091240
2 changed files with 196 additions and 115 deletions
+87 -16
View File
@@ -6,8 +6,20 @@ project follows [Semantic Versioning](https://semver.org/).
## [Unreleased]
Closes the v0.18.0 punch list's items B and D and clears the
`auto_save_writes_after_30_seconds` test flake.
_Nothing yet._
## [0.19.0] — 2026-05-06
Closes the v0.18.0 punch list (items B and D — async hint and
persistent replay share URLs), expands desktop platform fit
(Wayland session support + monitor-aware default window size for
HiDPI / 4K displays), polishes the win-celebration and
double-click animation paths, and clears two test-flake
contributors. A short-lived "Rusty Pixel" pixel-art card theme
was prototyped and reverted in the same window — the engine
plumbing it touched (`pixel_art` field on `ThemeMeta`, PNG
manifest face support, second `embedded://` theme channel) was
fully reverted and is not part of this release.
### Changed
@@ -37,27 +49,86 @@ Closes the v0.18.0 punch list's items B and D and clears the
selector's currently-displayed replay drives the clipboard
contents — each historical win keeps its own URL.
`LastSharedReplayUrl` removed (its role is now subsumed by the
share_url field on the replay record).
`share_url` field on the replay record).
### Added
- **Wayland session support** (`b57db01`). The workspace
`Cargo.toml` Bevy feature list now enables `wayland` alongside
`x11`. winit prefers Wayland when `WAYLAND_DISPLAY` is set on the
session, falling back to X11 when it isn't. Pre-fix, a Wayland
desktop environment fell through to XWayland, rendering the
game inside an X11 frame stitched into the Wayland compositor.
Post-fix, the game opens as a native Wayland surface. Costs a
few hundred KB of binary for the libwayland-client bindings;
cross-distro friendly because winit dlopen-probes the libraries
rather than hard-linking them.
- **Monitor-relative default window size** (`b57db01`). On launches
with no saved geometry, the new
`apply_smart_default_window_size` Update system queries
`Monitor` (with the `PrimaryMonitor` marker) and resizes the
primary window to ~70 % of the monitor's *logical* size on the
first frame. Before, every fresh launch opened at 1280×800
regardless of monitor; on a 4K monitor that's a comparatively
tiny window in one corner. Logical size already accounts for
the OS's HiDPI scale factor, so a Retina display reporting
scale_factor 2.0 yields the same physical inches as a 1080p
display reporting 1.0. Skipped entirely when saved geometry was
applied — the player's chosen size always wins.
### Fixed
- **`auto_save_writes_after_30_seconds` test flake.** The test's
single-frame `app.update()` was sensitive to first-frame
`Time::delta_secs()` variance under heavy parallel cargo-test
load, and to production-disk `~/.local/share/solitaire_quest/game_state.json`
state leaking into the test world via `GamePlugin::build`'s load
path. `test_app` now resets `PendingRestoredGame(None)` after
plugin build (preventing the dev machine's saved-game state from
tripping the auto-save guard) and the test re-arms the timer in a
small bounded loop until the file appears (robust against
- **Duplicate "You Win" toast on game-won** (`55c235b`). The
post-win UI was firing two celebration surfaces: a 4-second
toast banner ("You Win! Score: X Time: Y") on top of the
`win_summary_plugin`'s "You Won!" modal. In screenshots the
toast banner was partially clipped behind the modal card,
peeking out on either side. The toast predated the modal and is
strictly subsumed by it; removed. The cards-fly-off cascade
animation (`MotionCurve::Expressive` per-card rotation drift)
is unchanged — that's the visual celebration, distinct from
the textual celebration the modal owns. `WIN_TOAST_SECS` const
removed.
- **Double-click on a single card with no destination now plays
the reject animation** (`d7ffb16`). `handle_double_click` only
fired `MoveRejectedEvent` for multi-card stacks with no
destination; a double-click on a single card whose top didn't
fit any foundation or tableau slot produced zero feedback —
no `card_invalid.wav`, no source-pile shake. Both priorities'
failure paths now converge on a single rejection at the end of
the double-click branch, so single-card and stack misses get
the same feedback shape as drag-and-drop rejections.
- **Double-click move animation no longer plays twice**
(`6037596`). On a successful double-click, the slide-to-
destination animation rendered twice — once from the move's
`StateChangedEvent` landing, then again from the release's
`end_drag` firing a redundant `StateChangedEvent` mid-slide.
`sync_cards_on_change` saw the card mid-CardAnim (`cur ≠
target`) and replaced the in-flight tween with a fresh one
starting at the mid-position, visibly restarting the slide. The
defensive `StateChangedEvent` write in `end_drag`'s
uncommitted-drag branch is removed; `start_drag` only mutates
`DragState` (never card transforms), so an uncommitted drag
has no visual side effect to undo. The committed-drag branch
keeps its `StateChangedEvent` since real drag snap-backs do
need a resync.
- **`auto_save_writes_after_30_seconds` test flake** (`91b7605`).
The test's single-frame `app.update()` was sensitive to
first-frame `Time::delta_secs()` variance under heavy parallel
cargo-test load, and to production-disk
`~/.local/share/solitaire_quest/game_state.json` state leaking
into the test world via `GamePlugin::build`'s load path.
`test_app` now resets `PendingRestoredGame(None)` after plugin
build (preventing the dev machine's saved-game state from
tripping the auto-save guard) and the test re-arms the timer in
a small bounded loop until the file appears (robust against
first-frame Time variance). No production-code change.
### Stats
- 1170 passing tests (was 1166 at v0.18.0 close — 1 in
`solitaire_data` for share_url backwards-compat, 4 in
`solitaire_engine` for async hint coverage and the persistent
share URL persistence path).
- 1170 passing tests (was 1166 at v0.18.0 close — net +4 from
the persistent share URL backwards-compat test, the three
async-hint tests, minus the dropped synchronous hint tests).
- Zero clippy warnings under `--workspace --all-targets -- -D warnings`.
## [0.18.0] — 2026-05-06
+109 -99
View File
@@ -1,53 +1,59 @@
# Solitaire Quest — Session Handoff
**Last updated:** 2026-05-06 (post-v0.18.0, [Unreleased] accumulating
v0.19.0 candidates) — v0.18.0 tagged + pushed at `bfcd05f`. Three
commits sit on top: the H-key hint moved onto
`AsyncComputeTaskPool` (closing the last synchronous solver hot
path), persistent replay share URLs (no more
in-session-only sharing), and a fix for the
`auto_save_writes_after_30_seconds` test flake.
**Last updated:** 2026-05-06 (post-v0.19.0) — Tagged + pushed at
`6037596`. v0.19.0 closes the v0.18.0 punch list (async H-key hint,
persistent replay share URLs), expands desktop platform fit (Wayland
session support + monitor-aware default window size), polishes the
win-celebration and double-click animation paths, and clears two
test-flake contributors. A short-lived "Rusty Pixel" pixel-art card
theme was prototyped and reverted in the same window.
## Status at pause
- **HEAD on origin:** `42d90b1` (the persistent share-link
commit). Local HEAD is one ahead at `91b7605` (auto-save flake
fix), with this round's `[Unreleased]` doc refresh staged on
top.
- **HEAD on origin:** `6037596` (post-tag commit; the tag itself
points at this commit).
- **Working tree:** modified — `CHANGELOG.md` and
`SESSION_HANDOFF.md` carry the `[Unreleased]` doc updates.
`SESSION_HANDOFF.md` carry the v0.19.0 promotion + this refresh,
ready to commit.
- **Build:** `cargo clippy --workspace --all-targets -- -D warnings`
clean (verified this session).
- **Tests:** **1170 passing / 0 failing** across the workspace
(verified this session).
`auto_save_writes_after_30_seconds` reverified stable across
three back-to-back runs after the flake fix.
- **Tags on origin:** `v0.9.0` through `v0.18.0`.
- **CHANGELOG:** `[Unreleased]` populated with the three
post-v0.18.0 commits — promote to `[0.19.0]` whenever the
next cut feels right.
(verified this session). One known flake remains:
`solitaire_engine::sync_plugin::tests::pull_failure_sets_error_status`
occasionally fails when cargo-test parallelism starves the
`AsyncComputeTaskPool` within the test's 5-update budget. Same
shape as the auto-save flake before v0.19.0's hardening; could be
fixed similarly with a wall-clock-bounded loop.
- **Tags on origin:** `v0.9.0` through `v0.18.0` (v0.19.0 ready to
push once committed).
## Where we are
v0.18.0's resume-prompt menu (AD) is mostly closed:
v0.18.0's resume-prompt menu (AD) is closed:
- ~~**A — Tag v0.18.0:**~~ shipped at `bfcd05f`.
- ~~**B — Solver-on-`AsyncComputeTaskPool` for the H-key hint:**~~
shipped at `3e11e9e`. New module `pending_hint.rs` carries the
`PendingHintTask` resource and `poll_pending_hint_task` system,
mirroring the `PendingNewGameSeed` pattern.
- **C — Desktop packaging:** unchanged, still gated on artwork +
signing certs from the player.
shipped at `3e11e9e`.
- **C — Desktop packaging:** still gated on artwork + signing
certs. Icon export PNGs (11 sizes, 161024 px) sit in
`artwork/` from the v0.18-era export; not yet wired into the
Bevy window or assembled into `.icns` / `.ico`. App icon is
the first natural step.
- ~~**D — Persistent share link:**~~ shipped at `42d90b1`.
`Replay.share_url: Option<String>` (with `#[serde(default)]`),
Stats overlay's "Copy share link" reads from
`history.0.replays[selected.0].share_url`,
`LastSharedReplayUrl` resource removed.
The `auto_save_writes_after_30_seconds` flake has been fixed at
`91b7605` by clearing `PendingRestoredGame` in the test fixture
and re-arming the timer in a small bounded loop until the file
appears. No production-code change.
The Rusty Pixel theme arc is documented as a sub-history but
not part of v0.19.0's content:
| Commit | Status |
|---|---|
| `de47511` PNG-format thumbnail support | reverted |
| `17e3112` `pixel_art: bool` field + nearest-sampling opt-in | reverted |
| `21ec03b` bundle Rusty Pixel as `embedded://` theme | reverted |
| `aad8bb9` / `e41def8` / `0b3140a` reverts | landed |
The arc remains in commit history for archaeology but the
codebase reaches v0.19.0's HEAD identical to where it would be if
the arc had never landed.
### Design direction (unchanged)
@@ -55,88 +61,92 @@ appears. No production-code change.
satisfying micro-interactions.
- **Palette:** Midnight Purple base + Balatro yellow primary + warm
magenta secondary.
- See `~/.claude/projects/-home-manage-Rusty-Solitare/memory/project_ux_overhaul_2026-04.md`
(machine-local).
### Canonical remote
`github.com/funman300/Rusty_Solitaire` is the canonical repo.
Always push there.
## v0.19.0 candidates ([Unreleased] in CHANGELOG)
## v0.19.0 (2026-05-06)
| Area | Commit | What landed |
| Area | Commits | What landed |
|---|---|---|
| Async H-key hint | `3e11e9e` | New `pending_hint.rs` module: `PendingHintTask` resource, `poll_pending_hint_task` + `drop_pending_hint_on_state_change` systems, cancel-on-replace, stale-state guard via `move_count_at_spawn`. Removes the last synchronous solver hot path. |
| Persistent share URLs | `42d90b1` | `Replay.share_url: Option<String>` with `#[serde(default)]`. `poll_replay_upload_result` writes into `replays[0].share_url` + persists. Stats Copy button reads from the selected replay. `LastSharedReplayUrl` deleted. |
| Auto-save flake fix | `91b7605` | `test_app` clears `PendingRestoredGame(None)` after plugin build (preventing dev-machine `game_state.json` from leaking into tests); `auto_save_writes_after_30_seconds` re-arms the timer in a bounded loop instead of single-frame. No production-code change. |
| Persistent share URLs | `42d90b1` | `Replay.share_url: Option<String>` with `#[serde(default)]`. `poll_replay_upload_result` writes into `replays[0].share_url` + persists. Stats Copy button reads from selected replay. `LastSharedReplayUrl` deleted. |
| Auto-save flake fix | `91b7605` | `test_app` clears `PendingRestoredGame(None)` after plugin build; test re-arms the timer in a bounded loop. No production-code change. |
| Wayland support | `b57db01` | Adds `wayland` to Bevy features. winit prefers Wayland when `WAYLAND_DISPLAY` is set, falls back to X11. Native Wayland surface instead of XWayland frame. |
| Smart default window size | `b57db01` | New `apply_smart_default_window_size` Update system queries `PrimaryMonitor` and resizes the window to ~70 % of monitor's logical size on the first frame. Skipped when saved geometry was applied. |
| Win-celebration cleanup | `55c235b` | Drops the duplicate "You Win" toast that rendered behind the WinSummary modal. Cards-fly-off cascade kept; toast removed. |
| Double-click reject animation | `d7ffb16` | Single-card double-clicks with no destination now play the same shake + sound as multi-card stack misses. Both priorities' failure paths converge on one `MoveRejectedEvent` write. |
| Double-click animation dedup | `6037596` | Drops the redundant `StateChangedEvent` write in `end_drag`'s uncommitted-drag branch; previously raced an in-flight CardAnim and restarted the slide visibly. |
## Open punch list
### Carried forward
- **Desktop packaging** per `ARCHITECTURE.md §17`. Arch PKGBUILD
exists in `/home/manage/solitaire-quest-pkgbuild/` (separate
repo). Pending: app icon, macOS `.icns` + notarisation cert,
Windows `.ico` + Authenticode cert, AppImage recipe.
- **Per-mode artwork** for the Home picker tiles. Currently
Unicode glyphs from FiraMono's actual coverage as placeholders
(♣ ◆ ○ ▲ →). When real artwork lands, swap each tile's `Text`
node for an `Image` node — tile layout, focus order, click
handling, and chip rendering are unchanged.
- **Desktop packaging** per `ARCHITECTURE.md §17`. Eleven icon
PNG sizes (16, 24, 32, 48, 64, 96, 128, 192, 256, 512, 1024)
exported via `artwork/Icon Export.html` sit in `artwork/`
pending wiring. Pending: actual Bevy window-icon hookup,
macOS `.icns` assembly via `iconutil`, Windows `.ico` via
`magick convert`, Linux hicolor PNG hierarchy install,
AppImage recipe, macOS notarisation cert, Windows
Authenticode cert.
### Possible next-round candidates
- **Cut v0.19.0** — `[Unreleased]` is a coherent three-commit
bundle (one feature, one persistence enhancement, one test
hygiene fix). Tag whenever it feels right.
- **Pending hint task on `.before(GameMutation)`** — currently
`poll_pending_hint_task` runs on `Update` without explicit
ordering. Won't bite in practice (the result is purely
visual — no game state mutation), but matches the seed-async
template precisely.
- **Settings UI for share-link visibility** — once persistent,
surfacing whether a given replay has a URL on the Prev/Next
selector caption (e.g. "Replay 3 / 8 \u{2022} Shareable") is a
natural micro-feature. Two-line change in
`format_replay_caption`.
- **App icon round** — wire the icon into the Bevy window via
`Window::icon`, generate `.icns` and `.ico` from the existing
PNGs. Half-day task; doesn't depend on signing certs.
- **`pull_failure_sets_error_status` flake fix** — same pattern
as the auto-save flake. Wall-clock-bounded loop instead of
fixed 5-update budget. ~10 lines.
- **Settings UI for "open at this size on launch"** — once the
smart-default-size system is shipping, expose a checkbox to
*disable* it (player who specifically wants 1280×800 every
time). Trivial.
- **Persistent share link URL on selector caption** — surface
whether the currently-selected replay has a `share_url`
populated (e.g. "Replay 3 / 8 \u{2022} Shareable") so players
know which entries the Copy button can copy.
### Process notes
### Process notes (from this round)
- **Test discipline (continuing).** v0.19.0 candidates added 4
tests across `solitaire_data` + `solitaire_engine`. Each pins
a real behaviour contract (backwards-compat deserialisation,
spawn → poll → emit, cancel-on-replace, persist after upload)
rather than a stdlib / derive round-trip. The async hint port
removed 2 stale synchronous tests when their behaviours moved
to the new module.
- **Async port template (worked this round):** the H-key port
followed `d489e7a`'s `PendingNewGameSeed` shape one-to-one —
resource holds `Option<Task<...>>` plus snapshot data; spawn
helper drops any in-flight task before assigning new; poll
system runs in `Update`; cancel-on-state-change runs `.chain()`-ed
before poll. Two tests cover happy path + cancel.
- **Persistence migration template:** for purely-additive replay
fields, `#[serde(default)]` is the cheap migration. Bumping
`REPLAY_SCHEMA_VERSION` would have wiped every player's rolling
history (the loader rejects mismatched schema), so additive
changes should default-deserialise rather than version-bump.
- **Async port template (worked again):** the H-key port
followed `d489e7a`'s `PendingNewGameSeed` shape one-to-one
and the second async port required no new infrastructure.
Future async ports (e.g. moving `try_solve_with_first_move`'s
full-search variant, if it ever surfaces in the picker UI)
should follow the same shape.
- **Rusty Pixel reverted cleanly:** `git revert` of three
contiguous feature commits produced a clean three-revert
sequence with no manual conflict resolution. Bisect remains
fast over the full v0.19.0 history because the reverts are
individual commits, not a squash.
- **Defensive event writes pattern:** the
`auto_save_writes_after_30_seconds` flake AND the
`end_drag` double-animation bug shared a root cause:
defensive `MessageWriter` writes that originally covered an
edge case which no longer holds, but became load-bearing
once another system started paying attention to the event.
Worth a periodic pass: any event write that doesn't
correspond to a real state change is a candidate for
removal.
## Resume prompt
```
You are a senior Rust + Bevy developer working on Solitaire Quest.
Working directory: <Rusty_Solitaire clone path on this machine>.
Branch: master. v0.18.0 is tagged. Three commits sit on top:
async H-key hint, persistent replay share URLs, and an
auto-save test flake fix.
Branch: master. v0.19.0 just shipped. The next natural item is
desktop-packaging follow-through, starting with the app icon.
State: HEAD at 91b7605 (auto-save flake fix on top of v0.18.0
+ async hint + persistent share URL).
State: HEAD at 6037596 + the v0.19.0 docs commit on top (this
session). Tag v0.19.0 points at the docs commit.
READ FIRST (in order, before doing anything):
1. SESSION_HANDOFF.md — this file
2. CHANGELOG.md — [Unreleased] holds the v0.19.0 draft
2. CHANGELOG.md — [Unreleased] is empty; [0.19.0] just landed
3. CLAUDE.md — unified-3.0 rule set
4. CLAUDE_SPEC.md — formal architecture spec
5. ARCHITECTURE.md — crate responsibilities + data flow
@@ -146,25 +156,25 @@ READ FIRST (in order, before doing anything):
fresh machine)
DECISION TO ASK THE PLAYER FIRST:
A. Cut v0.19.0 — promote [Unreleased] to [0.19.0], tag,
push. Mechanical close-out.
B. Desktop packaging — needs artwork + signing certs from the
player; can't be driven by the agent alone.
C. Per-mode artwork — replace Home picker tile glyphs with real
images once art lands.
D. Smaller polish ideas in the punch list (pending_hint
ordering hardening, share-link visibility on selector caption).
A. App icon — wire artwork/icon-{size}.png into Bevy's
Window::icon, generate .icns + .ico, drop into Linux
hicolor hierarchy. Half-day task. No cert dependency.
B. Desktop packaging continued — AppImage recipe, .desktop
file, install scripts. Larger task; unlocks distro
packaging. No cert dependency.
C. macOS / Windows signing cert acquisition — needs user
action; agent can't drive.
D. `pull_failure_sets_error_status` flake fix — small, well-
scoped. Same pattern as the v0.19.0 auto-save flake fix.
WORKFLOW NOTES:
- Use the system git config (already correct: funman300 /
funman300@gmail.com). The previous handoff's `-c user.name=...`
workflow was for a different machine.
- Use the system git config (already correct).
- When attributing playtester feedback in commits/docs, use
"Quat" not "Rhys" (saved feedback memory).
- Sub-agents stage + verify only; orchestrator commits.
- Every commit must pass build / clippy / test before pushing.
- Push to GitHub (origin) via `gh auth setup-git` (already wired
on this machine after v0.18.0 was cut).
- Push to GitHub (origin) gh auth setup-git is already
wired on this machine.
OPEN AT THE START: ask which of AD. Don't pick unilaterally.
```