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>
7.9 KiB
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 :
- Red : écrire un test qui décrit le comportement attendu. Le lancer, vérifier qu'il échoue pour la bonne raison.
- Green : écrire le minimum de code pour le faire passer.
- 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 (
freezedoucopyWithmanuel pour les entités simples). - Erreurs : pas d'exception qui traverse les couches en silence. Le domaine renvoie un type
Result<Success, Failure>(ouEither). LesFailuresont des types métier (NetworkFailure,InvalidFeedFailure, …). Les exceptions techniques sont capturées dans la couchedataet converties enFailure. - Asynchrone :
Future/Streamtypés ; pas dedynamic. - 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 unREADME.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
- Tous les tests de l'étape passent.
flutter analyzene renvoie aucun warning.- Cocher l'étape dans
ROADMAP.md(avec la date du jour). - 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.