docs: update all project docs to reflect Phase 8 + Android work
- CLAUDE.md unified-3.1 → unified-4.0: narrowed error policy, relaxed ECS/embed/API rules, added Android pitfalls, modal conventions (§14), Android build guide (§15), context injection system (§16), auto-hide HUD chrome exception in UI-first rule - ARCHITECTURE.md: Android → Active platform; add Android to sync table; add SafeAreaInsets + HudVisibility to Key Resources; add solitaire_wasm - CLAUDE_SPEC.md: add solitaire_wasm crate; communication: events → events and resources - CLAUDE_PROMPT_PACK.md: fix §8 typo; narrow dep rule to core/sync only - SESSION_HANDOFF.md: add §5b Android UX punch list; resume prompt unified-4.0 - docs/android/PLAYABILITY_TODO.md: add P5 section (UX-1/UX-5b/UX-7/BUG-3) - docs/SESSION_HANDOFF.md: mark as archived (Phase 2 era) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+8
-1
@@ -43,6 +43,7 @@ Solitaire Quest is a cross-platform Klondike Solitaire game written in Rust, tar
|
||||
| macOS | Self-hosted server | Full feature set |
|
||||
| Windows | Self-hosted server | Full feature set |
|
||||
| Linux | Self-hosted server | Full feature set |
|
||||
| Android | Self-hosted server | Touch input; safe-area insets via JNI; `cargo-apk` build |
|
||||
|
||||
### Design Principles
|
||||
|
||||
@@ -322,6 +323,12 @@ struct FontResource(Handle<Font>);
|
||||
struct BackgroundImageSet {
|
||||
handles: Vec<Handle<Image>>, // indices 0–4 match selected_background setting
|
||||
}
|
||||
|
||||
// OS-reserved edge insets (physical px); zero on desktop
|
||||
struct SafeAreaInsets { top: f32, bottom: f32, left: f32, right: f32 }
|
||||
|
||||
// Whether the HUD band is visible (auto-hide chrome feature)
|
||||
enum HudVisibility { Visible, Hidden }
|
||||
```
|
||||
|
||||
### Key Bevy Events
|
||||
@@ -900,7 +907,7 @@ All sound effect WAV files are embedded at compile time via `include_bytes!()` i
|
||||
| macOS | Primary | Self-hosted server | x86_64 + Apple Silicon (universal binary via `cargo-lipo`) |
|
||||
| Windows | Primary | Self-hosted server | x86_64, MSVC toolchain |
|
||||
| Linux | Primary | Self-hosted server | x86_64, tested on Ubuntu 22.04+ and Fedora 39+ |
|
||||
| Android | Stretch | Self-hosted server | `cargo-mobile2`, touch input |
|
||||
| Android | Active | Self-hosted server | `cargo-apk`; touch + long-press + double-tap; safe-area JNI; portrait layout |
|
||||
| iOS | Stretch | Self-hosted server | `cargo-mobile2`, touch input |
|
||||
|
||||
Minimum Bevy window size enforced: 800×600. Desktop windows are freely resizable; layout recomputes on `WindowResized`.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# CLAUDE.md
|
||||
|
||||
version: unified-3.0
|
||||
version: unified-4.0
|
||||
|
||||
---
|
||||
|
||||
@@ -29,8 +29,9 @@ solitaire_sync/ # Shared API + merge logic
|
||||
solitaire_data/ # Persistence + sync client
|
||||
solitaire_engine/ # Bevy ECS + UI + gameplay orchestration
|
||||
solitaire_server/ # Axum backend (optional sync layer)
|
||||
solitaire_wasm/ # WASM bindings for browser-side replay player
|
||||
solitaire_app/ # Entry binary
|
||||
assets/ # Runtime assets (except audio)
|
||||
assets/ # Runtime assets (except audio + default theme)
|
||||
```
|
||||
|
||||
---
|
||||
@@ -72,12 +73,16 @@ These override all other instructions.
|
||||
|
||||
* NO `unwrap()`
|
||||
* NO `panic!()` in runtime/game logic
|
||||
* All state transitions:
|
||||
* Core game state mutations MUST return:
|
||||
|
||||
```rust id="err_model"
|
||||
Result<T, MoveError>
|
||||
```
|
||||
|
||||
* Engine / UI state changes follow ECS patterns (Resources, Events) —
|
||||
they do not return `MoveError`
|
||||
* Use `thiserror`-derived types for any new error enums outside `solitaire_core`
|
||||
|
||||
---
|
||||
|
||||
## 2.4 Threading Rules
|
||||
@@ -126,10 +131,15 @@ trait SyncProvider
|
||||
## 3.1 ECS Design
|
||||
|
||||
* systems = single responsibility
|
||||
* communication = Events only
|
||||
* shared state = Resources only
|
||||
* cross-system communication = Events (fire-and-forget triggers)
|
||||
* persistent shared state = Resources (polled every frame or on change)
|
||||
* per-entity state = Components only
|
||||
|
||||
Events and Resources are both valid communication paths — use Events when
|
||||
the receiver needs to react once; use Resources when the receiver polls
|
||||
or when multiple systems read the same value (e.g. `SafeAreaInsets`,
|
||||
`HudVisibility`, `LayoutResource`).
|
||||
|
||||
---
|
||||
|
||||
## 3.2 Game State Authority
|
||||
@@ -149,11 +159,22 @@ Every player action MUST:
|
||||
Keyboard shortcuts are:
|
||||
→ optional accelerators only
|
||||
|
||||
**Exception — UI chrome gestures:**
|
||||
Tap-to-toggle visibility of UI chrome (e.g. auto-hiding HUD band) is
|
||||
permitted without a visible button. The gesture MUST:
|
||||
* affect only chrome visibility, never game state
|
||||
* restore chrome automatically when any modal opens
|
||||
* be purely additive (game remains fully playable with chrome always visible)
|
||||
|
||||
---
|
||||
|
||||
## 3.4 Layout System
|
||||
|
||||
* recompute on `WindowResized`
|
||||
* recompute on `SafeAreaInsets` changed
|
||||
* recompute on `HudVisibility` changed
|
||||
* `compute_layout` MUST accept `hud_visible: bool`; pass `HUD_BAND_HEIGHT`
|
||||
when `true`, `0.0` when `false`
|
||||
* no fixed resolution assumptions
|
||||
|
||||
---
|
||||
@@ -178,11 +199,18 @@ Includes:
|
||||
|
||||
## 4.2 Embedded Assets
|
||||
|
||||
Only audio:
|
||||
Embed via `include_bytes!()` only when ALL of the following are true:
|
||||
|
||||
```text id="audio_rule"
|
||||
include_bytes!()
|
||||
```
|
||||
* the asset is small (< 500 KB uncompressed)
|
||||
* it changes rarely (not user-customisable)
|
||||
* a missing file would be a hard crash, not a graceful degradation
|
||||
|
||||
Currently embedded:
|
||||
* **Audio** — all `.wav` files in `audio_plugin.rs`
|
||||
* **Default card theme** — shipped via `embedded://` scheme in `ThemePlugin`
|
||||
|
||||
Do NOT embed card face PNGs, background images, or user fonts —
|
||||
these are loaded via `AssetServer` so art can be swapped without recompile.
|
||||
|
||||
---
|
||||
|
||||
@@ -210,7 +238,9 @@ Must degrade gracefully under `MinimalPlugins`.
|
||||
## 5.2 Public API Rules
|
||||
|
||||
* prefer `Into<T>` over concrete types
|
||||
* all public items require doc comments
|
||||
* publicly exported functions, traits, and non-trivial types require doc comments
|
||||
* simple marker components, newtype wrappers, and internal `pub` items
|
||||
used only within the same crate are exempt from doc comment requirements
|
||||
|
||||
---
|
||||
|
||||
@@ -276,11 +306,13 @@ NEVER commit otherwise
|
||||
|
||||
Claude must request confirmation before:
|
||||
|
||||
* adding dependencies
|
||||
* modifying `solitaire_sync`
|
||||
* changing DB schema
|
||||
* adding dependencies to `solitaire_core` or `solitaire_sync`
|
||||
(engine/server crates may add deps without confirmation)
|
||||
* modifying `solitaire_sync` types or the `SyncProvider` trait
|
||||
* changing DB schema (migrations are append-only)
|
||||
* introducing `unsafe`
|
||||
* changing merge strategy
|
||||
* changing the merge strategy in `solitaire_sync::merge`
|
||||
* changing the `SyncPayload` wire format (breaking change for existing servers)
|
||||
|
||||
---
|
||||
|
||||
@@ -304,10 +336,29 @@ Core is always the source of truth.
|
||||
|
||||
Must always be handled explicitly:
|
||||
|
||||
**All platforms**
|
||||
* Bevy `Time` uses `f32`
|
||||
* `sqlx::migrate!()` path is crate-relative
|
||||
* `dirs::data_dir()` may return `None`
|
||||
* Linux may lack keyring backend
|
||||
* Linux may lack keyring backend — handle `keyring::Error` gracefully
|
||||
|
||||
**Android (active target — not stretch)**
|
||||
* Safe-area insets arrive in frames 1–3 via JNI polling, not at startup;
|
||||
UI that depends on them must handle the zero-inset initial state
|
||||
* Physical pixels ≠ logical pixels: `SafeAreaInsets` values are physical
|
||||
(from `WindowInsets` API); divide by `window.scale_factor()` before
|
||||
passing to Bevy `Val::Px`
|
||||
* `adb shell input tap` uses physical pixel coordinates
|
||||
* FiraMono (bundled font) covers: ASCII, card suits U+2660–2666,
|
||||
Arrows U+2190–21FF. It does NOT cover Geometric Shapes (U+25xx) —
|
||||
those render as missing-glyph rectangles on Android
|
||||
* The gesture/navigation bar at the bottom (≈132px physical on common
|
||||
devices) is inside the Bevy viewport; use `SafeAreaInsets.bottom` to
|
||||
avoid placing interactive elements in that zone
|
||||
* `HUD_BAND_HEIGHT` is 128px on Android (two-row wrap) vs 64px on desktop;
|
||||
layout constants are `#[cfg(target_os = "android")]` gated
|
||||
* JNI calls must use `attach_current_thread_permanently` — not
|
||||
`attach_current_thread` — to avoid detach-on-drop panics
|
||||
|
||||
---
|
||||
|
||||
@@ -318,6 +369,12 @@ Must always be handled explicitly:
|
||||
* blocking async calls in ECS
|
||||
* insecure credential storage
|
||||
* bypassing core logic layer
|
||||
* hardcoded pixel coordinates in layout — always derive from `compute_layout`
|
||||
* Unicode Geometric Shapes block (U+25xx) in UI text — not in FiraMono
|
||||
* spawning a second `ModalScrim` while one already exists without first
|
||||
dismissing the existing one (use `scrims.is_empty()` guard)
|
||||
* reading `SafeAreaInsets` physical values directly into `Val::Px` without
|
||||
dividing by `window.scale_factor()`
|
||||
|
||||
---
|
||||
|
||||
@@ -345,9 +402,74 @@ If unclear:
|
||||
| Both combined | full system understanding |
|
||||
|
||||
---
|
||||
# 14. Context Injection System (AUTOMATIC SCOPE FILTER)
|
||||
# 14. Modal System Conventions
|
||||
|
||||
## 14.1 Purpose
|
||||
All full-screen overlay panels MUST use the `spawn_modal` / `ModalScrim` pattern
|
||||
from `solitaire_engine::ui_modal`.
|
||||
|
||||
## 14.1 Spawn pattern
|
||||
|
||||
```rust
|
||||
let scrim = spawn_modal(commands, MyScreenMarker, Z_MODAL_PANEL, |card| {
|
||||
spawn_modal_header(card, "Title", font_res);
|
||||
// ... body nodes ...
|
||||
spawn_modal_actions(card, |actions| {
|
||||
spawn_modal_button(actions, MyCloseButton, "Done", None,
|
||||
ButtonVariant::Primary, font_res);
|
||||
});
|
||||
});
|
||||
// Optional: allow clicking the scrim outside the card to dismiss
|
||||
commands.entity(scrim).insert(ScrimDismissible);
|
||||
```
|
||||
|
||||
## 14.2 Guard rule
|
||||
|
||||
Before spawning a new modal, check `scrims: Query<(), With<ModalScrim>>`
|
||||
and return early if `!scrims.is_empty()` — unless the new modal is
|
||||
explicitly replacing the current one (despawn first, then spawn).
|
||||
|
||||
## 14.3 Safe area
|
||||
|
||||
Every `ModalScrim` automatically receives `padding.bottom` equal to the
|
||||
logical gesture-bar height via `apply_safe_area_to_modal_scrims` in
|
||||
`SafeAreaInsetsPlugin`. Do not manually add bottom padding to scrim nodes.
|
||||
|
||||
## 14.4 Z-ordering
|
||||
|
||||
Use `Z_MODAL_PANEL` from `ui_theme` for all modal scrims. Do not use
|
||||
raw `z_index` values — they drift and cause ordering bugs.
|
||||
|
||||
---
|
||||
|
||||
# 15. Android Build & Verification
|
||||
|
||||
## 15.1 Build command
|
||||
|
||||
```bash
|
||||
cargo apk build --package solitaire_app --lib
|
||||
adb install -r target/debug/apk/solitaire-quest.apk
|
||||
```
|
||||
|
||||
## 15.2 Coordinate system reminder
|
||||
|
||||
Device physical: 1080×2400. Bevy logical: 900×2000. Scale factor: 1.20.
|
||||
`adb shell input tap X Y` takes PHYSICAL coordinates.
|
||||
To convert from what you see on screen (logical): multiply by 1.20.
|
||||
|
||||
## 15.3 Android-specific test checklist
|
||||
|
||||
Before shipping any Android build:
|
||||
- [ ] Safe area insets arrive and shift HUD correctly (check after 3s)
|
||||
- [ ] All modal Done buttons are above the gesture bar
|
||||
- [ ] No Geometric Shapes glyphs in UI text
|
||||
- [ ] HUD band does not overlap the top status bar
|
||||
- [ ] Touch drag-and-drop works on all pile types
|
||||
|
||||
---
|
||||
|
||||
# 16. Context Injection System (AUTOMATIC SCOPE FILTER)
|
||||
|
||||
## 16.1 Purpose
|
||||
|
||||
Before generating any response, Claude MUST construct a **minimal relevant context set**.
|
||||
|
||||
@@ -360,7 +482,7 @@ This prevents:
|
||||
|
||||
---
|
||||
|
||||
## 14.2 Input Classification Step (MANDATORY)
|
||||
## 16.2 Input Classification Step (MANDATORY)
|
||||
|
||||
Every request MUST be classified into exactly one task type:
|
||||
|
||||
@@ -381,13 +503,13 @@ If uncertain → ask clarification.
|
||||
|
||||
---
|
||||
|
||||
## 14.3 Context Selection Engine
|
||||
## 16.3 Context Selection Engine
|
||||
|
||||
After classification, Claude MUST include ONLY the relevant sections below.
|
||||
|
||||
---
|
||||
|
||||
## 14.4 Context Map (CORE RULESET)
|
||||
## 16.4 Context Map (CORE RULESET)
|
||||
|
||||
### feature
|
||||
|
||||
@@ -495,7 +617,7 @@ Include:
|
||||
|
||||
---
|
||||
|
||||
## 14.5 Context Compression Rules
|
||||
## 16.5 Context Compression Rules
|
||||
|
||||
Claude MUST obey:
|
||||
|
||||
@@ -506,7 +628,7 @@ Claude MUST obey:
|
||||
|
||||
---
|
||||
|
||||
## 14.6 Context Priority Order
|
||||
## 16.6 Context Priority Order
|
||||
|
||||
When space is limited:
|
||||
|
||||
@@ -517,7 +639,7 @@ When space is limited:
|
||||
|
||||
---
|
||||
|
||||
## 14.7 “No Context Pollution” Rule
|
||||
## 16.7 “No Context Pollution” Rule
|
||||
|
||||
Claude must NOT include:
|
||||
|
||||
@@ -529,7 +651,7 @@ Claude must NOT include:
|
||||
|
||||
---
|
||||
|
||||
## 14.8 Self-Check Before Execution
|
||||
## 16.8 Self-Check Before Execution
|
||||
|
||||
Before writing code, Claude MUST verify:
|
||||
|
||||
@@ -542,7 +664,7 @@ If any fail → revise context selection.
|
||||
|
||||
---
|
||||
|
||||
## 14.9 Injection Output Format (Internal Model)
|
||||
## 16.9 Injection Output Format (Internal Model)
|
||||
|
||||
Claude should behave as if it constructed:
|
||||
|
||||
@@ -560,7 +682,7 @@ Claude should behave as if it constructed:
|
||||
|
||||
---
|
||||
|
||||
## 14.10 Relationship to ARCHITECTURE.md
|
||||
## 16.10 Relationship to ARCHITECTURE.md
|
||||
|
||||
* ARCHITECTURE.md = source of truth
|
||||
* CLAUDE.md = execution constraints
|
||||
|
||||
@@ -12,7 +12,7 @@ You must follow CLAUDE_SPEC.md strictly.
|
||||
Rules:
|
||||
- Do not expand scope beyond what is defined
|
||||
- Do not refactor unrelated code
|
||||
- Do not introduce new dependencies
|
||||
- Do not introduce new dependencies to solitaire_core or solitaire_sync without confirmation
|
||||
- Prefer minimal, surgical changes
|
||||
- Use existing patterns in the codebase
|
||||
- Return minimal diffs or changed functions only
|
||||
@@ -360,7 +360,7 @@ notes:
|
||||
target:
|
||||
"<what is slow>"
|
||||
|
||||
constraints:CLAUDE_WORKFLOW.md
|
||||
constraints:
|
||||
- no behavior change
|
||||
- no architecture change
|
||||
- minimal code changes
|
||||
|
||||
+5
-1
@@ -41,6 +41,10 @@ solitaire_server:
|
||||
depends_on: [solitaire_sync, axum, sqlx, jsonwebtoken]
|
||||
role: "backend"
|
||||
|
||||
solitaire_wasm:
|
||||
depends_on: [solitaire_core, wasm-bindgen, serde-wasm-bindgen]
|
||||
role: "wasm_replay_player"
|
||||
|
||||
solitaire_app:
|
||||
depends_on: [solitaire_engine]
|
||||
role: "entrypoint"
|
||||
@@ -180,7 +184,7 @@ threading:
|
||||
|
||||
plugins:
|
||||
pattern: "feature_isolation"
|
||||
communication: "events"
|
||||
communication: "events and resources"
|
||||
|
||||
---
|
||||
|
||||
|
||||
+24
-2
@@ -89,6 +89,22 @@ Also shipped (pre-Phase 8 but post-v0.22.0, already in CHANGELOG):
|
||||
subcommand reads new password from stdin, bcrypt-hashes it, invalidates all
|
||||
active sessions for the user.
|
||||
|
||||
### 5b. Android UX polish (2026-05-12)
|
||||
|
||||
- [x] **UX-1 — Modal Done button in gesture zone.** `apply_safe_area_to_modal_scrims` system
|
||||
added to `SafeAreaInsetsPlugin` (`safe_area.rs`). Pads every `ModalScrim` bottom by
|
||||
`insets.bottom / scale`. Fires on resource change + `Added<ModalScrim>`. Verified on device.
|
||||
- [x] **UX-5b — Home mode glyph corruption.** Geometric Shapes (U+25xx, absent from FiraMono)
|
||||
replaced with card suits U+2660–2666 in `home_plugin.rs`. Affects Zen/Challenge/Daily mode
|
||||
selector buttons at level 5+.
|
||||
- [x] **UX-7 — Help text wrap.** Android HUD entry shortened to
|
||||
`"Open menu (Stats, Settings, Profile...)"` in `help_plugin.rs` — fits one line.
|
||||
- [x] **BUG-3 — Multi-modal stacking.** `handle_menu_button` now checks
|
||||
`scrims: Query<(), With<ModalScrim>>` and guards `spawn_menu_popover` with `scrims.is_empty()`.
|
||||
Verified on device: ≡ tap while Stats open does nothing.
|
||||
|
||||
**Note:** These 4 fixes are implemented and verified but not yet committed.
|
||||
|
||||
### 6. Testing gaps
|
||||
- [x] **Server 401 → refresh → retry path.** Done (`198df75`): both
|
||||
`jwt_refresh_on_401_succeeds` (pull) and
|
||||
@@ -141,7 +157,7 @@ Branch: master. v0.23.0 is the current version (HEAD: 03be4fc). Fully pushed.
|
||||
READ FIRST (in order):
|
||||
1. SESSION_HANDOFF.md — this file
|
||||
2. CHANGELOG.md — [0.23.0] section has full Phase 8 detail
|
||||
3. CLAUDE.md — unified-3.0 rule set
|
||||
3. CLAUDE.md — unified-4.0 rule set
|
||||
4. ARCHITECTURE.md — v1.3, fully up to date
|
||||
5. docs/ui-mockups/ — design system + mockup library
|
||||
6. docs/android/ — Android setup + build runbook
|
||||
@@ -151,5 +167,11 @@ OPEN WORK:
|
||||
Phase 8 punch list is fully closed. All items verified complete.
|
||||
Remaining nuisance: `cargo apk build --lib` noisy stderr (cosmetic, non-blocking).
|
||||
|
||||
Suggest starting Phase 9 planning — ask what the next arc should be.
|
||||
4 Android UX fixes are implemented and verified but NOT YET COMMITTED:
|
||||
- BUG-3 (hud_plugin.rs): multi-modal stacking guard
|
||||
- UX-7 (help_plugin.rs): help text wrap on Android
|
||||
- UX-5b (home_plugin.rs): FiraMono glyph corruption in mode selector
|
||||
- UX-1 (safe_area.rs): modal Done button in gesture zone
|
||||
|
||||
Commit those first, then suggest Phase 9 planning.
|
||||
```
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
# Solitaire Quest — Session Handoff
|
||||
# Solitaire Quest — Session Handoff (ARCHIVED)
|
||||
|
||||
> **This file is from Phase 2 (2026-04-25, 242 tests). It is kept for historical
|
||||
> reference only. The authoritative session handoff is at the repo root:
|
||||
> `SESSION_HANDOFF.md`.**
|
||||
|
||||
> Last updated: 2026-04-25
|
||||
> Branch: `master` — pushed to https://github.com/funman300/Rusty_Solitaire.git
|
||||
|
||||
@@ -230,6 +230,31 @@ rewrites required.
|
||||
|
||||
---
|
||||
|
||||
## P5 — UX polish (2026-05-12)
|
||||
|
||||
- [x] **UX-1 — Modal Done button unreachable in gesture zone.** *Closed
|
||||
2026-05-12.* New `apply_safe_area_to_modal_scrims` system in
|
||||
`safe_area.rs` pads every `ModalScrim` bottom by `insets.bottom /
|
||||
window.scale_factor()` (logical pixels). Fires when `SafeAreaInsets`
|
||||
changes AND when a new `ModalScrim` is spawned (`Added<ModalScrim>`
|
||||
filter). Verified on device: Settings Done button reachable at physical
|
||||
y ≈ 1800–2000 (was y ≈ 2232+, inside gesture zone).
|
||||
- [x] **UX-5b — Home mode selector glyph corruption.** *Closed
|
||||
2026-05-12.* `home_plugin.rs` mode glyphs changed from Geometric Shapes
|
||||
block (U+25xx — absent from FiraMono, renders as rectangles) to card
|
||||
suits U+2660 ♠ / U+2665 ♥ / U+2666 ♦. Affects Zen, Challenge, and
|
||||
Daily mode selector buttons shown at level 5+.
|
||||
- [x] **UX-7 — Help screen HUD button entry wraps to two lines.** *Closed
|
||||
2026-05-12.* Android `CONTROL_SECTIONS` entry for ≡ button shortened
|
||||
from `"Menu: Stats, Settings, Profile, Achievements"` to
|
||||
`"Open menu (Stats, Settings, Profile...)"` in `help_plugin.rs`.
|
||||
Fits on one line at 360 dp.
|
||||
- [x] **BUG-3 — Multi-modal stacking (Stats + Profile simultaneously).** *Closed
|
||||
2026-05-12.* `handle_menu_button` in `hud_plugin.rs` now checks
|
||||
`scrims: Query<(), With<ModalScrim>>` and only calls
|
||||
`spawn_menu_popover` when `scrims.is_empty()`. Tapping ≡ while any
|
||||
modal is open is a no-op. Verified on device.
|
||||
|
||||
## Notes / decisions
|
||||
|
||||
* This list is screenshot-driven; expect more items to surface once
|
||||
|
||||
Reference in New Issue
Block a user