feat(gating): reserve transaction splits for the Base edition #452

Merged
maximus merged 3 commits from issue-369-split-base-edition into main 2026-10-17 11:40:14 +00:00
Owner

Fixes #369

Réserve la Répartition (bouton de la page Transactions) à l'édition Base, comme l'écran de déverrouillage le promettait déjà (upsell.features.adjustments). La page Transactions reste Free ; seul ce bouton est gaté, par adjustments.

Comportement

Édition Bouton Répartition Clic
Gratuite, licence prête cadenas ; titre « Répartition — fonctionnalité Base » l'écran verrouillé dans une fenêtre (UpsellDialog), jamais la fenêtre de répartition, pour toute ligne
Base, Premium inchangé la fenêtre de répartition
Licence pas encore prête (chargement, erreur, rafraîchissement) ni cadenas, bouton inactif rien

L'icône orange d'une ligne répartie reste dans tous les cas. Les répartitions existantes restent en base, comptées par les rapports et le tableau de bord (ADR 0017 §5).

Code

  • src/shared/splitGate.ts : splitAccessFor(gate) → open, locked ou pending. Plus strict que profileGate : allowed n'y court-circuite pas ready (documenté et testé), puisque ready est faux pendant le chargement comme sur erreur.
  • src/components/transactions/SplitButton.tsx : bouton sans état, qui décide de ce qu'ouvre un clic.
  • src/components/shared/UpsellDialog.tsx : UpsellGate dans une fenêtre (role="dialog", aria-modal, nom accessible upsell.title), fermée par son X et par « J'ai déjà une clé ».
  • TransactionTable : prop splitAccess obligatoire, puisqu'une valeur par défaut « open » laisserait un nouvel appelant hors du gate sans rien dire. La fenêtre de répartition n'est pas rendue en locked, mais reste montée pendant un rafraîchissement de licence : refresh() repasse par loading, et une garde === "open" perdrait la saisie en cours. L'incitation n'est pas rendue en open.
  • TransactionsPage : useEntitlement("adjustments").

Docs

ADR 0017 (amendement daté, matrice, §4, §5, Liens), guide (Transactions, Éditions), docs intégrées FR et EN (docs.transactions, docs.editions), architecture.md (comptes de composants, table du gating, phrase « Modules Free »), commentaire d'en-tête d'entitlements.ts, CHANGELOG (Changed / Modifié).

Laissés tels quels, volontairement : docs.gettingStarted.features[2] et la liste de la section 1 du guide. Cette liste ne marque l'édition d'aucune fonction (ni le Budget, ni les profils multiples), alors que l'analyse de l'issue la citait parmi les textes sans mention d'édition.

