# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Commands

```bash
cargo build              # build
cargo run                # run ktype
cargo test               # run tests (std runner)
cargo nextest run        # run tests with nextest (preferred)
cargo nextest run <name> # run a single test by name substring
cargo clippy -- -D warnings  # lint
cargo fmt                # format
```

## Architecture

ktype follows **The Elm Architecture (TEA)**. All state lives in `Model`, all events flow as `Msg`, `update` is the only place state changes, and `view` is pure rendering.

```
main.rs        — terminal init/restore, 60fps event loop
app.rs         — current skeleton (will be replaced by TEA modules)
model.rs       — Model struct: Screen, SessionState, Stats, Config
msg.rs         — Msg enum: all events, no side effects
update.rs      — pure fn update(model, msg) -> Command
view.rs        — pure fn view(model, frame): only renders, no mutation
commands.rs    — side effects: save stats, generate words, start timer
input.rs       — keystroke routing and raw character handling
generator.rs   — random word generation (with punctuation/numbers toggles)
metrics.rs     — WPM, raw WPM, accuracy, consistency calculations
stats.rs       — session stats types
persistence.rs — JSON R/W to ~/.config/ktype/stats.json
```

The loop in `main.rs` polls events at 16ms (≈60fps), dispatches to `handle_event`, then redraws. `should_quit` on the app struct is the exit signal.

## Key Conventions

- `update` must remain pure — no I/O, no side effects; return a `Command` for anything async/effectful
- `view` must remain pure — read `Model` only, never mutate
- Character state (untyped / correct / incorrect / cursor) is the core data primitive for the test screen
- Stats persist to `~/.config/ktype/stats.json`; future migration path is SQLite
- Use `thiserror` for domain errors, `anyhow` for top-level CLI error propagation
- Property tests (`proptest`) cover `update` invariants and metric calculations; snapshot tests (`insta`) cover rendered UI frames

## Testing Strategy

- **Unit**: WPM/accuracy/character classification — pure functions, deterministic inputs
- **Property** (`proptest`): `update` state machine invariants, generator output, persistence round-trips
- **Snapshot** (`insta`): terminal frame rendering — catch UI regressions
- **Integration**: full session flows (typing → result screen → restart), config toggles, persistence writes

## Build Phases (from spec)

1. ✅ Core event loop (`main.rs` + `app.rs`)
2. → Typing engine (`model`, `msg`, `update`, `input`, `generator`)
3. Rendering (`view`, ratatui widgets)
4. Metrics (`metrics.rs`)
5. Persistence (`persistence.rs`, `stats.rs`)
6. Testing (nextest, proptest, insta)
7. Polish + OSS release (README, CI, rustfmt, clippy)
