Files
storytime/CLAUDE.md
T
Vincent Bourdon 16fd4c8c36 docs: cadrage initial Storytime (specs par jalon, roadmap, CLAUDE.md)
Lecteur d'histoires cadenassé pour le coucher (Android/Flutter).
- CLAUDE.md : principes craftsmanship/TDD/clean code/clean archi + decisions techniques
- ROADMAP.md : suivi haut niveau des 7 jalons, a tenir a jour par etape
- docs/specs/ : specs completes decoupees par jalon, etapes en sous-fichiers
- .gitignore Flutter (pubspec.lock versionne, projet applicatif)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 17:03:33 +02:00

151 lines
7.9 KiB
Markdown

# Storytime — Guide projet & principes d'ingénierie
> Lecteur d'histoires audio cadenassé pour le coucher, sur tablette Android.
> L'enfant écoute des histoires (podcasts) sans pouvoir sortir de l'application ;
> le parent gère les sources, le code parental et les limites.
Ce fichier est le contrat d'ingénierie du projet. **Toute contribution doit le respecter.**
---
## 1. Le produit en une page
- **Plateforme** : Android uniquement. **Flutter / Dart**.
- **Lecture** : streaming (pas de hors-ligne en v1).
- **Deux mondes** :
- **Espace enfant** (par défaut, verrouillé) : liste de titres d'histoires, gros boutons lecture/pause/suivant.
- **Espace parent** (protégé par code 4 chiffres) : gestion des podcasts, limites, réglages.
- **Verrouillage** : Screen Pinning (épinglage natif Android).
### Contraintes connues (à ne jamais oublier)
- L'épinglage empêche la **sortie facile**, mais la sortie réelle repose sur le **mécanisme natif Android + le PIN de la tablette**, PAS sur notre code parental. C'est un garde-fou enfant, pas un coffre-fort. Ne jamais promettre plus dans l'UI ou les messages.
- L'épinglage **ne survit pas à un redémarrage** de la tablette.
- Le code parental protège **l'accès aux écrans parent** et la **reprise après limite**, pas le désépinglage système.
---
## 2. Philosophie de développement
Le projet suit le **Software Craftsmanship** : code soigné, testé, simple, qui se lit comme une explication du domaine.
### TDD — non négociable
Cycle **Red → Green → Refactor** pour toute fonctionnalité ou correction :
1. **Red** : écrire un test qui décrit le comportement attendu. Le lancer, vérifier qu'il échoue **pour la bonne raison**.
2. **Green** : écrire le minimum de code pour le faire passer.
3. **Refactor** : nettoyer sans changer le comportement, tests toujours verts.
Ne jamais écrire de code de production sans test qui le justifie. Les seules exceptions : code purement déclaratif (constantes, thèmes) et glue framework triviale — et même là, le comportement observable se teste.
### Clean Code
- Noms qui révèlent l'intention ; pas d'abréviations obscures.
- Fonctions courtes, une seule raison d'exister.
- Pas de commentaire qui paraphrase le code ; un commentaire explique le **pourquoi**, jamais le **quoi**.
- Pas de nombre/chaîne magique : constantes nommées.
- Gestion d'erreur explicite (voir `Result`/`Either`, §4).
- **Boy Scout Rule** : laisser le code un peu plus propre qu'on ne l'a trouvé, sans dérive hors-sujet.
### YAGNI
On n'implémente que ce que le jalon courant exige. Pas d'abstraction « au cas où ».
---
## 3. Clean Architecture
Dépendances dirigées **vers le domaine**. Le domaine ne connaît ni Flutter, ni HTTP, ni la base de données.
```
Presentation ─────► Application (use cases) ─────► Domain ◄───── Data (infra)
(UI/Riverpod) (orchestration) (cœur métier) (impl. dépôts)
```
### Couches
- **Domain** (`domain/`) — entités, value objects, interfaces de dépôts (abstraites), erreurs métier. **Zéro dépendance externe.** Dart pur.
- **Application** (`application/`) — *use cases* : une classe = une intention métier (`PlayStoryUseCase`, `AddPodcastUseCase`). Orchestrent le domaine via les interfaces de dépôts. Dart pur.
- **Data** (`data/`) — implémentations concrètes des dépôts, *data sources* (HTTP iTunes, parsing RSS, SQLite, secure storage), DTO/mappers. Dépend du domaine, jamais l'inverse.
- **Presentation** (`presentation/`) — écrans, widgets, contrôleurs d'état (Riverpod Notifiers). Appelle les use cases. Ne contient pas de logique métier.
### Règle de dépendance
Une couche ne dépend que de couches plus internes. **Le domaine n'importe jamais `package:flutter`, `package:http`, `sqflite`, etc.** Toute violation est un bug d'architecture.
### Injection de dépendances
**Riverpod** comme conteneur DI + gestion d'état. Les use cases et dépôts sont fournis via des `Provider`. En test, on *override* les providers par des doubles.
---
## 4. Conventions de code
- **État/DI** : Riverpod (`Notifier`/`AsyncNotifier`).
- **Immutabilité** : modèles immuables (`freezed` ou `copyWith` manuel pour les entités simples).
- **Erreurs** : pas d'exception qui traverse les couches en silence. Le domaine renvoie un type `Result<Success, Failure>` (ou `Either`). Les `Failure` sont des types métier (`NetworkFailure`, `InvalidFeedFailure`, …). Les exceptions techniques sont capturées dans la couche `data` et converties en `Failure`.
- **Asynchrone** : `Future`/`Stream` typés ; pas de `dynamic`.
- **Lint** : `flutter_lints` + règles strictes activées (`analysis_options.yaml`). Le projet doit rester **0 warning**.
- **Format** : `dart format` ; pas de débat de style.
### Arborescence cible
```
lib/
core/ # transverse : Result, erreurs de base, constantes, thème, router
features/
locking/ # verrouillage / épinglage
domain/
application/
data/
presentation/
playback/ # lecture audio
podcasts/ # recherche, RSS, abonnements
parental/ # code parental, accès espace parent
limits/ # minuterie, compteur d'histoires, avertissements
main.dart
test/ # miroir de lib/ ; tests unitaires & use cases
test/widget/ # tests de widgets
integration_test/ # tests d'intégration (épinglage, parcours bout-en-bout)
```
Feature-first, puis découpage en couches dans chaque feature. **Un fichier = une responsabilité.** Quand un fichier grossit, c'est le signal qu'il fait trop de choses : on le scinde.
---
## 5. Stratégie de test
| Niveau | Cible | Outils |
|--------|-------|--------|
| Unitaire | Domain + use cases (la majorité des tests) | `flutter_test`, `mocktail` |
| Widget | Écrans/widgets isolés | `flutter_test` |
| Intégration | Épinglage réel, parcours enfant/parent | `integration_test` sur appareil/émulateur |
- Les tests de domaine/use cases ne touchent **jamais** au réseau, au disque ni à Flutter.
- Les dépendances externes (HTTP, RSS, stockage, plugin d'épinglage) sont derrière des interfaces, *mockées* en test.
- **Le jalon 1 (épinglage) exige une validation sur appareil réel** : un test/POC manuel documenté en plus des tests automatisés.
---
## 6. Processus & suivi
- **`docs/specs/`** : la spec, découpée par **jalon** (un dossier par jalon). Chaque jalon a un `README.md` (objectif + DoD) et ses **étapes en sous-fichiers numérotés**.
- **`ROADMAP.md`** : vue d'ensemble de l'avancement. **À mettre à jour à la fin de chaque étape** (cocher la case, dater, noter les écarts). C'est la source de vérité du « où on en est ».
- On traite **un jalon à la fois**, **une étape à la fois**, en TDD.
- **Definition of Done** d'une étape : code + tests verts + 0 warning lint + `ROADMAP.md` à jour.
### Rituel à chaque étape terminée
1. Tous les tests de l'étape passent.
2. `flutter analyze` ne renvoie aucun warning.
3. Cocher l'étape dans `ROADMAP.md` (avec la date du jour).
4. Si un écart avec la spec est apparu, le noter dans le fichier d'étape concerné.
---
## 7. Décisions techniques figées
| Sujet | Choix | Raison |
|-------|-------|--------|
| Langage/UI | Flutter / Material 3 | Mono-plateforme Android, connu de l'auteur |
| État + DI | Riverpod | Testable, override facile en test |
| Audio | `just_audio` + `audio_service` | Lecture + contrôle arrière-plan |
| Épinglage | `kiosk_mode` (plugin), fallback platform channel Kotlin | API native Screen Pinning |
| Recherche podcasts | API iTunes Search (gratuite, sans clé) | Annuaire public |
| RSS | `dart_rss` (ou `webfeed`) | Parsing de flux |
| Persistance | `sqflite`/`drift` (abonnements) + `flutter_secure_storage` (code haché) | Local, sûr |
| Lecture seule réglages | `shared_preferences` | Compteurs/limites |
Tout changement de cette table doit être justifié dans le commit et reflété ici.