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>
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
# 3.1 — Recherche annuaire (iTunes Search)
|
||||
|
||||
## Objectif
|
||||
Permettre au parent de rechercher un podcast par mots-clés et d'obtenir une liste
|
||||
de résultats (titre, image, URL du flux RSS) prêts à être ajoutés.
|
||||
|
||||
## Périmètre & hors-périmètre
|
||||
- Inclus : domaine `Podcast`/`PodcastSearchResult`, interface de recherche, impl. HTTP iTunes Search, use case, mapping DTO.
|
||||
- Exclus : ajout/persistance (3.2/3.3), UI parent finale (J5).
|
||||
|
||||
## Dépendances
|
||||
Jalon 0.
|
||||
|
||||
## Conception
|
||||
- **Domain** (`features/podcasts/domain/`) :
|
||||
- `Podcast` (entité) : `id`, `title`, `feedUrl`, `artworkUrl?`, `author?`.
|
||||
- `PodcastSearchRepository` (interface) : `Future<Result<List<Podcast>>> search(String query)`.
|
||||
- `PodcastFailure` : `SearchFailure`, `InvalidFeedFailure`, `FeedUnreachableFailure`.
|
||||
- **Application** : `SearchPodcastsUseCase` (trim/garde-fou requête vide → `Err` ou liste vide selon choix documenté).
|
||||
- **Data** (`features/podcasts/data/`) :
|
||||
- `ItunesPodcastSearchRepository` : appelle `https://itunes.apple.com/search?media=podcast&term=...`.
|
||||
- DTO `ItunesResultDto` + mapper → `Podcast` (`feedUrl` = champ `feedUrl` d'iTunes).
|
||||
- Client HTTP injecté (interface fine) pour testabilité ; erreurs réseau → `SearchFailure`.
|
||||
|
||||
## Plan TDD
|
||||
1. **Red** : `search_podcasts_use_case_test.dart` — requête non vide délègue au repo ; requête vide → comportement défini (documenter : `Err(SearchFailure)` ou `Ok([])`).
|
||||
2. **Green** : implémenter le use case.
|
||||
3. **Red** : `itunes_podcast_search_repository_test.dart` — client HTTP mocké renvoyant un JSON iTunes d'exemple → liste de `Podcast` correctement mappée ; HTTP 500 / timeout → `SearchFailure`.
|
||||
4. **Green** : implémenter repo + mapper.
|
||||
5. **Refactor**.
|
||||
|
||||
## Definition of Done
|
||||
- Tests use case + repository verts (HTTP mocké, fixture JSON iTunes).
|
||||
- `tool/check.sh` passe ; étape 3.1 cochée dans `ROADMAP.md`.
|
||||
|
||||
## Risques / notes
|
||||
- Les résultats iTunes peuvent être larges/inadaptés ; le filtrage fin relève du parent (il choisit). Ne pas sur-filtrer côté code en v1.
|
||||
- Certains résultats n'ont pas de `feedUrl` : les écarter du résultat (un podcast sans flux n'est pas exploitable).
|
||||
@@ -0,0 +1,41 @@
|
||||
# 3.2 — Ajout par URL RSS
|
||||
|
||||
## Objectif
|
||||
Permettre au parent de coller l'URL d'un flux RSS, d'en obtenir un aperçu (titre,
|
||||
image, nombre d'épisodes) et de récupérer les épisodes mappés vers `Episode`.
|
||||
|
||||
## Périmètre & hors-périmètre
|
||||
- Inclus : récupération + parsing RSS, validation de l'URL, aperçu du podcast, mapping items RSS → `Episode` (feature playback).
|
||||
- Exclus : persistance (3.3), UI finale (J5).
|
||||
|
||||
## Dépendances
|
||||
3.1 (entité `Podcast`, `PodcastFailure`), 2.1 (`Episode`).
|
||||
|
||||
## Conception
|
||||
- **Domain** (`features/podcasts/domain/`) :
|
||||
- Étendre `PodcastSearchRepository` ou ajouter `FeedRepository` :
|
||||
- `Future<Result<Podcast>> fetchFeedPreview(Uri feedUrl)` (titre/image depuis le flux).
|
||||
- `Future<Result<List<Episode>>> fetchEpisodes(Uri feedUrl)`.
|
||||
- **Application** :
|
||||
- `PreviewFeedUseCase` (valide l'URL : schéma http/https, non vide → sinon `InvalidFeedFailure`).
|
||||
- `LoadEpisodesUseCase`.
|
||||
- **Data** :
|
||||
- `RssFeedRepository` via `dart_rss` (ou `webfeed`) + client HTTP injecté.
|
||||
- Mapper RSS item → `Episode` : `title`, `audioUrl` = `enclosure.url`, `duration` (itunes:duration si présent), `artworkUrl`.
|
||||
- Items sans enclosure audio → ignorés (pas une histoire jouable).
|
||||
- Erreurs : flux illisible → `InvalidFeedFailure` ; réseau → `FeedUnreachableFailure`.
|
||||
|
||||
## Plan TDD
|
||||
1. **Red** : `preview_feed_use_case_test.dart` — URL vide / non http → `Err(InvalidFeedFailure)` ; URL valide délègue au repo.
|
||||
2. **Green** : implémenter validation + use case.
|
||||
3. **Red** : `rss_feed_repository_test.dart` — fixture XML RSS d'exemple → `Podcast` (aperçu) et `List<Episode>` corrects ; item sans enclosure ignoré ; XML invalide → `InvalidFeedFailure` ; HTTP KO → `FeedUnreachableFailure`.
|
||||
4. **Green** : implémenter repo + mapper.
|
||||
5. **Refactor**.
|
||||
|
||||
## Definition of Done
|
||||
- Tests verts avec fixtures RSS (cas nominal, item sans audio, XML invalide).
|
||||
- `tool/check.sh` passe ; étape 3.2 cochée dans `ROADMAP.md`.
|
||||
|
||||
## Risques / notes
|
||||
- Diversité des flux RSS podcast (champs optionnels, namespaces itunes). Prévoir des fixtures variées.
|
||||
- Garder le mapping RSS→`Episode` ici (feature podcasts), conformément à la note de 2.1.
|
||||
@@ -0,0 +1,44 @@
|
||||
# 3.3 — Persistance des abonnements + CRUD
|
||||
|
||||
## Objectif
|
||||
Stocker localement les podcasts auxquels le parent a souscrit, et offrir les use
|
||||
cases pour les lister, ajouter et supprimer.
|
||||
|
||||
## Périmètre & hors-périmètre
|
||||
- Inclus : table SQLite des abonnements, `SubscriptionRepository`, use cases CRUD, providers.
|
||||
- Exclus : UI de gestion finale (J5) ; cache des épisodes (streaming, pas de cache v1).
|
||||
|
||||
## Dépendances
|
||||
3.1, 3.2.
|
||||
|
||||
## Conception
|
||||
- **Domain** (`features/podcasts/domain/`) :
|
||||
- `Subscription` (entité) : `id`, `podcast` (`Podcast`), `addedAt`.
|
||||
- `SubscriptionRepository` (interface) :
|
||||
- `Future<Result<List<Subscription>>> all()`
|
||||
- `Future<Result<Unit>> add(Podcast podcast)` (idempotent sur `feedUrl`)
|
||||
- `Future<Result<Unit>> remove(String id)`
|
||||
- `Stream<List<Subscription>> watch()`
|
||||
- **Application** : `ListSubscriptionsUseCase`, `AddSubscriptionUseCase` (refuse un doublon de `feedUrl`), `RemoveSubscriptionUseCase`.
|
||||
- **Data** (`features/podcasts/data/`) :
|
||||
- `SqliteSubscriptionRepository` (`sqflite`/`drift`). Table `subscriptions(id, title, feed_url UNIQUE, artwork_url, author, added_at)`.
|
||||
- DTO/mapper ligne ↔ `Subscription`.
|
||||
- **DI** : provider du repository ; base ouverte via un provider initialisé au démarrage.
|
||||
|
||||
## Plan TDD
|
||||
1. **Red** : `add_subscription_use_case_test.dart` — ajout d'un nouveau `feedUrl` → `Ok` ; doublon → `Err`/no-op documenté.
|
||||
2. **Green** : implémenter le use case.
|
||||
3. **Red** : `list/remove` use cases — délèguent correctement au repo mocké.
|
||||
4. **Green** : implémenter.
|
||||
5. **Red** : `sqlite_subscription_repository_test.dart` — sur une base SQLite en mémoire : add→all renvoie l'élément ; contrainte UNIQUE respectée ; remove supprime ; `watch` émet après modification.
|
||||
6. **Green** : implémenter le repo + schéma.
|
||||
7. **Refactor**.
|
||||
|
||||
## Definition of Done
|
||||
- Tests use cases + repo (SQLite en mémoire) verts.
|
||||
- Les abonnements persistent après redémarrage de l'app (vérif. manuelle).
|
||||
- `tool/check.sh` passe ; étape 3.3 cochée dans `ROADMAP.md`.
|
||||
|
||||
## Risques / notes
|
||||
- Migrations : prévoir un numéro de version de schéma dès le départ, même simple.
|
||||
- Source unique de vérité de la liste = ce repository ; l'UI enfant (J4) lira les épisodes via les abonnements.
|
||||
@@ -0,0 +1,26 @@
|
||||
# Jalon 3 — Découverte & gestion des podcasts
|
||||
|
||||
## Objectif
|
||||
Permettre au parent de trouver des sources (recherche annuaire **et** URL RSS),
|
||||
de les enregistrer, et d'en lister les épisodes (= les histoires).
|
||||
|
||||
## Périmètre
|
||||
- Recherche via l'API iTunes Search (gratuite, sans clé).
|
||||
- Ajout par collage d'URL RSS (parsing + aperçu).
|
||||
- Persistance locale des abonnements (SQLite).
|
||||
- Récupération des épisodes d'un podcast abonné (mapping RSS → `Episode` de J2).
|
||||
|
||||
## Hors-périmètre
|
||||
- L'écran de gestion sera **abrité derrière le code parental en J5** ; ici, un accès temporaire (dev) suffit.
|
||||
- Pas de cache offline des épisodes (streaming).
|
||||
|
||||
## Étapes
|
||||
1. [3.1 — Recherche annuaire (iTunes Search)](01-recherche-itunes.md)
|
||||
2. [3.2 — Ajout par URL RSS](02-ajout-rss.md)
|
||||
3. [3.3 — Persistance des abonnements + CRUD](03-persistance-abonnements.md)
|
||||
|
||||
## Definition of Done (jalon)
|
||||
- Le parent peut chercher un podcast, ou coller une URL RSS, et l'ajouter.
|
||||
- Les abonnements survivent au redémarrage de l'app (SQLite).
|
||||
- La liste des épisodes d'un abonnement est récupérable et mappée vers `Episode`.
|
||||
- `tool/check.sh` passe ; `ROADMAP.md` 3.1→3.3 cochées.
|
||||
Reference in New Issue
Block a user