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,44 @@
|
||||
# 1.1 — Spike `kiosk_mode` sur appareil réel ⚠️ BLOQUANT
|
||||
|
||||
## Objectif
|
||||
Prouver, sur la tablette cible, qu'on peut épingler/désépingler l'app par code et
|
||||
que l'épinglage tient face à un usage enfant. C'est un **spike** : on cherche la
|
||||
preuve de faisabilité, pas le code définitif.
|
||||
|
||||
## Périmètre & hors-périmètre
|
||||
- Inclus : intégrer `kiosk_mode`, déclencher l'épinglage au démarrage, bouton de désépinglage (temporaire), observation du flux Android (confirmation, PIN au désépinglage).
|
||||
- Exclus : architecture propre (vient en 1.2), UI finale.
|
||||
|
||||
## Dépendances
|
||||
Jalon 0.
|
||||
|
||||
## Conception (jetable)
|
||||
- Ajouter `kiosk_mode` au `pubspec.yaml`.
|
||||
- Écran de spike : bouton « Épingler » → `startKioskMode()` (ou API équivalente du plugin), affichage de l'état (`getKioskMode()` / stream), bouton « Désépingler ».
|
||||
- Tester manuellement les réglages Android : épinglage activé, « exiger le PIN au désépinglage » ON/OFF.
|
||||
|
||||
## Protocole de validation (sur appareil réel — pas seulement émulateur)
|
||||
1. Lancer, appuyer « Épingler ». Vérifier que l'app est épinglée.
|
||||
2. Tenter de sortir : geste accueil, multitâche, retour. → doit être bloqué.
|
||||
3. Désépingler via le geste système → doit exiger le PIN Android (si option activée).
|
||||
4. Couper/rallumer l'écran : vérifier le comportement.
|
||||
5. Redémarrer la tablette : confirmer que l'épinglage ne survit pas (limite connue, à documenter).
|
||||
|
||||
## Définition du résultat
|
||||
- **Succès** → on passe à 1.2, et 1.3 devient « N/A » (noter dans roadmap).
|
||||
- **Échec / instable** → on documente précisément l'échec et on passe à 1.3 (fallback natif). Si 1.3 échoue aussi → escalade matérielle (cf. README jalon).
|
||||
|
||||
## Plan de test
|
||||
Spike = validation **manuelle documentée** (impossible d'automatiser l'épinglage système de façon fiable hors appareil). Consigner les résultats du protocole dans ce fichier (section « Résultats » ci-dessous).
|
||||
|
||||
## Definition of Done
|
||||
- Protocole exécuté sur la tablette cible, résultats consignés.
|
||||
- Décision plugin vs fallback prise.
|
||||
- Étape 1.1 cochée dans `ROADMAP.md` + entrée dans le journal des décisions.
|
||||
|
||||
## Résultats (à remplir lors de l'exécution)
|
||||
> _Tablette : … / Android : … / version plugin : …_
|
||||
> _Observations : …_
|
||||
|
||||
## Risques / notes
|
||||
- Comportements variables selon constructeur/version Android : c'est exactement ce que ce spike doit révéler tôt.
|
||||
@@ -0,0 +1,45 @@
|
||||
# 1.2 — Service de verrouillage (domaine + use cases)
|
||||
|
||||
## Objectif
|
||||
Encapsuler l'épinglage derrière une interface de domaine propre et des use cases
|
||||
testables, indépendants de l'implémentation (plugin ou natif).
|
||||
|
||||
## Périmètre & hors-périmètre
|
||||
- Inclus : interface `LockingRepository`, use cases, impl. data branchée sur le résultat de 1.1, providers Riverpod.
|
||||
- Exclus : UI enfant (jalon 4) ; c'est elle qui appellera ces use cases.
|
||||
|
||||
## Dépendances
|
||||
1.1 (faisabilité prouvée).
|
||||
|
||||
## Conception
|
||||
- **Domain** (`features/locking/domain/`) :
|
||||
- `LockState` (value object / enum) : `unlocked`, `locked`, `unsupported`.
|
||||
- `LockingRepository` (interface) :
|
||||
- `Future<Result<Unit>> startLock()`
|
||||
- `Future<Result<Unit>> stopLock()`
|
||||
- `Stream<LockState> watchState()`
|
||||
- `Future<LockState> currentState()`
|
||||
- `LockingFailure` (sous-type de `Failure`) : `LockUnsupportedFailure`, `LockDeniedFailure`.
|
||||
- **Application** (`features/locking/application/`) :
|
||||
- `StartLockUseCase`, `StopLockUseCase`, `WatchLockStateUseCase`.
|
||||
- `StopLockUseCase` **n'autorise pas** la sortie système ; il ne fait que demander l'arrêt de l'épinglage (la confirmation reste au système). À documenter.
|
||||
- **Data** (`features/locking/data/`) :
|
||||
- `KioskModeLockingRepository` (impl. via plugin) **ou** `NativeChannelLockingRepository` (impl. 1.3), selon décision de 1.1.
|
||||
- **DI** : provider `lockingRepositoryProvider` (override en test).
|
||||
|
||||
## Plan TDD
|
||||
1. **Red** : `start_lock_use_case_test.dart` — avec un `LockingRepository` mocké renvoyant `Ok`, le use case renvoie `Ok` ; en `Err(LockDenied)`, propage l'échec. Échoue (types absents).
|
||||
2. **Green** : créer interface + use case.
|
||||
3. **Red** : `watch_lock_state_use_case_test.dart` — le stream relaie les états émis par le repo mocké.
|
||||
4. **Green** : implémenter.
|
||||
5. **Red** : `kiosk_mode_locking_repository_test.dart` — avec un wrapper mockable autour du plugin, `startLock` mappe succès→`Ok` et exception→`LockingFailure`.
|
||||
6. **Green** : implémenter l'impl. data avec mapping d'erreurs.
|
||||
7. **Refactor**.
|
||||
|
||||
## Definition of Done
|
||||
- Tous les tests ci-dessus verts ; aucune dépendance au plugin dans `domain/`/`application/`.
|
||||
- `tool/check.sh` passe.
|
||||
- Étape 1.2 cochée dans `ROADMAP.md`.
|
||||
|
||||
## Risques / notes
|
||||
- Garder l'API du repository minimale : start/stop/observe. Pas de fioritures.
|
||||
@@ -0,0 +1,43 @@
|
||||
# 1.3 — Fallback natif Kotlin (platform channel)
|
||||
|
||||
## Objectif
|
||||
Si le plugin `kiosk_mode` s'est révélé insuffisant en 1.1, fournir une
|
||||
implémentation native Kotlin du verrouillage via un platform channel, derrière la
|
||||
même interface `LockingRepository`.
|
||||
|
||||
## Condition d'exécution
|
||||
**Étape conditionnelle.** À réaliser uniquement si 1.1 a conclu que le plugin ne
|
||||
tient pas. Sinon : marquer « N/A » dans la roadmap et passer au jalon 2.
|
||||
|
||||
## Périmètre & hors-périmètre
|
||||
- Inclus : `MethodChannel` Dart↔Kotlin, code Kotlin appelant `Activity.startLockTask()`/`stopLockTask()`, impl. `NativeChannelLockingRepository`.
|
||||
- Exclus : Device Owner / mode kiosque complet (relève d'un changement de stratégie matérielle, pas de cette étape).
|
||||
|
||||
## Dépendances
|
||||
1.1 (échec plugin), 1.2 (interface existante).
|
||||
|
||||
## Conception
|
||||
- **Côté Android** (`android/app/src/main/kotlin/...`) :
|
||||
- `MethodChannel("storytime/locking")` avec méthodes `startLock`, `stopLock`, `currentState`.
|
||||
- Appeler `startLockTask()` / `stopLockTask()` sur l'`Activity`. Sans Device Owner, cela déclenche le **Screen Pinning** standard (confirmation système).
|
||||
- Gérer les exceptions et renvoyer un code d'erreur exploitable côté Dart.
|
||||
- **Côté Dart** (`features/locking/data/`) :
|
||||
- `NativeChannelLockingRepository implements LockingRepository`, mappant les réponses du channel vers `Result`/`LockingFailure`.
|
||||
- Brancher `lockingRepositoryProvider` sur cette impl. à la place de la version plugin.
|
||||
|
||||
## Plan TDD
|
||||
1. **Red** : `native_channel_locking_repository_test.dart` — en mockant `MethodChannel` (via `TestDefaultBinaryMessengerBinding`), `startLock` qui répond OK → `Ok` ; qui lève `PlatformException` → `LockingFailure`.
|
||||
2. **Green** : implémenter le repository Dart + mapping.
|
||||
3. **Validation manuelle** : reprendre le protocole de 1.1 avec l'impl. native (épingler/sortir/désépingler/PIN).
|
||||
4. **Refactor**.
|
||||
|
||||
> Le code Kotlin lui-même est validé par le test d'intégration manuel (protocole 1.1) ; le test unitaire Dart couvre le mapping et la frontière du channel.
|
||||
|
||||
## Definition of Done
|
||||
- Test du repository natif vert ; protocole de 1.1 repassé avec succès sur la tablette.
|
||||
- `lockingRepositoryProvider` pointe sur l'impl. retenue.
|
||||
- `tool/check.sh` passe.
|
||||
- Étape 1.3 cochée dans `ROADMAP.md` (ou « N/A » si non nécessaire) + décision au journal.
|
||||
|
||||
## Risques / notes
|
||||
- Si même `startLockTask()` natif ne satisfait pas le besoin sans Device Owner → **escalade** : revenir à l'utilisateur sur le choix « tablette dédiée / mode kiosque », comme acté au cadrage.
|
||||
@@ -0,0 +1,28 @@
|
||||
# Jalon 1 — Verrouillage / épinglage ⚠️ BLOQUANT
|
||||
|
||||
## Objectif
|
||||
Valider et implémenter la capacité de l'app à **empêcher la sortie facile** via le
|
||||
Screen Pinning natif Android, et exposer cette capacité au reste de l'app par une
|
||||
interface de domaine propre.
|
||||
|
||||
## Pourquoi bloquant
|
||||
C'est l'exigence n°1 du produit. Tant que l'épinglage n'est pas prouvé sur la
|
||||
tablette cible, on n'investit pas dans le reste. Si ni le plugin ni le fallback
|
||||
natif ne tiennent, **on remonte au choix matériel** (tablette dédiée / mode kiosque
|
||||
Device Owner) avant d'aller plus loin.
|
||||
|
||||
## Rappel des limites (à respecter dans l'UI)
|
||||
- La sortie réelle dépend du mécanisme natif Android + PIN tablette, pas du code parental.
|
||||
- L'épinglage ne survit pas à un redémarrage.
|
||||
- Côté parent : prévoir un rappel pour activer « épinglage » + « exiger le PIN au désépinglage » dans les réglages Android.
|
||||
|
||||
## Étapes
|
||||
1. [1.1 — Spike `kiosk_mode` sur appareil réel](01-spike-kiosk-mode.md) — **bloquant**
|
||||
2. [1.2 — Service de verrouillage (domaine + use cases)](02-service-verrouillage.md)
|
||||
3. [1.3 — Fallback natif Kotlin (platform channel)](03-fallback-natif-kotlin.md)
|
||||
|
||||
## Definition of Done (jalon)
|
||||
- Sur la tablette cible : l'app s'épingle, l'enfant ne peut pas sortir par les gestes normaux, le désépinglage exige le PIN Android.
|
||||
- `LockingRepository` + use cases `StartLockUseCase`/`StopLockUseCase`/`IsLockedUseCase` testés (impl. mockée).
|
||||
- Décision tranchée : plugin suffisant **ou** fallback natif retenu — consignée dans le journal de la roadmap.
|
||||
- `ROADMAP.md` : 1.1→1.3 cochées (1.3 « N/A » si le plugin suffit).
|
||||
Reference in New Issue
Block a user