docs(architecture): update to v1.3 — all Phase 8 gaps closed
Adds solitaire_wasm crate (§2/§3), replay API endpoints (§9), web replay player routes, SyncProvider 7 optional methods, ThemePlugin + SyncSetupPlugin in plugin table (§5), Settings new fields (§8), and DB migration 002 replays table (§7). Also fixes missing [0.23.0] section header in CHANGELOG.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+77
-2
@@ -1,9 +1,9 @@
|
|||||||
# Solitaire Quest — Architecture Document
|
# Solitaire Quest — Architecture Document
|
||||||
|
|
||||||
> **Version:** 1.1
|
> **Version:** 1.3
|
||||||
> **Language:** Rust (Edition 2024)
|
> **Language:** Rust (Edition 2024)
|
||||||
> **Engine:** Bevy (latest stable)
|
> **Engine:** Bevy (latest stable)
|
||||||
> **Last Updated:** 2026-04-29
|
> **Last Updated:** 2026-05-12
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -86,6 +86,7 @@ solitaire_quest/
|
|||||||
├── solitaire_data/ # Persistence, sync client, settings
|
├── solitaire_data/ # Persistence, sync client, settings
|
||||||
├── solitaire_engine/ # Bevy ECS systems, components, plugins
|
├── solitaire_engine/ # Bevy ECS systems, components, plugins
|
||||||
├── solitaire_server/ # Self-hosted sync server (Axum + SQLite)
|
├── solitaire_server/ # Self-hosted sync server (Axum + SQLite)
|
||||||
|
├── solitaire_wasm/ # WebAssembly bindings — browser-side replay player
|
||||||
└── solitaire_app/ # Main binary entry point
|
└── solitaire_app/ # Main binary entry point
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -160,6 +161,20 @@ Owns:
|
|||||||
- Daily challenge seed generation
|
- Daily challenge seed generation
|
||||||
- Leaderboard management
|
- Leaderboard management
|
||||||
|
|
||||||
|
### `solitaire_wasm`
|
||||||
|
**Dependencies:** `solitaire_core`, `serde`, `serde_json`, `chrono`, `wasm-bindgen`, `serde-wasm-bindgen`.
|
||||||
|
|
||||||
|
WebAssembly bindings for browser-side replay playback. Compiled to `cdylib` via `wasm-pack build`; the output lives in `solitaire_server/web/pkg/` and is served statically by the server.
|
||||||
|
|
||||||
|
Intentionally **does not** depend on `solitaire_data` (which pulls in `dirs`, `keyring`, `reqwest`, and other non-WASM crates). Instead it defines a minimal `Replay` mirror with the same serde shape as `solitaire_data::Replay` — the JSON wire format is the compatibility contract.
|
||||||
|
|
||||||
|
Owns:
|
||||||
|
- `ReplayPlayer` — WASM-exported state machine; steps through a replay's `Vec<ReplayMove>` against a live `GameState`
|
||||||
|
- `StateSnapshot` — JS-facing pile snapshot returned by each `step()` call
|
||||||
|
- `ReplayMove` / `Replay` mirrors — same serde shape as `solitaire_data` v2 equivalents
|
||||||
|
|
||||||
|
Because `ReplayPlayer` uses the same `solitaire_core::GameState` as the desktop client, the two implementations cannot drift: the same seed + move list produces identical pile state at every step on both platforms.
|
||||||
|
|
||||||
### `solitaire_app`
|
### `solitaire_app`
|
||||||
**Dependencies:** `bevy`, `solitaire_engine`.
|
**Dependencies:** `bevy`, `solitaire_engine`.
|
||||||
|
|
||||||
@@ -261,6 +276,8 @@ The "Shortcut" column lists optional keyboard accelerators. Every action in this
|
|||||||
| `HomePlugin` | M | Main-menu overlay with keyboard shortcut reference |
|
| `HomePlugin` | M | Main-menu overlay with keyboard shortcut reference |
|
||||||
| `ProfilePlugin` | P | Player profile overlay: level, XP, achievements, sync status |
|
| `ProfilePlugin` | P | Player profile overlay: level, XP, achievements, sync status |
|
||||||
| `SettingsPlugin` | O | Settings panel: audio, draw mode, theme, sync, cosmetics |
|
| `SettingsPlugin` | O | Settings panel: audio, draw mode, theme, sync, cosmetics |
|
||||||
|
| `ThemePlugin` | — | Owns `ActiveTheme` resource; registers the `CardTheme` SVG asset loader; rasterises themes once per (theme, target size) at load time and caches the resulting `Image`; handles the embedded default theme and user themes from `user_theme_dir()` |
|
||||||
|
| `SyncSetupPlugin` | — | Sync setup modal (URL / username / password fields, "Log In" / "Register" buttons); account deletion confirm modal; re-auth trigger when `SyncError::Auth` is returned by a pull |
|
||||||
| `LeaderboardPlugin` | L | Leaderboard overlay |
|
| `LeaderboardPlugin` | L | Leaderboard overlay |
|
||||||
| `HelpPlugin` | H | Help / controls overlay |
|
| `HelpPlugin` | H | Help / controls overlay |
|
||||||
| `PausePlugin` | Esc | Pause and resume |
|
| `PausePlugin` | Esc | Pause and resume |
|
||||||
@@ -365,10 +382,22 @@ All sync backends implement a single trait in `solitaire_data`. The `SyncPlugin`
|
|||||||
```rust
|
```rust
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait SyncProvider: Send + Sync {
|
pub trait SyncProvider: Send + Sync {
|
||||||
|
// Required — must be implemented by every backend:
|
||||||
async fn pull(&self) -> Result<SyncPayload, SyncError>;
|
async fn pull(&self) -> Result<SyncPayload, SyncError>;
|
||||||
async fn push(&self, payload: &SyncPayload) -> Result<SyncResponse, SyncError>;
|
async fn push(&self, payload: &SyncPayload) -> Result<SyncResponse, SyncError>;
|
||||||
fn backend_name(&self) -> &'static str;
|
fn backend_name(&self) -> &'static str;
|
||||||
fn is_authenticated(&self) -> bool;
|
fn is_authenticated(&self) -> bool;
|
||||||
|
|
||||||
|
// Optional — all have default no-op / empty implementations:
|
||||||
|
async fn mirror_achievement(&self, _id: &str) -> Result<(), SyncError>;
|
||||||
|
async fn fetch_leaderboard(&self) -> Result<Vec<LeaderboardEntry>, SyncError>;
|
||||||
|
async fn fetch_daily_challenge(&self) -> Result<Option<ChallengeGoal>, SyncError>;
|
||||||
|
async fn opt_in_leaderboard(&self, _display_name: &str) -> Result<(), SyncError>;
|
||||||
|
async fn opt_out_leaderboard(&self) -> Result<(), SyncError>;
|
||||||
|
async fn delete_account(&self) -> Result<(), SyncError>;
|
||||||
|
// Returns the shareable web URL on success; defaults to Err(UnsupportedPlatform)
|
||||||
|
// so LocalOnlyProvider silently no-ops the push-on-win path.
|
||||||
|
async fn push_replay(&self, _replay: &Replay) -> Result<String, SyncError>;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -454,6 +483,24 @@ CREATE TABLE leaderboard (
|
|||||||
recorded_at TEXT NOT NULL,
|
recorded_at TEXT NOT NULL,
|
||||||
PRIMARY KEY (user_id)
|
PRIMARY KEY (user_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- migrations/002_replays.sql
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS replays (
|
||||||
|
id TEXT PRIMARY KEY, -- UUID v4 minted server-side
|
||||||
|
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
seed INTEGER NOT NULL,
|
||||||
|
draw_mode TEXT NOT NULL, -- "DrawOne" | "DrawThree"
|
||||||
|
mode TEXT NOT NULL, -- "Classic" | "Zen" | "Challenge" | "TimeAttack"
|
||||||
|
time_seconds INTEGER NOT NULL,
|
||||||
|
final_score INTEGER NOT NULL,
|
||||||
|
recorded_at TEXT NOT NULL, -- replay-side date (YYYY-MM-DD)
|
||||||
|
received_at TEXT NOT NULL, -- server insert timestamp (ISO 8601)
|
||||||
|
replay_json TEXT NOT NULL -- full Replay serialisation
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS replays_received_at_idx ON replays(received_at DESC);
|
||||||
|
CREATE INDEX IF NOT EXISTS replays_user_id_idx ON replays(user_id);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Request Lifecycle
|
### Request Lifecycle
|
||||||
@@ -584,7 +631,20 @@ pub struct Settings {
|
|||||||
pub animation_speed: AnimSpeed,
|
pub animation_speed: AnimSpeed,
|
||||||
pub theme: Theme,
|
pub theme: Theme,
|
||||||
pub sync_backend: SyncBackend, // Local | SolitaireServer
|
pub sync_backend: SyncBackend, // Local | SolitaireServer
|
||||||
|
pub selected_card_back: usize, // index into PlayerProgress::unlocked_card_backs
|
||||||
|
pub selected_background: usize, // index into PlayerProgress::unlocked_backgrounds
|
||||||
pub first_run_complete: bool,
|
pub first_run_complete: bool,
|
||||||
|
pub color_blind_mode: bool, // blue tint on red suits
|
||||||
|
pub high_contrast_mode: bool, // boosted luminance for low-vision users
|
||||||
|
pub reduce_motion_mode: bool, // WCAG reduce-motion — snaps instead of slides
|
||||||
|
pub window_geometry: Option<WindowGeometry>, // persisted size + position; None on first run
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WindowGeometry {
|
||||||
|
pub width: u32, // logical pixels
|
||||||
|
pub height: u32,
|
||||||
|
pub x: i32, // physical pixels, top-left corner
|
||||||
|
pub y: i32,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -617,6 +677,21 @@ All endpoints are under the base URL configured by the user (e.g., `https://soli
|
|||||||
| GET | `/api/leaderboard` | Bearer JWT | — | `Vec<LeaderboardEntry>` |
|
| GET | `/api/leaderboard` | Bearer JWT | — | `Vec<LeaderboardEntry>` |
|
||||||
| POST | `/api/leaderboard/opt-in` | Bearer JWT | — | `{ok: true}` |
|
| POST | `/api/leaderboard/opt-in` | Bearer JWT | — | `{ok: true}` |
|
||||||
|
|
||||||
|
### Replays
|
||||||
|
|
||||||
|
| Method | Path | Auth | Body | Response |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| POST | `/api/replays` | Bearer JWT | Replay JSON | `{id, share_url}` |
|
||||||
|
| GET | `/api/replays/recent` | None | — (`?limit=N`) | `Vec<ReplaySummary>` |
|
||||||
|
| GET | `/api/replays/:id` | None | — | Full Replay JSON |
|
||||||
|
|
||||||
|
### Web Replay Player
|
||||||
|
|
||||||
|
| Method | Path | Auth | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| GET | `/replays/:id` | None | Serves `web/index.html`; JS fetches `/api/replays/:id` and steps through via the `solitaire_wasm` WASM module |
|
||||||
|
| GET | `/web/*` | None | Static assets served via `ServeDir` from `solitaire_server/web/` (includes `web/pkg/` with wasm-bindgen output) |
|
||||||
|
|
||||||
### Account Management
|
### Account Management
|
||||||
|
|
||||||
| Method | Path | Auth | Body | Response |
|
| Method | Path | Auth | Body | Response |
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ project follows [Semantic Versioning](https://semver.org/).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [0.23.0] — 2026-05-12
|
||||||
|
|
||||||
Phase 8 sync UI: the self-hosted-server connection flow is now fully
|
Phase 8 sync UI: the self-hosted-server connection flow is now fully
|
||||||
playable end-to-end. Players can open a Connect modal from Settings,
|
playable end-to-end. Players can open a Connect modal from Settings,
|
||||||
enter a server URL + credentials, log in or register, and see the
|
enter a server URL + credentials, log in or register, and see the
|
||||||
|
|||||||
+128
-304
@@ -1,338 +1,162 @@
|
|||||||
# Solitaire Quest — Session Handoff
|
# Solitaire Quest — Session Handoff
|
||||||
|
|
||||||
**Last updated:** 2026-05-08 — **v0.21.8 tagged at `c50eaf8`**;
|
**Last updated:** 2026-05-12 — ARCHITECTURE.md updated to v1.3 (all 8 Phase 8 gaps closed);
|
||||||
nine post-cut commits on master. Push pending.
|
`SESSION_HANDOFF.md` updated. Push pending.
|
||||||
|
|
||||||
v0.21.8 closes the last optional polish items in the B-2
|
Phase 8 closes the self-hosted-server connection arc end-to-end: login/register
|
||||||
replay screen-takeover arc: **notch-label centering** (middle
|
modal, re-auth on token expiry, account deletion flow, server deployment
|
||||||
three scrub-bar labels now centred on their notch ticks via the
|
artifacts (Dockerfile + docker-compose), replay upload on win, web replay
|
||||||
CSS `translateX(-50%)` pattern for Bevy 0.18 UI) and **WIN
|
player (WASM + HTML/CSS/JS served by the server), leaderboard opt-in/out,
|
||||||
MOVE HC legibility** (lime stays lime under HC mode via the
|
and full server integration tests.
|
||||||
extended `HighContrastBackground::with_hc` constructor and a
|
|
||||||
new `STATE_SUCCESS_HC` brighter-lime constant). The replay
|
|
||||||
overlay arc is now fully closed with no known open items.
|
|
||||||
|
|
||||||
Full v0.21.8 detail lives in `CHANGELOG.md` § [0.21.8]. This
|
---
|
||||||
file from here on focuses on what's *open* post-cut and how to
|
|
||||||
resume.
|
|
||||||
|
|
||||||
## Status at pause
|
## Current state
|
||||||
|
|
||||||
- **HEAD locally:** `f281425` (Android Keystore JNI).
|
- **HEAD locally:** `bd388fe` (docs: CHANGELOG Phase 8 entry).
|
||||||
Docs ride on top; push pending.
|
- **HEAD on origin:** `272d31f` (feat: account deletion — last pushed commit).
|
||||||
- **HEAD on origin:** `395a322` (double-tap commit — last pushed).
|
- **Working tree:** `ARCHITECTURE.md` + `SESSION_HANDOFF.md` modified, uncommitted.
|
||||||
- **Working tree:** clean (docs uncommitted). No WIP outstanding.
|
- **Build:** `cargo clippy --workspace --all-targets -- -D warnings` clean.
|
||||||
- **`artwork/` directory:** still untracked. Intentional.
|
- **Tests:** **1300+ passing / 0 failing** across the workspace.
|
||||||
- **Build:** `cargo clippy --workspace --all-targets -- -D warnings`
|
- **Tags on origin:** `v0.9.0` through `v0.22.0`.
|
||||||
clean.
|
|
||||||
- **Tests:** **1292 passing / 0 failing** across the workspace.
|
|
||||||
- **Tags on origin:** `v0.9.0` through `v0.21.8`.
|
|
||||||
- **Android:** APK verified booting on Pixel_7 AVD (Android 14,
|
|
||||||
x86_64). All desktop-only systems (handle_fullscreen) now gated.
|
|
||||||
See Phase Android punch list for remaining work.
|
|
||||||
|
|
||||||
## Since the v0.21.8 cut
|
---
|
||||||
|
|
||||||
Seven commits since the v0.21.8 tag:
|
## What shipped in Phase 8 (432061c – bd388fe)
|
||||||
- `a449f60` — Stats Prev/Next selector spawn site
|
|
||||||
- `202a64d` — Android launch fixes (android_main, resize_constraints,
|
|
||||||
apply_smart_default_window_size) — **closes APK launch verification**
|
|
||||||
- `16242e6` — Ignore .idea/ IDE files
|
|
||||||
- `395a322` — double-tap auto-move for touch input
|
|
||||||
- `0cb1587` — Play-by-Seed dialog + HomeMode card
|
|
||||||
- `2062bd0` — 75 new challenge seeds + gen_seeds binary
|
|
||||||
- `45436d0` — gate handle_fullscreen to non-Android
|
|
||||||
- `2c822ba` — JNI clipboard bridge for Android Stats share-link
|
|
||||||
- `f281425` — Android Keystore AES-GCM token storage via JNI
|
|
||||||
|
|
||||||
CHANGELOG + SESSION_HANDOFF docs ride on top; push pending.
|
| Commit | Summary |
|
||||||
|
|--------|---------|
|
||||||
|
| `432061c` | Sync setup modal (login/register/connect/disconnect) |
|
||||||
|
| `6ce5564` | Re-auth on expired session + server deployment artifacts |
|
||||||
|
| `272d31f` | Account deletion flow + `handle_sync_buttons` refactor |
|
||||||
|
| `bd388fe` | CHANGELOG v0.23.0 documentation |
|
||||||
|
|
||||||
Open next-step menu:
|
Also shipped (pre-Phase 8 but post-v0.22.0, already in CHANGELOG):
|
||||||
1. **Phase 8 (sync)** — the biggest open arc. Local storage
|
- `solitaire_wasm` crate: WASM ReplayPlayer bindings for browser-side replay playback
|
||||||
scaffolding, self-hosted Axum server, GPGS stub.
|
- Server replay API: `POST /api/replays`, `GET /api/replays/recent`, `GET /api/replays/:id`
|
||||||
2. **Android follow-ups** — JNI ClipboardManager, Android Keystore,
|
- Server web UI: `/replays/:id` HTML route + `ServeDir /web` static assets
|
||||||
GPGS. Launch verification and double-tap both closed; these
|
- DB migration 002: `replays` table + two indexes
|
||||||
are the remaining Phase Android items.
|
- Full server integration tests for replay endpoints
|
||||||
3. **Move Log auto-scroll** — only relevant if the panel
|
- `push_replay` in `sync_plugin` (uploads on win, writes share URL into replay history)
|
||||||
row count grows beyond the current 5-row fixed window.
|
- Stats panel "Copy Share Link" button reads `share_url` from replay history
|
||||||
|
|
||||||
## Open punch list
|
---
|
||||||
|
|
||||||
### Phase Android (build + persistence shipped; runtime gaps remain)
|
## Open punch list (ordered by priority)
|
||||||
|
|
||||||
- *APK launch verification — closed 2026-05-08 by `202a64d`.*
|
### 1. Documentation debt (no code)
|
||||||
Three fixes shipped: `android_main` export (missing NativeActivity
|
- [x] CHANGELOG [Unreleased] → v0.23.0 — done this session
|
||||||
entry point), `resize_constraints` gated to non-Android (max=0
|
- [x] ARCHITECTURE.md update — all 8 gaps closed, bumped to v1.3
|
||||||
panic), `apply_smart_default_window_size` gated to non-Android
|
- [x] SESSION_HANDOFF.md update — this file
|
||||||
(clamp panic on zero-dimension window event). Verified booting on
|
|
||||||
Pixel_7 AVD (Android 14, x86_64, SwiftShader Vulkan), 2+ min
|
### 2. Leaderboard wiring gaps
|
||||||
runtime without crash. B0004 ECS hierarchy warnings remain
|
- **Best-score auto-post missing.** `POST /api/sync/push` merges stats/achievements/
|
||||||
(non-fatal; entity parent/child component mismatch); investigate
|
progress but never touches the `leaderboard` table. Players who opt in never
|
||||||
if they surface gameplay bugs.
|
have their `best_time_secs` / `best_score` updated automatically. Fix: update
|
||||||
- *Double-tap auto-move — closed 2026-05-08 by `395a322`.*
|
the leaderboard row inside the server's sync push handler (or on `GameWonEvent`
|
||||||
`handle_double_tap` fires `MoveRequestEvent` on two rapid
|
via a new async task in `sync_plugin`).
|
||||||
`TouchPhase::Ended` events within 0.5 s. Prefers foundation;
|
- **Display name = username.** `handle_opt_in_button` uses the `SyncBackend`
|
||||||
falls back to tableau stack move. Fires `MoveRejectedEvent` when
|
username as the leaderboard display name. Consider adding
|
||||||
no legal destination exists. System runs before `touch_end_drag`
|
`leaderboard_display_name: Option<String>` to `Settings` for players who
|
||||||
in the chain so drag state is readable.
|
want a different public identity.
|
||||||
- *F11 fullscreen gate — closed 2026-05-08 by `45436d0`.*
|
|
||||||
`handle_fullscreen` and its `MonitorSelection`/`WindowMode`
|
### 3. Security hardening
|
||||||
imports are `#[cfg(not(target_os = "android"))]`-gated. The
|
- **Refresh token rotation.** `POST /api/auth/refresh` returns only a new
|
||||||
`add_systems` call is a separate statement (not mid-chain).
|
access token; the refresh token never rotates. Standard mitigation: issue a
|
||||||
- *JNI ClipboardManager bridge — closed 2026-05-08 by `2c822ba`.*
|
new refresh token on each call and invalidate the old one (needs a
|
||||||
`android_clipboard::set_text(url)` calls `ClipboardManager` via
|
`last_refresh_token` column or a separate table).
|
||||||
JNI. Stats share-link button now writes to the clipboard with a
|
- **Sync endpoint rate limiting.** Only `/api/auth/*` has `tower-governor`;
|
||||||
"Copied: {url}" toast; falls back to "Share link: {url}" on JNI
|
`/api/sync/push` (1 MB body) has no per-user throttle.
|
||||||
error. Requires AVD functional test (see verification steps in
|
|
||||||
the approved plan).
|
### 4. Android validation
|
||||||
- *Android Keystore for credentials — closed 2026-05-08 by `f281425`.*
|
- **Android Keystore functional test** — JNI AES-GCM code ships (`f281425`) but
|
||||||
`android_keystore` module: AES-256/GCM/NoPadding device-bound key,
|
no AVD round-trip test has been run. Required before Phase 8 sync goes live on
|
||||||
tokens serialised to JSON and stored atomically at
|
|
||||||
`{data_dir}/auth_tokens.bin` as `[12-byte IV][ciphertext+tag]`.
|
|
||||||
`auth_tokens.rs` Android stubs now delegate to it. Key
|
|
||||||
invalidation (biometric reset) → `TokenError::KeychainUnavailable`.
|
|
||||||
Requires AVD functional test before Phase 8 sync goes live on
|
|
||||||
Android.
|
Android.
|
||||||
- **Cosmetic `cargo apk build --lib` workaround.** Post-sign
|
- **JNI clipboard functional test** — same status (`2c822ba`). Note: `adb tap`
|
||||||
panic doesn't affect the APK on disk but produces noisy stderr.
|
doesn't work in headless AVD (see memory); requires a touch-gesture path.
|
||||||
Either upstream a cargo-apk fix or document `--lib` as
|
- **`cargo apk build --lib` noisy stderr** — post-sign panic doesn't affect the
|
||||||
canonical in the runbook.
|
APK but pollutes CI output. Document `--lib` as canonical or upstream a fix.
|
||||||
|
|
||||||
### Visual-identity follow-ups (post-v0.21.0)
|
### 5. Feature completeness
|
||||||
|
- **Theme importer UI.** `import_theme()` (Phase 7, `theme/importer.rs`) is
|
||||||
|
complete but has no Settings button trigger. Players must copy theme files
|
||||||
|
manually.
|
||||||
|
- **`mirror_achievement` decision.** `SyncProvider` has this method with a
|
||||||
|
no-op default; `SolitaireServerClient` never overrides it, no server endpoint
|
||||||
|
exists. Either implement (`POST /api/achievements/mirror` + client call on
|
||||||
|
`AchievementUnlockedEvent`) or delete from the trait.
|
||||||
|
- **WASM build script.** `web/pkg/` contains compiled WASM committed to git.
|
||||||
|
Need a `build_wasm.sh` or Makefile target documenting the `wasm-pack build`
|
||||||
|
invocation to regenerate it.
|
||||||
|
- **Server password reset.** No admin endpoint or CLI tool for resetting a
|
||||||
|
user's password. Self-hosters have no recovery path short of direct SQLite
|
||||||
|
edits.
|
||||||
|
|
||||||
The visual-identity arc is effectively complete: token system,
|
### 6. Testing gaps
|
||||||
chrome migration, splash boot screen, replay-overlay banner,
|
- **Server 401 → refresh → retry path** — the `pull`/`push` retry logic in
|
||||||
card-face artwork (both rendering paths), and the `ACCENT_PRIMARY`
|
`SolitaireServerClient` has no integration test.
|
||||||
palette refresh all shipped in v0.20.0 + v0.21.0. What stays open:
|
- **WASM winning-replay step-through** — current tests cover 2 stock clicks;
|
||||||
|
a test stepping through a full winning sequence would catch
|
||||||
|
`GameState`/`ReplayMove` compatibility regressions.
|
||||||
|
|
||||||
- *Replay-overlay screen-takeover redesign — closed 2026-05-08
|
---
|
||||||
across 13 commits (v0.21.4–v0.21.7).* The full mockup
|
|
||||||
(`docs/ui-mockups/replay-overlay-mobile.html`) has shipped:
|
|
||||||
banner chrome (v0.21.0), floating MOVE chip (v0.21.2), WIN
|
|
||||||
MOVE scrub-bar marker (post-v0.21.3), playback controls /
|
|
||||||
Space accelerator (post-v0.21.3), scrub notches + labels +
|
|
||||||
keybind footer + ESC / ← / → accelerators + HC border
|
|
||||||
(v0.21.5), Move Log panel + HC scrub track + continuous
|
|
||||||
scrub (v0.21.6), and full-screen 50 % opacity dim layer
|
|
||||||
(v0.21.7). Every major B-2 sub-piece is now closed. The
|
|
||||||
only remaining items are minor polish: notch-label centering
|
|
||||||
and WIN MOVE HC contrast bump (see Open next-step menu).*
|
|
||||||
- *Floating `MOVE N/M` chip above the focused card during
|
|
||||||
playback — closed 2026-05-08 by `2fb2d63`.* World-space
|
|
||||||
`Text2d` entity sibling to the banner overlay; uses the same
|
|
||||||
`LayoutResource` pile coordinates so it survives window
|
|
||||||
resizes without UI/camera math.
|
|
||||||
- *Toast Warning variant wiring — closed 2026-05-08 by `279e23d`.*
|
|
||||||
Daily-challenge-expiry toast fires once per `daily.date` when
|
|
||||||
within 30 min of UTC midnight reset and today is incomplete.
|
|
||||||
`ToastVariant` is now fully load-bearing (every variant has at
|
|
||||||
least one real driver). Future Warning drivers can either reuse
|
|
||||||
the generic `WarningToastEvent(String)` carrier or add their
|
|
||||||
own domain message + `animation_plugin` handler.
|
|
||||||
- *Toast Error variant wiring — closed 2026-05-08 by `68d50b5`.*
|
|
||||||
`MoveRejectedEvent` now fires a 2-second pink-bordered
|
|
||||||
"Invalid move" toast as the third leg of the
|
|
||||||
audio + visual + text rejection-feedback stool.
|
|
||||||
- *High-contrast accessibility mode — closed 2026-05-08 by
|
|
||||||
`c5787c6` + `07e0357` (engine + UI) + v0.21.2's HC chrome
|
|
||||||
rollout (`c9af1ea` + `d87761d` + `ec804d5`) + post-cut
|
|
||||||
dynamic-paint rollout (`c153363`).* Card text rendering plus
|
|
||||||
8 static-border chrome surfaces (modal scaffold, tooltip,
|
|
||||||
onboarding key chips, help panel key chips, stats panel
|
|
||||||
cells, home Level/XP/Score row, home mode buttons, home
|
|
||||||
mode-hotkey chips, 4 settings panel surfaces) all boost
|
|
||||||
borders to `BORDER_SUBTLE_HC` under HC via the
|
|
||||||
`HighContrastBorder` marker. The previously-carved-out
|
|
||||||
dynamic-paint sites are now also covered: HUD action buttons
|
|
||||||
and modal buttons take the same marker (their paint cycles
|
|
||||||
only mutate `BackgroundColor`, so no race); the radial menu
|
|
||||||
rim folds HC into its per-frame spawn via
|
|
||||||
`radial_rim_outline` so the focused rim boosts to
|
|
||||||
`BORDER_SUBTLE_HC` under HC (preserving focused-vs-resting
|
|
||||||
hierarchy that naive marker substitution would invert).
|
|
||||||
- *Reduced-motion mode — closed 2026-05-08 by `c5787c6` +
|
|
||||||
v0.21.2's `ed152e2`.* `effective_slide_secs` forces 0 on
|
|
||||||
card animations; `pulse_splash_cursor` skips the per-frame
|
|
||||||
pulse multiplier; `spawn_splash` skips the scanline overlay
|
|
||||||
entirely. Future scope: gate any future card-lift z-bump
|
|
||||||
animation, warning-chip pulse (when one materialises).
|
|
||||||
|
|
||||||
### Carried forward from v0.19.0
|
## ARCHITECTURE.md gaps (for the update pass)
|
||||||
|
|
||||||
- *App icon round — closed 2026-05-08 by `3eb3a26` + `716a025`.*
|
Items missing from the doc:
|
||||||
Runtime `Window::icon` wired (Linux/macOS/Windows); 9-size
|
1. `solitaire_wasm` crate (§2 workspace + §3 responsibilities)
|
||||||
PNG hierarchy at `assets/icon/icon_<size>.png` covers Linux
|
2. Replay API endpoints (§9 API Reference — 3 new routes)
|
||||||
hicolor + downstream `.icns`/`.ico` packaging needs. The
|
3. Web replay player route (`/replays/:id` + `ServeDir /web`)
|
||||||
`.ico` and `.icns` bundle-format files themselves are *not*
|
4. `SyncProvider` trait: 6 added methods
|
||||||
generated — both would need new crate deps (`ico` and
|
5. Theme system in Bevy plugin table (§5)
|
||||||
`icns` respectively) and only matter at app-bundle time
|
6. `Settings` new fields: `color_blind_mode`, `high_contrast_mode`,
|
||||||
(cargo-bundle / packaging), not at `cargo run`. Open if the
|
`reduce_motion_mode`, `window_geometry`, `selected_card_back`,
|
||||||
project later ships as a packaged macOS / Windows app.
|
`selected_background`
|
||||||
|
7. DB migration 002 (§7)
|
||||||
|
8. Update "Last Updated" date
|
||||||
|
|
||||||
### Other small candidates
|
---
|
||||||
|
|
||||||
- *Play-by-Seed dialog — closed 2026-05-08 by `0cb1587`.*
|
## Process notes
|
||||||
`PlayBySeedPlugin` adds a numeric-input modal with async solver
|
|
||||||
preview (debounced 500 ms). `HomeMode::PlayBySeed` card fires
|
|
||||||
`StartPlayBySeedRequestEvent`. 5 unit tests. 75 new verified-win
|
|
||||||
seeds (`2062bd0`) expand `CHALLENGE_SEEDS` via the new
|
|
||||||
`solitaire_assetgen::gen_seeds` binary.
|
|
||||||
- *Prev/Next selector chips spawn site — closed 2026-05-08 by
|
|
||||||
`a449f60`.* `ReplayPrevButton` / `ReplayNextButton` /
|
|
||||||
`ReplaySelectorCaption` / `ReplaySelectorDetail` now spawn in
|
|
||||||
`spawn_stats_screen` as a compact chip row above the Watch
|
|
||||||
Replay action. The Shareable badge is in the detail line.
|
|
||||||
The click handler and repaint systems were already live since
|
|
||||||
v0.19.0; this was purely the missing spawn site.
|
|
||||||
- **Toast queue / immediate unification.** The two toast paths
|
|
||||||
(`spawn_queued_toast` for `InfoToastEvent` queue; `spawn_toast`
|
|
||||||
for 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
|
- **Commit attribution:** use `funman300` as git user. Co-author line:
|
||||||
|
`Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>`.
|
||||||
|
- **Commit format:** `type(scope): description` per CLAUDE.md §7.
|
||||||
|
- **Never commit without:** `cargo test --workspace` passing + clippy clean.
|
||||||
|
- **Sub-agents** stage/verify only; orchestrator commits.
|
||||||
|
- **`CARD_PLAN.md`** referenced in `theme/` module comments but not present in
|
||||||
|
repo. Clean up references or commit the file.
|
||||||
|
- **Token-port pattern** (v0.20.0): when migrating tokens, walk every concrete
|
||||||
|
artifact downstream — PNGs, SVGs, literals, comments. Three "walked past this"
|
||||||
|
follow-ups in v0.21.0 all had this shape.
|
||||||
|
|
||||||
- **The desktop-adaptation spec is the canonical reference for
|
---
|
||||||
geometry decisions** when porting any future plugin. Read
|
|
||||||
`docs/ui-mockups/desktop-adaptation.md` first; 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_variants` is unreliable for layout-only
|
|
||||||
adaptation prompts** as of 2026-05-07. The first call timed
|
|
||||||
out and no variant ever landed in `list_screens`. If a future
|
|
||||||
session wants visual desktop mockups, prefer
|
|
||||||
`generate_screen_from_text` with a fresh narrow prompt per
|
|
||||||
screen rather than `generate_variants` against 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":
|
|
||||||
1. Constants module (`ui_theme.rs`) is the source of truth.
|
|
||||||
2. Const sites that can't call `Alpha::with_alpha` (not yet
|
|
||||||
`const` on 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`).
|
|
||||||
3. 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.
|
|
||||||
4. Domain colours (suit pips, card faces, lerp helpers) stay
|
|
||||||
as literals with a comment naming the rationale; only UI
|
|
||||||
chrome routes through tokens.
|
|
||||||
- **`SplashFadable` scaffolding pattern** (introduced in
|
|
||||||
`cacb19c`). Any future overlay that needs to fade `N >> 3`
|
|
||||||
elements 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 the `Without<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. As of v0.21.0 origin matches local; the next
|
|
||||||
push happens when post-cut work accumulates and is ready to roll
|
|
||||||
into a v0.21.1 / v0.22.0 cut.
|
|
||||||
|
|
||||||
### 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`), brick-red primary CTA (`#a54242` —
|
|
||||||
swapped from cyan `#6fc2ef` in v0.21.0 commit `a292a7e`), 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 swaps red → lime `#acc267`
|
|
||||||
(was red → cyan pre-v0.21.0; lime is the next-best non-red
|
|
||||||
base16-eighties accent now that the primary itself is red).
|
|
||||||
- **Card glyphs render upright in both corners** — no 180°
|
|
||||||
inverted-corner-indicator rotation. Single-orientation
|
|
||||||
digital play doesn't benefit from the traditional flip-
|
|
||||||
readback convention. `design-system.md` § Game Cards
|
|
||||||
documents this deliberate deviation.
|
|
||||||
|
|
||||||
## Resume prompt
|
## Resume prompt
|
||||||
|
|
||||||
```
|
```
|
||||||
You are a senior Rust + Bevy developer working on Solitaire Quest.
|
You are a senior Rust + Bevy developer working on Solitaire Quest.
|
||||||
Working directory: <Rusty_Solitaire clone path on this machine>.
|
Working directory: <Rusty_Solitaire clone path>.
|
||||||
Branch: master. v0.21.8 is tagged at c50eaf8 (cut 2026-05-08,
|
Branch: master. v0.23.0 is the current version (HEAD locally: bd388fe).
|
||||||
replay-overlay polish). Seven post-cut commits are on master (see
|
Phase 8 sync is fully shipped. ARCHITECTURE.md is now v1.3 (all Phase 8 gaps closed).
|
||||||
"Since the v0.21.8 cut" above); push of the last four pending.
|
Push to origin pending (bd388fe + ARCHITECTURE.md + SESSION_HANDOFF.md commits).
|
||||||
v0.21.7 stays at da3e542, v0.21.6 at f63db76, v0.21.5 at a2432df,
|
|
||||||
v0.21.4 at 23ff62c, v0.21.3 at 3d92a91, v0.21.2 at f23df3b,
|
|
||||||
v0.21.1 at daa655a, v0.21.0 at 04f9bf9.
|
|
||||||
Working tree: uncommitted CHANGELOG + SESSION_HANDOFF docs; push
|
|
||||||
pending. See CHANGELOG.md § [0.21.9] for full detail.
|
|
||||||
|
|
||||||
State: HEAD locally — see `git rev-parse HEAD`. Workspace
|
READ FIRST (in order):
|
||||||
tests: 1292 passing / 0 failing. Clippy clean.
|
|
||||||
|
|
||||||
READ FIRST (in order, before doing anything):
|
|
||||||
1. SESSION_HANDOFF.md — this file
|
1. SESSION_HANDOFF.md — this file
|
||||||
2. CHANGELOG.md — [0.21.9] section has the pending-cut items
|
2. CHANGELOG.md — [0.23.0] section has the full Phase 8 detail
|
||||||
3. CLAUDE.md — unified-3.0 rule set
|
3. CLAUDE.md — unified-3.0 rule set
|
||||||
4. CLAUDE_SPEC.md — formal architecture spec
|
4. ARCHITECTURE.md — v1.3, fully up to date
|
||||||
5. ARCHITECTURE.md — crate responsibilities + data flow
|
5. docs/ui-mockups/ — design system + mockup library
|
||||||
6. docs/ui-mockups/ — design system + 24-mockup library +
|
6. docs/android/ — Android setup + build runbook
|
||||||
desktop-adaptation.md (the rules-based
|
7. ~/.claude/projects/<this-project>/memory/MEMORY.md
|
||||||
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:
|
OPEN WORK (in priority order):
|
||||||
A. Android follow-ups — JNI ClipboardManager bridge (arboard
|
B. Leaderboard best-score auto-post (server sync handler + optional
|
||||||
has no Android backend), Android Keystore (blocked on Phase 8).
|
GameWonEvent path in sync_plugin)
|
||||||
Launch verification + double-tap are closed.
|
C. Refresh token rotation (server auth handler + new column/table)
|
||||||
B. Phase 8 (sync) — local storage scaffolding, self-hosted
|
D. Android AVD functional tests (Keystore + clipboard)
|
||||||
Axum server, `SolitaireServerClient` impl. The biggest open
|
E. Theme importer UI button in Settings
|
||||||
arc by scope; rolls up Android dependencies (Keystore,
|
F. mirror_achievement: decide + implement or remove from trait
|
||||||
ClipboardManager).
|
|
||||||
C. Play-by-Seed polish — the dialog is functional but has no
|
|
||||||
visual preview of the solver verdict in the UI yet; the
|
|
||||||
HomeMode card is wired but the dialog spawn site and verdict
|
|
||||||
display could use a second pass.
|
|
||||||
|
|
||||||
WORKFLOW NOTES:
|
Ask which to start. All are independent; any is a valid next arc.
|
||||||
- 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.
|
|
||||||
- Token-port pattern: when migrating tokens, walk every
|
|
||||||
concrete artifact downstream of the token (PNG textures,
|
|
||||||
embedded SVGs, hardcoded literals, comment color names),
|
|
||||||
not just the token name. v0.21.0 surfaced three "the
|
|
||||||
migration walked past this" follow-ups that all matched
|
|
||||||
this shape — codified here so future similar work can
|
|
||||||
pattern-match instead of rediscovering.
|
|
||||||
- Doc-vs-implementation drift pattern: v0.21.1's pile-marker
|
|
||||||
visibility fix (`4d48cad`) implemented an invariant that
|
|
||||||
had been declared in a module doc comment but was never
|
|
||||||
enforced in code. When future work touches a module with
|
|
||||||
a "this does X" doc comment, verify the code actually does
|
|
||||||
X and add a test if not. Two layers, two checks.
|
|
||||||
|
|
||||||
OPEN AT THE START: ask which of A–C. Don't pick unilaterally.
|
|
||||||
Note: every remaining option is multi-session by nature (A is
|
|
||||||
gated on Android tooling; B and C are explicitly multi-session
|
|
||||||
arcs). A fresh session is a better fit for any of them than the
|
|
||||||
tail of a long working stretch.
|
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user