Files
Ferrous-Solitaire/SESSION_HANDOFF.md
T
funman300 2b1ad2161a test(e2e): add Playwright spec for /play Bevy canvas route
play_canvas.spec.js covers the window.__FERROUS_DEBUG__ bridge on the
/play route (five tests): bridge availability + seed param, draw3 URL
param, applyLegalMove/undo round-trip, failureReport schema, and
autonomous autoplay invariant batch across 7 seeds.

All tests drive exclusively through the debug bridge — no DOM selectors,
because the Bevy canvas is a single <canvas> element with no HTML
controls.

Also update SESSION_HANDOFF.md to reflect post-v0.35.1 work (10 commits
since 2026-05-18 handoff), new e2e architecture notes, and HiDPI fix doc.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 14:03:25 -07:00

174 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Ferrous Solitaire — Session Handoff
**Last updated:** 2026-06-02 — Web e2e test suite complete; `/play` canvas bridge added and tested. All commits on origin/master.
---
## Current state
- **HEAD:** `play_canvas.spec.js` added (Playwright tests for `/play` Bevy canvas route)
- **Latest tag:** `v0.35.1`
- **Working tree:** clean
- **Build:** `cargo clippy --workspace -- -D warnings` clean
- **Tests:** 1243 Rust tests passing; Playwright suite in `solitaire_server/e2e/`
---
## What shipped since the last handoff (v0.35.1 → present, 2026-06-02)
| Commit | Summary |
|--------|---------|
| `64f975e` | 14 cross-platform UX/UI fixes from 500-game audit |
| `763fdb4` | Fix input: hit-test deck at correct position; accept waste click |
| `1cdb78c` | cargo fmt; add analytics domain to CSP |
| `baf524e` | Rebuild Bevy canvas WASM; add SolitaireGame interactive API |
| `9ff0585` | Remove Quaternions registry auth; canvas WASM drift guard |
| `de7ae16` | Delay first-run modal until splash screen despawns |
| `8b736ca` | Debug drag failures (temp logging, removed in next commit) |
| `8b262af` | Clamp wgpu surface to CSS pixels on HiDPI (prevented WASM panic) |
| `d45b7cb` | Add Playwright e2e test suite for web routes |
| `2cf7282` | Add `window.__FERROUS_DEBUG__` bridge to `/play` for automation |
**Key audit bugs fixed (all 7 from 500-game UX audit):** timer-after-undo, radial-menu clamping, Android resume flash, tab-hidden timer, orphaned tmp files, drag threshold 4→6px, Draw-1 recycle doc comment.
**HiDPI wgpu fix:** `WindowResolution::default().with_scale_factor_override(1.0)` added to the Bevy canvas app. Root cause was physical pixels (CSS×DPR) exceeding WebGL2's 2048px per-dimension limit on HiDPI displays.
**E2E test architecture:** three-tier — Rust unit tests → Playwright smoke/review specs → cycle regression gate. Debug bridge contract in `docs/testing-architecture.md`.
---
## What shipped before v0.35.1
See git log. CHANGELOG.md currently ends at v0.33.0 (documentation debt, low priority).
---
## 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 error**`poll_opt_in_task` / `poll_opt_out_task` error
branches now fire `WarningToastEvent` instead of `InfoToastEvent`.
2. **Display name not pushed to server on change**`Settings` 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 change** — `LeaderboardPublicNameText` 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. CHANGELOG documentation debt
CHANGELOG.md currently ends at v0.33.0. All post-v0.33.0 work is in git log. Low
priority — git log is authoritative.
### 2. Android APK launch verification (Option A)
Physical device test: install the latest APK on a real Android device (not AVD),
confirm:
- App launches without crash
- Safe area insets arrive and shift HUD correctly after ~3 frames
- All modal Done buttons are above the gesture bar
- Drag-and-drop works on all pile types
- Leaderboard panel opens and the "Public name" label updates correctly after
using "Set Name"
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.
### 3. Matomo analytics wiring
`Settings` has `analytics_enabled: bool` and `matomo_url: Option<String>` but no
engine code consumes them — the analytics toggle in Settings is a no-op. If
analytics are ever needed, the Matomo HTTP Tracking API client needs to be written
and wired to `GameStateResource` events.
---
## 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`.