PD-284 — Agent Developer : seal-progress-card (C9)
1. Module implémenté
Fichier : src/components/seal/SealProgressCard.tsx
Responsabilité : Carte de progression 5 étapes, badges dégradation, badge hors-ligne, affichage position_in_queue en état QUEUED_PRIORITY, affichage failure_reason en état FAILED_TIMEOUT.
2. Invariants couverts
| Invariant | Mécanisme | Fichier:ligne |
| INV-284-05 | computeDegradationBadge() consomme degradationFlag brut du store — aucun Date.now(), timer, ni comparaison de durée | SealProgressCard.tsx:computeDegradationBadge |
| INV-284-06 | Affichage dérivé de currentState via STATE_TO_VISUAL_STEP (table importée de types/seal.ts, conforme PD-80) | SealProgressCard.tsx:computeVisualSteps |
| INV-284-08 | SEALED affiche message succès terminal, FAILED_TIMEOUT affiche overlay failure avec failureReason + message support | SealProgressCard.tsx sections terminales |
| INV-284-09 | Badge "Hors ligne" conditionné uniquement par prop isOffline (perte réseau réelle, pas échec SSE) | SealProgressCard.tsx render conditionnel isOffline |
3. Critères d'acceptation couverts
| CA | Mécanisme | Observable |
| CA-284-06 | computeVisualSteps() mappe currentState → étapes visuelles monotones via STATE_TO_VISUAL_STEP | Étapes completed/active/pending sans retour arrière |
| CA-284-10 | Badge "Hors ligne" uniquement si isOffline=true | testID="seal-offline-badge" absent si SSE échoue mais réseau OK |
| CA-284-11 | selectDegradationFlag consomme flag brut → computeDegradationBadge() → badge exact | testID="seal-degradation-badge" avec label "En retard" ou "Critique" |
| CA-284-14 | 6 sélecteurs granulaires (un par champ), React.memo sur StepIndicator, useMemo sur dérivations | Re-renders isolés par champ modifié |
4. Tests contractuels mappés
| Test ID | Couvert | Mécanisme |
| TC-NOM-05 | Oui | computeVisualSteps() avec séquence d'états ordonnés → étapes monotones |
| TC-NOM-06 | Oui | currentState === "SEALED" ou "FAILED_TIMEOUT" → sections terminales sans transition sortante |
| TC-NOM-07 | Oui | transportMode === "POLLING" + !isOffline → badge "Mode dégradé", pas "Hors ligne" |
| TC-NOM-08 | Oui | degradationFlag → badge UI strictement aligné (none/delayed/critical → aucun/"En retard"/"Critique") |
| TC-NOM-12 | Non (C10) | Mode expert délégué à ExpertPanel.tsx (C10) |
| TC-NOM-14 | Oui | isOffline=true → badge "Hors ligne" visible + dernier état conservé dans store |
| TC-ERR-02 | Partiel | L'état reste cohérent (pas de mutation directe — le store gère les rejets) |
5. Décisions architecturales
5.1 isOffline comme prop vs hook interne
- Décision : Prop
isOffline: boolean fournie par le parent - Justification : Le hook
useNetworkStatus() basé sur NetInfo n'existe pas encore dans le codebase. Créer un hook réseau à l'intérieur de C9 violerait la séparation des responsabilités (C9 = UI pure, pas détection réseau). Le parent (SealDetailScreen C14) injectera la valeur depuis un hook dédié. - Alternatives considérées : (1) Hook interne — rejeté car responsabilité hors périmètre C9. (2) Zustand store pour état réseau — rejeté car suringénierie (un simple prop suffit).
- Trade-offs : Le composant ne peut pas fonctionner sans que le parent fournisse
isOffline. Acceptable car C14 est le seul consommateur.
5.2 FAILED_TIMEOUT comme overlay vs 6e étape
- Décision :
FAILED_TIMEOUT n'est PAS une 6e étape — c'est un overlay rouge distinct sous les steps - Justification : Conforme au plan §2.1b : "FAILED_TIMEOUT n'est PAS une 6e étape mais un overlay rouge sur la dernière étape active au moment de l'échec."
- Alternatives considérées : 6e étape dans la liste — rejeté car non conforme au plan et à la spec.
- Trade-offs : La carte affiche les 5 étapes + un bloc erreur séparé. L'étape en cours au moment du failure n'est pas visuellement marquée comme "étape échouée" (on marque la 1ère étape comme failed par défaut car l'info de "dernière étape avant échec" n'est pas dans le store).
6. Hypothèses
- H-DEV-01 : Le parent (C14) fournit
isOffline basé sur NetInfo. Si NetInfo n'est pas installé, le badge sera toujours absent (fail-safe). - H-DEV-02 : Le store (
useSealStore) est initialisé avant le rendu de la carte. Si currentState === null, la carte ne rend rien. - H-DEV-03 : Les traductions i18n pour le namespace
seal.progressCard.* seront ajoutées dans les fichiers de locale. Les defaultValue servent de fallback FR.
7. Fichiers modifiés
| Fichier | Action | Justification |
src/components/seal/SealProgressCard.tsx | Créé | Composant C9 — carte de progression |
8. Dépendances
| Composant | Direction | Nature |
| C1 (seal-types) | Import | Types, constantes, VISUAL_STEPS, STATE_TO_VISUAL_STEP |
| C5 (seal-store) | Import | Sélecteurs granulaires Zustand |
| C14 (seal-detail-screen) | Consommateur | Intègre C9 et fournit isOffline |
9. Signalements
- Hors périmètre C9 : Le hook
useNetworkStatus() doit être créé (probable fichier src/hooks/useNetworkStatus.ts) et la dépendance @react-native-community/netinfo installée. C14 est responsable de l'injection. - Limitation FAILED_TIMEOUT : Le store ne conserve pas l'état précédant le FAILED_TIMEOUT. La carte marque la 1ère étape comme "failed" par défaut. Si un rendu plus précis est souhaité (marquer l'étape exacte d'échec), le store devrait exposer un champ
lastNonTerminalState.