feat(budget) : adopter le repli multi-niveaux sur la grille Budget #293

Closed
maximus wants to merge 1 commit from issue-289-budget-collapse into issue-288-collapse-socle
Owner

Resolves #289

Stacks on #292 (issue #288's collapse socle) — base is issue-288-collapse-socle, NOT main. Review/merge #292 first.

Summary

  • Budget grid now collapses/expands at every category level (reversing #278), opens fully collapsed, and gets a one-click "Expand all" — reusing the #288 useCollapsibleGroups socle with accessors declared inline (no budgetTableModel.ts).
  • Section subtotals switched to the tested sumLeavesForType on the raw group, so folding is purely visual; STORAGE_KEY renamed to budget-subtotals-position to decouple from the real-vs-budget report.
  • Extracted the pure buildBudgetYearRows from useBudget and pinned its level-order (BFS) emission with a regression test — the invariant the abandoned v1 collapse plan got wrong.

Build clean (0 TS errors), 828 vitest pass, cargo check clean.

Generated autonomously by /autopilot run of 2026-07-15

Resolves #289 **Stacks on #292** (issue #288's collapse socle) — base is `issue-288-collapse-socle`, NOT `main`. Review/merge #292 first. ## Summary - Budget grid now collapses/expands at **every** category level (reversing #278), opens **fully collapsed**, and gets a one-click "Expand all" — reusing the #288 `useCollapsibleGroups` socle with accessors declared inline (no `budgetTableModel.ts`). - Section subtotals switched to the tested `sumLeavesForType` on the **raw** group, so folding is purely visual; `STORAGE_KEY` renamed to `budget-subtotals-position` to decouple from the real-vs-budget report. - Extracted the pure `buildBudgetYearRows` from `useBudget` and pinned its **level-order (BFS)** emission with a regression test — the invariant the abandoned v1 collapse plan got wrong. Build clean (0 TS errors), 828 vitest pass, `cargo check` clean. Generated autonomously by /autopilot run of 2026-07-15
maximus added 1 commit 2026-07-16 01:16:35 +00:00
Reverse #278's deliberate no-collapse decision for the budget grid: it now
folds/unfolds at every category level like the hierarchical reports, opening
fully collapsed with a one-click "Expand all".

- BudgetTable: wire useCollapsibleGroups (defaultExpanded: false), inline
  BUDGET_COLLAPSE_ACCESSORS + BUDGET_EXPANDED_KEY (mirrors ComparePeriodTable /
  BudgetVsActualTable — no budgetTableModel.ts). Chevron + aria-expanded +
  aria-level on every parent row; groups.visible(group) before reorderRows.
- BudgetTable: section subtotal now uses the tested sumLeavesForType on the RAW
  group (drop-in for the hand-rolled loop) so folding stays purely visual.
- BudgetTable: rename STORAGE_KEY to "budget-subtotals-position", decoupling the
  subtotals-position preference from BudgetVsActualTable (they collided).
- useBudget: extract the pure buildBudgetYearRows(); the grid's rows are
  level-order (BFS), not DFS — document the invariant and pin it in a test, since
  the #288 ancestor-walk collapse is order-independent (the v1 plan assumed DFS
  and would have broken here).
- Tests: useBudget.test.ts locks the level-order emission, the DFS-killer, the
  end-to-end multi-level collapse on real builder output, and that subtotals sum
  raw rows regardless of collapse. 828 vitest pass.

Resolves #289

Generated autonomously by /autopilot run of 2026-07-15
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
maximus added the
autopilot:pending-human
label 2026-07-16 01:16:52 +00:00
Author
Owner

Review PR #293feat(budget) : repli multi-niveaux sur la grille Budget

Verdict : APPROVE

Résumé

Implémentation fidèle de l'issue #289. La grille Budget adopte le repli multi-niveaux via le socle #288 (useCollapsibleGroups + remontée d'ancêtres), avec extraction pure de buildBudgetYearRows hors de useBudget et un test qui verrouille l'invariant d'ordre par niveau (BFS). Aucun point bloquant.

Revue statique adversariale (git show sans checkout ; tip 9c325e2 vs base 48adb3d).

