docs: refresh handoff + populate CHANGELOG [Unreleased] for v0.20

The v0.19.0 handoff had drifted material across seven commits:
HEAD pointer was wrong (still claimed 6037596; actually 59424a3),
"Tags on origin" still claimed v0.19.0 wasn't pushed, the
known-flake list still mentioned `pull_failure_sets_error_status`
(fixed in 67c150b), and three of four v0.19.0 punch-list
"candidates" had silently shipped without the doc tracking it.
The Android build target landing in fb8b2ac wasn't mentioned at
all despite being the largest single change in the cycle.

CHANGELOG [Unreleased] populated with all seven commits grouped
into Added / Fixed:

  Added:
    - Android build target — first working APK (fb8b2ac)
    - Android developer setup + build runbook (59424a3)
    - F3 FPS / frame-time overlay (690e1d2)
    - "Smart window size" Settings toggle (e1b8766)
    - "Shareable" badge on Latest-win caption (9b065e5)
    - Help: M / P / Win-Summary-Enter rows (35516d3)

  Fixed:
    - pull_failure_sets_error_status flake (67c150b)

SESSION_HANDOFF.md fully rewritten:
  - Status section reflects HEAD 59424a3, clean working tree (apart
    from this commit's docs), 1170 passing tests, no known flakes
  - "Where we are" tracks v0.19.0 candidates' close status (3 of 4
    shipped, App icon still open and now blocked on a re-export)
  - New v0.20 candidates table covers all seven commits
  - New "Phase Android" punch-list section captures the unblocked-
    by-fb8b2ac work: APK launch verification, dirs::data_dir port,
    JNI ClipboardManager, Android Keystore, gpgs integration, the
    cosmetic cargo-apk panic workaround
  - Process notes call out the async-test starvation pattern
    (seen twice now), the bin→lib+shim refactor as a reusable
    pattern, and target-gating-by-default for cross-platform deps
  - Resume prompt's A–D menu refreshed to reflect actually-open
    work: APK verification, Phase-Android persistence, app icon,
    and a v0.20 cut

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-07 13:28:04 -07:00
parent 59424a370c
commit f2d2119db5
2 changed files with 232 additions and 114 deletions
+138 -113
View File
@@ -1,59 +1,58 @@
# Solitaire Quest — Session Handoff
**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.
**Last updated:** 2026-05-07 — `[Unreleased]` accumulating v0.20
candidates. Seven commits sit on top of v0.19.0: three close items
from v0.19.0's punch list (pull-failure flake, smart-window-size
opt-out, Shareable badge), one extends Help cheat-sheet coverage,
and three open a new performance / portability arc — F3 FPS
overlay, Android build target (first working APK), and the matching
developer-setup runbook. The Android build is the load-bearing
change: `solitaire_app` now compiles into both a desktop `bin` and
an Android `cdylib` from the same source.
## Status at pause
- **HEAD on origin:** `6037596` (post-tag commit; the tag itself
points at this commit).
- **HEAD on origin:** `59424a3` (post-Android-runbook commit).
- **Working tree:** modified — `CHANGELOG.md` and
`SESSION_HANDOFF.md` carry the v0.19.0 promotion + this refresh,
ready to commit.
`SESSION_HANDOFF.md` carry the v0.20-candidates promotion + this
refresh, ready to commit. `artwork/` remains untracked.
- **Build:** `cargo clippy --workspace --all-targets -- -D warnings`
clean (verified this session).
- **Tests:** **1170 passing / 0 failing** across the workspace
(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).
(verified this session). The previously-flaky
`pull_failure_sets_error_status` is fixed; no known flakes remain.
- **Tags on origin:** `v0.9.0` through `v0.19.0`.
## Where we are
v0.18.0's resume-prompt menu (AD) is closed:
v0.19.0's "Possible next-round candidates" list shipped 3 of 4:
- ~~**A — Tag v0.18.0:**~~ shipped at `bfcd05f`.
- ~~**B — Solver-on-`AsyncComputeTaskPool` for the H-key hint:**~~
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`.
- ~~**`pull_failure_sets_error_status` flake fix:**~~ shipped at
`67c150b`.
- ~~**Settings UI for smart-default-size opt-out:**~~ shipped at
`e1b8766`.
- ~~**Persistent share link URL on selector caption:**~~ shipped at
`9b065e5` (as a "Shareable" badge on the Latest-win caption —
the Prev/Next selector chips don't have a live spawn site yet,
so the badge attaches to the existing single-replay caption).
- **App icon round** — still open. 11 PNGs generated by
`artwork/Icon Export.html` are *not* in the `artwork/` directory
any more (current `artwork/` holds the reverted Rusty Pixel
card PNGs); icon-export needs to be re-run before this item can
be picked up.
The Rusty Pixel theme arc is documented as a sub-history but
not part of v0.19.0's content:
Two new threads opened that weren't on any prior punch list:
| 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.
- **F3 FPS / frame-time overlay** (`690e1d2`). `DiagnosticsHudPlugin`
wraps `FrameTimeDiagnosticsPlugin`; F3 toggles a top-right
readout. Hidden by default; primarily a developer affordance.
- **Android build target** (`fb8b2ac` + `59424a3`).
`solitaire_app` is now a bin + cdylib hybrid. `cargo apk build`
produces a 54 MB APK with full assets. Five gating points
resolved (split, manifest, bevy feature, arboard target-gate,
keyring target-gate). What's *not* yet verified: APK launch on
AVD/phone, `dirs::data_dir()` Android behaviour for the
persistence/sync layer.
### Design direction (unchanged)
@@ -67,105 +66,131 @@ the arc had never landed.
`github.com/funman300/Rusty_Solitaire` is the canonical repo.
Always push there.
## v0.19.0 (2026-05-06)
## v0.20 candidates ([Unreleased] in CHANGELOG)
| Area | Commits | What landed |
| Area | Commit | 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 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. |
| Async-pull flake fix | `67c150b` | `pull_failure_sets_error_status` swaps a fixed 5-update budget for a 5-second wall-clock deadline + `yield_now` between pumps. Closes the last v0.19-era flake. |
| Smart window size opt-out | `e1b8766` | New `Settings::disable_smart_default_size: bool` (#[serde(default)]). `solitaire_app::main` reads the flag at startup and skips `apply_smart_default_window_size` registration when set. Settings → Gameplay row toggles it; tooltip notes saved geometry always wins. |
| Shareable badge | `9b065e5` | Stats overlay's Latest-win caption gains `\u{2022} Shareable` when the displayed replay carries a `share_url`. Badge appears on the single-replay caption (no Prev/Next live spawn site yet). |
| Help: M / P / Win-Summary-Enter | `35516d3` | Three rows added to F1 Help → Overlays. Closes coverage drift on post-v0.18 keys. |
| F3 FPS overlay | `690e1d2` | `DiagnosticsHudPlugin` + `FrameTimeDiagnosticsPlugin`. Hidden by default; F3 toggle is not gated by pause/modal state. Smoothed FPS / frame_time. Anchored top-right at `Z_SPLASH + 100`. |
| Android first APK | `fb8b2ac` | `solitaire_app` split into bin + lib (cdylib). `[package.metadata.android]` pins SDK 34 / 26. Bevy gains `android-native-activity`. `arboard` and `keyring`/`keyring-core` target-gated to non-Android. 54 MB APK builds; launch on device not yet verified. |
| Android runbook | `59424a3` | Debian 13 toolchain install, `cargo apk build` invocation, post-sign panic workaround, wired-vs-stubbed table. Fresh-clone runnable. |
## Open punch list
### Carried forward
### Carried forward from v0.19.0
- **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.
- **App icon round.** `Window::icon` not yet wired; no `.icns` /
`.ico` / Linux hicolor PNG hierarchy. The 11-size icon export
the v0.19 handoff referenced is *not* currently in
`artwork/` — re-running `artwork/Icon Export.html` is the
prerequisite. Half-day task once the PNGs are back in place.
No cert dependency.
### Possible next-round candidates
### New — Phase Android (opened by `fb8b2ac` + `59424a3`)
- **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.
- **APK launch verification on AVD / device.** `adb install` then
`adb logcat` against the bevy_test AVD. Shakes out any
Bevy/winit Android-specific runtime bugs not caught by the
build alone.
- **`dirs::data_dir()` Android port.** Persistence (settings,
stats, achievements, replays, progress) all route through this
function. Android sandboxing usually demands `getFilesDir()` via
JNI. Until verified, the APK may launch but fail to persist
anything across cold starts.
- **JNI ClipboardManager bridge.** Replaces the Android stub for
the Stats "Copy share link" toast. `arboard` doesn't ship an
Android backend, so this is a small custom JNI call.
- **Android Keystore for credentials.** `keyring` is target-gated
to a stub that returns `KeychainUnavailable`; replace with
Android Keystore via JNI when sync auth ships on mobile.
- **Google Play Games (gpgs) integration.** Listed in `solitaire_gpgs`
as a Phase-Android target since Phase 1; now unblocked by the
build target.
- **Cosmetic `cargo apk build --lib` workaround.** The
post-sign panic doesn't affect the APK on disk but produces
noisy stderr. Either upstream a cargo-apk fix or document the
`--lib` invocation as canonical in the runbook.
### Other small candidates
- **Cut v0.20.0** — the seven commits are a coherent bundle (3
punch-list closes, 1 docs polish, 1 perf tool, 2 Android arc
starters). Tag whenever feels right; the Android arc has more
surface to land before it's "done", but the build-works state
is a defensible release point.
- **Prev/Next selector chips spawn site.** `9b065e5` notes
Prev/Next markers exist in `stats_plugin` but no spawn site
renders them today — the Shareable badge therefore lands on
the single-replay caption. If/when Prev/Next is plumbed to the
Stats overlay, the badge will need to follow.
### Process notes (from this round)
- **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.
- **Async-test starvation pattern (resolved twice now).**
Auto-save flake (v0.19) and pull-failure flake
(v0.20-candidates) shared the same root cause: a fixed
N-update budget for an `AsyncComputeTaskPool` task to surface
its result, which starves under cargo-test parallelism. The
wall-clock-bounded loop pattern (`std::thread::yield_now()`
between pumps, deadline = `Instant::now() + Duration::from_secs(5)`)
is the canonical fix. Future async-test work should reach for
this shape on the first commit, not after a flake materialises.
- **Bin → lib + thin shim refactor.** The `solitaire_app` split
for cargo-apk is a textbook cdylib-as-NativeActivity pattern.
Worth knowing as a reusable shape for any future Bevy-on-mobile
project: keep `main.rs` to ≤10 lines that delegate to
`pub fn run`, put all setup in `lib.rs`, and let the build
system pick which artifact (`bin` or `cdylib`) it needs.
- **Target-gating defensive defaults.** Three of v0.20-candidates'
Android-arc commits had to disable a default-on dependency
(`arboard`, `keyring`, `keyring-core`) for the new target.
When a future contributor adds a desktop-implicit dependency,
reaching for `[target.'cfg(not(target_os = "android"))'.dependencies]`
preemptively is cheaper than rediscovering the gating during
the next platform port.
## 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.19.0 just shipped. The next natural item is
desktop-packaging follow-through, starting with the app icon.
Branch: master. v0.19.0 is tagged on origin; seven commits sit on
top opening the v0.20 cycle (3 punch-list closes + a perf tool +
the Android build target).
State: HEAD at 6037596 + the v0.19.0 docs commit on top (this
session). Tag v0.19.0 points at the docs commit.
State: HEAD at 59424a3 + the v0.20-candidates docs commit on top
(this session). Working tree may carry the doc updates if not yet
committed.
READ FIRST (in order, before doing anything):
1. SESSION_HANDOFF.md — this file
2. CHANGELOG.md — [Unreleased] is empty; [0.19.0] just landed
2. CHANGELOG.md — [Unreleased] holds the v0.20 draft
3. CLAUDE.md — unified-3.0 rule set
4. CLAUDE_SPEC.md — formal architecture spec
5. ARCHITECTURE.md — crate responsibilities + data flow
6. ~/.claude/projects/<this-project>/memory/MEMORY.md
6. docs/android/* — Android setup + build runbook (59424a3)
7. ~/.claude/projects/<this-project>/memory/MEMORY.md
— saved feedback / project context
(machine-local; may be missing on a
fresh machine)
DECISION TO ASK THE PLAYER FIRST:
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.
A. APK launch verification — `adb install` + `adb logcat` on
bevy_test AVD or a physical x86_64 device. Shakes out
runtime bugs the build can't catch.
B. Phase-Android persistence — port dirs::data_dir() through a
JNI getFilesDir() bridge so the APK can survive a cold
start. Largest unblocked Android piece.
C. App icon — re-run artwork/Icon Export.html, then wire
Window::icon + generate .icns / .ico. Half-day task. No
cert dependency.
D. Cut v0.20.0 — promote [Unreleased] to [0.20.0], tag,
push. Mechanical close-out; the Android arc has more
surface to land but the build-works state is a defensible
release point.
WORKFLOW NOTES:
- Use the system git config (already correct).
@@ -173,8 +198,8 @@ WORKFLOW NOTES:
"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) — gh auth setup-git is already
wired on this machine.
- Push to GitHub (origin) — gh auth setup-git wired on
primary dev box; verify on laptop before first push.
OPEN AT THE START: ask which of AD. Don't pick unilaterally.
```