37 KiB
STATE — Simpl'Résultat
Derniere MAJ : 2026-08-14 (Chantier import CSV livré — milestone
planned-2026-08-12-import-csv-format10/10, pile de 10 PRs #333-#342 mergée ff-only (main37b832e). Le symptôme rapporté par Max — « l'app oublie le format, y compris les colonnes montant positif/négatif » — n'était pas une faiblesse d'heuristique mais un trou de persistance :import_sourcesne portait niamount_modenisign_convention(ils n'existaient que surimport_config_templates), doncuseImportWizard.ts:323réécrivaitsignConvention: "negative_expense"en dur à chaque restauration et une source réglée en montants positifs inversait tous ses montants au 2e import, sans erreur. Migration v17 (4 colonnes +CHECK, backfillLIKE '%debitAmount%'reproduisant la règle runtime → aucune source ne change de comportement) ; codec uniqueformatToRow/formatFromRowà test de complétude ;credit − debitsur magnitudes ;parseFrenchAmountancré ; détection par libellé d'en-tête ; score de confiance (seuil 90 %) ; aperçu obligatoire à récap signé ; signatures de banques + dérive de format ; sources et modèles enfin sauvegardés dans l'export SREF. 871 → 1181 vitest, 106 → 111 Rust. 20 tables / 24 index inchangés (v17 est un ALTER pur). ADR 0019. Candidate v0.15.0. Rappels infra :mainest protégée (whitelist pushmaximus) + shadowing d'identité dans~/.git-credentials→ remote ancréhttps://maximus@…)
Position actuelle
v0.9.1 shippée (2026-05-10). Milestones spec-refonte-rapports, spec-refonte-seed-categories-ipc, spec-price-fetching, overnight-2026-04-26-bilan et overnight-2026-04-27-prices complétées et fermées. Maximus-api 0.3.0 LIVE en prod (api.lacompagniemaximus.com) — pubkey Ed25519 alignée, smoke test Phase B (#161) validé. Backlog actif concentré sur spec-monetisation (7/12) : activation en ligne (#53), pipeline Stripe desktop (#50, #52) et maximus-api Stripe webhooks (#135, #136).
Audit critique de la page Bilan livré (docs/audit-bilan-2026-05.md, revue CPA + UX). Étape 0 — quick wins terminologie « catégorie »→« type », symbole optionnel, date de snapshot déplaçable — mergée (#201, issues #198/#199/#200 fermées). Chantier structurel = Étapes 1-2 de l'audit (séparation véhicule fiscal × classe d'actif via vehicle_type, puis détail par titre balance_securities+holdings+book_cost, bascule agrégé→détaillé, réouverture ADR 0012). Étape 1 livrée (sprint 2026-06-01 : PRs #206-#209 mergées, milestone overnight-2026-06-01-bilan-axe-vehicule fermée 4/4) — vehicle_type nullable = enveloppe fiscale portée par le compte, catégorie = pure classe d'actif (5 classes), migrations additives v12/v13 (v1-v11 intactes), renommage via custom_label (fix bug I), axe graphique classe/enveloppe + rendements repliables persistés ; ADR 0014 Accepted, ADR 0012 Rejected. CHANGELOG sous [Unreleased] (pas encore taggé). Étape 2 livrée (merge manuel 2026-06-09 : pile de 9 PRs stackées #219-#227 mergées bottom-up, milestone overnight-2026-06-05-bilan-detail-titres à 9/10, issues #210-#218 fermées) — détail par titre via balance_securities + balance_snapshot_holdings, balance_accounts.kind ('simple'|'detailed') + detailed_since, migrations additives v14/v15/v16 (v1-v13 intactes), conversion des comptes cotés existants en détaillés 1-position (v16), service securities transactionnel, reducer holdings + dispatch account.kind, UI multi-titres (SecurityPicker), assistant détailler-un-compte (date pivot), drill-down par titre + gain latent, tests intégration/régression ; ADR 0015 Accepted. #228 mergé (PR #229, fix-forward : garde d'abort v16 scopée aux comptes convertibles via JOIN balance_categories + c.asset_type IS NOT NULL dans les 3 copies — Migration v16 / V16_SQL / V16_CORRUPT — + test régression ; CI vert car #229 ciblait main) → milestone overnight-2026-06-05-bilan-detail-titres complète 10/10 et fermée. v0.10.0 shippée (2026-06-29 : Étapes 1+2 du bilan, migrations v12→v16) puis hotfix v0.10.1 (2026-06-30, PR #230, déployé) — corrige un « database is locked » introduit en 0.10.0 (apparaissait après l'abandon d'un snapshot en cours) : tout l'accès DB est désormais sérialisé via withTransaction dans db.ts (tauri-plugin-sql = pool sqlx multi-connexions sans primitive de transaction JS → BEGIN/COMMIT en db.execute séparés pouvaient strander une transaction d'écriture = verrou zombie), appliqué aux 5 sites transactionnels ; + console de log live-update + API logInfo/logWarn/logError. 20 tables / 24 index, 16 migrations (v1→v16).
Milestone deps-security-2026-07 livrée (2026-07-01, 4/4 fermée) — 4 vulns de dépendances Defenseur remédiées en 2 PRs (#239 react-router-dom 7.18.1 ; #240 vite 6.4.3 + vitest 4.1.9) ; npm audit 5→0 — le dernier @babel/core low remédié le 2026-07-04 (override scopé @vitejs/plugin-react→@babel/core@^7.29.7, issue #241 / PR #242 mergée). Ce « 0 » n'est plus l'état courant : des advisories publiées depuis l'ont ramené à 3, traitées en #311 le 2026-07-27 → 2 restantes, acceptées (react-router, cf. entrée du jour). Nouveau milestone spec-ci-build-optimization (2/4) ouvert (baseline #231 : cause #2 = reserveCache timeout du runner ; #234 connectivité cache VPS, #232 split workflows).
v0.11.0 shippée (2026-07-05, tag v0.11.0 → CI release Windows/Linux + JSON updater) — milestone overnight-2026-07-05-bilan-rapports-ux complétée 5/5 et fermée via un run /autopilot (5 workers séquentiels, PRs #248-#252 toutes /pr-review APPROVE, puis merge local de la pile + réconciliation Forgejo). Contenu : rapport comparable réel-vs-réel hiérarchique avec sous-totaux (#247) ; netting des transferts ciblé (catégories type transfer → SUM signé ~0, autres types inchangés au byte) dans le compare (#243) ; landing Bilan en tuiles HubReportNavCard + guard empty-state découplé + gestion comptes accessible partout (#244) ; import CSV de titres dans un snapshot détaillé (front pur, autoDetectHoldingColumns, prix flexible) (#245) ; cible de migration catégories éditable sur chaque ligne + type-ahead CategoryCombobox feuilles-seulement (#246). Aucune migration DB (v1→v16 inchangées), 20 tables / 24 index, 16 migrations. Point de suivi non bloquant : nets de transfert signés traversent du code UI compare pensé pour des magnitudes positives (mord seulement les transferts déséquilibrés à une jambe).
v0.12.0 shippée (2026-07-05, tag v0.12.0 → CI release #311) — recadrage de #243 (montants de transfert inclus dans le comparable) par Max en « analyse de résultat » : deux rapports comparables refondus en états de résultat (income statement). Le compare réel-vs-réel (#253) et le tableau de tendance par catégorie (#256) affichent le revenu, un Résultat avant transferts (revenus − dépenses) et un Résultat net (après transferts), sections ordonnées revenu→dépense→transfert ; les rapports comparables ouvrent par défaut sur le mois complet précédent (#253). Modules purs extraits et testés (compareResults.ts, overTimeResults.ts) ; 2 blocages /pr-review corrigés (fuite du revenu dans les top-movers Cartes ; garde StrictMode sur le sync du mois de référence). Livré en 2 issues/PRs stackées #255→#257 (merge local + réconciliation Forgejo, branches supprimées), 701 vitest verts, aucune migration DB (v1→v16 inchangées), 20 tables / 24 index. Backlog ouvert au passage : #258 (db-locked proposeStarterAccounts) → fermé le 2026-07-07 (déjà corrigé v0.10.1, pas de nesting) ; #254 (collapse/expand) → livré le 2026-07-07 (PR #261) ; #259 (mapping manuel d'un compte standard sans similaire auto) et #260 (epic « rapports uniformes » — tous les rapports sur le modèle income-statement) ouverts.
Decisions recentes
- 2026-08-14 : Import CSV — chantier complet livré (10 issues, migration v17). Cycle intégral en une session : revue d'implémentation →
/spec→/review-spec→/plan-run→/autopilot→/pr-review×10 → merge ff-only. Le diagnostic a tenu, mais trois choses ont été trouvées en cours de route que personne n'avait vues. (1) Au re-ancrage Phase 3b :dataExportServicene sérialise niimport_sourcesniimport_config_templateset faitDELETE FROM import_sourcesà la restauration — restaurer une sauvegarde détruisait toutes les configurations d'import, 3e voie de perte du format, indépendante du bug racine (→ #331, +withTransactionque le service n'avait nulle part). (2) Un worker a mesuré que l'exemple « Solde 2024 » de l'issue n'est pas défectueux (parseFloats'arrête sur leS) et a ajouté la fixture qui l'est vraiment. (3) Un autre a mesuré que chez RBCCAD$et unCheque Numberpresque vide sont sparse-complementary : le générique les apparie et une ligne de chèque s'importe à −234,05 au lieu de −6,95 — d'où l'override par signature. Correction de la revue initiale :parseFrenchAmount("50,00-")ne rend pas 50 mais 5000 —"100,00 CAD"→ 10000,"1 234,56 CR"→ 123456 : erreur de facteur 100 qui passaitisNaNet comptait donc la ligne comme VALIDE. Le ×100 atteignait aussi l'import de titres #245. Durcissement global assumé : un relevé à suffixe de devise produit désormais des lignes en erreur plutôt que des valeurs fausses./pr-reviewa bloqué 3 des 10 PRs, tous fondés : (a) #336 — la règle débit/crédit testaitisNaN(debit) && isNaN(credit), donc une cellule illisible à côté du remplissage0,00calculait 0 − 0 = 0 et importait en silence, le bug même que la PR fermait, une cellule plus loin (rejoué sur sa propre fixture : 6 transactions à 0,00 $, 0 erreur) ; (b) #337 —detectDescriptionColumnretournait la colonne préférée sans veto par les données, et le mot-clétransactioncapturait la colonne DEBIT/CREDIT de Tangerine, tuant la catégorisation ; (c) #340 — régression réelle : la signature Desjardins est 4 libellés génériques appariés par sous-ensemble, et une signature court-circuitait le scan sparse-complementary → un fichierDate;Description;Débit;Crédit;Montant;Soldelu correctement AVANT devenaitpositive_expenseAPRÈS, chaque dépôt en dépense. Corrigés en 3 commits sur la tête de pile (484c4be,ef7de3c,37b832e), chacun vérifié par mutation — et cette vérification a révélé qu'un de mes propres tests passait la mutation (le fix sur les signatures génériques empêchait la branche gardée d'être atteinte) : il a fallu un fichier reconnu par une signature légitime pour l'exercer. Leçon : un garde qui ne peut pas échouer ne garde rien, y compris quand c'est soi qui l'écrit. Incident run : le worker #331 tué par une limite de session pendant sa validation — travail récupéré depuis son worktree et revalidé de zéro (le rationale de sa PR est une reconstruction, signalé comme tel). Gotcha parallélisation : deux agents de revue partageaientscratchpad/review.md, l'un a écrasé l'autre entre Write et POST → donner un chemin de scratch unique par agent. Suivis ouverts : refus du format à indicateur borné àamountCol ± 1; dérives de comptes préexistantes dansCLAUDE.md/architecture.md(mesurées et documentées, non corrigées). (ref #323-#332, PRs #333-#342) - 2026-07-27 : #310 + #311 mergées —
cargo audit9→0,npm audit3→2 acceptées (maina14258b, ff-only ; PRs #316 → #318 stackées, CI verte des deux côtés). Parties d'un/analyse-vulnerabilite: rapport Défenseur écarté comme source (VPS injoignable — SSH Tailscale en attente d'auth navigateur, aucun lien imprimé ; rapport local du 2026-05-06, 82 jours) → vérité live =cargo audit 0.22.2+npm auditsurmain, advisory-db0bfde9d6. Le triage d'origine de #310 révisé sur un point :quick-xml(2× 7.5 high) n'est compilé sur aucune des deux cibles livrées —cargo tree -ivide surx86_64-pc-windows-msvcetx86_64-unknown-linux-gnu, présent seulement surx86_64-apple-darwinviaplist←tauri(dép. conditionnelle Apple). Il n'y avait donc rien à attendre de Tauri, contrairement au « probablement bloqué en amont » du corps. Méthode retenue : tracer par triple explicite, jamais--target all(qui répond « présent » pour des deps Apple jamais compilées ici) — mémoire reference-cargo-audit-reachability-par-cible. 6 advisories atteignables corrigées parcargo update -p rustls-webpki -p tar(0.103.9→0.103.13 couvrant les 4, 0.4.44→0.4.46) sans toucherCargo.toml; dérive de lock expliquée et bornée (7 pointeurswindows-sysrepointés vers des versions déjà présentes, 666 paquets avant/après, deps Windows-only → no-op sur Linux ;--precisedonne le même résultat, c'est la re-résolution de cargo 1.94.1). Les 3 restantes →.cargo/audit.tomlversionné, lu par les deux workflows sans qu'aucun ne le sache. Le plan checker a levé un MAJOR fondé : une liste de suppressions sans déclencheur de retrait inverse le problème de l'alarme (l'audit resterait vert siquick-xmlredevenait atteignable) — d'où 3 règles en ADR 0018 : admission sur preuve par cible livrée ou absence de correctif ; clé par ID d'advisory jamais par crate (une nouvelle advisory sur le même crate repasse au rouge) ; garde-fou bloquant danscheck-rust.yml, placé aprèscargo check(index chaud) avec--locked, testant le code de sortie séparément de la sortie (un crate absent et uncargo treeen panne impriment tous deux du vide) + canaritar. Le pendant — suppression devenue inutile — est #312. Leçon du 1er run CI : le garde était muet en cas de succès, donc indiscernable dans les logs d'une étape non exécutée — exactement le mode d'échec silencieux que la PR combat (même famille que le « globsrc-tauri/**non prouvé » de #232) → il logge maintenant chaque vérification (Suppression guard: 4 checks, exit 0, visible run 336). Preuve que.cargo/audit.tomlest bien lu en conteneur : leworkflow_dispatchdeaudit.ymlsur la branche est vert, alors que le bump seul n'aurait éliminé que 6 des 9. npm (#311) :postcss8.5.13→8.5.23 sans override —vitedéclare^8.5.3, seul le lock était périmé (≠ cas #241 où le parent pinnait) ;nanoidsuit dans sa borne ; CSS émis identique octet pour octet (postcss est le pipeline CSS, un build vert n'aurait prouvé que la compilation) ;react-routeraccepté — advisory mode RSC, orApp.tsx:109monte unBrowserRouterclient-only, etreact-router-domest figé à 7.18.1 (v8 a fusionné le paquet dansreact-router) donc le « fix » npm est un downgrade en 7.11.0 → sortir de la plage = migration, pas bump (#317, titre portantreact-routerpour la dédup par sous-chaîne de/analyse-vulnerabilite). Asymétrie à connaître : aucunnpm auditen CI, donc rien ne rougit côté front — écrit dansarchitecture.md+CLAUDE.mdpour que les 2 high permanents ne se lisent pas comme une régression. Gotchas Forgejo relevés : logs de job uniquement via l'URL web (l'API v1 répond 404 partout) ethead_branch=#<PR>sur les runspull_request→ reference-forgejo-ci-logs-et-head-branch. 106 Rust + 871 vitest + builds verts. Aucune migration DB (v1→v16). (ref #310, #311, PRs #316/#318, #312-#315, #317) - 2026-07-27 : #232 mergée — CI split, caches morts retirés,
cargo-auditpré-buildé (main7779f7d, PR #309 rebase, milestonespec-ci-build-optimization3/4)./analyzea confirmé la prémisse mais révisé le cadrage sur 3 points mesurés : (1) les 2 jobs ne tournent pas en parallèle — runner à capacité 1,frontenddémarre à la seconde oùrustfinit sur tous les runs de l'historique → chaque PR payait rust+frontend ≈ 24,5 min ; (2) 3 des 40 derniers commits first-parent touchentsrc-tauri/, dont 2chore: release(push surmain, ne déclenche pas la CI) → le path-filter est le gain dominant, pas un effet de bord ; (3) le risque « required check skippé » est nul (enable_status_check: falsesur la protection demain, vérifié API). Chrono re-mesuré du run 326 : 12m15s de gaspillage sur 21m44 — savetarget/6m11 + save registry 43s (reserveCache failed, cause #234) +cargo install cargo-audit4m41 + restores ~40s ; nouveau vs baseline #231 : le restore timeout aussi désormais (getCacheEntry failed), le 30 juin n'avait qu'un MISS propre. Résultat : rust 21m44 → 8m55 (−59 %), frontend 2m28 → 1m43, PR frontend-only 24m12 → 1m43 (−93 %). Trou de couverture fermé :branches: [main]ne matchait pas une PR stackée → #305-#308 (pile feature-gating) n'ont eu aucune CI, seules #303/#304 (basemain) ont tourné ; plus aucun filtrebranches:. 2 écarts assumés vs le corps d'origine, tranchés sur mesure : cache npm retiré aussi (23s/run) et denylist pour le frontend (job à 2,5 min → le faire tourner pour rien est bon marché, ne PAS le faire tourner silencieusement ne l'est pas ; le job cher garde un allowlist strict)./pr-reviewAPPROVE — a récupéré les logs CI plutôt que juger le diff, et levé que l'overridePATHjob-level aurait pu masquer le binaire d'install-action(vérifié : audit bien exécuté) ; 2 des 6 suggestions appliquées (.claude/**au denylist, refcheck.ymlpérimée dans le skillrelease). Skip prouvé empiriquement : le 2e push (nisrc-tauri/**nicheck-rust.yml) n'a pas déclenchécheck-rust. Reste non prouvé — le globsrc-tauri/**lui-même (seule l'entrée chemin-exact a matché) : mode d'échec silencieux sur la PR Rust (1/40, nicargo checknicargo test) → #310 (cargo updatetouchantCargo.lockseul) sera le test isolé, à surveiller. Le retrait du|| truea découvert 9 advisories RUSTSEC réelles (préexistantes, seulement masquées) → #310 ouverte :rustls-webpki×4 +tar×2 atteignables viatauri-plugin-updater/reqwest(correctifs patch),quick-xml×2 bloqué en amont parplist←tauri(bump majeur),rsasans correctif publié mais seul parentsqlx-mysql— absent de tout arbre de compilation (cargo tree -i rsa --target allvide, projet SQLite) donc vraisemblablement inatteignable. Conséquence :audit.yml(quotidien, bloquant par choix) sera rouge dès son 1er run tant que #310 n'est pas traitée — alarme permanente = alarme ignorée, donc à lander tôt. Aucune migration DB (v1→v16). (ref #232, PR #309, #310) - 2026-07-21 : Milestone
planned-2026-07-19-feature-gatinglivrée 6/6 et fermée — le gating par tier est surmain([Unreleased], candidate v0.15.0). Reprise du run/autopilotinterrompu le 19 au soir (mort après la PR #303 ; worker #298 tué sans commit — 2 worktrees + branche vide nettoyés). Séquence :/pr-review#303 REQUEST_CHANGES (le retry backoff CWE-703 s'armait aussi sur un échec desubmitKey→ l'auto-refresh effaçait le message « clé invalide » ~1 s après ; etstatus: "error"persistant aurait laisséready=falseà jamais pour les futurs RequireFeature) → fixb9e13b5: validation de clé orthogonale au lifecycle de chargement (validationErrordédié,statusintouché, reducer exporté + 6 tests) → APPROVE → merge API. Relance/autopiloten session : 5 workers séquentiels en pile linéaire (#302 dépend de tout ; CHANGELOG centralisé dans #302 → zéro conflit inter-maillons) → PRs #304-#308, 0 needs-clarification, 0 blocked (rapport)./pr-review×5 parallèles → APPROVE ×5 ; seule retouche user-facing : coquille FRdocs.editions(« tout la » → « tout de la »,6de9617). Merge : tip cumulé validé (871 vitest + build + cargo 106) puis ff-only → pushmainrefusé : branche protégée (whitelistmaximus, règle du 2026-03-07, jamais rencontrée avant) — cause réelle :~/.git-credentialsporte 3 identités Forgejo etdefenseur-auto-boten 1re position shadowmaximus(git prend la 1re entrée du host ; les pushes de branches passaient, seulemainwhitelistée refusait). Fix scopé :git remote set-url origin https://maximus@…(ne PAS réordonner le fichier partagé — les agents defenseurs s'authentifient par lui). Push OK → 5 issues auto-fermées (Resolves #N), 5 PRs fermées + commentées, milestone 6/6 fermée, branches supprimées (+ purge de 7 branchesworktree-agent-*du harness ; le « résiduorigin/issue-259» signalé au warmup était un tracking ref périmé faute defetch --prune— la branche remote avait bien été supprimée le 19). Décisions workers notables : tuilesProfileSelectionPagenon verrouillées (page atteinte seulement sans profil actif résoluble → un verrou heuristique risquerait un soft-lock hors de tous les profils) ; dev-override en tête de résolution (sinon un compte Premium actif masqueraitSR_DEV_EDITION) ;#[serde(default)]préexistant surfeatures= les licences Base déjà émises sans le champ ne régressent pas. Gotcha process : un fork/pr-reviewlancé pendant que la CI estpendingse termine « en attente du monitor CI » sans jamais poster (rien ne survit au fork) → monitorer la CI depuis le main loop, relancer le skill après le vert. Post-merge non bloquant : extractionLockBadge/UpsellPanelsi une 3e surface apparaît ; label PIN « (annuler) » (bug cosmétique préexistant) ; job CI ponctuel--features dev-override. ADR 0017 Accepted. Aucune migration DB (v1→v16). (ref planned-2026-07-19-feature-gating, #297-#302, PRs #303-#308) - 2026-07-19 : Chantier gating par tier planifié via
/spec→ milestonespec-feature-gating(#297-#302). Origine : réflexion monétisation de Max (« changer le périmètre des abonnements »). Matrice tranchée : Free = Dashboard/Import/Transactions/Catégories/rapport Tendance/Export chiffré/Changelog (mono-profil) ; Base = + les 4 autres rapports/Budget/multi-profils/auto-update ; Premium = + Bilan complet (patrimoine + cours). Admin (Max) = licence Premium auto-émise (pas de 4e édition). Décisions clés : upsell verrouillé (pas masqué) ; durcissement sec non destructif (blocage d'accès, données conservées, récupérables par upgrade) ; entitlements statique + overridefeatures[]; enforcement UI-only (soft-paywall GPL assumé, seul le gate cours reste dur/server-enforced) ; #271 absorbé (auto-update Base+, fermé superseded). Re-ancrage Phase 3b : socle déjà là (3 éditions Ed25519,current_edition,check_entitlement) maisis_feature_allowedignorefeatures[]+useLicenseper-appel → LicenseProvider requis pour unuseEntitlementsync ; rapports = routes distinctes (gate par route) ; #271 = 1 ligne. 6 issues (socle→garde UI→routes/sidebar→multi-profils→#271 Rust→docs), specs force-add (gitignorées, précédent #295), aucune migration DB./review-spec3 experts → verdict 🟡, tout corrigé dans le plan : 2 🔴 (navreportsgaté à tort alors que hub Free ;advanced-reportsRust mort contreditreports-advanced) + 6 🟡 (overridefeatures[]fail-closed en Free CWE-863 : une clé copiée downgrade free mais expose ses features signées ;SR_DEV_EDITIONderrière une Cargo featuredev-overridepasdebug_assertionsCWE-489 ; LicenseProvider récup. d'erreur CWE-703 ;useEntitlement→{allowed,ready}anti-flash ;useIsPremium.testà migrer ; gate création profil dansProfileFormModal, point unique). Ajustements tranché Base par Max. Re-homéeplanned-2026-07-19-feature-gating(/plan-runStep 0 : bodies rendus auto-suffisants ; résiduel drainé — CTA « Obtenir » désactivé + « bientôt », dev-override gardé). Prête/autopilot. (ref planned-2026-07-19-feature-gating, #297-#302) - 2026-07-18 : Epic #260 « rapports uniformes » fermée + #259 livrée en PR #296 (review). #260 fermée après
/analyze: 4/5 rapports pleinement conformes (income-statement + filtres partagés + collapse par-profil) ; les 3 écarts restants vs le texte de l'epic sont des décisions de conception assumées, entérinés par Max — (1) lignes vides budget non masquées (spec décision 6 : grille = surface d'édition) ; (2) collapse budget replié « comme partout » (#289/ADR 0016 inverse le « sauf budget » de l'epic) ; (3) dashboard convergé sur le modèle Cartes plutôt qu'une table income-statement hiérarchique (décision 5). Correction STATE : les entrées 07-08/07-11 « reste de #260 = #259 » sont fausses — #259 est une migration de taxonomie sans lien avec l'epic (recadrée 07-12), et les vraies déviations #260 n'y étaient pas tracées. #259 (fusion des catégories custom) livrée en PR #296 (issue-259-merge-custom-categories) : bloc préservé deStepSimulaterendu enMappingRow, reducerRESOLVE_ROWrésout rows+preserved (unresolvedcompté sur seed only → ne bloque pas « Suivant »), writer via helperisResolvedTarget(fourre-tout créé seulement s'il reste une custom non fusionnée ; customs fusionnées désactivées au lieu d'être re-parentées). Plan-check pass (6 MINOR, 3 intégrés), 836 vitest + build tsc/vite propres, régression parent/enfant custom couverte, aucune migration DB./pr-reviewAPPROVE, mergée (rebase) le 2026-07-19 →main2314a64, #259 fermée, branche supprimée. (ref #260, #259 PR #296) - 2026-07-18 : Milestone collapse multi-niveaux (#288-291) livrée → v0.14.0. Cycle complet en une session :
/plan-run(spec 2 fichiers, 8 décisions drainées) →/review-spec(3 experts, verdict 🔴) → refonte v2 →/autopilot(4 workers) →/pr-review×4 → ff-merge → release. Le point clé : la revue a tué l'algo v1. Il suivait un curseur de profondeur supposant un ordre DFS ; or la grille Budget (useBudget.ts:361) trie par niveau — les 3 experts l'ont trouvé indépendamment. Refonte v2 : visibilité par remontée deparent_id(une ligne visible ssi tous ses ancêtres dépliés), order-independant → résout d'un coup l'ordre budget, le tri par type qui sépare parent/enfant, la feuille « (direct) » qui partage la clé du parent, et la contraintevisible()-avant-reorderRows. Pile linéaire forcée (B/C/D dépendent tous du nouveau hook de A → pas de wave parallèle) : PRs #292-#295, chacune basée sur la précédente./pr-reviewAPPROVE ×3 + 1 REQUEST_CHANGES (#295 : ligne- Spec:de l'ADR 0016 pointant des specs gitignorées → 404 ; corrigé par force-add des specs à la racine, précédent repospec-refonte-rapports.md; au passage la revue s'est trompée en disant que 0015 n'avait pas de ligne Spec — elle en a une, cassée pareil, bug pré-existant signalé). ff-merge de la pile (4 issues auto-fermées viaResolves #N, milestone 4/4, 4 branches supprimées) → v0.14.0 taggée. Persistance migréelocalStorage→user_preferences(base du profil) :deleteProfilene purge aucunlocalStorage→ une clé par-profil y serait un résidu survivant à la suppression, révélant les catégories explorées d'un profil PIN-protégé (exigence privacy-first, ADR 0016, frontière tracée : état UI par-profil → DB profil, état UI machine → localStorage). Le hook gagnedefaultExpanded+storageKeynullable, qui unifie aussi les 2 arbres de catégories (#290 :CategoryTreedéplié-par-défaut, guide replié ; corrige le bugallExpanded = size>0du guide ; worker a trouvé un 4e consommateurStepDiscovernon listé au plan, migré). 828 vitest, aucune migration DB. Écarts protocole tracés au rapport : workers auto-validés sans forker/pr-review(évite le double-post feedback-pr-review-subagent-forks) ; #294 sans test (refactor de rendu, non testable sans jsdom) → vérif runtime déléguée à la revue. Restespec-ci-build-optimization(2/4) +spec-paiements(#270/#271) + #259. (ref #288-291, PRs #292-#295) - 2026-07-12 : #259 recadrée — l'issue décrivait un flux qui n'existe pas (rectifie les entrées des 2026-07-05 / 07-11 qui la classaient « mapping manuel compte sans similaire auto » et « reliquat de l'epic #260 » : les deux sont faux). Le corps d'origine, rédigé à chaud le 2026-07-05 pendant le run v0.12.0, avait transposé par analogie le signalement de Max vers le module Bilan sans ouvrir le fichier : il décrivait une auto-association des comptes standards aux comptes existants du profil dans
StarterAccountsModal, avec « case désactivée quand aucun similaire n'est auto-identifié ». Aucune notion de mapping n'existe dans ce flux — la case est un simple « créer ce compte : oui/non », désactivée quand une collision EST détectée (StarterAccountsModal.tsx:160), soit la polarité inverse ; le commentairel.8cité (« the matching checkbox ») désignait « la case correspondante », pas « la case de matching ». Le vrai sujet (confirmé par Max) est la migration des catégories :computeMigrationPlanrange les catégories en 2 seaux et un seul est éditable —plan.rows(seed) passe par le moteur d'appariement + type-ahead par ligne (#246/#252), tandis queplan.preserved(catégories custom) est poussé avecv1TargetId: nullsans même être soumis au moteur d'appariement, rendu en<li>texte brut (StepSimulate.tsx:202-216, aucun picker) et déversé d'office par le writer sous le fourre-tout « Catégories personnalisées (migration) ». Sémantique tranchée avec Max : fusion (choisir une feuille standard réassigne tx/budgets/mots-clés/fournisseurs et fait disparaître la custom ; ne rien choisir = comportement actuel, et ne doit pas bloquer le bouton « Suivant »). Re-parentage écarté. Travail = câblage sur 3 couches (UIStepSimulate→MappingRowréutilisable tel quel ; reducerRESOLVE_ROWqui ne voit queplan.rows; 4 retouches du writer) — la machinerie de fusion est déjà générique (buildMappingFromRowsfiltre les cibles nulles, étapes 3-7 bouclent sur la Map). Complexité Medium. Piège à couvrir : fusionner une custom parente ayant des enfants custom (listepreservedplate → l'enfant non résolu retombe au fourre-tout, pas d'orphelin, mais aucun test ne le garantit). Leçon process : une issue rédigée par analogie en fin de run, sans lecture du code, peut inverser la prémisse et se tromper de module — le/analyzel'a rattrapée 6 jours plus tard. (ref #259) - 2026-07-11 : M2 « rapports-parite » (#277-#279) shippée — dernière grosse tranche de l'epic #260 « rapports uniformes ». Run
/autopilot(3 workers en worktree → PRs #285/#286/#287), reviewée via 3/pr-reviewadversariales parallèles (read-only,git showsans checkout) : APPROVE #285 (grille budget income-first, #278) + APPROVE #286 (BVA réel-vs-budget income-first, #277) + REQUEST_CHANGES #287 (dashboard Cartes, #279). Blocage #287 = le point I7 prédit au plan confirmé réel :getCartesSnapshotpropageaitaccountIdsaux sous-rapports top-movers/budget mais pas aux sériesfetchMonthlyFlows(KPIs/sparklines/overlay 12 mois) nifetchSeasonality→ sur le dashboard (1re page à exposer le filtre par-dessus ces séries) les KPI montraient des totaux non filtrés à côté de barres/tendance filtrées. Fix (9ee5ad3) : threaderaccountIds?dans les 2 fetchers (source_id IN (...)paramétré viainPlaceholders, index$3/$4) + câblagegetCartesSnapshot; le CHANGELOG #279 promettait déjà le filtrage → code aligné sur la promesse, texte inchangé ; +2 assertions cartes. Merge local de la pile (3 merges--no-ff, conflit additif CHANGELOG ×2 résolu par union #277/#278/#279 — i18n non conflictuel car #277/#278 réutilisent des clés existantes, seul #279 touche les locales ; STATE non conflictuel car branches ne l'ont pas commité) → tip cumulé validé (tsc + vite build + 811 vitest + cargo check + 98 Rust) → pushmaina982f9e. Réconciliation Forgejo : 3 issues auto-fermées viaResolves #N, 3 PRs fermées manuellement (merges locaux non détectés merged), milestoneovernight-2026-07-08-rapports-parite3/3 fermée, branches supprimées, worktrees leftover nettoyés (gotcha recursion). Aucune migration DB (v1→v16), non taggé ([Unreleased]). Wrinkle process : le fork du skill/pr-reviewavait posté un APPROVE prématuré sur #287 (I7 gradé non bloquant) → superséé + commentaire stale supprimé par la passe adversariale indépendante (trace code + « 1re page à exposer le filtre » + CHANGELOG à honorer) ; valeur de la double-vérif indépendante. Suivi non bloquant :getDashboardSummary= code mort à retirer. Reste de #260 : #259 (mapping manuel compte sans similaire auto). (ref #260, #277-#279, PRs #285-#287) - 2026-07-08 : Suite #260 — M1 « filtres-fondation » mergée (
mainfe9ae01), M2 prête. Planifiée via/plan-overnight+/review-spec(filtre multi-comptesaccountIds[]/IN(...)tranché après analyse, révise le « sourceId singulier » initial). M1 livrée via/autopilot(5 workers séquentiels en worktree → PRs #280-#284 en pile linéaire) +/pr-reviewAPPROVE ×5 + merge local fast-forward : hookuseReportsPeriodadditif (+accountIdsURLsources+ typeReportFilters, #272), 7 services enaccountIds[]via helper paramétrésqlFilters.inPlaceholders(#273),<FilterPanel>slot temporel + multi-select « sources d'import » (libellé distinct du Bilan, #274), adoption Tendances (#275) + Compare/Budget (#276 — incl.CompareBudgetView; câblage budget viagetActualTotalsForYear, le body citait à tortgetBudgetVsActualData). Aucune migration DB. Build + 791 vitest verts (728 + 63). NB : le « 4676 / 277 fichiers » vu en pré-merge était une pollution de worktrees —vitestrécursait dans les 5 worktrees leftover sous.claude/worktrees/(791 × ~6) ; nettoyés depuis, compteur réel = 791. Gotcha : nettoyer les worktrees (ou exclure.claude/worktrees/) avant de valider un tip. Réconciliation Forgejo complète (5 issues auto-fermées viaResolves #N, 5 PRs fermées, milestone fermée, branches supprimées ; worktrees nettoyés). M2overnight-2026-07-08-rapports-parite(#277-#279) prête →/autopilot(BVA + grille budget income-first + dashboard Cartes). Notes review pour M2 : sériesgetCartesSnapshot(fetchMonthlyFlows/fetchSeasonality) non filtrées (I7/#279) ; grille budget câblée viagetActualTotalsForYear. (ref #260, #272-#276, PRs #280-#284)
Blockers actifs
- Aucun blocker externe dur.
spec-monetisationfermée 12/12 (2026-07-08) — #50/#52/#53/#135/#136 livrées, ne sont plus des blockers. - Backlog ouvert (
status:ready, non bloqué) :spec-paiements(#270 activation /v1 + product explicite + URL achat localisée — c'est elle qui activera le CTA « Obtenir » des UpsellGate, aujourd'hui désactivé « bientôt disponible » ; #271 absorbé par #301, livré) ;spec-ci-build-optimization3/4 (#232 livrée le 2026-07-27 ; reste #234 connectivité du serveur de cache runner, qui débloqueraitSwatinem/rust-cache— accès hôte VPS requis). - Suivis ouverts des advisories (#310/#311 fermées) : #314 —
audit.ymln'a jamais été déclenché (zéro runschedulesur 420 tâches Actions) ; leworkflow_dispatchfonctionne, donc le problème est isolé au scheduler, et tant qu'il n'est pas réglé la couverture quotidienne n'existe pas, quel que soit l'état vert du workflow. Puis #312 (retirer les suppressionsquick-xmlquandplist/tauripassera à>= 0.41.0— le garde-fou attrape la suppression devenue injustifiée, #312 celle devenue inutile), #313 (tauri-plugin-deep-link 2.4.8yanked, dépendance directe), #315 (/releasene teste aucun cycle téléchargement+extraction réel, alors quetar/rustls-webpkine sont ici que compilés), #317 (re-évaluerreact-routerà une migration v8). - Suivis ouverts du chantier import (milestone fermée) : le refus du format « montant absolu + indicateur D/C » ne scanne que
amountCol ± 1, donc une colonne D/C placée ailleurs passe encore ; dérives de comptes préexistantes dansCLAUDE.mdetdocs/architecture.md(composants, pages, services,Version actuelle : 0.6.3vs tag v0.14.0) mesurées et documentées en revue, non corrigées. - Release à considérer : le
[Unreleased]porte le feature-gating, les deux lots d'advisories et le chantier import (migration v17) → candidatev0.15.0via/release.