Correction

  • Extraction buildBudgetYearRows behavior-preserving — le corps déplacé est identique à l'ancien bloc inline de useBudget ; le site d'appel buildBudgetYearRows(allCategories, entries, prevYearActuals) reçoit exactement les mêmes variables, et TYPE_ORDER (module scope) reste en portée.
  • Sous-total de section purement visuelrenderTypeSection calcule sumLeavesForType(group, type) sur le groupe brut ; seul le rendu des lignes passe par groups.visible(group). sumLeavesForType est strictement équivalent à l'ancien calcul fait-main : signe appliqué à months/annual, pas à previousYearTotal (déjà signé en base). Idem results = computeBudgetResults(rows) sur rows brut. Replier ne déplace jamais un total.
  • Visibilité par remontée d'ancêtres — la feuille « (direct) » (même category_id que son parent) ne clobbe pas le sous-total dans la map (visibleRows n'indexe que les isParent) et est masquée avec son parent via parentKeyOf. Le toggle n'est câblé que sur les lignes parentes ; groupCount/collapsibleKeys ne comptent que les parents → pas de double comptage ni de faux chevron.

Tests

  • Les 3 blocs de useBudget.test.ts verrouillent exactement les invariants exigés par #289 : ordre BFS ([10,1,3,2,4,5]), cascade de repli ([10,1] tout replié → [10,1,3,2] après dépliage d'une racine, petits-enfants toujours masqués sous l'intermédiaire replié), et sous-totaux sur lignes brutes (-1150 brut vs 0 visible). Tracés à la main sur la fixture : corrects.
  • Le pattern d'import buildBudgetYearRows depuis ./useBudget suit le précédent établi (useCompare.test.ts + 9 autres tests de hooks service-backed dans la même suite node) → le chargement du module ne casse pas en env node.
  • Types du test conformes (Category/BudgetEntry/BudgetYearRow, champs requis tous fournis) → pas d'erreur tsc attendue.

Qualité / conformité au plan #289

  • Accesseurs déclarés inline au module scope (BUDGET_COLLAPSE_ACCESSORS + BUDGET_EXPANDED_KEY), pas de budgetTableModel.ts — comme mandaté.
  • Collision localStorage corrigée : budget-subtotals-positionsubtotals-position (encore utilisé par BudgetVsActualTable).
  • Aucune string en dur : le bouton réutilise reports.collapse.expandAll/.collapseAll (présents FR+EN) ; le diff ne touche aucun fichier de locale. Persistance via user_preferences (requêtes paramétrées, clés constantes) — aucune migration DB, pas d'injection.

Suggestions (non bloquantes)

  1. Grille repliée par défaut sur une surface de saisie — la page ouvre sur les seules racines (lignes parentes non éditables). C'est le compromis explicitement tranché avec toi dans #289 (« page compacte, état persisté, Tout déplier à un clic »), donc conforme — juste le point UX à garder en tête.
  2. Reset one-shot de la préférence subtotals-position — le renommage de la clé remet la position à son défaut une fois par utilisateur. Effet de bord acté dans l'issue.
  3. Régression « déplacer les sous-totaux n'affecte plus le rapport réel-vs-budget » — garantie structurellement par la divergence de clé, mais sans test dédié (difficile à couvrir en env node sans localStorage). Mineur.
  4. Merge de pile#293 cible issue-288-collapse-socle, pas maincheck.yml n'a pas tourné dessus (gap CI des piles). Valider le tip cumulé en local (tsc && vite build, vitest run, cargo check) avant de merger #292 puis #293.
