# Solitaire Quest — Claude Code Instructions

See @ARCHITECTURE.md for full project design, crate responsibilities, data models, and API reference.

---

## Project Layout

```text
solitaire_core/    # Pure Rust game logic — NO Bevy, NO network, NO I/O
solitaire_sync/    # Shared API types — NO Bevy, serde/uuid/chrono only
solitaire_data/    # Persistence + SyncProvider trait + server client
solitaire_engine/  # Bevy ECS systems, components, plugins
solitaire_server/  # Axum sync server binary
solitaire_app/     # Thin binary entry point
assets/            # Source assets — embedded at compile time via include_bytes!()
```

---

## Build & Test Commands

```bash
# Dev run (fast compile via dynamic linking)
cargo run -p solitaire_app --features bevy/dynamic_linking

# Release build
cargo build --workspace --release

# All tests — MUST pass before any commit
cargo test --workspace

# Lint — MUST pass clean (zero warnings)
cargo clippy --workspace -- -D warnings

# Run sync server locally
cargo run -p solitaire_server

# Check a single crate
cargo test -p solitaire_core
cargo clippy -p solitaire_core -- -D warnings
```

---

## Hard Rules

- `solitaire_core` and `solitaire_sync` must never gain Bevy or network dependencies.
- No `unwrap()` or `panic!()` in game logic. All state transitions return `Result<_, MoveError>`.
- Audio assets are embedded at compile time using `include_bytes!()` in `audio_plugin.rs`.
- Card faces (52 PNGs in `assets/cards/faces/`), card backs (`assets/cards/backs/back_N.png`), board backgrounds (`assets/backgrounds/bg_N.png`), and the UI font (`assets/fonts/main.ttf`) are loaded at runtime via `AssetServer::load()` and stored as `Handle<Image>`/`Handle<Font>` in the `CardImageSet`, `BackgroundImageSet`, and `FontResource` resources. The `assets/` directory must ship alongside the binary.
- Asset-loading systems take `Option<Res<AssetServer>>` so they degrade cleanly under `MinimalPlugins` (tests). When `CardImageSet` is absent, `card_plugin` falls back to a `Text2d` rank+suit overlay; when `BackgroundImageSet` is absent, the board falls back to a solid colour.
- Atomic file writes only: write to `filename.json.tmp`, then `rename()`.
- Passwords and tokens are stored in the OS keychain via the `keyring` crate — never in plaintext files or logs.
- Sync runs on `AsyncComputeTaskPool` — never block the Bevy main thread.
- All sync backends implement the `SyncProvider` trait. The `SyncPlugin` is backend-agnostic — never `match` on `SyncBackend` inside a Bevy system.
- `cargo clippy --workspace -- -D warnings` must pass clean after every change.
- `cargo test --workspace` must pass after every change.

---

## Code Style

- Use `thiserror` for error types. Never `Box<dyn Error>` in library crates.
- Prefer `Into<T>` over concrete types in public API function parameters.
- All public items must have doc comments (`///`). Private items: comment only when non-obvious.
- Derive order convention: `#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]`
- Bevy systems: one responsibility per system. Use `Events` for cross-system communication, never shared mutable state.
- SQL queries: use `sqlx::query!` macros (compile-time checked), not raw string queries.
- No `clone()` calls in hot paths (game loop systems). Profile before optimising elsewhere.

---

## Bevy Conventions

- One `Plugin` per major feature: `CardPlugin`, `AudioPlugin`, `AchievementPlugin`, `UIPlugin`, `SyncPlugin`.
- Resources own shared state. Events communicate between systems. Components own per-entity data.
- All UI screens are built with Bevy UI (`bevy::ui`). Never mix UI layout and game logic in the same system.
- Layout is recomputed on `WindowResized` — never assume a fixed window size.
- **UI-first.** Every player-triggered action (new game, undo, draw, pause, open stats / settings / help / profile / leaderboard, switch mode, etc.) must be reachable from a visible UI control. Keyboard shortcuts are optional accelerators — never the sole entry point. New gameplay features ship with the UI control alongside the system that backs it; do not merge a feature that is keyboard-only.

---

## Git Workflow

- Commit after each passing phase, not after every file change.
- Commit message format: `type(scope): description`
  - `feat(core): add draw-three mode validation`
  - `fix(engine): card z-order during drag`
  - `test(core): undo stack boundary conditions`
  - `chore(server): add sqlx migration 002`
- Never commit with failing tests or clippy warnings.
- Never commit secrets, `.env` files, or `*.db` files.

---

## Ask Before Doing

- Adding a new crate dependency (discuss alternatives first).
- Changing a type in `solitaire_sync` (breaking change on both client and server).
- Altering the database schema (requires a new sqlx migration).
- Introducing `unsafe` code anywhere.
- Changing the merge strategy in `solitaire_sync::merge()`.

---

## Lessons Learned

> Add entries here when Claude makes a mistake so it isn't repeated.

- Bevy's `Time` resource uses `f32` seconds; convert to `u64` only when writing to `StatsSnapshot`.
- `sqlx::migrate!()` macro path is relative to the crate root, not the workspace root.
- `keyring` on Linux requires a running secret service (e.g. GNOME Keyring or KWallet) — handle `Error::NoStorageAccess` gracefully and fall back to prompting the user.
- `dirs::data_dir()` returns `None` on some minimal Linux environments — always handle the `None` case explicitly, do not unwrap.
