feat(onboarding): Budget page step card and its highlight #406
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#406
Loading…
Reference in a new issue
No description provided.
Delete branch "issue-400-onboarding-budget-card"
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?
Resolves #400
Generated autonomously by /autopilot run of 2026-10-08
Ce qui change pour l'utilisateur
RequireFeature) et la ligne Budget est verrouillée, donc jamais de carte Budget, et le parcours se termine à 5/5. Comportement déjà assuré par l'état partagé, vérifié et épinglé par un test.Technique
budgetOnboardingTarget(visibleRows, hasGroups, monthIdx), module pursrc/components/budget/budgetOnboardingTarget.ts(à côté debudgetTableResults.ts) :{ kind: "cell", categoryId, monthIdx }pour la première feuille visible, sinon{ kind: "expandAll" }s'il existe des groupes, sinonnull.BudgetTable:reorderRows(groups.visible(section), subtotalsOnTop)), que le rendu des sections réutilise, et les passe à la fonction dans l'ordre des sections, avecnew Date().getMonth();onboardingTarget?: booleanetonOnboardingTargetChange?: (kind) => void;data-onboarding-target(budget-celloubudget-expand-all, constantes exportées) etdata-onboarding-highlightsur le bouton de la cellule ou sur « Tout déplier », jamais les deux ;useEffect(dépendances[kind, rappel]), placé avant le retour « aucune catégorie » ;useCollapsibleGroups(clébudget-grid-expanded), inchangé.budgetCardModel(input, checklist)(onboardingPath.ts) : textecollapsed(aveclabels: { expand: "reports.collapse.expandAll" }) pourexpandAll, texteflatpourcell, ni phrase ni cible pournull, et{ text: null, done: true, target: null }une fois l'étape cochée.BudgetCardInputetBudgetOnboardingTargetKinddansshared/types.CARD_PAGESreçoit/budget.BudgetPage: unuseStategarde la cible remontée, et son setter (stable) est le rappel passé à la grille.OnboardingStepCardest le premier enfant, avant l'en-tête, hors du voile de chargement. La grille ne reçoitonboardingTargetque si la carte est affichée (showsStepCard) et que le modèle a une cible.onboarding.card.budget.collapsedet.flat, textes exacts de l'issue. CHANGELOG FR et EN (Ajouté).docs/architecture.mdinchangé : aucune ligne n'est rendue fausse. Aucun changement Rust, aucune migration.Preuves
npm run buildvert (tsc + vite).npx vitest run: 1844 → 1885 tests verts (103 → 108 fichiers).budgetOnboardingTarget.test.ts(nouveau, +8) : groupes repliés, première feuille visible (sous-totaux au-dessus ou en dessous, ligne « (direct) »), feuille visible à côté de groupes repliés, sans groupes, aucune ligne, mois de janvier à décembre.BudgetTable.render.test.tsx(nouveau, +9) : rendu statique, hook de repli remplacé par les vrais helpers purs sur un état posé par le test. Un seul élément mis en évidence, « Tout déplier » ou la cellule, dans la colonne du mois de l'horloge (figée parvi.setSystemTime) ; revenus d'abord, sous-totaux en bas ; rien sansonboardingTarget, rien sans ligne.BudgetTable.wiring.test.ts(nouveau, +3) : hooks React remplacés, effets exécutés selon leurs dépendances comme React. Le genre remonte une fois au montage puis seulement à son changement, jamais pendant le rendu, et même sans mise en évidence.BudgetPage.render.test.tsx(nouveau, +6) : carte avant le titre, « Étape 6 sur 6 », hors du voile de chargement, « Fait. » puis fin, profil sans transaction, pas de carte hors du mode carte.BudgetPage.wiring.test.ts(nouveau, +5) : la cible remontée choisit la phrase et allume l'anneau ; jamais d'anneau sans carte ni après « Fait. » ; rappel identique d'un rendu à l'autre.onboardingPath.test.ts(+9) :CARD_PAGESavec/budget;budgetCardModel(deux textes, cible nulle, « Fait. », étape passée) ; les deux phrases résolues par la vraie instance i18n, en FR et en EN, contre le texte exact de l'issue.AppShell.render.test.tsx(+1 net) : la table « bandeau tant qu'aucune carte n'est livrée » disparaît avec sa dernière ligne ; /budget gagne son test de carte, comme /import et /transactions ; édition Gratuite avec le vraiCARD_PAGES: pas de carte, fin à 5/5.git checkout; 8 fichiers de test concernés, 128 tests verts avant chaque mutation et après la dernière restauration :budgetOnboardingTargetrend la première ligne visible, sous-total comprisgetMonth() + 1)collapsedetflatintervertisCARD_PAGESsans/budget8/8 rouges, arbre propre après chaque restauration.
Décisions
14 décisions consignées dans le journal du run ; les principales :
budgetCardModelprend aussi la liste (checklist), commetransactionsCardModeldans #405 : sans elle, pas dedone, donc ni « Fait. » ni mode fin (critère d'acceptation). La signature de l'issue ne portait que{ target }.new Date().getMonth(), quelle que soit l'année affichée : le compteur compte les entrées de toute année, donc toute cellule coche l'étape, et la colonne ne saute pas au changement d'année. Écartés : janvier pour une autre année ; aucune cible hors de l'année courante.flatsur une page sans grille ; un modèle nul, qui laisserait la page verrouillée sans bandeau ni carte.opacity-50 pointer-events-none: la grille se recharge après chaque enregistrement, au moment où la carte annonce « Fait. ». Le reste de la page garde son voile.budgetTableResults.tsplutôt qu'exportée parBudgetTable.tsx, pour un test qui ne charge ni hook, ni préférences, ni base.BudgetTableen plus du test pur, sans stockage ni hydratation : sans eux, quatre des mutations (M3, M4, M5a, M5b) restaient vertes.Pour relire
src/components/budget/BudgetTable.tsx: le bloc placé avant le retour « aucune catégorie » (lignes affichées, cible, effet) ;renderTypeSectionlit maintenantshownByType.src/pages/BudgetPage.tsx: le diff est surtout une ré-indentation sous la nouvelle racine ;git diff -wle réduit à la carte, à l'état et aux deux props.🤖 Generated with Claude Code
Auto-revue (autopilot) : APPROVE
Revue du diff de cette PR seule (16 fichiers, tête
d76c387) contre le corps de #400.Critères d'acceptation
budgetOnboardingTargettestée : groupes repliés, première feuille visible, sans groupes, aucune ligne. En plus : sous-totaux en bas, ligne « (direct) », catégorie sans sous-catégorie à côté de groupes repliés, mois 0 à 11.budgetCardModel: les deux textes, et les deux phrases résolues par la vraie instance i18n, en FR et en EN, contre le texte exact de l'issue.upsertBudgetEntrynotifie déjà) ; la carte reste, annonce « Fait. » et passe au mode fin (rendu de la page).CARD_PAGES.npm run buildvert, vitest 1844 → 1885, CI frontend run 504 verte (log lu : têted76c387, build, 108 fichiers et 1885 tests).Caveats de l'issue
useCollapsibleGroups(budget-grid-expanded), inchangé.BudgetTables'y ajoutent, sans stockage ni hydratation (hook de repli remplacé par les vrais helpers purs). Sans eux, M3, M4, M5a et M5b restaient vertes.Points relevés, non bloquants
BudgetPagegagne une racine, pour que la carte reste hors du voile de chargement ; le diff de la page est surtout de la ré-indentation (git diff -w).budgetCardModelprendchecklisten plus de{ target }, commetransactionsCardModeldans #405 : écart à la signature de l'issue, consigné au journal.Label laissé à
status:review: seule une revue indépendante (/pr-review) posestatus:approved.Revue
/pr-review: APPROVERésumé : la carte Budget, la cible calculée par la grille et
budgetCardModeltiennent leur contrat sur le parcours nominal, et ma passe de mutations le confirme (16/16 rouges sous le fuseau local). Un état figé touche le cas « profil sans transaction », que le corps de la PR annonce couvert. Non bloquant (phrase trompeuse, cas rare), mais le correctif tient en une ligne : à appliquer en tête de pile avant/merge-stack.Exécuté (copie hors dépôt, tête
d76c387)tsc --noEmitvert ;vitest run: 108 fichiers, 1885 tests verts. CI run 504 lue dans les logs : checkout ded76c387, build, 108 fichiers et 1885 tests.pull_refde #406 encloses, et le commitd76c387porteResolves #400: l'issue se ferme aussi par un merge ff local.Mutations du relecteur
Suite complète à chaque mutation, restauration par
git checkout, arbre propre vérifié après chacune.[](montage seulement)getUTCMonth)America/Toronto, 0 sousTZ=UTCSuggestions (non bloquantes)
1. Cible figée quand la grille quitte la page (profil sans transaction) :
src/pages/BudgetPage.tsx:35et:47.gridTargetne revient jamais ànullquandBudgetTableest démonté. Or, en production,useBudgetreçoit les lignes avant le nombre de transactions : ses deux effets de montage (src/hooks/useBudget.ts:426et:433) passent par le verrou FIFO dedb.ts, etrefreshDataentre dans la file avantgetTransactionCount.SET_DATApart donc pendant quetotalInDbvaut encorenull. La grille se monte avec les catégories du seed (racines non saisissables, donc repliées) et remonteexpandAll. EnsuitetotalInDb = 0la remplace par l'état vide. La carte garde alors « Cliquez Tout déplier pour afficher vos catégories, puis cliquez sur un mois… » au-dessus de « Importez d'abord… », sans aucune grille sur la page.shared/types/index.ts:1037etonboardingPath.ts:391(« no grid on the page »). Le testBudgetPage.render.test.tsx:173ne peut pas voir ce défaut : un rendu statique n'exécute aucun effet, donc la grille n'y remonte jamais rien.BudgetPage.wiring.test.ts, avec un test jetable hors dépôt. Profil aux étapes 3 à 5 passées,totalInDb: nullavec des lignes, la grille remonteexpandAll. PuistotalInDb: 0: la grille disparaît, etmodel.textvaut toujoursonboarding.card.budget.collapsed. La séquence des requêtes est déduite du code (verrou FIFO et ordre des effets). La conséquence est reproduite dans le harnais, pas dans l'app.refreshDataest jeté (fetchIdRef) et le second passe après le compte, donc la grille est démontée avant d'avoir reçu ses lignes. Seul un build de production le montre, et la liste QA de #401 ne couvre pas ce cas.budgetCardModel({ target: isEmptyProfile ? null : gridTarget }, onboarding.checklist), le test jetable passe et les 7 fichiers desrc/pages/restent verts. Autre voie : un nettoyage d'effet dansBudgetTablequi remontenullau démontage. Dans les deux cas, un test de câblage doit rejouer cette séquence.2. Épingle du mois aveugle sous
TZ=UTC:src/components/budget/BudgetTable.render.test.tsx:175-178.« the cell follows the clock's month » règle l'horloge à 23 h le 31 décembre. Cette heure ne bascule sur un autre mois en UTC qu'à l'ouest de Greenwich : R12 rougit sous
America/Torontoet reste verte sousTZ=UTC, le fuseau du runner. Le dépôt a déjà le motif dansuseImportWizard.test.ts:153(#356) :process.env.TZ = "America/Toronto"enbeforeAll, restauré enafterAll. Vérifié dans la copie : avec cette épingle, R12 rougit sousTZ=UTC(1 rouge), et sans la mutation la suite reste verte.3. Cellule hors champ à la taille de fenêtre par défaut : l'auteur l'a déjà relevé, voici le chiffrage. La fenêtre fait 1200 px (
tauri.conf.json), la barre latéralew-60240 px, etmaina unp-6. Il reste environ 912 px visibles pour une grille d'au moins 1160 px (140 + 90 + 90 + 12 × 70), et la colonne catégorie s'élargit avec les noms du seed. D'août à décembre, l'anneau de la cellule tombe donc hors champ à droite, et rien ne défile. La phrase reste juste (« cliquez sur un mois »), d'où « non bloquant ». À trancher pendant la QA de #401 : en octobre, le cas se présentera d'office.Vérifié sans remarque
useCollapsibleGroupset la clébudget-grid-expandedsont inchangés.buildBudgetYearRowsn'émet jamais de parent sans feuille, donc « Tout replier » ne peut pas recevoir l'anneau.setGridTargetest stable, et l'effet ne repart qu'au changement de genre./budgetn'est jamais verrouillée en mode carte. Le test AppShell le prouve avec le vraiCARD_PAGES..only/.skip, aucun secret ni donnée personnelle dans les lignes ajoutées (dépôt public). Commits conventionnels, ni Rust ni migration.BudgetPage.tsxnisrc/components/budget/: la suggestion 1 reste vraie sur le tip cumulé.Actions faites
Commentaire posté, et #400 passée de
status:reviewàstatus:approved. La copie/tmp/pr406-reviewet les fichiers/tmp/pr406-*sont supprimés en fin de revue.