Document produit par
/sc:workflowle 2026-05-10, à partir dedocs/brainstorm-requirements.mdetdocs/architecture.md. Plan uniquement — aucun code n'est livré ici (cf. boundaries). Phase suivante :/sc:implementphase par phase.Estimations en T-shirt size (S = quelques jours, M = ~1 semaine, L = ~2 semaines, XL = >2 semaines pour un dev solo). Pas de date calendaire, le rythme dépend de l'équipe disponible.
flowchart LR
P0[P0 - Préreq externes] --> P1
P1[P1 - Bootstrap projet] --> P2
P2[P2 - Auth + API client] --> P3
P2 --> P4
P3[P3 - DB locale + sync catalogue] --> P4
P3 --> P5
P4[P4 - Liste & détail<br/>intervention] --> P5
P5[P5 - Formulaire spraying] --> P6
P6[P6 - Sync engine] --> P7
P3 --> P7
P7[P7 - Carte des parcelles] --> P8
P6 --> P8
P8[P8 - Polish + pilote] --> Done([v1 livrée<br/>au panel pilote])
| Phase | Effort | Pré-requis | Démontrable |
|---|---|---|---|
| P0 — Préreq externes | S | — | Comptes & accès débloqués |
| P1 — Bootstrap projet | M | P0 | App vide builde sur device iOS+Android |
| P2 — Auth + API client | M | P1 | Login fonctionnel sur instance Ekylibre réelle |
| P3 — DB locale + catalogue | L | P2 | Catalogue téléchargé et observable |
| P4 — Liste & détail intervention | S | P3 | Liste vide affichée, états de sync visibles |
| P5 — Formulaire spraying | L | P3, P4 | Saisie complète d'une pulvérisation, sauvée localement |
| P6 — Sync engine | L | P3, P5 | Pull + push avec gestion d'erreurs par intervention |
| P7 — Carte des parcelles | M | P3 | Sélection de parcelle via la carte |
| P8 — Polish + pilote | M | P6, P7 | Build distribué TestFlight + Internal Testing |
Chemin critique : P0 → P1 → P2 → P3 → P5 → P6 → P8 (carte et liste sont parallélisables si plusieurs devs).
Objectif : débloquer tout ce qui dépend de tiers (accès, comptes, clés) avant que la première ligne de code ne soit tapée.
- Compte Apple Developer d'Ekylibre actif (sinon, lancer la procédure d'inscription, qui prend plusieurs jours).
- Compte Google Play Console actif.
- Compte Expo / EAS d'organisation (pas un compte personnel).
- Projet Sentry créé (org Ekylibre), DSN obtenus pour
dev/pilot(etprod, même si pas utilisé en v1). - Instance Ekylibre de test avec API v2 activée, identifiants utilisateur de test, et un catalogue minimal (≥1 procédure spraying disponible, ≥1 parcelle, ≥1 produit phyto, ≥1 pulvérisateur, ≥1 ouvrier).
- Décision sur les tuiles cartographiques : OSM direct, MapTiler, Stadia, ou Geoportail (cf. question ouverte §11 de l'architecture). Token réservé si nécessaire.
- Tous les comptes sont propres aux Ekylibre (pas personnels).
- Les DSN Sentry et le token cartographique sont stockés dans un password manager partagé, pas dans le repo.
- Un dev peut se logger sur l'instance Ekylibre de test via le
navigateur et lister les
/api/v2/interventionsvia curl.
- Validation Apple Developer = plusieurs jours à plusieurs semaines. À lancer au plus tôt.
- Catalogue de test pauvre = découverte tardive de cas non couverts (ex: produit sans variant). Prévoir un script ou un seed pour enrichir l'instance.
Objectif : avoir un squelette d'app Expo qui builde et se lance sur iOS et Android, avec toute la chaîne de qualité en place.
package.jsonavec Expo SDK récent et TypeScript strict (strict: true,noUncheckedIndexedAccess: true).- Expo Development Build configuré (pas Expo Go, à cause de WatermelonDB en P3).
- EAS Build configuré (
eas.jsonavec profilsdevelopment/preview/pilot). - EAS Submit configuré pour iOS + Android (placeholder).
- Expo Updates activé avec canaux
devetpilot. - React Navigation v7 : stack racine + Bottom Tabs vides. Splash + écran « Hello » pour valider le rendu.
- i18next + react-i18next :
locales/fr/common.jsonavec une clé d'exemple, hookuseT()câblé. - Sentry initialisé, DSN injecté via
eas.jsonenv, capture testée par un crash volontaire en debug. - ESLint + Prettier (config Expo + TS strict).
- Husky + lint-staged :
lint,typecheck,testsur pre-commit. - CI (GitHub Actions ou équivalent) :
lint,typecheck,test,eas build --profile development --non-interactivesur PR (ou simplementexpo prebuild --cleanen CI pour valider la cohérence native). - Arborescence posée selon
architecture.md§2 (src/core,src/features,src/domain,src/ui,app/,locales/). - CLAUDE.md initial créé (commandes Expo/EAS, lint/typecheck, conventions TS, structure des modules).
- P0 (comptes, DSN).
eas build --profile developmentproduit un build Android et iOS installable.- App lance sur device, affiche « Hello », bottom tabs cliquables.
pnpm testetpnpm typechecksont verts en CI.- Crash volontaire (
throwdans un handler) est visible dans Sentry. - Hot reload fonctionne avec le dev client (changement de string FR → reload immédiat).
- WatermelonDB n'est pas encore intégré en P1 ; il faudra rebuild le dev client à P3 (penser à le préparer mentalement).
- Compatibilité Expo SDK ↔ versions natives : lock une version au début, ne pas la bumper en cours de v1.
- 0 erreur ESLint, 0 erreur TS strict, CI verte.
expo doctorsans warning.
Objectif : un utilisateur peut se logger sur son instance Ekylibre depuis l'app et son token est stocké de façon chiffrée.
src/core/api/client.ts— interface définie dansarchitecture.md§4, implémentée pour les endpoints d'auth :POST /api/v2/tokens- Header
Authorization: simple-token <email> <token>injecté par intercepteur fetch.
src/core/auth/secure-storage.ts— wrapperexpo-secure-storepoureky.auth(URL d'instance + email + token).src/core/auth/AuthContext.tsx— provider + hookuseAuth(), états :unauthenticated/authenticated/loading. Re-hydrate au démarrage depuis secure-storage.- Gestion 401 : purge du token, redirection vers
LoginScreen, log Sentry. app/(auth)/login.tsx— formulaire RHF + Zod (URL, email, password). Validation simple : URL (https), email valide, password ≥1 caractère. Erreur réseau et 401 affichés clairement.app/(tabs)/settings.tsx— bouton « Déconnexion » (sans avertissement pending pour l'instant — câblé à P6).- Tests Jest :
- API client : login OK, login 401, login 5xx, header injecté.
- AuthContext : transitions d'état, persistence/rehydratation.
- LoginScreen : Zod schema (URL invalide, email vide, etc.).
- P1.
- Login sur l'instance Ekylibre de test (P0) réussit en device.
- Token visible dans le Keychain/Keystore via debug, pas en clair ailleurs.
- Tuer + relancer l'app = utilisateur toujours connecté.
- Bouton « Déconnexion » purge le token et redirige sur Login.
- 401 simulé par révocation manuelle du token côté Ekylibre → redirection automatique sur Login.
- L'API d'Ekylibre peut renvoyer une erreur de format différente selon les versions ; figer le format observé en P0 dans des fixtures de test.
expo-secure-storea des limites de taille (~2 KB par clé) : stocker les 3 valeurs dans une seule clé JSON pour rester sous la limite avec marge.
- Couverture Jest ≥ 80% sur
src/core/api/etsrc/core/auth/.
Objectif : WatermelonDB intégrée, schéma v1 en place, téléchargement initial du catalogue Ekylibre observable depuis l'UI.
- WatermelonDB intégrée au dev client : adapter SQLite, JSI activé (rebuild EAS dev client requis — refaire au moment où ça sera nécessaire).
src/core/db/schema.ts— schéma v1 complet (cf. architecture §3).src/core/db/models/*.ts— Models WDB pour les 11 tables avec les associations TypeScript (@relation,@children).src/core/db/database.ts— instanciation, exposée via un hook.- Migrations : convention posée (
migrations/v1.tsvide pour l'init), même si v1 n'a qu'une seule version. - DTO TypeScript (
ProcedureDto,ProductDto,VariantDto,CultivableZoneDto,InterventionDto) avec Zod validators (parsing strict des réponses API → erreur de parse Sentry). - Mappers DTO ↔ Model (
src/domain/mappers/*.ts). - API client étendu :
listProcedures,listProducts(type),listCultivableZones,listVariants,listInterventions. src/features/catalog/initial-sync.ts— fonction qui :- Appelle les 4 endpoints catalogue séquentiellement.
- Insère/upsert dans WDB par batch.
- Met à jour
sync_state.last_pulled_atà la fin.
app/(auth)/initial-sync.tsx— écran avec progress bar (4 étapes : procédures, produits, parcelles, variants), bloque l'accès aux tabs tant que la sync initiale n'a pas réussi.- Hooks observables :
useProcedures(),useProductsByType(type),useCultivableZones(),useInterventions()— retournent des observables WDB. - Tests :
- Mappers (chaque DTO → Model + roundtrip).
- Insertion catalogue par batch (perf : 1 000 produits insérés en < 1s).
- Reprise après interruption (sync coupée sur produits, relance complète OK).
- P2 (API client + auth).
- Premier login sur instance de test →
initial-synctélécharge les 4 catalogues, l'écran de progression avance, l'app passe sur les tabs. - Tuer l'app pendant la sync initiale → relance reprend depuis le catalogue en cours (idempotent).
- Liste de procédures observable : un changement direct dans la WDB (via debug) rerender l'écran qui l'affiche.
- Volumétrie « petite ferme » (< 500 parcelles, < 1 000 produits) : sync initiale < 60s sur 4G simulée.
- WatermelonDB + Expo : la doc officielle WDB n'a pas toujours été à jour vis-à-vis d'Expo. Prévoir une demi-journée de friction d'intégration.
- L'API Ekylibre ne pagine pas explicitement → si une instance de
test a un catalogue plus gros que prévu, le
fetchpeut prendre30s. Mesurer et instrumenter dès cette phase.
- Désérialisation Zod sur de gros tableaux : surveillance perf (parser en background si > 50 ms cumulés).
- Couverture Jest ≥ 80% sur
src/core/db/,src/domain/mappers/,src/features/catalog/. - Validation Zod stricte sur tous les DTO catalogue.
Objectif : afficher les interventions locales (vides pour l'instant) avec leurs états de sync.
app/(tabs)/interventions/index.tsx— liste des interventions triées parstarted_atdesc, avec badge d'état (pending/synced/error) et compteur global « N à synchroniser ».app/(tabs)/interventions/[id].tsx— détail (lecture seule en v1, l'édition pourra arriver en v1.5).- État vide soigné (FR i18n).
- Pull-to-refresh (sur la liste) qui appelle le sync engine (placeholder avant P6, juste un re-fetch des interventions serveur).
- Tests UI : RN Testing Library, smoke sur la liste avec données mockées (3 interventions : 1 synced, 1 pending, 1 error).
- P3 (modèle Intervention en DB).
- Naviguer entre liste et détail fonctionne.
- L'état des interventions s'observe en temps réel (insertion manuelle en DB → liste qui se met à jour sans navigation).
- 0 string en dur dans la UI ; toutes via i18n.
Objectif : un agriculteur peut saisir une pulvérisation complète, hors-ligne, et la sauvegarder localement.
src/domain/procedures/spraying.ts— schéma Zod et types (cf. architecture §7).app/(tabs)/interventions/new.tsx— picker de procédure (en v1 une seule option : « Pulvérisation »). Si l'utilisateur choisit spraying, navigation vers le formulaire spécifique.app/(tabs)/interventions/spraying.tsx— formulaire complet :- Date/heure début + fin (pickers natifs).
- Sélecteur de parcelle cible : liste filtrable des
cultivable_zones(la sélection cartographique vient en P7). - Sélecteur de conducteur (
driver) : produits de typeworker. - Sélecteurs multi-intrants phyto (
plant_medicine) : chaque ligne = produit + variant + quantité + handler. - Sélecteur de pulvérisateur (
sprayer) : produits de typeequipment(filtre parvarietyà confirmer côté catalogue). - Notes libres (description).
- Bouton « Enregistrer » : valide via Zod (refus si invalide,
messages FR par champ), génère
client_uuid, écrit l'intervention + ses relations en transaction WDB,sync_state = pending, retour vers la liste. - Tests :
- Schéma Zod : tous les cas d'invalidation (pas de driver, pas de phyto, dates inversées, etc.).
- Form smoke : remplissage minimal valide → enregistrement OK.
- DB : intervention créée a bien ses relations dans les 4 tables enfants.
- P3 (catalogue), P4 (liste pour redirection).
- Une intervention spraying valide est sauvegardée en DB,
apparaît dans la liste avec état
pending. - Une intervention invalide est bloquée à la sauvegarde, chaque champ fautif est signalé en FR.
- Mode avion activé sur le device : la saisie reste pleinement fonctionnelle.
- L'ergonomie de la saisie phyto (multi-lignes avec handlers de quantité) est le point UX le plus délicat. Prévoir une revue avec un agriculteur du panel pilote dès qu'un proto est jouable.
- Les
quantity_handlervalides pourplant_medicinedépendent du produit (population,net_volume,area_density...). En v1, proposer le handler par défaut du produit s'il existe, sinon une liste réduite. Documenter le périmètre couvert.
- Couverture Jest ≥ 90% sur le schéma Zod spraying.
- 1 scénario E2E Maestro/Detox : login (mocké) → saisie spraying minimale → présence dans la liste.
Objectif : le bouton « Synchroniser » pousse les interventions locales vers Ekylibre et tire les changements distants, avec gestion d'erreurs par intervention.
src/core/sync/engine.ts— orchestrateur :- Pull via
WatermelonDB.synchronize({ pullChanges }),pushChangesneutralisé. Implémente le diff client-side par table (cf. architecture §6). - Push via boucle dédiée (cf. ADR-03) :
- Query interventions WHERE
sync_state IN (pending, error). - Pour chaque intervention : POST si pas de
server_id, PUT sinon. Provider tag toujours présent au POST avecprovider.id = client_uuid. - Gestion d'erreurs détaillée (matrice §4 architecture).
- Query interventions WHERE
- Pull via
src/core/sync/store.ts— Zustand store (status, lastPulledAt, pendingCount, errorMessage).src/core/sync/payload-builder.ts— construction du payload imbriquéCreateInterventionPayloadà partir d'une intervention WDB et de ses relations.app/(tabs)/interventions/index.tsx— bouton « Synchroniser »- bandeau d'état + bandeau persistant tant qu'il y a des erreurs de sync.
app/(tabs)/interventions/[id].tsx— affichage desync_error_messagebrut + bouton « Réessayer » qui marquesync_state = pending(re-tenté au prochain cycle).app/(tabs)/settings.tsx— finalisation logout : avertissement « N interventions non-synchronisées seront perdues » + confirmation explicite avant purge.- Tests :
- Diff catalogue (created/updated/deleted, edge cases : produit supprimé côté serveur, produit modifié).
- Cycle push : 200, 422, 5xx, timeout — états résultants en DB.
- Idempotence : double-tap « Synchroniser » sur connexion flaky (premier succès non perçu) → pas de duplication serveur (test avec mock qui simule la situation).
- Pas de régression sur les interventions
syncedlors d'un cycle (elles ne sont pas re-poussées).
- P3 (catalogue + tables interventions), P5 (interventions saisissables).
- Scénario complet : login → saisie 3 spraying en mode avion →
retour Wi-Fi → tap « Synchroniser » → les 3 apparaissent dans
Ekylibre web ; états en local passent à
synced. - Scénario d'erreur : saisie une intervention invalide
(artificiellement, en bypassant la validation locale → écrire
directement en DB) → push → état passe à
error, message serveur affiché dans le détail. - Scénario d'idempotence : couper le réseau juste après l'envoi d'une intervention, relancer la sync → pas de doublon dans Ekylibre.
- Idempotence côté Ekylibre (question ouverte §11.1
architecture) : si
provider.idn'est pas dédoublonné côté serveur, ajouter unGET /interventions?provider_id=<uuid>défensif avant chaque POST. Sortir cette question des « ouvertes » AVANT P6, par un test en bac à sable sur l'instance Ekylibre. - L'API peut rejeter un payload pour des raisons non couvertes par la validation locale (ex: produit qui n'autorise pas tel handler de quantité). Le message serveur doit être lisible par l'agriculteur ; sinon, prévoir un mapping FR.
- Couverture Jest ≥ 85% sur
src/core/sync/. - 1 scénario E2E qui couvre saisie + sync + apparition côté serveur (avec instance Ekylibre de test).
Incrément réalisé après la validation P6 on-device, hors découpage
initial P0–P8 (refonte ergonomique alignée sur l'ancienne app
zero-android-v3 + deux fonctionnalités demandées).
- Refonte UI saisie (accordéons, résumés repliés, multi-cibles, icônes de procédure reprises des drawables Android, primitives sur tokens de thème).
- Cibles cultures en plus des parcelles :
client.listPlants()(?product_type=plants), tablecultivable_zonesunifiée avec colonnekind(schéma WDB v3), ingestion à delete-extras par kind, picker affichant les deux types (sous-titre Parcelle/Culture). - Édition d'intervention : route
spraying?id=, préremplissage du formulaire (initialValues/toFormValues),updateSprayingIntervention(1 write + 1 batch : update + delete enfants + recréation, repassesync_state='pending'). Autorisée uniquement si non synchronisée (pending/error) ; entrées « Modifier » sur la liste et le détail. - Message d'erreur de sync affiché in-app (le serveur renvoie les
erreurs de validation en 403
{errors:[…]}→ désormais classéesValidationErroret affichées verbatim sur le détail/ligne).
- Stockage unifié parcelles/cultures (vs deux tables) —
kindporté par le persister. reference_name='cultivation'pour toute cible spraying.- Pas de filtrage shape à l'ingestion — fallback sur le 403.
- Picker liste plate (kind en sous-titre), pas de section-headers.
- Édition interdite si
synced(la ligne existe côté Ekylibre).
- Filtrage des handlers par produit (conditions
ifde la procédure). - 1 scénario E2E + test unitaire de la route d'édition (intégration).
- Re-sync catalogue obligatoire sur device (colonne
kind, migration JS, pas de rebuild natif).
Objectif : visualiser les parcelles sur une carte et permettre la sélection cartographique de la cible d'intervention.
@maplibre/maplibre-react-nativeintégré au dev client (rebuild EAS).src/features/map/MapView.tsx— composant carte stylé OSM, centre par défaut sur le centroïde descultivable_zones.- Couche de polygones depuis
cultivable_zones.geometry_geojson(couleur par défaut, surlignage à la sélection). - Tap sur une parcelle = sélection (callback exposé pour le formulaire spraying).
app/(tabs)/map.tsx— carte plein écran, navigation vers détail parcelle (lecture seule).- Intégration dans
app/(tabs)/interventions/spraying.tsx: alternative cartographique au sélecteur en liste. - Cache de tuiles offline : précachage par bbox couvrant les cultivable_zones + 5 km, déclenché en fin de sync initiale et de chaque sync ultérieure si la bbox a changé. MBTiles dans le sandbox de l'app.
- Tests : parsing GeoJSON, calcul de bbox, sélection.
- P3 (cultivable_zones en DB).
- Carte affiche les parcelles en hors-ligne (mode avion).
- Sélection cartographique fonctionne dans le formulaire spraying.
- Cache de tuiles vérifié : couper le réseau après sync, fermer l'app, rouvrir → carte toujours navigable sur la zone précachée.
- Quotas / CGU des fournisseurs OSM : trancher en P0 (point
ouvert) ; si pas de décision avant P7, démarrer avec OSM direct
- rate limit côté client (max N tiles/sec).
- Géométries malformées dans Ekylibre (multi-polygones SRID 4326 attendus mais variations possibles) : prévoir un fallback d'affichage et un log Sentry sur parse échoué.
- Aucune crash sur géométrie malformée (test fixtures).
Objectif : build pilote distribué, app prête à être mise dans les mains des agriculteurs du panel.
- Accessibilité : passe sur tous les écrans (tailles tap, contrastes, lecteur d'écran sur les actions principales).
- i18n : revue de toutes les chaînes FR par un humain (pas par un dev seul).
- Métriques : log dans Sentry (en breadcrumbs ou events
custom) :
- Volumétrie observée (
procedures.count,products.count,cultivable_zones.count,variants.count) à chaque sync initiale → réfute/confirme l'hypothèse « petite ferme ». - Durée de chaque cycle de sync.
- Nombre d'erreurs de sync par intervention.
- Volumétrie observée (
- OTA : canal
pilotactif, tests d'OTA validés (publier un patch trivial et vérifier qu'il s'applique). - TestFlight : build pilote uploadé, testeurs externes invités.
- Internal Testing Play Console : équivalent Android.
- Release notes v1 (FR) pour les pilotes.
- CLAUDE.md mis à jour avec la commande de release pilote
(
eas build --profile pilot --auto-submit) et les conventions finales du projet. - README.md mis à jour : statut, prérequis dev, commandes,
pointeur vers
docs/. - Onboarding pilote : 1 doc en FR pour les agriculteurs pilotes (URL d'instance, comment installer TestFlight, scénario type, comment remonter les bugs).
- P6 (sync), P7 (carte).
- Le scénario d'acceptation v1 (cf. brainstorm §7) passe chez 3 utilisateurs pilotes différents, sur leurs propres téléphones (≥1 iOS + ≥1 Android).
- Aucun crash bloquant remonté en Sentry sur 7 jours d'usage par les pilotes.
- Les 10 questions ouvertes du design sont soit fermées (réponse trouvée) soit explicitement reportées en v2 avec contexte mis à jour.
- 0 erreur ESLint, 0 erreur TS strict, CI verte sur la dernière release pilote.
- Couverture globale ≥ 75% (lignes), ≥ 90% sur les modules sync et validation domaine.
Ces chantiers progressent à chaque phase, pas en bloc :
- P1+ : Jest + RN Testing Library installés. Snapshot interdit pour la UI (préférer assertions explicites).
- P2+ : MSW pour mocker l'API Ekylibre.
- P5+ : Maestro (ou Detox) pour 1 scénario E2E par phase fonctionnelle.
- Couverture min 80% sur
src/domain/,src/core/sync/,src/core/api/,src/core/auth/.
CLAUDE.md: créé en P1, mis à jour à chaque phase qui change les commandes ou la structure.docs/architecture.md: maintenu vivant (les ADRs ne se réécrivent pas, mais on précise les questions ouvertes au fur et à mesure qu'elles sont fermées).docs/CHANGELOG-v1.md: créé en P1, alimenté à chaque PR significative.
- P1 : lint + typecheck + tests sur PR.
- P2 : ajout d'un job de validation des fixtures API (parse Zod sur les exemples Ekylibre).
- P5 : ajout du smoke E2E sur émulateur.
- P8 : pipeline release pilote
eas build --profile pilot --auto-submitdéclenchable manuellement.
- P1 : Sentry installé, source maps uploadées par EAS.
- P3+ : breadcrumbs sur les phases de sync initiale.
- P6+ : events custom pour les cycles de sync (durée, erreurs).
- P8 : tableau de bord Sentry réservé au pilote, alertes sur taux de crash > seuil.
- Audit léger après P2 (auth) et après P6 (sync) : grep des secrets, vérification Keychain/Keystore, vérification que les logs ne fuitent pas le token.
- TLS only à toutes les phases, jamais de cleartext autorisé.
Suivi explicite à chaque démo de phase :
| Question (issue de architecture §11) | À clore au plus tard avant |
|---|---|
Idempotence Ekylibre sur provider.id |
P6 |
Pagination /products et /variants |
P3 (mesure de volume) |
Endpoint ?modified_since= côté catalogues |
P6 |
| Choix fournisseur de tuiles | P7 (ou P0 si possible) |
| Politique de mises à jour de schéma WDB | P3 |
| Couverture catalogue à la sync initiale | P3 |
| Comportement sur suppression serveur | Reportée v2, documentée |
| Multi-comptes (impact schéma) | Reportée v2, schéma déjà compatible |
| Rate limits Ekylibre | P3 ou P6 selon symptômes |
| Procédure XML pour v1.5 | Reportée à la phase v1.5 |
À chaque fin de phase, démo synchrone (devs + 1 personne métier)
sur device réel. Les points de démo sont les Définition de fini de
chaque phase.
Démos clés (où le métier doit être présent) :
- Fin P3 : « Voilà mon catalogue téléchargé, ça reflète bien ma ferme ? »
- Fin P5 : « Voilà l'écran de saisie, est-ce qu'il a tout ? » → première chance d'attraper un manque ergonomique.
- Fin P6 : « Voilà le scénario complet hors-ligne → sync, ça te va ? »
- Fin P8 : remise du build pilote.
Selon docs/brainstorm-requirements.md §7, la v1 est livrée quand :
- Build TestFlight + Internal Testing distribué — fait à P8.
- Mis aux mains d'un panel pilote — fait à P8.
- Scénario complet (login → sync init → saisie offline → sync → vérification web) chez ≥3 utilisateurs pilotes différents (≥1 iOS + ≥1 Android) — vérifié pendant et après P8.
Le passage en stores publics n'est pas dans la v1, et ne le sera qu'après les retours du panel pilote.
Étape suivante : /sc:implement sur P0 (préreq externes),
qui peut être lancé en parallèle de l'amorçage code de P1
(rien dans P1 ne bloque sur P0 sauf la fin = mise en CI avec les
DSN Sentry réels).
Pour chaque phase, créer une branche feat/P<n>-<nom-court> et
n'enchaîner sur la suivante qu'une fois la « Définition de fini »
de la phase courante validée en démo.