## Review PR #293 — `feat(budget)` : repli multi-niveaux sur la grille Budget **Verdict : APPROVE** ✅ ### Résumé Implémentation fidèle de l'issue #289. La grille Budget adopte le repli multi-niveaux via le socle #288 (`useCollapsibleGroups` + remontée d'ancêtres), avec extraction pure de `buildBudgetYearRows` hors de `useBudget` et un test qui verrouille l'invariant d'ordre par niveau (BFS). Aucun point bloquant. Revue statique adversariale (`git show` sans checkout ; tip `9c325e2` vs base `48adb3d`). ### Correction - **Extraction `buildBudgetYearRows` behavior-preserving** — le corps déplacé est identique à l'ancien bloc inline de `useBudget` ; le site d'appel `buildBudgetYearRows(allCategories, entries, prevYearActuals)` reçoit exactement les mêmes variables, et `TYPE_ORDER` (module scope) reste en portée. - **Sous-total de section purement visuel** — `renderTypeSection` calcule `sumLeavesForType(group, type)` sur le groupe **brut** ; seul le rendu des lignes passe par `groups.visible(group)`. `sumLeavesForType` est strictement équivalent à l'ancien calcul fait-main : signe appliqué à `months`/`annual`, **pas** à `previousYearTotal` (déjà signé en base). Idem `results = computeBudgetResults(rows)` sur `rows` brut. Replier ne déplace jamais un total. - **Visibilité par remontée d'ancêtres** — la feuille « (direct) » (même `category_id` que son parent) ne clobbe pas le sous-total dans la map (`visibleRows` n'indexe que les `isParent`) et est masquée avec son parent via `parentKeyOf`. Le toggle n'est câblé que sur les lignes parentes ; `groupCount`/`collapsibleKeys` ne comptent que les parents → pas de double comptage ni de faux chevron. ### Tests - Les 3 blocs de `useBudget.test.ts` verrouillent exactement les invariants exigés par #289 : ordre BFS (`[10,1,3,2,4,5]`), cascade de repli (`[10,1]` tout replié → `[10,1,3,2]` après dépliage d'une racine, petits-enfants toujours masqués sous l'intermédiaire replié), et sous-totaux sur lignes brutes (`-1150` brut vs `0` visible). Tracés à la main sur la fixture : corrects. - Le pattern d'import `buildBudgetYearRows` depuis `./useBudget` suit le précédent établi (`useCompare.test.ts` + 9 autres tests de hooks service-backed dans la même suite node) → le chargement du module ne casse pas en env node. - Types du test conformes (`Category`/`BudgetEntry`/`BudgetYearRow`, champs requis tous fournis) → pas d'erreur tsc attendue. ### Qualité / conformité au plan #289 - Accesseurs déclarés **inline au module scope** (`BUDGET_COLLAPSE_ACCESSORS` + `BUDGET_EXPANDED_KEY`), pas de `budgetTableModel.ts` — comme mandaté. - Collision localStorage corrigée : `budget-subtotals-position` ≠ `subtotals-position` (encore utilisé par `BudgetVsActualTable`). - Aucune string en dur : le bouton réutilise `reports.collapse.expandAll`/`.collapseAll` (présents FR+EN) ; le diff ne touche aucun fichier de locale. Persistance via `user_preferences` (requêtes paramétrées, clés constantes) — aucune migration DB, pas d'injection. ### Suggestions (non bloquantes) 1. **Grille repliée par défaut sur une surface de saisie** — la page ouvre sur les seules racines (lignes parentes non éditables). C'est le compromis explicitement tranché avec toi dans #289 (« page compacte, état persisté, Tout déplier à un clic »), donc conforme — juste le point UX à garder en tête. 2. **Reset one-shot de la préférence subtotals-position** — le renommage de la clé remet la position à son défaut une fois par utilisateur. Effet de bord acté dans l'issue. 3. **Régression « déplacer les sous-totaux n'affecte plus le rapport réel-vs-budget »** — garantie structurellement par la divergence de clé, mais sans test dédié (difficile à couvrir en env node sans localStorage). Mineur. 4. **Merge de pile** — #293 cible `issue-288-collapse-socle`, pas `main` → `check.yml` n'a pas tourné dessus (gap CI des piles). Valider le tip cumulé en local (`tsc && vite build`, `vitest run`, `cargo check`) avant de merger #292 puis #293.
Author
Owner

Mergé en fast-forward dans main (pile #292→#295), commit 9c325e2. Forgejo ne détecte pas le merge local → fermeture manuelle. L'issue liée s'est auto-fermée via Resolves #N.

Mergé en fast-forward dans `main` (pile #292→#295), commit `9c325e2`. Forgejo ne détecte pas le merge local → fermeture manuelle. L'issue liée s'est auto-fermée via `Resolves #N`.
maximus closed this pull request 2026-07-18 18:47:38 +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#293
No description provided.