Tests

  • splitGate.test.ts : les trois éditions, et pending avant ready, que allowed soit vrai ou faux.
  • SplitButton.test.tsx : cadenas, orange, disabled et titre selon l'état ; le clic, par les props du bouton (verrouillé → incitation, sur une ligne répartie comme sur une ligne simple ; ouvert → fenêtre de répartition ; en attente → rien) ; la clé transactions.splitLocked dans les deux langues.
  • UpsellDialog.render.test.tsx : rendu nommé, fermeture par le X et par onNavigate, tier requis par la fonction.
  • TransactionTable.wiring.test.ts, avec le harnais hookHarness (#409) : ce qu'ouvre chaque clic, sur les deux lignes ; la fenêtre de répartition survit à un rafraîchissement et se ferme en Gratuite ; l'incitation survit à un rafraîchissement et disparaît en Base.
  • TransactionTable.render.test.tsx et TransactionsPage.render.test.tsx : un cadenas par ligne en Gratuite, aucun en Base, bouton inactif tant que la licence n'est pas prête, et la page demande bien adjustments. Les vérifications de la page ne regardent que le bouton Répartition, la pagination ayant toujours un bouton désactivé.

Résultats : npm run build vert ; npm test 2248 tests sur 134 fichiers (2222 avant).

Passe de mutations

Une mutation à la fois sur cb7f847, restaurée par git, arbre propre à la fin : 21 mutations, 21 rouges, aucune survivante.

Mutation Tests rouges
Gratuite prête donne open 2
pending donne locked 3
pending avec allowed donne open 2
Clic verrouillé qui ouvre la répartition 1
Cadenas retiré 3
Orange retiré en locked 2
disabled retiré en pending 2
Table : l'incitation ouvre la fenêtre de répartition 2
Table : l'incitation ne fait rien 2
Fenêtre d'incitation pour budget 1
Fenêtre d'incitation jamais rendue 2
Garde de l'incitation retirée 1
Garde de la fenêtre de répartition retirée 1
Garde de la fenêtre de répartition en === "open" 1
Table : access="open" en dur 4
Page : useEntitlement("budget") 1
Page : gate mal converti (ready ? "locked" : "pending") 1
onNavigate non câblé 1
Bouton X non câblé 1
Tier base en dur dans la fenêtre 1
splitLocked absent d'en.json 1

Après le merge

  • STATE.md : la notification de la-compagnie-maximus#180 rejoint « Vérifications dues à la prochaine release », avec la vérification dans l'app (Gratuite : cadenas et écran de déverrouillage ; Base : fenêtre de répartition). Un commentaire sur la-compagnie-maximus#180 dira « mergé, publié avec la prochaine release ».
  • Suivi ouvert : #451 (fenêtre d'incitation de ProfileSwitcher sur UpsellDialog, fermeture sur Échap).

🤖 Generated with Claude Code

Fixes #369 Réserve la Répartition (bouton de la page Transactions) à l'édition Base, comme l'écran de déverrouillage le promettait déjà (`upsell.features.adjustments`). La page Transactions reste Free ; seul ce bouton est gaté, par `adjustments`. ## Comportement | Édition | Bouton Répartition | Clic | |---|---|---| | Gratuite, licence prête | cadenas ; titre « Répartition — fonctionnalité Base » | l'écran verrouillé dans une fenêtre (`UpsellDialog`), jamais la fenêtre de répartition, pour toute ligne | | Base, Premium | inchangé | la fenêtre de répartition | | Licence pas encore prête (chargement, erreur, rafraîchissement) | ni cadenas, bouton inactif | rien | L'icône orange d'une ligne répartie reste dans tous les cas. Les répartitions existantes restent en base, comptées par les rapports et le tableau de bord (ADR 0017 §5). ## Code - `src/shared/splitGate.ts` : `splitAccessFor(gate)` → `open`, `locked` ou `pending`. Plus strict que `profileGate` : `allowed` n'y court-circuite pas `ready` (documenté et testé), puisque `ready` est faux pendant le chargement comme sur erreur. - `src/components/transactions/SplitButton.tsx` : bouton sans état, qui décide de ce qu'ouvre un clic. - `src/components/shared/UpsellDialog.tsx` : `UpsellGate` dans une fenêtre (`role="dialog"`, `aria-modal`, nom accessible `upsell.title`), fermée par son X et par « J'ai déjà une clé ». - `TransactionTable` : prop `splitAccess` obligatoire, puisqu'une valeur par défaut « open » laisserait un nouvel appelant hors du gate sans rien dire. La fenêtre de répartition n'est pas rendue en `locked`, mais reste montée pendant un rafraîchissement de licence : `refresh()` repasse par `loading`, et une garde `=== "open"` perdrait la saisie en cours. L'incitation n'est pas rendue en `open`. - `TransactionsPage` : `useEntitlement("adjustments")`. ## Docs ADR 0017 (amendement daté, matrice, §4, §5, Liens), guide (Transactions, Éditions), docs intégrées FR et EN (`docs.transactions`, `docs.editions`), `architecture.md` (comptes de composants, table du gating, phrase « Modules Free »), commentaire d'en-tête d'`entitlements.ts`, CHANGELOG (Changed / Modifié). Laissés tels quels, volontairement : `docs.gettingStarted.features[2]` et la liste de la section 1 du guide. Cette liste ne marque l'édition d'aucune fonction (ni le Budget, ni les profils multiples), alors que l'analyse de l'issue la citait parmi les textes sans mention d'édition. ## Tests - `splitGate.test.ts` : les trois éditions, et `pending` avant `ready`, que `allowed` soit vrai ou faux. - `SplitButton.test.tsx` : cadenas, orange, `disabled` et titre selon l'état ; le clic, par les props du bouton (verrouillé → incitation, sur une ligne répartie comme sur une ligne simple ; ouvert → fenêtre de répartition ; en attente → rien) ; la clé `transactions.splitLocked` dans les deux langues. - `UpsellDialog.render.test.tsx` : rendu nommé, fermeture par le X et par `onNavigate`, tier requis par la fonction. - `TransactionTable.wiring.test.ts`, avec le harnais `hookHarness` (#409) : ce qu'ouvre chaque clic, sur les deux lignes ; la fenêtre de répartition survit à un rafraîchissement et se ferme en Gratuite ; l'incitation survit à un rafraîchissement et disparaît en Base. - `TransactionTable.render.test.tsx` et `TransactionsPage.render.test.tsx` : un cadenas par ligne en Gratuite, aucun en Base, bouton inactif tant que la licence n'est pas prête, et la page demande bien `adjustments`. Les vérifications de la page ne regardent que le bouton Répartition, la pagination ayant toujours un bouton désactivé. Résultats : `npm run build` vert ; `npm test` 2248 tests sur 134 fichiers (2222 avant). ## Passe de mutations Une mutation à la fois sur `cb7f847`, restaurée par git, arbre propre à la fin : 21 mutations, 21 rouges, aucune survivante. | Mutation | Tests rouges | |---|---| | Gratuite prête donne `open` | 2 | | `pending` donne `locked` | 3 | | `pending` avec `allowed` donne `open` | 2 | | Clic verrouillé qui ouvre la répartition | 1 | | Cadenas retiré | 3 | | Orange retiré en `locked` | 2 | | `disabled` retiré en `pending` | 2 | | Table : l'incitation ouvre la fenêtre de répartition | 2 | | Table : l'incitation ne fait rien | 2 | | Fenêtre d'incitation pour `budget` | 1 | | Fenêtre d'incitation jamais rendue | 2 | | Garde de l'incitation retirée | 1 | | Garde de la fenêtre de répartition retirée | 1 | | Garde de la fenêtre de répartition en `=== "open"` | 1 | | Table : `access="open"` en dur | 4 | | Page : `useEntitlement("budget")` | 1 | | Page : gate mal converti (`ready ? "locked" : "pending"`) | 1 | | `onNavigate` non câblé | 1 | | Bouton X non câblé | 1 | | Tier `base` en dur dans la fenêtre | 1 | | `splitLocked` absent d'`en.json` | 1 | ## Après le merge - `STATE.md` : la notification de la-compagnie-maximus#180 rejoint « Vérifications dues à la prochaine release », avec la vérification dans l'app (Gratuite : cadenas et écran de déverrouillage ; Base : fenêtre de répartition). Un commentaire sur la-compagnie-maximus#180 dira « mergé, publié avec la prochaine release ». - Suivi ouvert : #451 (fenêtre d'incitation de `ProfileSwitcher` sur `UpsellDialog`, fermeture sur Échap). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
maximus added 1 commit 2026-10-17 00:04:49 +00:00
feat(gating): reserve transaction splits for the Base edition
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m16s
cb7f8479e4
The Répartition (split) button of the Transactions page now belongs to
the `adjustments` feature, as the unlock screen already promised
(upsell.features.adjustments). On the Free edition the button keeps its
place and its icon, carries a lock and opens the locked screen in a
dialog instead of the split window, for every row, split or not. While
the license is not ready it shows no lock and does nothing. Existing
splits stay in the data, counted by the reports and flagged by the
orange icon.

- splitGate.ts: pure splitAccessFor(gate) -> open | locked | pending,
  stricter than profileGate: `allowed` does not short-circuit `ready`.
- SplitButton: stateless button that decides what a click opens.
- UpsellDialog: UpsellGate in a named modal dialog, closed by its X and
  by "I already have a key".
- TransactionTable: required splitAccess prop. The split window is not
  rendered once the edition resolves to Free, and stays mounted through
  a license refresh; the upsell is not rendered once it resolves to Base.
- TransactionsPage reads useEntitlement("adjustments").
- ADR 0017 amendment and matrix, user guide, in-app docs (FR, EN),
  architecture, entitlements header comment, both changelogs.

Tests: the predicate, the button's markup and click, the dialog, the
table's wiring through the shared hook harness (#409), the page under
the three gate states, and the new key in both locales.

Fixes #369

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Owner

CI lue dans le log : PR Check — Frontend, run 557, sur cb7f847 (pull/452/head), succès en 2 min 16.

  • ✓ built in 13.19s (tsc puis vite, l. 518)
  • Test Files 134 passed (134) et Tests 2248 passed (2248) (l. 703 et 704)
  • aucune ligne FAIL ni ×

check-rust n'a pas tourné, ce qui est attendu : rien sous src-tauri/ ni .cargo/.

CI lue dans le log : `PR Check — Frontend`, run 557, sur `cb7f847` (`pull/452/head`), succès en 2 min 16. - `✓ built in 13.19s` (tsc puis vite, l. 518) - `Test Files 134 passed (134)` et `Tests 2248 passed (2248)` (l. 703 et 704) - aucune ligne `FAIL` ni `×` `check-rust` n'a pas tourné, ce qui est attendu : rien sous `src-tauri/` ni `.cargo/`.
Author
Owner

Revue adversariale — PR #452 (cb7f847)

Verdict : APPROVE

Résumé. Le gate de la Répartition est complet et correct : le bouton était le seul point d'entrée non gaté de la fenêtre de répartition, splitAccessFor couvre les trois états de la licence, et les gardes de TransactionTable suivent les vraies transitions de LicenseContext. Les tests rougissent sur 14 des 15 mutations du relecteur ; la survivante est une épingle manquante sur un code correct.

Ce qui a été exécuté

  • Copie hors dépôt de cb7f847 (git archive), npm ci, puis npm run build vert et vitest run : 134 fichiers, 2248 tests verts, les chiffres du corps de la PR et du run 557 (statut agrégé success sur la tête).
  • 15 mutations du relecteur, distinctes de la table de l'auteur, une à la fois, chaque fichier restauré depuis une copie vierge (empreinte identique à la fin) :
Mutation Tests rouges
R1 clic en pending qui ouvre la fenêtre de répartition 1
R2 page sans anti-flash (allowed ? "open" : "locked") 1
R3 fenêtre ouverte sur la première ligne, pas sur la ligne cliquée 1
R4 incitation démontée pendant un rafraîchissement (=== "locked") 1
R5 bouton désactivé aussi en locked (dans un DOM, le clic n'atteindrait jamais l'incitation) 2
R6 bouton masqué en Gratuite au lieu d'être verrouillé 6
R7 cadenas seulement sur les lignes déjà réparties 4
R8 la Gratuite répartit encore une ligne simple 1
R9 fenêtre sans nom accessible 1
R10 page qui lit multi-profile (mêmes éditions, mauvaise clé) 1
R11 fermeture de l'incitation sans effet 1
R12 Gratuite résolue en pending, jamais d'incitation 2
R13 splitLocked FR avec l'édition en dur 1
R14 le clic verrouillé arme aussi la fenêtre de répartition 0, survivante
R15 bouton X sans aria-label 2

Vérifié à la lecture

  • Points d'entrée. La fenêtre de répartition n'est montée que par TransactionTable (gatée ici) et par AdjustmentsPage, déjà sous RequireFeature feature="adjustments" (App.tsx:87-88). Aucun autre appelant de saveSplitAdjustment ni de deleteSplitAdjustment ; le menu contextuel de la page n'offre que « Ajouter comme mot-clé », et TransactionDetailModal (exploration depuis le tableau de bord) est en lecture seule.
  • Transitions de la licence. LOAD_START passe en loading en gardant l'édition, LOAD_DONE passe en ready avec la nouvelle, VALIDATE_START ne touche pas status : aucun état ready + free transitoire pendant un rafraîchissement, donc la garde !== "locked" ne démonte la fenêtre de répartition qu'à une vraie résolution en Gratuite. Le seul passage Gratuite → Base sans démontage de la table est le contrôle de révocation du démarrage qui revient « valide » ; la garde symétrique de l'incitation le couvre.
  • « Toujours comptées ». Hors des tests, parent_transaction_id n'apparaît que dans transactionService.ts (la liste écarte les enfants, l. 231, et la lecture des répartitions) et dans l'export : le tableau de bord additionne toutes les lignes, enfants compris (dashboardService.ts:87-99). Le CHANGELOG, le guide et l'ADR disent vrai.
  • i18n. Arbres FR et EN identiques (clés et longueurs de tableaux), common.close présent dans les deux. La section 1 du guide et docs.gettingStarted.features[2] laissées telles quelles : cohérent, cette liste ne marque l'édition ni du Budget ni des profils multiples.
  • Docs. Comptes d'architecture.md justes : 13 composants dans shared/ sur main (le « 12 » était périmé), 14 avec UpsellDialog ; 5 → 6 dans transactions/. Amendement de l'ADR 0017 à sa place chronologique ; entrées du CHANGELOG sous Changed / Modifié, dans les deux fichiers.
  • Fermeture. Timeline de #369 : pull_ref de #452 en closes, et le commit porte Fixes #369. L'avis à la-compagnie-maximus#180 après la release passe par l'étape 0 du skill de release (PR #450) et par la puce STATE.md annoncée.
  • Commit au format conventionnel, aucun .only ni .skip, aucune migration, aucun secret.

Suggestions (non bloquantes)

  1. Épingle sur R14. Dans TransactionTable.wiring.test.ts:128-132, le passage en Base vérifie que l'incitation disparaît, pas qu'aucune fenêtre de répartition n'apparaît à sa place. Garder le rendu "open" dans une variable et y ajouter expect(splitWindows(upgraded)).toHaveLength(0) : vert sur le code actuel, rouge sous R14 (rejoué). Même forme à la ligne 125 pour le cas symétrique, upsells(...) vide sur le rendu "locked" (non rejoué).
  2. Une seule source pour la clé. "adjustments" est écrit trois fois pour la Répartition (TransactionsPage.tsx:47, SplitButton.tsx:43, TransactionTable.tsx:354). Une constante exportée de splitGate.ts (SPLIT_FEATURE: FeatureKey = "adjustments") ramènerait un futur déplacement (clé propre, Premium) à une ligne. Aujourd'hui, R10 et la mutation « fenêtre d'incitation pour budget » de l'auteur gardent les trois littéraux alignés.
  3. Focus, à verser dans #451 avec Échap. La fenêtre ne prend pas le focus : au clavier, Entrée sur le bouton l'ouvre, le focus reste sur le bouton derrière le voile, et Tab parcourt les lignes restantes avant d'atteindre la fenêtre, rendue après le tableau. Même défaut dans la fenêtre de ProfileSwitcher ; PinDialog et ImportConfirmModal posent un autoFocus.

Hors périmètre, préexistant

Reclasser une ligne déjà répartie laisse la ligne de compensation dans l'ancienne catégorie : updateTransactionCategory (transactionService.ts:301-315) ne met à jour que le parent, alors que la compensation a reçu parent.category_id à l'enregistrement (saveSplitAdjustment, l. 447-461). Rejoué en SQLite sur consolidated_schema.sql : parent −120 en A, répartition −50 en B, d'où A −70 et B −50 ; parent reclassé en C, d'où A +50, B −50 et C −120, compté tel quel par le tableau de bord. À la réouverture, SplitAdjustmentModal (l. 49-52) ne reconnaît la compensation que par « même catégorie que le parent, signe opposé » et l'affiche donc comme une entrée ordinaire (A, 50). En Base, retirer cette entrée puis enregistrer répare ; en Gratuite, cette PR ferme ce chemin. Rare et sans lien avec le diff : mérite une issue (propager la catégorie à la compensation, ou la reconnaître autrement que par sa catégorie).

Copie hors dépôt et script de mutations supprimés en fin de revue ; dépôt local inchangé.

## Revue adversariale — PR #452 (`cb7f847`) **Verdict : APPROVE** **Résumé.** Le gate de la Répartition est complet et correct : le bouton était le seul point d'entrée non gaté de la fenêtre de répartition, `splitAccessFor` couvre les trois états de la licence, et les gardes de `TransactionTable` suivent les vraies transitions de `LicenseContext`. Les tests rougissent sur 14 des 15 mutations du relecteur ; la survivante est une épingle manquante sur un code correct. ### Ce qui a été exécuté - Copie hors dépôt de `cb7f847` (`git archive`), `npm ci`, puis `npm run build` vert et `vitest run` : 134 fichiers, 2248 tests verts, les chiffres du corps de la PR et du run 557 (statut agrégé `success` sur la tête). - 15 mutations du relecteur, distinctes de la table de l'auteur, une à la fois, chaque fichier restauré depuis une copie vierge (empreinte identique à la fin) : | Mutation | Tests rouges | |---|---| | R1 clic en `pending` qui ouvre la fenêtre de répartition | 1 | | R2 page sans anti-flash (`allowed ? "open" : "locked"`) | 1 | | R3 fenêtre ouverte sur la première ligne, pas sur la ligne cliquée | 1 | | R4 incitation démontée pendant un rafraîchissement (`=== "locked"`) | 1 | | R5 bouton désactivé aussi en `locked` (dans un DOM, le clic n'atteindrait jamais l'incitation) | 2 | | R6 bouton masqué en Gratuite au lieu d'être verrouillé | 6 | | R7 cadenas seulement sur les lignes déjà réparties | 4 | | R8 la Gratuite répartit encore une ligne simple | 1 | | R9 fenêtre sans nom accessible | 1 | | R10 page qui lit `multi-profile` (mêmes éditions, mauvaise clé) | 1 | | R11 fermeture de l'incitation sans effet | 1 | | R12 Gratuite résolue en `pending`, jamais d'incitation | 2 | | R13 `splitLocked` FR avec l'édition en dur | 1 | | R14 le clic verrouillé arme aussi la fenêtre de répartition | **0, survivante** | | R15 bouton X sans `aria-label` | 2 | ### Vérifié à la lecture - **Points d'entrée.** La fenêtre de répartition n'est montée que par `TransactionTable` (gatée ici) et par `AdjustmentsPage`, déjà sous `RequireFeature feature="adjustments"` (`App.tsx:87-88`). Aucun autre appelant de `saveSplitAdjustment` ni de `deleteSplitAdjustment` ; le menu contextuel de la page n'offre que « Ajouter comme mot-clé », et `TransactionDetailModal` (exploration depuis le tableau de bord) est en lecture seule. - **Transitions de la licence.** `LOAD_START` passe en `loading` en gardant l'édition, `LOAD_DONE` passe en `ready` avec la nouvelle, `VALIDATE_START` ne touche pas `status` : aucun état `ready` + `free` transitoire pendant un rafraîchissement, donc la garde `!== "locked"` ne démonte la fenêtre de répartition qu'à une vraie résolution en Gratuite. Le seul passage Gratuite → Base sans démontage de la table est le contrôle de révocation du démarrage qui revient « valide » ; la garde symétrique de l'incitation le couvre. - **« Toujours comptées ».** Hors des tests, `parent_transaction_id` n'apparaît que dans `transactionService.ts` (la liste écarte les enfants, l. 231, et la lecture des répartitions) et dans l'export : le tableau de bord additionne toutes les lignes, enfants compris (`dashboardService.ts:87-99`). Le CHANGELOG, le guide et l'ADR disent vrai. - **i18n.** Arbres FR et EN identiques (clés et longueurs de tableaux), `common.close` présent dans les deux. La section 1 du guide et `docs.gettingStarted.features[2]` laissées telles quelles : cohérent, cette liste ne marque l'édition ni du Budget ni des profils multiples. - **Docs.** Comptes d'`architecture.md` justes : 13 composants dans `shared/` sur `main` (le « 12 » était périmé), 14 avec `UpsellDialog` ; 5 → 6 dans `transactions/`. Amendement de l'ADR 0017 à sa place chronologique ; entrées du CHANGELOG sous Changed / Modifié, dans les deux fichiers. - **Fermeture.** Timeline de #369 : `pull_ref` de #452 en `closes`, et le commit porte `Fixes #369`. L'avis à la-compagnie-maximus#180 après la release passe par l'étape 0 du skill de release (PR #450) et par la puce STATE.md annoncée. - Commit au format conventionnel, aucun `.only` ni `.skip`, aucune migration, aucun secret. ### Suggestions (non bloquantes) 1. **Épingle sur R14.** Dans `TransactionTable.wiring.test.ts:128-132`, le passage en Base vérifie que l'incitation disparaît, pas qu'aucune fenêtre de répartition n'apparaît à sa place. Garder le rendu `"open"` dans une variable et y ajouter `expect(splitWindows(upgraded)).toHaveLength(0)` : vert sur le code actuel, rouge sous R14 (rejoué). Même forme à la ligne 125 pour le cas symétrique, `upsells(...)` vide sur le rendu `"locked"` (non rejoué). 2. **Une seule source pour la clé.** `"adjustments"` est écrit trois fois pour la Répartition (`TransactionsPage.tsx:47`, `SplitButton.tsx:43`, `TransactionTable.tsx:354`). Une constante exportée de `splitGate.ts` (`SPLIT_FEATURE: FeatureKey = "adjustments"`) ramènerait un futur déplacement (clé propre, Premium) à une ligne. Aujourd'hui, R10 et la mutation « fenêtre d'incitation pour `budget` » de l'auteur gardent les trois littéraux alignés. 3. **Focus, à verser dans #451 avec Échap.** La fenêtre ne prend pas le focus : au clavier, Entrée sur le bouton l'ouvre, le focus reste sur le bouton derrière le voile, et Tab parcourt les lignes restantes avant d'atteindre la fenêtre, rendue après le tableau. Même défaut dans la fenêtre de `ProfileSwitcher` ; `PinDialog` et `ImportConfirmModal` posent un `autoFocus`. ### Hors périmètre, préexistant Reclasser une ligne déjà répartie laisse la ligne de compensation dans l'ancienne catégorie : `updateTransactionCategory` (`transactionService.ts:301-315`) ne met à jour que le parent, alors que la compensation a reçu `parent.category_id` à l'enregistrement (`saveSplitAdjustment`, l. 447-461). Rejoué en SQLite sur `consolidated_schema.sql` : parent −120 en A, répartition −50 en B, d'où A −70 et B −50 ; parent reclassé en C, d'où A +50, B −50 et C −120, compté tel quel par le tableau de bord. À la réouverture, `SplitAdjustmentModal` (l. 49-52) ne reconnaît la compensation que par « même catégorie que le parent, signe opposé » et l'affiche donc comme une entrée ordinaire (A, 50). En Base, retirer cette entrée puis enregistrer répare ; en Gratuite, cette PR ferme ce chemin. Rare et sans lien avec le diff : mérite une issue (propager la catégorie à la compensation, ou la reconnaître autrement que par sa catégorie). Copie hors dépôt et script de mutations supprimés en fin de revue ; dépôt local inchangé.
Author
Owner

Suites de la revue (commentaire 24129) appliquées en tête, à la demande de Max, en deux commits non relus : la nouvelle tête est a8c3fd6, l'APPROVE portait sur cb7f847.

  • d5d0e89 test(gating): pin that one click never arms the other window (suggestion 1) : les deux changements d'édition vérifient maintenant que l'autre fenêtre reste fermée, pendant le rafraîchissement de licence comme après.
  • a8c3fd6 refactor(gating): one constant for the split's feature key (suggestion 2) : SPLIT_FEATURE dans splitGate.ts, lu par la page, le bouton et la table. Aucun changement de comportement.

Preuve du rouge, sur l'arbre corrigé, chaque fichier restauré depuis une copie et l'arbre vérifié identique après coup :

Mutation Avant Après
R14 de la revue : le clic verrouillé arme aussi la fenêtre de répartition 0 test rouge (survivante) 1 test rouge
Symétrique : le clic ouvert arme aussi l'incitation non testée 1 test rouge
SPLIT_FEATURE passé à budget sans objet 2 tests rouges

Les tests concernés passent avant et après les mutations (42 sur 42). Tip a8c3fd6 validé en worktree propre : npm ci, sonde rouge (rc=1), npm run build, npm test à 2248 tests sur 134 fichiers.

Le point 3 (focus de la fenêtre) est versé dans #451 (commentaire 24139) ; le défaut préexistant de la compensation devient #454.

Classement selon /merge-stack : un test et un refactor sans changement de règle, donc merge sans nouvelle relecture, en nommant ici les deux commits non relus.

Suites de la revue (commentaire 24129) appliquées en tête, à la demande de Max, en deux commits **non relus** : la nouvelle tête est `a8c3fd6`, l'APPROVE portait sur `cb7f847`. - `d5d0e89` `test(gating): pin that one click never arms the other window` (suggestion 1) : les deux changements d'édition vérifient maintenant que l'autre fenêtre reste fermée, pendant le rafraîchissement de licence comme après. - `a8c3fd6` `refactor(gating): one constant for the split's feature key` (suggestion 2) : `SPLIT_FEATURE` dans `splitGate.ts`, lu par la page, le bouton et la table. Aucun changement de comportement. Preuve du rouge, sur l'arbre corrigé, chaque fichier restauré depuis une copie et l'arbre vérifié identique après coup : | Mutation | Avant | Après | |---|---|---| | R14 de la revue : le clic verrouillé arme aussi la fenêtre de répartition | 0 test rouge (survivante) | 1 test rouge | | Symétrique : le clic ouvert arme aussi l'incitation | non testée | 1 test rouge | | `SPLIT_FEATURE` passé à `budget` | sans objet | 2 tests rouges | Les tests concernés passent avant et après les mutations (42 sur 42). Tip `a8c3fd6` validé en worktree propre : `npm ci`, sonde rouge (rc=1), `npm run build`, `npm test` à 2248 tests sur 134 fichiers. Le point 3 (focus de la fenêtre) est versé dans #451 (commentaire 24139) ; le défaut préexistant de la compensation devient #454. Classement selon `/merge-stack` : un test et un refactor sans changement de règle, donc merge sans nouvelle relecture, en nommant ici les deux commits non relus.
maximus added 2 commits 2026-10-17 11:37:47 +00:00
Review of PR #452 (comment 24129): the upgrade to Base checked that the
upsell goes away, not that no split window appears in its place, so a
locked click that also armed the split window (R14) left the suite
green. Both edition changes now assert the other window stays closed,
during the license refresh and after it.

R14 now turns 1 test red, and its mirror (a split click that also arms
the upsell) 1 test red.

Refs #369

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
refactor(gating): one constant for the split's feature key
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m56s
a8c3fd6997
Review of PR #452 (comment 24129): "adjustments" was written three
times for the split (the page's gate, the button's edition, the
dialog's locked screen). SPLIT_FEATURE in splitGate.ts is now the one
source, so moving the split to its own key or to Premium is a one-line
change. No behaviour change; setting it to "budget" turns 2 tests red.

Refs #369

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
maximus merged commit a8c3fd6997 into main 2026-10-17 11:40:14 +00:00
maximus deleted branch issue-369-split-base-edition 2026-10-17 11:40:14 +00:00
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#452
No description provided.