Bookkeeping pass after54005d5(GAME #YYYY-DDD caption) ande080b49(MOVE N/M chip restyle) shipped — both pieces of the Option C banner-local enrichments arc are now done. Changes: - "Since the v0.20.0 cut": added per-commit narratives for54005d5ande080b49with the implementation notes worth preserving past the commit log (the BANNER_HEIGHT 48→60 bump rationale, the Bevy 0.18 BorderColor::all() correction, the "marker on the leaf, not the wrapper" ECS-design choice). - "Open punch list" → "Replay-overlay enrichments beyond the scrub bar": pivoted from "tractable banner additions still open" to "all banner-local pieces shipped; remaining are cross-plugin or multi-session". Reflects current state without erasing the forward-looking work. - Resume prompt → Option C: marked closed with a forward pointer to the cross-plugin/multi-session items that should get their own decision tree next time. - Resume prompt → test count: dropped the hardcoded "1180 tests pass" (already stale at 1182) for "~1180+; check with `cargo test --workspace`" — same dynamic-reference pattern as 44f5972's commit-count fix, applied to the next aggregate that was vulnerable to it.
24 KiB
Solitaire Quest — Session Handoff
Last updated: 2026-05-07 — v0.20.0 cut and tagged at 41a009a,
several post-cut commits sit on top of the tag (see git log --oneline origin/master..HEAD for the live list), working tree is
clean.
The cut itself shipped two through-lines: a full Terminal visual-
identity port (token system, modal scaffold, gameplay-feedback,
toasts, table / card chrome, splash cursor) and the Android
persistence shim that closes the dirs::data_dir() = None pitfall
flagged in CLAUDE.md §10. Since the cut, four more pieces landed:
the rules-based desktop-adaptation spec (closes the spec gap
exposed when we noticed 23 of 24 mockups were mobile-only), the
splash boot-screen port (full mockup-spec splash with header, boot
log, progress bar, palette swatches, version footer + the
SplashFadable scaffold refactor), the replay-overlay scrub bar
(1 px cyan fill at the bottom of the banner, mirroring
cursor / total), and the replay banner label port to the
▌ replay cursor-block treatment that aligns it with the splash
boot-screen idiom.
Status at pause
- HEAD locally: see
git rev-parse HEAD. Most recent narrative entry below names the latest substantive commit; this status line intentionally avoids hard-coding the SHA so a docs-only edit doesn't immediately stale the handoff. - HEAD on origin:
41a009a(the v0.20.0 cut). Local master is ahead of origin by the post-cut commits enumerated under "Since the v0.20.0 cut" below; rungit log --oneline origin/master..HEADfor the live count. Decide whether to roll these into v0.20.1 / v0.21.0-candidates before pushing. - Working tree: clean. No WIP outstanding.
artwork/directory: still untracked. Intentional.- Build:
cargo clippy --workspace --all-targets -- -D warningsclean. - Tests: 1180 passing / 0 failing across the workspace.
Up from 1176 at the v0.20.0 cut: the splash boot-screen port
added two (
splash_renders_terminal_boot_screen_content,fadables_start_transparent_and_reach_full_alpha) and the replay scrub-bar finish added two more (scrub_pct_covers_state_corners,overlay_scrub_fill_tracks_cursor). - Tags on origin:
v0.9.0throughv0.20.0. v0.20.0 is on41a009a.
Since the v0.20.0 cut (un-pushed)
39b8496 docs(ui): add Terminal desktop-adaptation spec
docs/ui-mockups/desktop-adaptation.md — 283 lines covering
viewport assumptions, seven universal adaptation rules, and per-
screen geometry rules for the priority surfaces (Game Table, Win
Summary, Settings, Help, Pause, Home, Splash, Stats, and the
modal-pattern screens Profile / Achievements / Theme Picker /
Daily Challenge). Closes the spec gap — 23 of 24 mockups were
mobile-only, but the v0.20.0 token-port pass was already layout-
agnostic so nothing shipped broken. The spec matters for next
ports.
Why rules > visual mockups for this gap: Stitch's
generate_variants API timed out on the layout-only adaptation
prompt (server-side flake, not a prompt-shape issue — confirmed
by polling list_screens with no new variant landing). A markdown
rules file applies to every screen including the 9 missing-plugin
surfaces (splash, challenge, time-attack, weekly-goals,
leaderboard, sync, level-up, replay-overlay, radial-menu) that
aren't in the Stitch project at all. It's also referenceable from
code comments and commit messages without loading an image.
cacb19c feat(engine): port the splash to the Terminal boot-screen treatment
Implements the full mockup-spec splash from
docs/ui-mockups/splash-mobile.html plus the desktop adaptation
rules:
- Header: cursor block (96 px
▌), wordmark ("Solitaire Quest"), 192 px divider, "TERMINAL EDITION" subtitle. - Boot log: three ✓ check rows (
assets loaded,theme: terminal,progress restored) + a▌ ready_line. Capped at 480 px width on desktop (else 70 % viewport). - Progress bar: 1 px track (
BORDER_SUBTLE) with a 100 %- width cyan (ACCENT_PRIMARY) fill +DONE · 247 ASSETScaption. Capped at 720 px on desktop (else 80 %). - Footer:
BASE16-EIGHTIESlabel, eight palette swatches (12 × 12 px each — one per named token in the design system), version line.
Refactored the alpha-fade scaffold from per-marker queries
(SplashTitle / SplashSubtitle / SplashCursor) to a single
SplashFadable { base_color: Color } + SplashFadableBg
variant. ~15 fadable elements share one global query each;
adding more is one component-attach, not three new query types.
Skipped, with rationale captured in the commit:
- Scanline overlay (needs a tiled-pattern asset or custom shader). Open in "Visual-identity follow-ups" below.
- Pulsing cursor on the "ready_" line (would fight the global fade timeline). Open in "Visual-identity follow-ups" below.
- "RUSTY SOLITAIRE" wordmark from the mockup (the actual product is "Solitaire Quest"; the mockup leaked the repo name). Closed — the in-engine wordmark stays "Solitaire Quest".
c84d9f4 feat(engine): scrub fill bar + per-frame updater for replay overlay
Closes the WIP described in the prior handoff. Adds the 1 px cyan
scrub bar called for in docs/ui-mockups/replay-overlay-mobile.html:
a track in BORDER_SUBTLE spans the bottom edge of the banner and
the cyan ACCENT_PRIMARY fill mirrors cursor / total via a new
ReplayOverlayScrubFill component + update_scrub_fill system.
The pure scrub_pct helper is shared between the spawn path
(initial fill width) and the per-frame updater so the first paint
already reflects state instead of popping 0 → cursor on the
first tick — same shape as the existing format_progress /
update_progress_text split. Two new tests cover the four corners
of scrub_pct and an end-to-end drive of ReplayPlaybackState
asserting Node.width on the unique scrub-fill entity. Same
change-detection guard as the text updaters, so an idle replay
leaves the node untouched.
Header text treatment (closed by 6204db8 immediately below),
move-log scroll, MOVE chip, and WIN MOVE callout from the same
mockup are still open — separate commits.
6204db8 feat(engine): port replay banner label to ▌ cursor-block treatment
Aligns the replay overlay's headline with the splash boot-screen
idiom landed in cacb19c: Replay → ▌ replay and
Replay complete → ▌ replay complete. The cursor block (▌,
U+258C) prefixed to a lowercased label reads as a Terminal output
line rather than a generic UI title, tightening the family
resemblance between the two top-level overlay surfaces. Pure
text-content change; no behavioural shift, no new components, no
new systems.
Mockup deviation (intentional): the source mockup string in
docs/ui-mockups/replay-overlay-mobile.html is ▌replay.tsx. The
.tsx is a prototyping leak — Stitch renders in React, so the
mockup author reached for a familiar filename — and was dropped
for the in-engine version since the codebase is Rust. The ▌ +
lowercase pattern is what reads as a Terminal-output-line; the
extension is incidental. (Same shape as the "RUSTY SOLITAIRE"
wordmark deviation noted under cacb19c — the mockup leaked the
repo name; the actual product is "Solitaire Quest".)
54005d5 feat(engine): add GAME #YYYY-DDD caption beneath the replay headline
Adds the right-anchored game-identifier piece of the replay-overlay
mockup, adapted to live under the existing "▌ replay" headline as
a TYPE_CAPTION (11 px) / TEXT_SECONDARY subtitle. Format is
GAME #{year}-{ordinal:03} (e.g. GAME #2026-122 for a replay
recorded 2026-05-02) — year + chrono ordinal gives a compact,
monotonically-increasing identifier matching the mockup's
GAME #2024-127 motif. New ReplayOverlayGameCaption marker, new
pure helper format_game_caption(state) -> Option<String> (None
for Inactive / Completed since the replay is consumed in those
branches; spawn-time fall-through to empty string).
Layout impact: BANNER_HEIGHT bumped 48 → 60 px so the new
left column (headline + 2 px gap + caption ≈ 39 px content) fits
under the scrub bar with room to spare. +12 px banner mass is the
deliberate cost of the new content; no other plugin observes
BANNER_HEIGHT so the change is local.
Two new tests (1180 → 1182): format_game_caption_covers_state_corners
pins the three branches plus the zero-pad-to-3-digits invariant
for early-January ordinals; overlay_game_caption_shows_replay_date
drives ReplayPlaybackState end-to-end.
e080b49 feat(engine): restyle replay progress text as Terminal MOVE chip
Closes the centre-text half of the replay-overlay enrichments. The
plain "Move N of M" text becomes a 1px ACCENT_PRIMARY-bordered
chip containing "MOVE N/M" — uppercase + slash separator reads as
a Terminal output line and matches the floating-chip motif in
docs/ui-mockups/replay-overlay-mobile.html. The chip lives
in-banner rather than floating above the focused card (the
screen-takeover treatment that requires plumbing cursor → card
identity remains deferred).
Implementation note: BorderColor in Bevy 0.18 is a per-side
struct, not a tuple — BorderColor::all(ACCENT_PRIMARY) is the
correct constructor. Worth pinning for next time we touch a
border-painted UI surface. The ReplayOverlayProgressText marker
stays on the inner Text rather than the new chip Node so
update_progress_text keeps repainting unchanged — a deliberate
"markers belong on the entity that updates change" choice.
Test count unchanged (1182); overlay_progress_text_reflects_cursor
swapped its assertion from "Move 5 of 10" to "MOVE 5/10".
This pair (54005d5 + e080b49) closes Option C from the
SESSION_HANDOFF Resume prompt's banner-local enrichments. Floating-
chip-above-focused-card and the full screen-takeover redesign
remain — both data-layer or cross-plugin and intentionally still
open.
What shipped in v0.20.0 (frozen at 41a009a)
Terminal visual-identity port
Top-down stack — every commit downstream of the token system reads from it, so swapping the palette is now a one-file edit:
ui_themetoken system (0d477ac). base16-eighties palette, 5-rung type scale, 7-rung 4-multiple spacing scale, 3-step radius, 14-rung z-index hierarchy, full motion budget, 4 invariant-pinning unit tests. Card-shadow alphas pinned to 0 (Terminal achieves depth via 1px borders + tonal layering).- Modal scaffold already on tokens —
ui_modalwas ported in the same commit's wake; three stale "loud yellow" / "magenta secondary" doc comments fixed. - Gameplay feedback → semantic state tokens (
ceec4fc). Selection / valid-drop tints route throughACCENT_PRIMARY/STATE_WARNING/STATE_SUCCESS. - Toasts (
a137607). NewToastVariantenum (Info / Warning / Error / Celebration); opaqueBG_ELEVATED- 1px accent border + bottom-anchor. All ten call sites pass their semantic variant.
table_pluginchrome (651f406).PILE_MARKER_DEFAULT_COLOURpromoted;cursor_pluginimports it, replacing a "kept in sync" doc comment with a compile- enforced invariant.HINT_PILE_HIGHLIGHT_COLOUR→STATE_WARNING.card_pluginchrome (d752870). Drag-elevation shadow routes throughCARD_SHADOW_*tokens.RIGHT_CLICK_HIGHLIGHT_COLOUR→STATE_SUCCESS. Stock recycle "↺" text →TEXT_PRIMARY @ 0.7α. Card-face / suit / card-back palette intentionally NOT migrated (artwork dependency — see open-list item below).- Splash cursor (
cdcadda). The signature▌cyan glyph (96 px) added above the wordmark, matching the spec. Subsequently expanded post-cut bycacb19cinto the full boot-screen treatment. - Hint-source / dest pairing (
9891ae4).input_plugin's source-card tint now matches the destination pile'sSTATE_WARNING. - Design system + 24-mockup library (
fa7f98a).docs/ui-mockups/design-system.md+ 24 Stitch mockups (HTML + PNG) covering every screen plus 9 missing-plugin surfaces. card_shadow_paramstest aligned (1d1543e). Drag-vs- idle shadow assertion loosened to>=to accept the Terminal "no shadow" intent without losing the regression-guard.
Android persistence
solitaire_data::data_dirshim (4b51e50). Newsolitaire_data::platform::data_dir()falls through todirs::data_dir()on desktop and returns the per-app sandbox at/data/data/com.solitairequest.app/fileson Android — no JNI needed (package id pinned in[package.metadata.android]). Sixsolitaire_datacallsites +solitaire_engine/assets/user_dir.rsmigrated. Settings, stats, achievements, replays, game-state, time-attack sessions, and user themes now persist on Android.
Inherited from earlier in the cycle (pre-session)
- Android build target + APK (
fb8b2ac), runbook (59424a3), F3 FPS overlay (690e1d2), Smart Window Size opt-out (e1b8766), Shareable badge (9b065e5), Help cheat-sheet M/P/Enter rows (35516d3),pull_failure_sets_error_statusflake fix (67c150b).
Open punch list
Phase Android (build + persistence shipped; runtime gaps remain)
- APK launch verification on AVD / device.
adb installthenadb logcatagainst thebevy_testAVD or an x86_64 device. The build works and persistence is wired, but no end-to-end device run has been logged. Shakes out runtime bugs the build + unit tests can't catch. - JNI ClipboardManager bridge. Replaces the Android stub for
the Stats "Copy share link" toast.
arboarddoesn't ship an Android backend; small custom JNI call. - Android Keystore for credentials.
keyringis target-gated to a stub returningKeychainUnavailable; replace with Android Keystore via JNI when sync auth ships on mobile. - Google Play Games (gpgs) integration. Listed as a Phase-Android target since Phase 1; now unblocked by the build target.
- Cosmetic
cargo apk build --libworkaround. Post-sign panic doesn't affect the APK on disk but produces noisy stderr. Either upstream a cargo-apk fix or document--libas canonical in the runbook.
Visual-identity follow-ups (opened by v0.20.0's port)
- Card-face / suit / card-back artwork regeneration. The
Terminal spec calls for dark
#1a1a1acards with light suit pips (pink for hearts/diamonds, foreground gray for spades/ clubs); the runtime path still renders the legacy white-card PNG artwork. The fallback constants incard_plugin(CARD_FACE_COLOUR,RED_SUIT_COLOUR,BLACK_SUIT_COLOUR,CARD_FACE_COLOUR_RED_CBM,card_back_colourpalette) are intentionally unmigrated and should swap in lockstep with the artwork. Largest visible payoff remaining in the visual- identity arc. - Splash boot-loader scanline overlay.
cacb19cshipped the rest of the boot screen but skipped the scanline overlay (1px lines at 2 px pitch in#1a1a1aover the whole splash, 30 % opacity). Needs a tiled-pattern asset (a 2 × 2 px PNG) or a custom shader. Pure aesthetic, no behaviour change. - Splash cursor pulse. The "ready_" line's mockup pulses a
cyan 6 × 12 px block at the end of the text.
cacb19cskipped this because a per-element pulse fights the globalSplashFadablefade timeline. Either layer the pulse on top of the fade (multiply alphas) or accept the static cursor. - Replay-overlay enrichments beyond the scrub bar. Banner-local
pieces of the mockup (
docs/ui-mockups/replay-overlay-mobile.html) all shipped: scrub bar (c84d9f4),▌ replaycursor-block label (6204db8),GAME #YYYY-DDDcaption (54005d5),MOVE N/Mchip restyle (e080b49). What's still open are the cross-plugin / data-layer pieces: aMOVE N/Mchip floating above the focused card during playback (would need to thread the cursor through to the card layer —update_progress_textwrites the banner chip but the card-position lookup belongs incard_plugin). The full mockup's screen-takeover treatment — mini-tableau preview, playback controls, move-log scroll, WIN MOVE marker on the scrub bar — is a multi-session redesign with data-layer impact (move-log scroller; the WIN MOVE marker needs awin_move_indexfield onReplaythat doesn't yet exist). Banner-overlay behaviour is intentionally preserved for now. - Toast Warning / Error variants. The
ToastVariantenum has slots forWarning(gold) andError(pink) but no in-engine event uses them yet. Wire when a warning- or error- flavoured toast event materialises.
Carried forward from v0.19.0
- App icon round.
Window::iconnot yet wired; no.icns/.ico/ Linux hicolor PNG hierarchy. The 11-size icon export the v0.19 handoff referenced is not currently inartwork/(currentartwork/holds the reverted Rusty Pixel card PNGs and is intentionally untracked); icon-export needs to be re-run before this item can be picked up. Half-day task once the PNGs are back in place. No cert dependency.
Other small candidates
- Prev/Next selector chips spawn site. v0.19.0's
9b065e5noted Prev/Next markers exist instats_pluginbut no spawn site renders them today — the Shareable badge therefore lands on the single-replay caption. If/when Prev/Next is plumbed, the badge will need to follow. - Toast queue / immediate unification. The two toast paths
(
spawn_queued_toastforInfoToastEventqueue;spawn_toastfor fire-and-forget) now share visual treatment but remain separate functions because they serve different temporal needs (sequential vs. parallel). If overlap becomes a UX issue, merge into one queue with priority lanes.
Process notes
- The desktop-adaptation spec is the canonical reference for
geometry decisions when porting any future plugin. Read
docs/ui-mockups/desktop-adaptation.mdfirst; apply the universal rules to every surface; consult the per-screen table for the priority surfaces. The 9 missing-plugin screens (splash now ported; eight remaining) inherit the universal rules without dedicated guidance. - Stitch
generate_variantsis unreliable for layout-only adaptation prompts as of 2026-05-07. The first call timed out and no variant ever landed inlist_screens. If a future session wants visual desktop mockups, prefergenerate_screen_from_textwith a fresh narrow prompt per screen rather thangenerate_variantsagainst existing mobile screens. - Token-port pattern. v0.20.0's chrome-migration commits
set a reusable shape for "centralised design system applied
across N plugins":
- Constants module (
ui_theme.rs) is the source of truth. - Const sites that can't call
Alpha::with_alpha(not yetconston stable) use a literal RGB matching the token, with a unit test pinning the RGB to the token (e.g.MARKER_VALID,HINT_PILE_HIGHLIGHT_COLOUR,RIGHT_CLICK_HIGHLIGHT_COLOUR). - Cross-plugin duplication (e.g.
MARKER_DEFAULT↔PILE_MARKER_DEFAULT_COLOUR) collapses to a single promoted const re-exported from one plugin and imported by the other — replaces "kept in sync" doc comments with a compile-time invariant. - Domain colours (suit pips, card faces, lerp helpers) stay as literals with a comment naming the rationale; only UI chrome routes through tokens.
- Constants module (
SplashFadablescaffolding pattern (introduced incacb19c). Any future overlay that needs to fadeN >> 3elements together should follow the same shape: one tiny marker carrying the full-alpha base colour, one global query that lerps every marker's alpha each frame, no per-element query plumbing. Cleanly outscales theWithout<X>, Without<Y>query exclusion pattern that the old splash was hitting at three siblings.
Canonical remote
github.com/funman300/Rusty_Solitaire is the canonical repo.
Always push there. Local master has unpushed post-cut commits
— run git log --oneline origin/master..HEAD for the live list;
git push is the next durability step (or roll the post-cut
commits into v0.20.1).
Design direction (Terminal — base16-eighties)
- Tone: retro-terminal / synthwave — flat depth (no box-shadows), monospaced-forward typography (JetBrains Mono / FiraMono), tight 16 px edge margins, 8 px card radius.
- Palette: near-black surface ramp (
#151515/#202020/#2a2a2a/#353535), cyan primary CTA (#6fc2ef), lime success (#acc267), gold warning (#ddb26f), pink error / suit-red (#fb9fb1), lavender celebration (#e1a3ee), teal info (#12cfc0). - Two-color suits. Red =
#fb9fb1, black =#d0d0d0. Outlined glyphs for diamonds & clubs are always on; the Settings "color-blind mode" toggle only swaps red → cyan.
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.20.0 is tagged at 41a009a; several post-cut
commits sit on top locally and have NOT been pushed yet — run
`git log --oneline origin/master..HEAD` for the live list (current
substantives: 39b8496 desktop-adaptation spec, cacb19c splash
boot-screen port, c84d9f4 replay scrub-bar finish, 6204db8 replay
banner ▌ cursor-block label, plus any handoff edits since).
State: HEAD locally — see `git rev-parse HEAD`. Working tree is
clean. All workspace tests pass (~1180+; check with
`cargo test --workspace`), clippy clean.
READ FIRST (in order, before doing anything):
1. SESSION_HANDOFF.md — this file
2. CHANGELOG.md — [0.20.0] 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
6. docs/ui-mockups/ — design system + 24-mockup library +
desktop-adaptation.md (the rules-based
companion to the mockups; read this
before any plugin port)
7. docs/android/* — Android setup + build runbook
8. ~/.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. Push the post-cut commits to origin. Either as-is on master
or rolled into a v0.20.1 cut (CHANGELOG entry + tag).
Mechanical, but local master diverges from origin until done.
B. Splash polish — scanline overlay + cursor pulse. The two
pieces of the mockup `cacb19c` skipped (scanline needs a
tiled-pattern asset or shader; pulse needs to layer on top
of the SplashFadable timeline). Pure polish; no behaviour
change.
C. *Closed by `54005d5` + `e080b49`.* Banner-local replay-overlay
pieces all shipped (scrub bar, ▌ label, GAME caption, MOVE
chip). Remaining are cross-plugin (floating MOVE chip above
the focused card — needs cursor → card-position plumbing) or
multi-session (full screen-takeover redesign — move-log
scroll, mini tableau, WIN MOVE marker, data-layer impact).
Either belongs in its own decision tree the next time replay
work surfaces.
D. Card-face artwork regeneration. Generate Terminal-aesthetic
card PNGs (dark face, light suit pips), then migrate
CARD_FACE_COLOUR / RED_SUIT_COLOUR / BLACK_SUIT_COLOUR /
CARD_FACE_COLOUR_RED_CBM in lockstep. Largest visible
payoff remaining in the visual-identity arc. Multi-session.
E. App icon round — re-run artwork/Icon Export.html (the
export PNGs are not currently in `artwork/`), then wire
Window::icon + generate .icns / .ico. Half-day task. No
cert dependency.
F. APK launch verification on AVD / device + the JNI bridges
it would shake out (ClipboardManager, Keystore).
WORKFLOW NOTES:
- 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) — gh auth setup-git wired on
primary dev box; verify on laptop before first push.
OPEN AT THE START: ask which of A–F. Don't pick unilaterally.