Files
Ferrous-Solitaire/SESSION_HANDOFF.md
T
funman300 968721eeb4 docs(handoff): mark physical-device smoke test as the only v0.40.0 item left
Elevates the physical-device smoke test as the single remaining task for the
v0.40.0 release and clarifies the Matomo validation is an independent task,
not a release blocker.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 10:48:16 -07:00

11 KiB
Raw Blame History

Ferrous Solitaire — Session Handoff

Last updated: 2026-06-25 — v0.40.0 released (Android APK published); physical-device gate remains.


Current state

  • Branch state: master pushed to origin; latest commits are the Draw-Three waste fan fix, its regression tests, and the NDK doc update (PRs #105#108).
  • Latest tag: v0.40.0 (released — signed arm64-v8a APK published to the Gitea release for Obtainium/sideload). v0.39.1 was the prior published release.
  • Working tree: clean. Local scripts/ helpers (incl. scripts/watch_deploy.sh) are intentionally not committed.
  • Latest verification this session: cargo clippy --workspace --all-targets -- -D warnings; cargo test --workspace; cargo build -p solitaire_app; Android cross-compile + clippy for aarch64-linux-android (clean); full local signed arm64-v8a APK via scripts/build_android_apk.sh; CI android-release for v0.40.0 completed/success with APK download HTTP 200.
  • Full previous gate: card_game work pushed to origin with cargo test / clippy gates passing.

v0.40.0 release (2026-06-25)

Released via tag push → .gitea/workflows/android-release.yml built and signed the arm64-v8a release APK (release keystore, versionCode 4000 / versionName 0.40.0, 29.2 MB) and published it to the Gitea release. Obtainium clients tracking the repo pick it up automatically.

PR Summary
#106 fix(engine): Draw-Three waste fan hit-test now shares the renderer's fan step (card_plugin::waste_fan_step / tableau_col_step). The two had diverged under Android's tighter column spacing (H_GAP_DIVISOR=32), shifting the top fanned waste card's click target onto the card beneath it — so dragging the visible top card played the wrong one. Desktop/web were unaffected (the formulas already coincided there).
#105 test(engine): waste-card draggability regression tests (find_draggable_at picks the waste top with multiple cards and as a lone card).
#108 docs(android): NDK reference updated 26.3.1157926430.0.14904198; noted versions are not load-bearing and build_android_apk.sh auto-discovers the newest NDK/build-tools.

Pre-release validation performed locally this session: workspace clippy/test/build gates; aarch64-linux-android cross-compile + clippy clean (covers the #[cfg(target_os = "android")] paths that host CI never lints); release manifest sanity (solitaire_app/android/AndroidManifest.xml has no version fields so CI injection works; lib_name matches [lib].name); and a full signed local APK proving the build_android_apk.sh packaging pipeline end-to-end.


What shipped since v0.39.0

  • Browser Bevy canvas route and window.__FERROUS_DEBUG__ automation bridge landed, with Playwright coverage for /play.
  • In-place card_game / klondike rewrite phases are complete through the latest follow-up:
    • 5e87358 integrates upstream deps cleanly.
    • ae1ecc8 unifies Suit / Rank with upstream card_game types.
    • d864d98 routes klondike/card imports through solitaire_core.
    • 9bcf13d, 56e3b62, 26f1b00 finish schema-v3 migration coverage, undo/recycle score correctness, and rewrite-plan docs.
  • Android keystore wiring, Android build-script hardening, server auth/runtime hardening, and modal safe-area centering have landed.
  • CHANGELOG.md has been caught up from v0.34.0 through current unreleased work and committed in 7fe6ac6.
  • Matomo analytics was re-reviewed: MatomoClient and AnalyticsPlugin are wired through CoreGamePlugin on non-wasm targets, and targeted tests now cover opt-in client creation, event encoding, buffer trimming, and analytics mode labels.
  • Native analytics and Android physical-device validation now have runbooks in docs/analytics-validation.md and docs/ANDROID.md.

Historical notes before v0.39.0

See git log and CHANGELOG.md. The changelog now includes v0.34.0 through v0.39.0, plus current unreleased work.


What shipped since the last handoff (v0.23.0 → v0.35.1)

v0.34.0 — Android polish + code-quality sweep (2026-05-16/17)

Commit Summary
9623bde Wire FiraMono to Android corner label; CardImageSet load tests
980312c Fix wrong bottom-right suit symbol on JS/QS/KS card assets
04e99a8 Correct Android waste fan overlap and resume layout desync
3bb3ddb Eliminate panics, fix dismiss hit-test scope, guard home respawn
f8f1f26 Adaptive drop zones, touch event correctness, modal lifecycle guards
1eb4043 Auth-guard avatar serving; atomic write; user_id assertion in merge
69c6e88 Deterministic pile serialization, undo skip, url-encode bytes, merge_at
aa7b0f6 Gate frame-hot ECS systems on resource changes (perf)
6727126 Consolidate APP_DIR_NAME; add #[must_use] on pure fns
a4dfb0c Differentiate leaderboard opt-in vs opt-out error toasts (M-12)
7fc98f8 WASM: state() and step() return Result, errors throw JS exceptions (CR-6)
ffed6b2 Share Tokio runtime across all network tasks (M-16)
fa84152 Correct Android help hint label to ! (M-17)
18d7937 Derive Copy for DrawMode; drop redundant .clone() calls (M-18)
132fea9 Use saturating_add for move_count increments (M-19)
0ecc1a9 Add missing derives to AchievementContext (M-20)
2301cc6 Align android_keystore temp extension with cleanup glob (M-21)
2e52f54 Enforce 32-char display_name limit at sync client boundary (M-22)
c8878d6 Fix stale FOCUS_RING colour comment (M-23)
4aafc0a Name HUD popover Z-layers; replace raw Z arithmetic (M-24)

v0.35.0 — Accessibility + sync reliability (2026-05-18)

Commit Summary
eb6c93f Silence B0004 by adding Transform to ModalScrim
6f5cebd Fire WarningToastEvent on sync pull failure (was InfoToastEvent)
87aec5b Gate all decorative motion animations under reduce_motion_mode

reduce_motion_mode now gates: score pulse, score floater, streak flourish (hud_plugin), card-shake on rejected move, foundation completion flourish (feedback_anim_plugin). Pattern: gate at the trigger/start system, never at the tick system — if the component isn't inserted, the tick path never runs.

v0.35.1 — Leaderboard bug fixes (2026-05-18)

Commit Summary
8f86d66 Fix three leaderboard bugs: wrong toast type, stale label, name not synced

Three bugs fixed:

  1. Wrong toast type on errorpoll_opt_in_task / poll_opt_out_task error branches now fire WarningToastEvent instead of InfoToastEvent.

  2. Display name not pushed to server on changeSettings gains leaderboard_opted_in: bool (serde-defaulted false). Set true/false when opt-in/out tasks succeed and persisted to settings.json. handle_display_name_confirm now spawns an opt_in_leaderboard task when already opted in — the server's upsert endpoint updates only display_name without re-opting-in.

  3. "Public name" label stale after name changeLeaderboardPublicNameText marker component added to the label node. update_leaderboard_public_name_label system rewrites the text each frame the panel is open; O(0) cost when panel is closed.

5 new regression tests cover all three bugs.


Open punch list

1. Physical-device smoke test — THE ONLY REMAINING v0.40.0 ITEM

This is the single outstanding task for the v0.40.0 Android release. Everything else is done and verified: workspace gates, aarch64-linux-android cross-compile + clippy, release manifest sanity, a full local signed APK, the published release, and Obtainium-facing delivery (public releases API, latest non-draft release, APK downloadable anonymously). The only thing that cannot be done without hardware is running the app on a real phone.

Install the published APK on a real Android device (not AVD) and run the checklist in docs/ANDROID.md §4. This has never been gated in CI — AVD adb shell input tap doesn't deliver real touch events, so physical-device smoke testing is the only gate.

The signed release APK is published (grab it from the release page, or use the local target/debug/apk/ferrous-solitaire.apk). When testing, specifically exercise the Draw-Three waste fan fixed in #106: switch to Draw-Three, draw several cards, and confirm dragging the visible top waste card plays that card, not the one beneath it.

Latest AVD smoke (2026-06-08 local / 2026-06-09 UTC): built target/debug/apk/ferrous-solitaire.apk for x86_64-linux-android, installed it on AVD Pixel_7, launched android.app.NativeActivity, confirmed Bevy rendered the board, safe-area insets resolved as top=136 bottom=63 left=0 right=0 after 2 frames, onboarding could be dismissed via AVD input, and filtered logcat showed no Ferrous panic/fatal/ANR.

2. Matomo analytics live validation (independent — NOT a v0.40.0 release blocker)

Separate, ongoing task unrelated to the Android release. Settings has analytics_enabled, matomo_url, and matomo_site_id; the engine consumes them via AnalyticsPlugin on non-wasm targets. Remaining work is live validation against the deployed Matomo instance. Use docs/analytics-validation.md for the native validation checklist and the current web/WASM decision notes.


Architectural notes for next session

  • Reduce-motion pattern: always gate in the start_* / detect_* system (the trigger), not the tick_* system. If the component is never inserted, the tick path never runs. See hud_plugin.rs::detect_score_change and feedback_anim_plugin.rs::start_shake_anim for the canonical pattern.

  • Leaderboard server upsert: POST /api/leaderboard/opt-in is idempotent — calling it when already opted in just updates display_name. Safe to call from handle_display_name_confirm without tracking a separate "needs update" flag.

  • Messages<T> API (Bevy 0.18.1): write with resource_mut::<Messages<T>>().write(value); read in tests with msgs.get_cursor() + cursor.read(msgs).next().

  • Test input-state pitfall: MinimalPlugins has no input-tick system, so ButtonInput::just_pressed state persists across frames unless explicitly cleared with input.release(key); input.clear() between updates.

  • /play debug bridge design: play.html runs two independent WASM instances in Promise.all([bootstrap(), init()]). bootstrap() sets window.__FERROUS_DEBUG__ (logic layer via solitaire_wasm.js); init() starts the Bevy canvas. The bridge operates its own SolitaireGame — moves applied through the bridge do NOT affect the Bevy visual game. This is intentional for automation/invariant checking.

  • HiDPI Bevy canvas: WindowResolution::default().with_scale_factor_override(1.0) is set in the canvas app. Without this, physical pixels exceed WebGL2's 2048px limit on HiDPI displays, causing an immediate wgpu panic on the first resize event.

  • /play-classic vs /play in e2e: smoke.spec.js + gameplay_review.spec.js target /play-classic (DOM-heavy game.html); play_canvas.spec.js targets /play using only the __FERROUS_DEBUG__ bridge (no DOM selectors). cycle_metrics.js supports both via --route play-classic|play.