feat(gating): routes + Sidebar (budget, rapports avancés, bilan) #305

Closed
maximus wants to merge 1 commit from issue-299-routes-sidebar into issue-298-require-feature
Owner

2e maillon de la pile feature-gating (base = issue-298-require-feature, PR #304).

Contenu

  • App.tsx : layout-routes pathless <RequireFeature feature=… /> (rendu <Outlet/>) — balance groupe /balance + /balance/accounts + /balance/snapshot ; reports-advanced groupe /reports/highlights|compare|category|cartes ; budget sur /budget ; adjustments sur /adjustments. Le hub /reports et /reports/trends restent Free, hors de tout gate (caveat CRITIQUE du /review-spec respecté).
  • NavItem (src/shared/types) : champ optionnel feature?: FeatureKey (import type-only, pas de cycle) ; renseigné dans constants/index.ts sur budget, adjustments, balance UNIQUEMENT — pas reports.
  • Sidebar : sous-composant local NavLock (hook useEntitlement au top-level du composant, jamais dans le map callback) → cadenas affiché seulement si ready && !allowed (aucun flash au boot) ; l'item reste cliquable et mène à l'upsell via la route gatée. Tooltip + aria réutilisent nav.locked.
  • ReportsPage (hub) : un seul appel useEntitlement("reports-advanced"), flag locked propagé aux 4 tuiles avancées via une prop additive locked? sur HubReportNavCard (badge cadenas, tuile cliquable) ; la tuile Tendance n'est jamais verrouillée. HubReportNavCard non listé dans l'issue mais requis pour rendre le badge dans la tuile — prop optionnelle, sans impact sur l'autre consommateur (BalancePage).

Tests / build

  • 861 vitest verts (858 baseline + 3 nouveaux : contrat NAV_ITEMS — trio gaté avec la bonne clé, reports + modules Free jamais gatés, exactement 3 items gatés sur 9).
  • npm run build (tsc + vite) propre.
  • Sidebar/App/tuiles non testables en render (pas de jsdom dans ce repo — précédent accepté) : vérification runtime déléguée à la review.

Aucune nouvelle chaîne i18n (nav.locked livré par #298). Aucune migration DB. CHANGELOG intentionnellement non touché (centralisé dans #302).

Resolves #299
Generated autonomously by /autopilot run of 2026-07-20

2e maillon de la pile feature-gating (base = `issue-298-require-feature`, PR #304). ## Contenu - **App.tsx** : layout-routes pathless `<RequireFeature feature=… />` (rendu `<Outlet/>`) — `balance` groupe `/balance` + `/balance/accounts` + `/balance/snapshot` ; `reports-advanced` groupe `/reports/highlights|compare|category|cartes` ; `budget` sur `/budget` ; `adjustments` sur `/adjustments`. Le hub `/reports` et `/reports/trends` restent Free, hors de tout gate (caveat CRITIQUE du /review-spec respecté). - **NavItem** (`src/shared/types`) : champ optionnel `feature?: FeatureKey` (import type-only, pas de cycle) ; renseigné dans `constants/index.ts` sur budget, adjustments, balance UNIQUEMENT — pas `reports`. - **Sidebar** : sous-composant local `NavLock` (hook `useEntitlement` au top-level du composant, jamais dans le map callback) → cadenas affiché seulement si `ready && !allowed` (aucun flash au boot) ; l'item reste cliquable et mène à l'upsell via la route gatée. Tooltip + aria réutilisent `nav.locked`. - **ReportsPage (hub)** : un seul appel `useEntitlement("reports-advanced")`, flag `locked` propagé aux 4 tuiles avancées via une prop additive `locked?` sur `HubReportNavCard` (badge cadenas, tuile cliquable) ; la tuile Tendance n'est jamais verrouillée. `HubReportNavCard` non listé dans l'issue mais requis pour rendre le badge dans la tuile — prop optionnelle, sans impact sur l'autre consommateur (BalancePage). ## Tests / build - **861 vitest verts** (858 baseline + 3 nouveaux : contrat NAV_ITEMS — trio gaté avec la bonne clé, `reports` + modules Free jamais gatés, exactement 3 items gatés sur 9). - `npm run build` (tsc + vite) propre. - Sidebar/App/tuiles non testables en render (pas de jsdom dans ce repo — précédent accepté) : vérification runtime déléguée à la review. Aucune nouvelle chaîne i18n (`nav.locked` livré par #298). Aucune migration DB. CHANGELOG intentionnellement non touché (centralisé dans #302). Resolves #299 Generated autonomously by /autopilot run of 2026-07-20
maximus added 1 commit 2026-07-21 02:13:39 +00:00
Apply the tier gating to routes and navigation on top of the #298 UI
guard:

- App.tsx: pathless RequireFeature layout-routes grouping /balance,
  /balance/accounts, /balance/snapshot under "balance"; /reports/
  highlights|compare|category|cartes under "reports-advanced"; /budget
  under "budget"; /adjustments under "adjustments". The /reports hub and
  /reports/trends stay Free and ungated.
- NavItem gains an optional `feature?: FeatureKey`; set in NAV_ITEMS on
  budget, adjustments and balance only — NOT on reports (Free hub).
- Sidebar: local NavLock child component (hook at component top level)
  renders a lock badge only when the license is ready AND the feature is
  not allowed — no locked flash at boot; items stay clickable and lead
  to the upsell via the gated route. Tooltip/aria reuse nav.locked.
- ReportsPage hub: single useEntitlement("reports-advanced") call
  drives a `locked` badge on the 4 advanced tiles via a new additive
  HubReportNavCard `locked?` prop; the Trends tile is never locked.
- Pure contract test on NAV_ITEMS (gated trio present, reports/Free
  items ungated, exactly 3 of 9 gated).

No new i18n strings (nav.locked shipped with #298), no DB migration.
Changelog centralized in #302.

Resolves #299

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
maximus added the
status:review
type:feature
source:human
autopilot:pending-human
labels 2026-07-21 02:13:49 +00:00
Author
Owner

Review adversariale — APPROVE

Résumé : Gating par route + badge Sidebar implémentés exactement selon #299. Le caveat CRITIQUE du /review-spec est respecté (hub /reports et /reports/trends hors de tout gate) et verrouillé par un test de contrat. Aucune route perdue dans le remaniement d'App.tsx, aucun piège de hooks, aucun cycle d'import, anti-flash correct des deux côtés.

Vérifications (contre la base issue-298-require-feature)

  • RequireFeature rend <Outlet/> quand children est omis → usage layout-route pathless valide ; les 4 clés (adjustments, budget, balance, reports-advanced) sont toutes membres de FeatureKey.
  • Comparaison ligne à ligne avant/après d'App.tsx : les 18 routes préexistantes sont conservées ; /reports, /reports/trends, /settings/*, docs et changelog restent hors gate.
  • NavLock : hook au top-level d'un vrai composant — item.feature && <NavLock/> est du rendu conditionnel de composant, pas un hook conditionnel ; !ready → null supprime le flash au boot. Le NavLink parent est flex items-center, le ml-auto du badge fonctionne.
  • LicenseProvider monté dans main.tsx au-dessus d'AppShell → Sidebar/ReportsPage ne peuvent pas crasher sur le contexte.
  • Pas de cycle d'import : types/index.ts → entitlements est type-only, licenseService n'importe que @tauri-apps/api/core.
  • nav.locked présent en FR et EN (livré par #298). BalancePage (autre consommateur de HubReportNavCard) intact — prop locked? additive.
  • Test contrat ramassé par vitest (include par défaut, 53 fichiers .test.ts co-localisés préexistants) ; strict sans noUncheckedIndexedAccessbyKey.x.feature compile. Pas de skip/only.
  • Aucune migration DB, aucun secret, aucune chaîne hors i18n. CHANGELOG centralisé dans #302 — cohérent avec le plan de milestone.

Suggestions (non bloquantes)

  1. NetWorthTile (dashboard Free) reste visible après un downgrade avec d'anciens snapshots et linke vers /balance (→ upsell au clic). Cohérent avec « données conservées », mais un badge locked (pattern HubReportNavCard) unifierait le langage visuel. Suivi hors scope #299.
  2. Markup du badge cadenas dupliqué entre NavLock et HubReportNavCard — extraire un LockBadge partagé si une 3e surface apparaît.

Rappel process (pas un défaut de la PR) : maillon intermédiaire ciblant une base non-main → jamais vu par check.yml ; valider le tip cumulé en local avant le merge de la pile.

## Review adversariale — APPROVE **Résumé** : Gating par route + badge Sidebar implémentés exactement selon #299. Le caveat CRITIQUE du `/review-spec` est respecté (hub `/reports` et `/reports/trends` hors de tout gate) et verrouillé par un test de contrat. Aucune route perdue dans le remaniement d'`App.tsx`, aucun piège de hooks, aucun cycle d'import, anti-flash correct des deux côtés. ### Vérifications (contre la base `issue-298-require-feature`) - `RequireFeature` rend `<Outlet/>` quand `children` est omis → usage layout-route pathless valide ; les 4 clés (`adjustments`, `budget`, `balance`, `reports-advanced`) sont toutes membres de `FeatureKey`. - Comparaison ligne à ligne avant/après d'`App.tsx` : les 18 routes préexistantes sont conservées ; `/reports`, `/reports/trends`, `/settings/*`, docs et changelog restent hors gate. - `NavLock` : hook au top-level d'un vrai composant — `item.feature && <NavLock/>` est du rendu conditionnel de composant, pas un hook conditionnel ; `!ready → null` supprime le flash au boot. Le `NavLink` parent est `flex items-center`, le `ml-auto` du badge fonctionne. - `LicenseProvider` monté dans `main.tsx` au-dessus d'`AppShell` → Sidebar/ReportsPage ne peuvent pas crasher sur le contexte. - Pas de cycle d'import : `types/index.ts → entitlements` est type-only, `licenseService` n'importe que `@tauri-apps/api/core`. - `nav.locked` présent en FR et EN (livré par #298). `BalancePage` (autre consommateur de `HubReportNavCard`) intact — prop `locked?` additive. - Test contrat ramassé par vitest (include par défaut, 53 fichiers `.test.ts` co-localisés préexistants) ; `strict` sans `noUncheckedIndexedAccess` → `byKey.x.feature` compile. Pas de `skip`/`only`. - Aucune migration DB, aucun secret, aucune chaîne hors i18n. CHANGELOG centralisé dans #302 — cohérent avec le plan de milestone. ### Suggestions (non bloquantes) 1. `NetWorthTile` (dashboard Free) reste visible après un downgrade avec d'anciens snapshots et linke vers `/balance` (→ upsell au clic). Cohérent avec « données conservées », mais un badge `locked` (pattern `HubReportNavCard`) unifierait le langage visuel. Suivi hors scope #299. 2. Markup du badge cadenas dupliqué entre `NavLock` et `HubReportNavCard` — extraire un `LockBadge` partagé si une 3e surface apparaît. Rappel process (pas un défaut de la PR) : maillon intermédiaire ciblant une base non-`main` → jamais vu par `check.yml` ; valider le tip cumulé en local avant le merge de la pile.
Author
Owner

Mergé en fast-forward sur main (pile #304→#308), voir commit 553da0c. Verdict /pr-review : APPROVE. Fermeture manuelle : le merge local ff n est pas détecté comme merged par Forgejo.

Mergé en fast-forward sur main (pile #304→#308), voir commit 553da0c. Verdict /pr-review : APPROVE. Fermeture manuelle : le merge local ff n est pas détecté comme *merged* par Forgejo.
maximus closed this pull request 2026-07-22 00:23:43 +00:00

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: maximus/Simpl-Resultat#305
No description provided.