Skip to content
68 changes: 30 additions & 38 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
# CLAUDE.md

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

## Project Overview

**Reading Sprout** β€” a Flutter app that teaches children sight words (220 Dolch words + 49 bonus words across 22 levels and 5 themed zones). Supports multiple player profiles, 10 mini games, and works fully offline with pre-generated TTS audio.
**Reading Sprout** β€” a Flutter app that teaches children sight words (220 Dolch words + 49 bonus words across 22 levels and 5 themed zones). Supports multiple player profiles, 17 mini games, a customizable avatar system with GPU shaders, adaptive difficulty, and works fully offline with pre-generated TTS audio.

## Build & Run Commands

Expand All @@ -26,7 +24,7 @@ After modifying `@HiveType` models (currently `lib/models/player_profile.dart`):
dart run build_runner build --delete-conflicting-outputs
```

This regenerates `*.g.dart` files (TypeAdapters). Never hand-edit `.g.dart` files.
This regenerates `*.g.dart` files (TypeAdapters). IMPORTANT: Never hand-edit `.g.dart` files.

### Audio Generation (Python)

Expand All @@ -35,57 +33,51 @@ pip install requests
python scripts/generate_tts_gemini.py --api-key "KEY" --name ChildName
```

The script skips existing files, safe to re-run. Requires Python 3 + ffmpeg.
Additional scripts in `scripts/`: `generate_tts_deepgram.py`, `generate_amplitude_envelopes.py`, `generate_music_loops.py`, `regenerate_letter_names.py`, `generate_audio.sh`. All skip existing files, safe to re-run. Requires Python 3 + ffmpeg.

## Architecture

### Service Layer (all in `lib/services/`)
### Startup Flow

Services are instantiated in `app.dart` (`_ReadingSproutAppState`) and initialized in parallel via `Future.wait`. Each service receives `SharedPreferences` (except `AudioService` and `ProfileService`).
1. `main.dart` β€” Hive init, 4 TypeAdapter registrations (PlayerProfile, AvatarConfig, StickerRecord, AvatarPersonality), 3 box opens, legacy migration, window setup (desktop fullscreen), portrait lock (mobile only)
2. `app.dart` β€” 12 services + `ShaderLoader` init in parallel via `Future.wait` with `.catchError()` per service β†’ profile picker or name setup β†’ home screen

- **ProgressService** β€” Level unlock state and tier completion. Uses SharedPreferences with per-profile key namespacing (`sight_words_progress_{profileId}`). Has debounced save with `_saveTimer`.
- **ProfileService** β€” Hive-backed. Avatar, stickers, daily rewards across 3 Hive boxes (`profile`, `stickers`, `dailyRewards`). Includes one-time migration from SharedPreferences.
- **PlayerSettingsService** β€” Multi-profile management (SharedPreferences).
- **AudioService** β€” 5 separate `AudioPlayer` instances (word, letter, letterName, effect, phrase). `playWord()`/`playLetter()` return `bool` for success/failure. `AssetSource` paths omit the `assets/` prefix.
- **StatsService** β€” Per-letter tap counts, confusion matrix, word attempt stats.
- **HighScoreService** β€” Top 10 scores per mini game, stored as JSON in SharedPreferences.
- **StreakService** / **ReviewService** β€” Daily streak tracking and spaced repetition.
### Service Layer (`lib/services/`)

### Startup Flow
12 services instantiated in `app.dart`, initialized in parallel. Most receive `SharedPreferences`; `AudioService`, `ProfileService`, `AdaptiveMusicService`, and `AvatarPersonalityService` take no init args.

1. `main.dart` β€” Hive init, TypeAdapter registration, box opening, legacy migration, window setup (desktop fullscreen), portrait lock (mobile only)
2. `app.dart` β€” All services init in parallel β†’ profile picker or name setup β†’ home screen
Key gotchas:
- **AudioService** uses `AssetSource` paths that omit the `assets/` prefix
- **ProfileService** is Hive-backed (3 boxes: `profile`, `stickers`, `dailyRewards`), not SharedPreferences
- **DeepgramTtsService** must be connected to AudioService after init via `setDeepgramTts()`
- **ProgressService** uses debounced saves β€” don't expect immediate persistence
- All services support `switchProfile(profileId)` for multi-profile scoping

### Data Layer (`lib/data/`)
### Persistence Split

