From 3a3fa6272d9d35d9ff96bcdf5c51d9ff067278e1 Mon Sep 17 00:00:00 2001 From: Hermann_Kitio Date: Sun, 26 Apr 2026 05:57:15 +0300 Subject: [PATCH] docs(sprint-5): CHANGELOG + ROADMAP + TECH_DEBT (Sprint 5e clean) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CHANGELOG: entrées Sprint 5b/5c/5d (198 → 219 tests) - ROADMAP: Sprint 5 Billing marqué ✅ - TECH_DEBT: FTD-42 (modal prorata) + FTD-43 (race webhook) ouvertes. v1.26. 21 FTD actives. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/CHANGELOG.md | 70 ++++++++++++++++++++++++++++++++ docs/ROADMAP.md | 7 +++- docs/TECH_DEBT.md | 101 +++++++++++++++++++++++++++++++++------------- 3 files changed, 147 insertions(+), 31 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 103309f..e610a63 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -29,6 +29,76 @@ Chaque entrée suit ce format : --- +## [Unreleased] — 2026-04-26 — Sprint 5d — Customer Portal + page Paramètres + +### Added + +- `src/features/billing/hooks/useCustomerPortal.ts` — hook `{ openPortal, isLoading, error }` autour de `createCustomerPortalSession` + redirect full-page vers Stripe Customer Portal. Message d'erreur backend (`NO_ACTIVE_SUBSCRIPTION`) propagé tel quel. +- `src/features/billing/components/AccountBillingSection.tsx` — section UI `Card` : badge plan + CTA contextuel (Free → lien « Voir les plans » vers `/plan` ; Standard/Premium → bouton « Gérer mon abonnement » → Customer Portal). +- `src/features/account/pages/ParametresPage.tsx` — page conteneur `/parametres` avec section Abonnement + section Session (bouton « Se déconnecter » → `signOut()` + `queryClient.clear()` + `navigate('/login')`). +- 6 tests (3 useCustomerPortal + 3 AccountBillingSection). + +### Changed + +- `src/features/billing/pages/PricingPage.tsx` — branche **Standard→Premium** routée vers `useCustomerPortal.openPortal()` au lieu de Stripe Checkout direct (le Customer Portal Stripe affiche le montant prorata + confirmation native). `buildCtaConfigs` refactor : signature `(plan, isStandardPending, isPremiumPending, onUpgrade)` ; loading state combiné selon source ; erreur unifiée `checkoutError ?? portalError`. +- `src/features/billing/__tests__/PricingPage.test.tsx` — 6e test : Standard click « Passer en Premium » → `createCustomerPortalSession` appelé (et `createCheckoutSession` non appelé). +- `src/app/router.tsx` — `/parametres` → `` (sous PrivateLayout). + +### Notes + +- Tests : 212 → 219 verts (+7). +- Customer Portal Stripe doit être configuré côté Dashboard Stripe (hors code) pour fonctionner en prod. + +--- + +## [Unreleased] — 2026-04-26 — Sprint 5c — Flow Checkout post-redirect + +### Added + +- `src/features/billing/hooks/useStripeCheckout.ts` — hook `{ checkout, isLoading, pendingPriceType, error }` autour de la mutation Stripe Checkout + redirect full-page sur succès. `pendingPriceType` permet l'affichage loading par carte sans state local. +- `src/features/dashboard/hooks/useUpgradeSuccessHandler.ts` — détecte `?upgrade=success` au mount du Dashboard, invalide le cache `PLAN_QUERY_KEY` (refetch automatique du plan), nettoie l'URL via `history.replaceState` (préserve les autres params utm\_\*, etc.). +- `src/features/dashboard/components/UpgradeSuccessBanner.tsx` — callout success-soft « Bienvenue ! Votre plan a été mis à jour. » + bouton dismiss. +- 9 tests (4 useStripeCheckout + 5 useUpgradeSuccessHandler). + +### Changed + +- `src/features/billing/pages/PricingPage.tsx` — migration vers `useStripeCheckout` (suppression `useMutation` inline + `pendingType` state local). +- `src/features/dashboard/pages/DashboardPage.tsx` — branche `useUpgradeSuccessHandler` + rend `` au-dessus de `` quand `showSuccess`. + +### Cross-repo + +- `expria-backend@28f8373` — `fix(stripe): cancel_url /tarifs → /plan`. Bug détecté lors de cette session : la route `/tarifs` n'existe pas côté frontend, les checkouts annulés aboutissaient sur un 404. Corrigé en commit séparé sur le backend. + +### Notes + +- Tests : 203 → 212 verts (+9). +- Race condition connue (FTD-43) : le webhook Stripe peut arriver après le redirect frontend ; `usePlan()` peut retourner l'ancien plan brièvement. Le banner indique « rafraîchissez dans quelques secondes » pour gérer ce cas. + +--- + +## [Unreleased] — 2026-04-26 — Sprint 5b — Page tarifaire `/plan` + +### Added + +- `src/features/billing/api.ts` — `createCheckoutSession(priceType)` + `createCustomerPortalSession()` (utilisée Sprint 5d). +- `src/features/billing/components/PlanCard.tsx` — carte plan présentationnelle pure : props `cta`, `currentBadge`, `highlighted`, `ctaHint`, `errorMessage`. +- `src/features/billing/pages/PricingPage.tsx` — orchestration 3 colonnes (Découverte / Standard / Premium) avec gating dynamique selon `usePlan()`. CTA payant → Stripe Checkout (full-page redirect). Callout d'erreur sous la carte cliquée. +- 5 tests PricingPage (rendu Free/Standard/Premium + click + erreur). + +### Changed + +- `src/shared/config/env.ts` + `.env.example` — ajout `VITE_STRIPE_PRICE_STANDARD` + `VITE_STRIPE_PRICE_PREMIUM` (optionnels — public price_ids Stripe). +- `src/app/router.tsx` — `/plan` → `` (sous PrivateLayout, donc ProtectedRoute). +- **Uniformisation CTA upgrade** : `SimulationsList`, `RapportPage`, `TaskSelector`, `DashboardFreeView`, `PaywallBanner` → libellé « Voir les plans » (au lieu de « Passer en Standard » / « Passer en Premium → » / « Voir les offres »). Cibles navigation inchangées (`/plan`). +- `SimulationsList.test.tsx` — assertion adaptée au nouveau libellé. + +### Notes + +- Tests : 198 → 203 verts (+5). +- `DashboardStandardView` et `BlurredProgression` conservent leurs CTA orientés (« Passer en Premium ») — sémantiquement corrects (Standard a un seul upgrade possible ; pattern_analysis est Premium-only). + +--- + ## [Unreleased] — 2026-04-26 — Sprint 4.8 — Phonologie EO (frontend) ### Added diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 1a0e0e3..aa1e06e 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -133,9 +133,12 @@ - Analyse phonologique réelle via Gemini audio (TD-08 backend) - Score phonologie dans les 4 critères EO (actuellement fixé à 0) -## Sprint 5 — Billing +## Sprint 5 — Billing ✅ -17. features/billing (Stripe Checkout + prorata) +- **5a (backend)** : TD-13 webhook idempotency (table `stripe_webhook_events` + helpers + 10 tests) ; route `POST /stripe/customer-portal` + `createBillingPortalSession` ; doc cleanup `ARCHITECTURE-backend.md` (`POST /plans/upgrade` retiré, duplication doc) ; tests backend 261 → 278. +- **5b (frontend)** : `PricingPage` `/plan` (3 colonnes Découverte/Standard/Premium) + `useStripeCheckout` initial + uniformisation CTA upgrade « Voir les plans » sur 5 emplacements ; env vars `VITE_STRIPE_PRICE_*` ; tests 198 → 203. +- **5c (frontend + cross-repo backend fix)** : `useStripeCheckout` hook isolé + `useUpgradeSuccessHandler` (détection `?upgrade=success` + invalidation cache plan + URL clean) + `UpgradeSuccessBanner` ; migration `PricingPage` + injection banner `DashboardPage` ; fix backend `cancel_url /tarifs → /plan` ; tests 203 → 212. +- **5d (frontend)** : `useCustomerPortal` hook + `AccountBillingSection` + `ParametresPage` `/parametres` (Abonnement + Session/déconnexion) ; **Standard→Premium routé via Customer Portal** (prorata natif Stripe) ; tests 212 → 219. ## Sprint 5.5 — Clean diff --git a/docs/TECH_DEBT.md b/docs/TECH_DEBT.md index 2523a32..bf1f37c 100644 --- a/docs/TECH_DEBT.md +++ b/docs/TECH_DEBT.md @@ -1,6 +1,6 @@ # TECH_DEBT.md — Expria Frontend -> **Document de référence — Version 1.25** +> **Document de référence — Version 1.26** > Ce document recense les décisions techniques prises par pragmatisme qui devront être revisitées, les stubs temporaires, et les fonctionnalités reportées. > À mettre à jour après chaque session de développement. > @@ -261,6 +261,48 @@ Vient du pattern `c.json(result, result.status)` où `result` contient déjà `s --- +### FTD-42 — Modal prorata Standard→Premium avec montant exact + +**Priorité :** 🟡 Important +**Statut :** Ouvert — introduit Sprint 5d (2026-04-26) +**Estimation de session :** 1 jour +**Description :** Le flux Standard→Premium passe actuellement par le **Stripe Customer Portal** (Sprint 5d). Le portal natif Stripe affiche bien le montant prorata + confirmation, mais hors de l'app : l'utilisateur quitte expria.app pendant la confirmation. **Divergence avec PARCOURS_UTILISATEURS.md §3** qui prévoit une modal in-app avec : + +> « Vous paierez [montant calculé]€ aujourd'hui pour accéder au plan Premium jusqu'au [date de fin]. » [Confirmer] [Annuler] + +Endpoint backend déjà disponible : `POST /plans/upgrade-prorata` retourne `{ amount, currency, newPlanExpiry }` calculé par Stripe (preview). + +**À faire :** + +- Hook `usePreviewProrata()` → consomme `POST /plans/upgrade-prorata` au mount de la modal. +- Composant `ProrataConfirmationModal` (montant + date affichés + bouton Confirmer). +- Bouton Confirmer → appel d'un nouvel endpoint backend `POST /plans/upgrade-prorata/confirm` qui réalise effectivement le `subscription.update()` Stripe. +- Tests : 5+ (preview, confirm, erreurs). +- Décision côté UX : remplacer entièrement le flux Customer Portal pour Standard→Premium, OU garder le Customer Portal en fallback « Gérer mon abonnement » ailleurs ? + +**Condition de résolution :** modal in-app livrée + Standard→Premium ne passe plus par Customer Portal mais par cette modal. + +--- + +### FTD-43 — Race condition webhook post-redirect Stripe Checkout + +**Priorité :** 🟢 Mineur +**Statut :** Ouvert — introduit Sprint 5c (2026-04-26) +**Estimation de session :** 0,5 jour +**Description :** Après un Stripe Checkout réussi, le frontend revient sur `/dashboard?upgrade=success`. `useUpgradeSuccessHandler` invalide `PLAN_QUERY_KEY` immédiatement. Mais le webhook `checkout.session.completed` peut arriver côté backend **après** le redirect frontend (latence réseau Stripe → Render typiquement 1-3 s). Si l'invalidation refetch trop tôt, `usePlan()` retourne encore l'ancien plan. L'utilisateur voit son ancien plan dans le dashboard pendant quelques secondes. + +**Mitigation actuelle :** `UpgradeSuccessBanner` affiche explicitement « Si certaines features semblent manquer, rafraîchissez la page dans quelques secondes ». Acceptable au MVP. + +**À faire si remonté en prod :** + +- Polling court : refetch automatique de `usePlan()` toutes les 2 s pendant 30 s tant que le plan reçu === ancien plan. +- OU : ajouter un endpoint `GET /plans/status?wait_for_change=true` côté backend qui long-poll jusqu'au changement (max 10 s) et retourne le nouveau plan. +- OU : Stripe envoie un event WebSocket via Pusher / SSE — out of scope MVP. + +**Condition de résolution :** observer en production. Si > 5 % des upgrades produisent un message support, prioriser. + +--- + ### FTD-33 — Carte EO_T2_LIVE verrouillée en dur (pas via `hasAccess`) **Priorité :** 🟢 Mineur @@ -553,31 +595,32 @@ Frontend : ## 6. Historique de ce document -| Version | Date | Changements | -| ------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| 1.0 | 2026-04-17 | Création initiale avec 9 FTD identifiées depuis l'audit backend et les décisions d'architecture | -| 1.1 | 2026-04-17 | Ajout FTD-10 (Semgrep CI), FTD-11 (`@theme` Tailwind 4), FTD-12 (tests `api-client`) suite à l'étape 11 du Sprint 0 | -| 1.2 | 2026-04-17 | Ajout FTD-13 résolu (incompatibilité Vitest 3 / Vite 8) suite à l'étape 12-bis du Sprint 0 | -| 1.3 | 2026-04-18 | FTD-11 résolu (design system Sprint 0.5) ; ajout FTD-14 (anti-FOUC), FTD-15 (option 'system' thème) | -| 1.4 | 2026-04-18 | FTD-16 résolu (VITE_MAINTENANCE_MODE implémenté — Sprint 1 étape 6) | -| 1.5 | 2026-04-19 | Ajout FTD-17 (clé ['plan'] dupliquée entre features — Sprint 3 étape 14) | -| 1.6 | 2026-04-20 | Ajout FTD-18 (SimulationForm shadcn Button — Sprint 0.5 bis D2) ; ajout FTD-19 (token --shadow-focus manquant — Sprint 0.5 bis D2) | -| 1.7 | 2026-04-20 | Ajout FTD-20 🔴 (GET /simulations/:id manquant backend — bloque RapportPage Sprint 3 étape 15) | -| 1.8 | 2026-04-20 | Ajout FTD-21 🔴 (persistance session simulation — prod + sujet perdus au refresh, session dédiée après G1-G5) | -| 1.9 | 2026-04-21 | FTD-22 résolu partiellement (nettoyage code orphelin refonte `/sujets` — `SujetSelector` + `selectSujet` supprimés ; `choosing-subject` + `goToSubjectPicker` conservés) | -| 1.10 | 2026-04-21 | FTD-21 résolu partiellement pour `/simulation/ee` (autosave 30 s + `beforeunload` + reprise via `localStorage` + `PATCH /:id/contenu` + `PATCH /:id/sujet` + `getById` tolère `rapport=null`) ; EO + examen restent ouverts | -| 1.11 | 2026-04-22 | Sprint 3.5 Clean — FTD-17, FTD-18, FTD-19 résolus. 15 FTD actives restantes (cap de 15 respecté) | -| 1.12 | 2026-04-22 | Sprint 3.6a — Ajout FTD-23 🟡 (useAutosave fire après correction). 16 FTD actives → cap de 15 dépassé temporairement, à revoir au prochain clean. | -| 1.13 | 2026-04-22 | Sprint 3.6b — Ajout FTD-24 🟡 (polling auto exercices/modèle pending). 17 FTD actives → cap dépassé, un clean 3.6.5 devra résoudre FTD-23/24 ensemble. | -| 1.14 | 2026-04-23 | Triage : FTD-04, FTD-05, FTD-20, FTD-22 fermées. FTD-25, FTD-26 ajoutées. 15 FTD actives (cap respecté). | -| 1.15 | 2026-04-23 | Réorg sécurité : FTD-06, FTD-08, FTD-15 gelées (backlog post-MVP). FTD-27 🔴, FTD-28 🔴, FTD-29 🟡 ajoutées (sécurité). 15 FTD actives (cap respecté). | -| 1.16 | 2026-04-23 | FTD-29 fermée (Dependabot config). 14 FTD actives. | -| 1.17 | 2026-04-23 | FTD-27 fermée (CI backend). 13 FTD actives. | -| 1.18 | 2026-04-23 | FTD-28 fermée (Semgrep CI). CI frontend verte pour la première fois. 12 FTD actives. | -| 1.19 | 2026-04-23 | FTD-23 et FTD-24 fermées (clean useAutosave après correction + polling automatique jobs pending dans useRapport). 10 FTD actives (cap 15). | -| 1.20 | 2026-04-25 | Sprint 4c-1 — Ajout FTD-30 🟡 (rotation token Deepgram sans grace period), FTD-31 🟢 (page enregistrement EO non resumable), FTD-32 🟢 (Safari iOS non testé), FTD-33 🟢 (EO_T2_LIVE verrouillé en dur). 14 FTD actives (cap 15 respecté). | -| 1.21 | 2026-04-25 | Sprint 4c-2 — Ajout FTD-34 🟢 (présentation T1 en localStorage clair), FTD-35 🟡 (refresh sans simulation active sur PresentationGenereeT1Page). **16 FTD actives — cap dépassé temporairement, accepté par Hermann pour cette session ; clean à planifier au prochain Sprint.** | -| 1.22 | 2026-04-25 | Sprint 4c-3 — Ajout FTD-36 🟡 (upload audio base64 sans progression), FTD-37 🟢 (code Deepgram live dormant à trancher). FTD-30 dégradée 🟡→🟢 et passée en « gelé » (Deepgram live mis en pause). **17 FTD actives — cap toujours dépassé, clean prioritaire au Sprint suivant.** | -| 1.23 | 2026-04-25 | FTD-25 et FTD-26 fermées (ARCHITECTURE.md §3 reflète l'arborescence réelle + convention `shared/ui/` vs `shared/components/ui/` documentée). 15 FTD actives (cap respecté). | -| 1.24 | 2026-04-25 | Sprint 4.5 Clean — Ajout FTD-38 🟢 (`useAudioRecorder` ref mise à jour pendant render — eslint-disable local en place) et FTD-39 🟡 (Règle D violée dans `StatCards.tsx` — préexistant Sprint UI Polish). 17 FTD actives — cap dépassé temporairement, à résorber au Sprint 5.5. | -| 1.25 | 2026-04-25 | Sprint 4.5 — Ajout FTD-40 🟡 (conclusion `conseil_nclc` backend incohérente quand NCLC atteint > cible — patch frontend en place dans `ConseilNclcCallout`) et FTD-41 🔴 (persistance présentation EO T1 en BDD — résout FTD-35). **19 FTD actives — cap 15 dépassé de 4. Résorption obligatoire au Sprint 5.5 avant toute nouvelle FTD.** | +| Version | Date | Changements | +| ------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 1.0 | 2026-04-17 | Création initiale avec 9 FTD identifiées depuis l'audit backend et les décisions d'architecture | +| 1.1 | 2026-04-17 | Ajout FTD-10 (Semgrep CI), FTD-11 (`@theme` Tailwind 4), FTD-12 (tests `api-client`) suite à l'étape 11 du Sprint 0 | +| 1.2 | 2026-04-17 | Ajout FTD-13 résolu (incompatibilité Vitest 3 / Vite 8) suite à l'étape 12-bis du Sprint 0 | +| 1.3 | 2026-04-18 | FTD-11 résolu (design system Sprint 0.5) ; ajout FTD-14 (anti-FOUC), FTD-15 (option 'system' thème) | +| 1.4 | 2026-04-18 | FTD-16 résolu (VITE_MAINTENANCE_MODE implémenté — Sprint 1 étape 6) | +| 1.5 | 2026-04-19 | Ajout FTD-17 (clé ['plan'] dupliquée entre features — Sprint 3 étape 14) | +| 1.6 | 2026-04-20 | Ajout FTD-18 (SimulationForm shadcn Button — Sprint 0.5 bis D2) ; ajout FTD-19 (token --shadow-focus manquant — Sprint 0.5 bis D2) | +| 1.7 | 2026-04-20 | Ajout FTD-20 🔴 (GET /simulations/:id manquant backend — bloque RapportPage Sprint 3 étape 15) | +| 1.8 | 2026-04-20 | Ajout FTD-21 🔴 (persistance session simulation — prod + sujet perdus au refresh, session dédiée après G1-G5) | +| 1.9 | 2026-04-21 | FTD-22 résolu partiellement (nettoyage code orphelin refonte `/sujets` — `SujetSelector` + `selectSujet` supprimés ; `choosing-subject` + `goToSubjectPicker` conservés) | +| 1.10 | 2026-04-21 | FTD-21 résolu partiellement pour `/simulation/ee` (autosave 30 s + `beforeunload` + reprise via `localStorage` + `PATCH /:id/contenu` + `PATCH /:id/sujet` + `getById` tolère `rapport=null`) ; EO + examen restent ouverts | +| 1.11 | 2026-04-22 | Sprint 3.5 Clean — FTD-17, FTD-18, FTD-19 résolus. 15 FTD actives restantes (cap de 15 respecté) | +| 1.12 | 2026-04-22 | Sprint 3.6a — Ajout FTD-23 🟡 (useAutosave fire après correction). 16 FTD actives → cap de 15 dépassé temporairement, à revoir au prochain clean. | +| 1.13 | 2026-04-22 | Sprint 3.6b — Ajout FTD-24 🟡 (polling auto exercices/modèle pending). 17 FTD actives → cap dépassé, un clean 3.6.5 devra résoudre FTD-23/24 ensemble. | +| 1.14 | 2026-04-23 | Triage : FTD-04, FTD-05, FTD-20, FTD-22 fermées. FTD-25, FTD-26 ajoutées. 15 FTD actives (cap respecté). | +| 1.15 | 2026-04-23 | Réorg sécurité : FTD-06, FTD-08, FTD-15 gelées (backlog post-MVP). FTD-27 🔴, FTD-28 🔴, FTD-29 🟡 ajoutées (sécurité). 15 FTD actives (cap respecté). | +| 1.16 | 2026-04-23 | FTD-29 fermée (Dependabot config). 14 FTD actives. | +| 1.17 | 2026-04-23 | FTD-27 fermée (CI backend). 13 FTD actives. | +| 1.18 | 2026-04-23 | FTD-28 fermée (Semgrep CI). CI frontend verte pour la première fois. 12 FTD actives. | +| 1.19 | 2026-04-23 | FTD-23 et FTD-24 fermées (clean useAutosave après correction + polling automatique jobs pending dans useRapport). 10 FTD actives (cap 15). | +| 1.20 | 2026-04-25 | Sprint 4c-1 — Ajout FTD-30 🟡 (rotation token Deepgram sans grace period), FTD-31 🟢 (page enregistrement EO non resumable), FTD-32 🟢 (Safari iOS non testé), FTD-33 🟢 (EO_T2_LIVE verrouillé en dur). 14 FTD actives (cap 15 respecté). | +| 1.21 | 2026-04-25 | Sprint 4c-2 — Ajout FTD-34 🟢 (présentation T1 en localStorage clair), FTD-35 🟡 (refresh sans simulation active sur PresentationGenereeT1Page). **16 FTD actives — cap dépassé temporairement, accepté par Hermann pour cette session ; clean à planifier au prochain Sprint.** | +| 1.22 | 2026-04-25 | Sprint 4c-3 — Ajout FTD-36 🟡 (upload audio base64 sans progression), FTD-37 🟢 (code Deepgram live dormant à trancher). FTD-30 dégradée 🟡→🟢 et passée en « gelé » (Deepgram live mis en pause). **17 FTD actives — cap toujours dépassé, clean prioritaire au Sprint suivant.** | +| 1.23 | 2026-04-25 | FTD-25 et FTD-26 fermées (ARCHITECTURE.md §3 reflète l'arborescence réelle + convention `shared/ui/` vs `shared/components/ui/` documentée). 15 FTD actives (cap respecté). | +| 1.24 | 2026-04-25 | Sprint 4.5 Clean — Ajout FTD-38 🟢 (`useAudioRecorder` ref mise à jour pendant render — eslint-disable local en place) et FTD-39 🟡 (Règle D violée dans `StatCards.tsx` — préexistant Sprint UI Polish). 17 FTD actives — cap dépassé temporairement, à résorber au Sprint 5.5. | +| 1.25 | 2026-04-25 | Sprint 4.5 — Ajout FTD-40 🟡 (conclusion `conseil_nclc` backend incohérente quand NCLC atteint > cible — patch frontend en place dans `ConseilNclcCallout`) et FTD-41 🔴 (persistance présentation EO T1 en BDD — résout FTD-35). **19 FTD actives — cap 15 dépassé de 4. Résorption obligatoire au Sprint 5.5 avant toute nouvelle FTD.** | +| 1.26 | 2026-04-26 | Sprint 5e (clean Sprint 5 Billing) — Ajout FTD-42 🟡 (modal prorata Standard→Premium avec montant exact — divergence PARCOURS_UTILISATEURS §3, actuellement Customer Portal natif sans preview in-app) et FTD-43 🟢 (race condition webhook post-redirect Stripe — `usePlan()` peut retourner ancien plan brièvement). **21 FTD actives — cap 15 dépassé de 6. Résorption FTD critique au Sprint 5.5 avant Sprint 6.** |