feat(gating): garde UI — RequireFeature + UpsellGate + i18n #304
No reviewers
Labels
No labels
autopilot:pending-human
source:analyste
source:defenseur
source:human
source:medic
status:approved
status:blocked
status:in-progress
status:needs-clarification
status:needs-fix
status:ready
status:review
status:triage
type:bug
type:feature
type:infra
type:refactor
type:schema
type:security
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: maximus/Simpl-Resultat#304
Loading…
Reference in a new issue
No description provided.
Delete branch "issue-298-require-feature"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Composants de garde réutilisables au-dessus du socle #297.
UpsellGate(src/components/shared/) : écran verrouillé plein (cadenas, titre « Fonctionnalité », description par feature). 2 CTA : « Obtenir » visible mais désactivé avec la note « Achat en ligne bientôt disponible » (décision de planification — #270 l'activera), et « J'ai déjà une clé » →navigate("/settings/users")(route vérifiée :App.tsx:125, LicenseCard montée dansUsersSettingsPage.tsx:34). Libellé du tier via les clés existanteslicense.editions.*.RequireFeature: loader neutre tant que!ready(aucun flash upsell au boot), puischildrensiallowed, sinonUpsellGate. Rend<Outlet/>quandchildrenest omis → utilisable en layout-route groupant les routes d'une même feature (les deux formes marchent pour l'issue #299).requiredTierFor()(helper pur additif danssrc/shared/entitlements.ts) : dérive le tier minimal débloquant depuis l'appartenance à la matrice (pas l'ordre du tableau) — testable sans jsdom, réutilisable par les tooltips de #299.upsell.*(titre, descriptions par feature, CTA) +nav.lockeddans les deux locales.nav.lockedsans interpolation (robuste pour tous les call-sites Sidebar/profils).requiredTierFor+ couverture i18nupsell.features.<FeatureKey>fr+en (clé manquante = clé brute à l'écran) + pas de clé stale. Les composants eux-mêmes ne sont pas testables sans jsdom (précédent accepté) — vérif runtime déléguée à la review.Validation : 858 vitest verts (852 + 6),
npm run build(tsc + vite) propre. Pas de CHANGELOG (centralisé dans #302).Resolves #298
Generated autonomously by /autopilot run of 2026-07-20
Review — APPROVE
Maillon 2 de la pile feature-gating, correctement basé sur
main(socle #303/#297 mergé). Le code implémente fidèlement le contrat du socle et les décisions de la spec ; tous les claims du body ont été vérifiés contre le code réel de la branche. CI verte (rust + frontend), 6 tests pertinents, aucun point bloquant.Vérifications
RequireFeaturerend un loader neutre tant questatus !== "ready"— jamais l'upsell pendant boot/erreur IPC ; la boucle de retry (backoff cappé 30s) vit bien dansLicenseProvider. Double sûreté :allowedest fail-closed pendant le boot (édition par défautfree), le contenu gated ne fuite jamais.requiredTierForcorrect et total : la matrice ne contient que des features gated (les features Free n'ont pas de clé), doncincludes("base") ? "base" : "premium"donne le tier minimal exact pour les 5 clés ; le tier passé àlicense.editions.${tier}ne peut jamais êtrefree→ la clé résout toujours./settings/usersconfirmée (App.tsx:125) etLicenseCardmontée danssrc/pages/settings/UsersSettingsPage.tsx:34. CTA « Obtenir » désactivé + note « bientôt » = conforme à la décision de planification (#270 l'activera).upsell.*+nav.lockedidentiques en structure fr/en,{{tier}}des deux côtés,license.editions.*préexistantes confirmées.FeatureKeyarrive sans traduction ou si une clé stale traîne. Pas deskip/only.PR Check / rust+PR Check / frontendsuccess sur le head. Rappel pile : #305-#308 (bases non-main) ne seront pas CI-validées → valider le tip cumulé en local avant merge.Suggestions (non bloquantes)
requiredTierForn'est pas défensif sur une clé hors matrice (ENTITLEMENTS[f].includescrasherait sur un cast inconnu), là oùisEntitledgère le drift du namespace partagé avec Rust. Aucun risque aux call-sites actuels (littérauxFeatureKey) ; à durcir seulement si le helper reçoit un jour une valeur du namespace JWT.RequireFeaturen'a pas derole="status"/aria-label— pattern cohérent avec les loaders existants d'App.tsx; à harmoniser si un chantier accessibilité s'ouvre.nav.lockedlivré sans consommateur dans ce maillon — préparation assumée pour #299 (PR #305), déjà testée ; à confirmer consommée lors de la review de #305.Review générée par
/pr-reviewMergé en fast-forward sur main (pile #304→#308), voir commit
554373e. Verdict /pr-review : APPROVE. Fermeture manuelle : le merge local ff n est pas détecté comme merged par Forgejo.Pull request closed