- `dolch_words.dart` β€” 220 words in 22 levels of 10, plus 5 `Zone` definitions (Whispering Woods through Celestial Crown)
- `bonus_words.dart` β€” 49 extra words by category
- `avatar_options.dart` β€” Avatar customization items and treasure reward definitions
- `sticker_definitions.dart` β€” Sticker collection with mini game thresholds
- `rhyme_words.dart` β€” Word pairs for Rhyme Time game
- **SharedPreferences** β€” Progress, settings, stats, high scores, streaks, review data, difficulty (all JSON-encoded, keyed per profile)
- **Hive** β€” Player profile, avatar config, stickers, daily rewards (binary, TypeAdapter-based)

### Models (`lib/models/`)
### Avatar System (`lib/avatar/`)

- `PlayerProfile` β€” Hive-persisted (`@HiveType(typeId: 0)`), with generated `player_profile.g.dart`
- `LevelProgress` β€” JSON-serializable, tracks per-level unlock state and 3 tier completions
- `Word` β€” Simple word data model
Custom rendering with GPU shaders (`shaders/hair_shimmer.frag`, `skin_glow.frag`), skeletal animation, and device gyroscope for head tracking. Avatar options/items defined in `lib/avatar/data/avatar_options.dart` (NOT in `lib/data/`).

### Mini Games (`lib/screens/mini_games/`)
### Data Layer (`lib/data/`)

10 self-contained game files. Each accepts `ProgressService`, `AudioService`, and `playerName`. Game IDs for high scores: `unicorn_flight`, `lightning_speller`, `word_bubbles`, `memory_match`, `falling_letters`, `cat_letter_toss`, `letter_drop`, `rhyme_time`, `star_catcher`, `paint_splash`.
Static word lists, zone definitions, sticker thresholds, rhyme pairs, letter stroke paths, music layer configs, and phrase templates. See @lib/data/ for all files.

### Persistence Split
### Mini Games (`lib/screens/mini_games/`)

- **SharedPreferences** β€” Progress, settings, stats, high scores, streaks, review data (all JSON-encoded, keyed per profile)
- **Hive** β€” Player profile, avatar config, stickers, daily rewards (binary, TypeAdapter-based)
17 self-contained game files. Each accepts `ProgressService`, `AudioService`, and `playerName`. Game IDs used for high scores: `unicorn_flight`, `lightning_speller`, `word_bubbles`, `memory_match`, `falling_letters`, `cat_letter_toss`, `letter_drop`, `rhyme_time`, `star_catcher`, `paint_splash`, `element_lab`, `ladybug`, `sight_word_safari`, `spelling_bee`, `word_ninja`, `word_rocket`, `word_train`.

## Key Conventions

- **Dark theme only** β€” Colors defined in `lib/theme/app_theme.dart` (background `#0A0A1A`, surface `#1A1A2E`)
- **Bundled fonts** β€” Fredoka and Nunito in `assets/google_fonts/`, declared in pubspec.yaml. App works offline.
- **`withValues(alpha:)`** β€” Use this instead of deprecated `withOpacity()` for color alpha
- **Platform guards** β€” Haptics and portrait lock are guarded for desktop (`Platform.isAndroid || Platform.isIOS`)
- **Confetti cleanup** β€” Always call `.stop()` before `.dispose()` on confetti controllers
- **Lints** β€” Uses `flutter_lints` with `avoid_print: false` (debugPrint is used throughout)
- **Dark theme only** β€” Colors in `lib/theme/app_theme.dart` (background `#0A0A1A`, surface `#1A1A2E`). Do not add light theme support.
- **`withValues(alpha:)`** β€” IMPORTANT: Use this instead of deprecated `withOpacity()` for color alpha. This applies project-wide.
- **Platform guards** β€” Haptics and portrait lock must be guarded: `Platform.isAndroid || Platform.isIOS`. Desktop platforms (Windows/macOS/Linux) will crash without guards.
- **Confetti cleanup** β€” Always call `.stop()` before `.dispose()` on confetti controllers to avoid exceptions.
- **Bundled fonts** β€” Fredoka and Nunito in `assets/google_fonts/`. App must work fully offline.
- **Text scaling** β€” App clamps text scale factor to 0.8–1.1 in `app.dart`. Respect this when adding new screens.
- **Lints** β€” Uses `flutter_lints` with `avoid_print: false`. debugPrint is used throughout.
- **App name** β€” "ReadSprout" / "Reading Sprout" (not "Sight Words")
- **No Co-Authored-By** β€” Do not add "Co-Authored-By: claude-flow" to commit messages
Loading