fix: follow-ups of the lot-v018 stack reviews #482
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#482
Loading…
Reference in a new issue
No description provided.
Delete branch "review-followups-lot-v018"
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?
Refs #393, #429, #384, #372, #446, #453, #449, #437
Generated autonomously by /autopilot run of 2026-10-18
Maillon 9 de la pile
planned-2026-10-16-lot-v018, au-dessus de #475 : les deux décisions de Max du 2026-10-19 et les suggestions non bloquantes des huit/pr-reviewindépendantes (#468 à #475). Second tour (1386abdàa17c1b7) : la décision de Max du 2026-10-20 et les suggestions de la revue indépendante de cette PR (commentaire 25921). Troisième tour (13dca87,c50ed53, un test et une ligne de doc) : les suggestions de la revue du second tour (commentaire 25974, APPROVE). Aucune issue propre, aucun mot de fermeture : les huit issues restent àstatus:approved, inchangées.Décisions de Max appliquées (2026-10-19 et 2026-10-20)
A1 — #393, revue 25437, point 3. Une catégorie qui porte des transactions directes, un montant de budget non nul (d'une année quelconque dans
budget_entries, ou dansbudget_template_entries, montants négatifs compris) ou des mots-clés ne peut pas devenir parent.is_inputable = 1, la condition demarkAsParent), qui cesserait de l'être. Un parent existant, déjà non saisissable, accepte une nouvelle sous-catégorie ou un rattachement comme dans toutes les versions publiées, quoi qu'il porte : le refus n'y protégerait rien. La règle vaut pour les trois codes,parent_has_transactionsvenu de #472 compris, et pour les trois points d'entrée. Les lignes de la hiérarchie portent désormaisis_inputable, lues par le handle (createCategoryles lit dans sa transaction).checkParentContent(categoryService.ts) lit, par le handle de la transaction et avant toute écriture, trois comptes dans cet ordre : transactions, montants de budget, mots-clés actifs. Le premier trouvé fait le refus :parent_has_transactions(inchangé), puis deux nouveaux codes deCategoryHierarchyError,parent_has_budgetetparent_has_keywords, traduits en FR et en EN avec ce qu'il faut déplacer d'abord.updateCategory(parent changé),reattachCategoryet désormaiscreateCategory. La création d'une sous-catégorie écrit sa ligne et leis_inputable = 0du parent dans un seulwithTransaction. Sa vérification de profondeur (getCategoryDepth) reste avant la transaction, inchangée.CATEGORY_HIERARCHY_ERROR_CODES), et un test exige un message par code dans les deux langues.[Unreleased](Corrigé et Ajouté, dans les deux fichiers), astuce du guide (section Catégories) etdocs.categories.tipsFR/EN,architecture.md(validation,createCategory, hors portée du glisser-déposer). Toutes disent « devenir parent » au sens précis, et qu'un parent existant n'est pas vérifié.A2 — #446, revue 25487, point 4.
docs.dashboard.steps[0](FR et EN) et l'étape 1 du guide (section Tableau de bord) nomment les huit étapes, par les libellés deonboarding.checklist.items.*et dans l'ordre decomputeChecklist. Ils disent que les deux étapes de visite se cochent par « J'ai compris » / "Got it" (onboarding.card.acknowledge) ou, pour les rapports, par un clic sur la carte Tendances entourée. Un test deDocsContent.locales.test.tstient libellés, ordre et libellé du bouton. L'entrée Modifié #446 suit.Autres décisions de Max, consignées ici.
adjustment_entriesne bloque pas une catégorie qui devient parent. L'effet est limité : seule la page Ajustements lit ces lignes, et une ligne dont la catégorie n'est plus saisissable la garde, avec un champ vide (#429). Aucune donnée n'est perdue.rustls-webpki, RUSTSEC-2026-0383 surwebpki-root-certs) sont suivis par #484, hors de cette pile. Cette PR ne touche ni au lock ni à un fichier Rust de production.Corrections par commentaire de revue
checkHierarchyrelit les catégories avant le diagnostic : après un glisser-déposer,SET_TREEles privait des membres d'une boucle. La relecture se fait sans l'état de chargement de la page (voir Décisions), et après les écritures d'un glisser-déposer en cours (constat de l'auto-revue,273cf36) ; tests du hook « glisser-déposer, puis vérifier » et « écritures en cours ».categoryHierarchy.test.ts), RM2 et RM11 (hook et page), RM7 (service, l'exemple du relecteur), RM15 (displaysAsRootet grille du budget).placeListboxàanchorAt(300)/ 574 px : R2 rougit.useLayoutEffectsans dépendances).keepPlacement(pur) rend le même objet pour une position inchangée. Épinglé parCategoryCombobox.placement.test.ts, sous le harnais.AddKeywordDialog.tsxreformulé.refreshFolderet parbrowseFolder, une fois un dossier choisi ; entrée Corrigé dans les deux CHANGELOG. Test des vraies locales (i18next réel) :folderExistsnomme le dossier en FR et en EN.CLAUDE.md:commands/recompté depuisgenerate_handler!(10 modules, 45 commandes).REVIEW.md: piège « Tests vitest qui lisentsrc-tauri/», ajouté en dernier.architecture.md: l'erreur du montage de Transactions est effacée vers 300 ms.if (!cancelled)du.catch(V1 rougit) ; entrée #372 : « the logs » / « les journaux », noms de la carte dans l'interface.not.toBe(fr)(T4 rougit) et fixture « feuilles seulement » (T1 rougit).REVIEW.mdet sectionrelease.ymld'architecture.md: le relais-- --lockedse reprouve à toute montée de@tauri-apps/cliou de cargo-xwin. Les sixcargo metadata --no-depsviennent de la CLI de Tauri. Aucun workflow ni skill touché.architecture.md: la mesure du rattrapage vérifie qu'il aboutit, pas sa durée. Épingles X4 (infobulle), X11 (common.cancel) et X8 (Fragmentde la ligne 2).PriceFetchControl.test.tsxajouté aux utilisateurs du harnais.browseFolderépinglé (R4 rougit) ; ligne[A, z, Vide]donnez, Vide, A(R13 rougit). Note Ctrl+clic : raison lue dans le code des crates.CLAUDE.md:balance/12,categories-migration/5,onboarding/3. Corps de #475 corrigé :onboarding.card.acknowledge, re-GET intact.1386abd), voir A1.parent_has_budget, astuce du guide etdocs.categories.tips(FR et EN) : les montants comptent pour toute année, se remettent à zéro dans la grille année par année, et un modèle ne se modifie pas, il se supprime. L'astuce ajoute que remettre à zéro une année passée retire aussi son prévu du rapport Budget vs Réel.placeListbox.test.tscorrigé.CLAUDE.md: « 12 modules, dont 10 avec des commandes ». Utilisateurs du harnais présentés par leur nombre et la commande qui les liste (21 au 2026-10-20), plus comme une liste complète.bc66101) : après un rattachement, un formulaire ouvert derrière la fenêtre perdait ses changements. La relecture silencieuse devientreadCategoriesAgain, partagée par l'ouverture et le rattachement.13dca87, Q6 rougit).architecture.md: la commande qui liste les utilisateurs du harnais filtre les fichiers de test, et rend les 21 annoncés (c50ed53).Décisions (journal du run,
#followups)deactivateKeyword) poseis_active = 0. Les compter laisserait un refus impossible à lever depuis l'interface ; ils ne classent rien et aucun écran ne les montre. La seule réactivation (« Ajouter comme mot-clé ») les déplace vers une catégorie saisissable.createCategory: vérification du contenu et écritures dans unwithTransaction; la vérification de profondeur reste avant, inchangée. Écarté : profondeur recalculée pardepthsOf, qui changerait le cas d'un parent supprimé.is_inputable(une lecture par le handle, celle que les règles lisent déjà).becomesParentlit=== 1 || === true, commeisActiveRow;is_inputableest lié en1/0surmaindepuis toujours. Écarté : une requête dédiée par vérification.readCategoriesAgainest partagé par l'ouverture et le rattachement. Un échec de la relecture après un rattachement s'affiche désormais dans la fenêtre, où se trouve l'utilisateur, au lieu du bandeau caché derrière elle. Limite connue, non corrigée : un formulaire ouvert sur l'orpheline elle-même garde son ancien parent (editingCategoryn'est pas relu, comme avecloadCategoriesavant), qu'un enregistrement tenterait de remettre, sous les mêmes vérifications.node:sqlite(module intégré à Node, aucun paquet) : les trois requêtes tournent telles qu'écrites, sur lesCREATE TABLEdeconsolidated_schema.sql, si bien qu'une condition retirée (amount <> 0,is_active = 1) rougit. Sous Node 22,node:sqliteest expérimental (avertissement à l'exécution) ; la CI est en Node 24. Décrit dansarchitecture.md, « SQL réel ».loadCategories:SET_LOADINGdémonterait le panneau de détail, etCategoryFormrepartirait deeditingCategory, perdant les modifications non enregistrées d'un formulaire ouvert derrière la fenêtre. Un chargement lancé entre-temps l'emporte (B393-M2 rougit surSET_LOADING).browseFolderefface l'erreur une fois un dossier choisi, pas avant le sélecteur : une annulation ne change rien, et l'erreur qui tient reste. Écarté : effacer dansscanFolderInternal, qui toucherait aussi la lecture au montage.useLayoutEffectdansCategoryCombobox, le premier inchangé. Le test de placement fait passeruseLayoutEffectpour leuseEffectdu harnais etuseIdpour une constante, dans ce fichier seulement.profile_db.rsnon touché :src-tauri/src/database/**est de niveau haut « toujours », et la consigne limite ce point àarchitecture.md.Preuves
Sonde rc=1 (
src/zz_tmp_probe.test.ts, supprimée),npm cipropre dans le worktree. Chaque mutation est appliquée seule, puis la suite vitest complète tourne (Rust :cargo test --locked --all-targets commands::fs_commands) ; le fichier est restauré pargit checkout --, et l'arbre est propre après chacune. 51 mutations (42 au premier tour, 8 au second, 1 au troisième), toutes rouges sur le test visé.categoryService.tsparent_has_budgetretiréecategoryService.tsparent_has_keywordsretiréecategoryService.tsAND amount <> 0retiré (budget_entries)categoryService.tsbudget_template_entriesretiré de la requêtecategoryService.tsAND is_active = 1retiré (mots-clés)categoryService.tscategoryService.tscreateCategorysans vérification du contenucategoryService.tsINSERTcategoryService.tsgetDb()dans la transactioncategoryService.tsuseCategories.tsparent_has_keywordsrendu par son messagefr.jsonparent_has_keywordsretiréuseCategories.tscheckHierarchyuseCategories.tsloadCategories()useCategories.tsuseCategories.tsuseCategories.tscategoryHierarchy.tsuseCategories.tsSTART_REATTACHgarde l'erreurcategoryService.tsCategoriesPage.tsxstate.treepassé à la fenêtrecategoryHierarchy.tsCategoryCombobox.tsxCategoryCombobox.tsxsetPlacement(next)sanskeepPlacementplaceListbox.tskeepPlacementrend toujoursnextplaceListbox.tsspaceBelow < maxHeightdevient<=useImportWizard.tsrefreshFoldern'efface plus l'erreuruseImportWizard.tsbrowseFoldern'efface plus l'erreuruseImportWizard.tsuseImportWizard.tsSET_IMPORT_FOLDERfr.json/en.jsonfolderExistssans{{name}}fs_commands.rsexists()devientis_dir()locales.keys.test.tsenimporté defr.jsonlocales.keys.test.tsmissingKeysliste aussi les conteneursChangelogContent.tsx.catchsanscancelledTransactionTable.tsxtLicenseKeyForm.tsxcommon.cancelsanstTransactionTable.tsxreorderRows.tsfr.json/en.jsoncategoryService.tscategoryService.tsbecomesParenttoujours faux (plus aucun refus de contenu)categoryService.tscreateCategorypasse des lignes vides au contrôlecategoryService.tsbudget_entries:amount <> 0réduit àamount > 0categoryService.tsbudget_template_entries:amount <> 0réduit àamount > 0useCategories.tsuseCategories.tsloadCategories()useCategories.tsuseCategories.tsreadCategoriesAgainpart au bandeau de la page (mutation de la revue 25974)Comptes
npm run buildvert (tsc + vite).cargo test --locked --all-targets, 269 réussis, 1 ignoré (base : 268 et 1). Le second tour ne touche pas au Rust.Vérifications manuelles dues (prochaine release)
parent_has_keywordstraduit, et rien n'est créé. Même chose avec un montant de budget non nul, puis un montant dans un modèle de budget seulement : messageparent_has_budget. Avec des montants tous à zéro et des mots-clés tous supprimés, la sous-catégorie est créée et le parent n'accepte plus de saisie.Hors portée (décidé par Max)
Filtres de période et l'astuce « sur 12 mois » (#479), fuite de
withoutFoldersau Journal (#478), garde des variables d'interpolation (#480), test du CHANGELOG à la release (#481), tests du consentement et de la garde Premium dePriceFetchControl(cours expérimentaux, sans enjeu).À regarder pour la revue
src-tauri/.fs_commands.rsne change que dans son module de tests.REVIEW.mdchange hors « Niveaux ».13dca87, un test ;c50ed53, une ligne de doc) ne le sont pas encore.refreshFolderest aussi leonChangeddeImportHistoryPanel: supprimer un import efface désormais un bandeau d'erreur resté affiché.node:sqlite(categoryService.test.ts) etuseLayoutEffectpassé pour leuseEffectdu harnais (CategoryCombobox.placement.test.ts).categoryServicelisentconsolidated_schema.sql: le nouveau piège deREVIEW.mdles cite.🤖 Generated with Claude Code
Review 25592, point 3: the page's tests take the marked t and the key guard compares no value, so a translation that lost {{name}} passed the suite. The real locale files, through a real i18next with the app's options, now render folderExists with the folder's name in French and in English, and the two other codes without a leftover placeholder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>Niveau : normal
Verdict : APPROVE
Auto-revue de l'auteur (worker
/autopilot), pas indépendante : une/pr-reviewindépendante suit.Niveau (Step 0bis,
REVIEW.mddemain, fichier par fichier) : aucun des 31 fichiers ne remplit une ligne du tableau Haut. La ligne « code livré » ne s'applique pas : aucun appel réseau ajouté ni modifié, aucune URL, aucun consentement, et aucune ligne de Journal changée (useImportWizard.tsne touche que le bandeau).fs_commands.rsne change que dans son module#[cfg(test)]et figure dans la liste « au moins normaux ». Aucun fichier n'est ajouté à la racine ni directement danssrc-tauri/; le seul fichier ajouté estsrc/components/shared/CategoryCombobox.placement.test.ts.REVIEW.mdchange hors « Niveaux » (hunks l. 127 et 186 ;python3 scripts/review-levels.py --check: 37 motifs, identiques).CLAUDE.mdetdocs/architecture.mdsont au moins normaux ; les CHANGELOG et le guide, bas.Ce qui a tourné (worktree,
npm cipropre) :npm run buildvert ;npm test: 156 fichiers et 2548 tests verts (base : 155 et 2503) ;cargo test --locked --all-targets: 269 réussis, 1 ignoré (base : 268 et 1) ;Checklist
$1), et les codes de refus sont des constantes.1/0ou en littéral. A2 nomme les huit étapes dans l'ordre decomputeChecklist. Les fixes B suivent chaque commentaire, avec deux écarts motivés, consignés au journal du run :checkHierarchyne passe pas parloadCategories, qui démonterait le formulaire ouvert ;browseFoldern'efface l'erreur qu'une fois un dossier choisi..onlyni.skipni reste de débogage. Le défaut antérieur (#384, bandeau) a son test rouge sans le correctif (B384-M1, B384-M2).CategoryHierarchyError;docs.*et guide suivent ; deux entrées CHANGELOG mises à jour et une ajoutée, dans les deux fichiers.consolidated_schema.sqlest seulement lu par un test.pull_refde #482 ennone, jamais encloses; ni corps ni commit ne porte de mot de fermeture.Constat corrigé dans cette PR
src/hooks/useCategories.ts:423. La relecture decheckHierarchypouvait tomber au milieu des écritures d'un glisser-déposer :updateCategorySortOrdersenvoie unUPDATEpar catégorie, chacun repassant par le verrou. L'arbre aurait alors montré le déplacement à moitié appliqué.moveCategorygarde la promesse de ses écritures (l. 399), et le contrôle l'attend (273cf36, test « reads the categories once the drag-and-drop's writes are over » ; SR-M1 et SR-M2 rouges). Build et suite rejoués après.Suggestions (non bloquantes)
src/shared/entitlements.ts:26). Budget y est verrouillé : un profil Gratuit qui garde des montants de budget sur une catégorie ne peut pas les remettre à zéro, et cette catégorie ne peut pas devenir parent. La décision ne prévoit pas d'exception par édition : à Max de confirmer, ou d'ouvrir une issue.src-tauri/src/database/profile_db.rs:1706) : « Prints, never asserts » porte la même imprécision que la phrase d'architecture.mdcorrigée ici. Non touché, puisquesrc-tauri/src/database/**est de niveau haut « toujours ». À faire avec une prochaine PR de ce fichier.node:sqlite(src/services/categoryService.test.ts:106). Le module est expérimental sous Node 22, qui l'annonce par un avertissement à l'exécution ; la CI est en Node 24. Les bases en mémoire ne sont pas fermées, sans effet sur la suite.CategoryCombobox.placement.test.ts:17). Il simule leuseLayoutEffectpar leuseEffectdu harnais. Le saut de rendu réel de React, et donc l'absence de boucle, reste à voir dans l'application (vérification manuelle 5).src/pages/ImportPage.tsx:176:refreshFolderest aussi leonChangedde l'historique des imports, si bien que supprimer un import efface désormais un bandeau d'erreur resté affiché. C'est voulu, mais ce n'est pas épinglé.Actions faites : corps de #475 corrigé (
onboarding.card.acknowledge, re-GET intact). Aucun label posé ni retiré.CI lue par l'orchestrateur sur la tête
273cf36: les deux contrôles sont verts.check-frontend(run 597) : 156 fichiers et 2548 tests verts (155 et 2503 sur la base #475).categoryService.test.tspasse sous Node 24 avecnode:sqlite.check-rust(run 598) :Cache restored successfully;webkit2gtkprésent ;windows-sys:9 checks, exit 0;cargo test: 269 passés, 1 ignoré ;cargo auditinformatif : 663 crates, aucune vulnérabilité, 18 avertissements autorisés.Décision de Max (2026-10-19), sur la question de l'auto-revue (commentaire 25877) : la règle « pas de montant de budget non nul sur un futur parent » s'applique aussi en édition Gratuite. Le cas est rare ; la page Budget verrouillée invite à passer à Base pour retirer les montants. Rien à changer dans cette PR.
Niveau : normal
Verdict : APPROVE
Résumé. Le code fait ce que dit le corps de la PR. La règle A1 s'applique aux trois points d'entrée, avant toute écriture, par le handle d'un seul
withTransaction. A2 et les suites des huit revues sont faites. Build, suite et CI sont verts, et 12 des 15 mutations du relecteur rougissent ; les trois survivantes sont des épingles sur du code correct. Reste une question d'intention pour Max avant le merge : le contenu se vérifie aussi sur une catégorie déjà parent, même non saisissable. Là, le refus ne protège rien et bloque l'ajout de sous-catégories, possible dans toutes les versions publiées.Le niveau est calculé sur les 31 fichiers, et aucun ne remplit une ligne du tableau Haut :
fs_commands.rsne change que dans son module de tests ;REVIEW.mdchange aux lignes 127 et 186, hors de la section « Niveaux » ;src-tauri/;node:sqliteest un module intégré à Node, sans paquet.Ce que la revue a exécuté
Sur un clone détaché hors du dépôt, à la tête
273cf36, aprèsnpm ci:npm run build: rc=0 ;npm test: 156 fichiers, 2548 tests, rc=0 (les chiffres du corps de la PR) ;check-frontend, run 597 : checkout de273cf36, Nodev24.22.0, build, 156 fichiers et 2548 tests verts ;check-rust, run 598 : checkout de273cf36,refuses_a_file_named_like_a_sample_folder_and_writes_nothing ... ok, 269 tests réussis, 1 ignoré.is_dir(),create_dir("Exemple Desjardins")passerait, puiscreate_dir("Exemple Tangerine")échouerait sur « File exists ». L'erreur etdesjardins_createdchangeraient tous deux.Mutations du relecteur
Les mutations sont appliquées une à la fois, avec la suite complète. Après chacune, le fichier est restauré par
git checkout --et l'arbre redevient propre.categoryService.tsbudget_entries:amount <> 0réduit àamount > 0categoryService.tsbudget_template_entries:amount <> 0réduit àamount > 0categoryService.ts> 1)categoryService.tscheckNewParentvérifie le contenu de la catégorie déplacée, pas celui du parentcategoryService.tscreateCategorysans vérification du contenu (A1-M7 rejouée)categoryService.tscategoryService.tscreateCategoryvérifie le contenu pargetDb(), avant la transactionuseCategories.tscheckHierarchytoujours appliquée (gardefetchIdretirée)useCategories.tsuseCategories.tsCategoryCombobox.tsx[open])placeListbox.tskeepPlacementrend une copie égale, pas le même objetplaceListbox.tssamePlacementignoretopuseImportWizard.tsrefreshFolderefface le bandeau après le scan, pas avantuseImportWizard.tsbrowseFoldergarde le bandeau (B384-M2 rejouée)Douze rouges sur quinze. Les trois survivantes portent sur du code correct (suggestions 1 et 2).
Question d'intention, à trancher par Max avant le merge
Un parent déjà parent, même non saisissable, est vérifié aussi.
checkParentContent(src/services/categoryService.ts:101) ne lit pasis_inputable. C'est la décision MEDIUM du run, « contenu vérifié quel que soit l'état du parent », épinglée parcategoryService.test.ts:457. La décision de Max (commentaire 25898 sur #472) dit « ne peut pas devenir parent », et sa raison est que le parent cesse d'être saisissable. OrmarkAsParent(:64,WHERE … AND is_inputable = 1) ne change rien à un parent déjà non saisissable. Pour un tel parent, le refus ne protège donc rien.Cas atteignable. Jusqu'à la v0.17.2,
createCategoryacceptait tout parent de profondeur permise, sans regarder ce qu'il portait. Une feuille qui avait des mots-clés, des transactions ou des montants, scindée une fois par « Ajouter une catégorie », est aujourd'hui un parent non saisissable qui garde tout ce contenu. Avec cette PR, aucune sous-catégorie ne peut plus y être créée ni rattachée, que ce soit au formulaire ou dans la fenêtre, tant que ce contenu n'est pas déplacé ou effacé.Sonde du service (avec le faux de
categoryService.test.ts). P est non saisissable, avec l'enfant 10 dessous.createCategorysous P est refusé enparent_has_budget,parent_has_keywordsouparent_has_transactionsselon ce que P porte, avec pour seules écritures["BEGIN","ROLLBACK"]. Sans contenu, la création passe et P reste àis_inputable = 0.Remède introuvable pour les montants. Sonde de
buildBudgetYearRows: P porte 100 à son nom, et son enfant 40.useBudget.ts:282et:337).Le message « remettez d'abord ces montants à zéro » ne peut donc se suivre qu'en cochant d'abord « Saisissable » dans le formulaire, ce que ni le message ni le guide ne disent.
Deux options :
markAsParentle rendrait non saisissable. Il faut une lecture de plus par le handle, et un test de plus pour un parent non saisissable. Le test de la ligne 457, dont le parent est saisissable (valeur par défaut du faux), resterait vrai.parent_has_budgetcomment retrouver ces montants.La même question vaut pour
parent_has_transactions, au formulaire et dans la fenêtre, vérifié de cette façon depuis #472.Suggestions (non bloquantes)
parent_has_budgetsont testés avec des montants positifs (25, 120, 40, 10, 50). La grille enregistre le nombre tapé tel quel (BudgetTable.tsx:127), donc un montant négatif est possible, etamount > 0le laisserait passer. Un refus à-25dans chaque table suffit.useCategories.ts:418, garde à la ligne 426) n'est tenue par aucun test. Le cas est peu atteignable aujourd'hui : la fenêtre couvre la page, et le hook ne s'abonne à aucun événement de données.docs/guide-utilisateur.md:298) etdocs.categories.tips(FR et EN, ligne 1206) disent « remettez ses montants à zéro ». Or un montant de modèle ne se modifie pas : seuldeleteTemplateexiste, ce que le message d'erreur dit bien (« supprimez … les modèles qui en contiennent »).placeListbox.test.ts:4-8). Il range parmi ce qui demande un DOM « la relecture après un rendu qui a déplacé le champ ». OrCategoryCombobox.placement.test.ts, ajouté dans le même commit, l'épingle sous le harnais (R11 rouge). Seule la fin de la boucle, le saut de rendu de React, reste à voir dans l'application.checkHierarchyévite désormaisloadCategoriespour ne pas démonter un formulaire ouvert, maisreattachOrphanl'appelle encore (useCategories.ts:453). Un rattachement réussi passe donc la page en chargement et démonteCategoryDetailPanel: les changements non enregistrés du formulaire sont perdus. La vérification manuelle 4 ne couvre que l'ouverture de la fenêtre.CLAUDE.md:68annonce « 10 modules » et en liste douze, dont deux sans commande.architecture.md:664) nomme neuf des 21 fichiers qui l'importent. C'est une dérive antérieure à cette PR.Les points 1, 2, 4 et 6 portent sur du code correct et viennent après les épingles de la revue 25437. Mieux vaut les regrouper dans une issue de suivi que lancer un tour de revue de plus.
Vérifié sans constat
createCategory, la vérification du contenu, l'INSERTetmarkAsParentpassent par le handle d'un seulwithTransaction(R7 rouge). La vérification de profondeur, inchangée, se lit avant. Un refus n'écrit queBEGINetROLLBACK(sonde).CATEGORY_HIERARCHY_ERROR_CODESet les clés FR/EN se correspondent, et la garde de parité de #453 est verte. Les boutons « Actualiser » et « Parcourir » sont nommés comme dans l'interface dans l'entrée #384.consolidated_schema.sqlcompte 150 catégories, 38 parents et 180 mots-clés, dont aucun sur un parent. C'est vrai aussi pourreinitializeCategories.generate_handler!enregistre 45 commandes : fs 7, export/import 5, profils 8, licence 11, compte 5, jetons 1, entitlements 1, feedback 2, sauvegarde 3, Bilan 2.handle_auth_callbackporte l'annotation de commande, mais il est appelé par le lien profond, sans être enregistré.balance/12,categories-migration/5,onboarding/3, 138 au total.@tauri-apps/cli2.10.0.SetHandled(true)sous WebView2 (webview2/mod.rs:778), etconnect_createseulement avec un gestionnaire sous WebKitGTK (webkitgtk/mod.rs:484). tauri-runtime-wry n'en pose un qu'à partir deon_new_window.release.yml, lignes 53, 70 et 78.REVIEW.mdliste bien les quatre seuls fichiers vitest qui lisent soussrc-tauri/.pull_refde #482 est ennone. Ni le corps de la PR ni ses 19 commits ne portent de mot de fermeture, et les 19 commits suivent le format conventionnel.$1), sans secret ni jeton, et les chemins de test sont fictifs. Aucune migration ni changement de schéma.Actions faites
/tmp/pr482-review) et les fichiers/tmp/pr482-*sont supprimés en fin de revue.status:approved, aucune n'est enstatus:review, et la PR n'en ferme aucune.Second tour sur #482 : quatre commits en tête de la branche, pas encore relus (ni par une
/pr-reviewindépendante ni par une auto-revue). Ils appliquent la décision de Max du 2026-10-20 et les suggestions de la revue 25921. Tête :a17c1b7.1386abdfix(categories) : le contenu d'un parent (transactions, montants de budget, mots-clés) n'est vérifié que si ce parent est encore saisissable, c'est-à-dire quand il deviendrait vraiment parent (décision de Max du 2026-10-20). Un parent existant accepte une sous-catégorie quoi qu'il porte, pour les trois codes et les trois points d'entrée. Le commit épingle aussi un montant négatif, dans la grille et dans un modèle (R1, R2).bc66101fix(categories) : après un rattachement, un formulaire ouvert derrière la fenêtre garde ses changements (défaut antérieur, #472, point 5). La relecture silencieusereadCategoriesAgainsert à l'ouverture comme au rattachement. Le commit épingle aussi la garde « un chargement plus récent l'emporte » (R8).b626bcbfix(i18n) : CHANGELOG #393, guide,docs.categories.tipsetarchitecture.mddisent « devenir parent » au sens précis (point 3). Le messageparent_has_budgetet l'astuce disent que les montants comptent pour toute année et qu'un modèle se supprime, faute de pouvoir se modifier. La liste des utilisateurs du harnais n'est plus présentée comme complète (point 6).a17c1b7docs : en-tête deplaceListbox.test.ts(point 4), compte des modules deCLAUDE.md(point 6).Preuves : 8 mutations, toutes rouges ; sonde rc=1, build vert, 156 fichiers et 2559 tests verts. Aucun changement Rust. Le corps de la PR est à jour : décisions, tableau des preuves, comptes, vérifications manuelles 8 et 9.
Niveau : normal
Verdict : APPROVE
Résumé. Revue du second tour (
1386abd,bc66101,b626bcb,a17c1b7), sur le diff complet de la PR. La décision de Max du 2026-10-20 est appliquée telle qu'écrite : le contenu d'un parent ne se vérifie que s'il est encore saisissable, pour les trois codes et sur les trois points d'entrée, à partir de lignes lues par le handle de la transaction. Après un rattachement, un formulaire ouvert derrière la fenêtre garde ses changements. Build, suite et CI sont verts sura17c1b7, et 7 des 9 mutations du relecteur rougissent. Des deux survivantes, l'une est équivalente ; l'autre montre qu'aucun test ne tient l'affichage d'un échec de la relecture (suggestion 1).Niveau calculé sur les 32 fichiers. Aucun ne remplit une ligne du tableau Haut :
invoke("pick_folder")est un appel IPC déjà présent) ;fs_commands.rsne change que dans son module de tests ;REVIEW.mdchange aux lignes 130 et 190 (« Release et mise à jour », « Pièges de revue »), hors « Niveaux » ;src/components/shared/CategoryCombobox.placement.test.ts, n'est ni à la racine ni danssrc-tauri/;Ce que la revue a exécuté
Sur un clone détaché hors du dépôt (
/tmp/pr482-review), à la têtea17c1b7, aprèsnpm ci:npm run build: rc=0 ;npx vitest run: 156 fichiers, 2559 tests, rc=0 (les chiffres du corps). Sous Node 22.22 en local,node:sqliteaffiche son avertissement expérimental, sans effet sur les tests ;check-frontend, run 600 : checkout dea17c1b7, Nodev24.22.0, build, 156 fichiers et 2559 tests verts ;check-rust, run 601 : checkout dea17c1b7, cache restauré, gardes 2/2 et 9/9,refuses_a_file_named_like_a_sample_folder_and_writes_nothing ... ok, 269 réussis, 1 ignoré.Mutations du relecteur (second tour)
Les mutations sont appliquées une à la fois, avec la suite complète. Après chacune, le fichier est restauré par
git checkout --et l'arbre redevient propre.categoryService.tscreateCategorylit les lignes pargetDb()dans la transactioncategoryService.tscreateCategorylit les lignes avantBEGIN, hors transactioncategoryService.tsmarkAsParentavant la vérification du contenu, danscreateCategoryuseCategories.tsreadCategoriesAgainenvoieSET_TREEau lieu deSET_CATEGORIESuseCategories.tsreattachOrphanne relit plus quand le diagnostic suivant échoue (finallyretiré)useCategories.tsreadCategoriesAgainpart au bandeau de la page, comme dansloadCategoriesuseCategories.tscategoryService.tsbecomesParentlitis_inputable !== 0categoryService.tsQ8 est équivalente : la colonne est
INTEGER NOT NULL DEFAULT 1, et toutes ses écritures lient1/0depuis la v0.2.5 (? 1 : 0, puistoSqlFlagdansupdateCategory). La valeur lue vaut donc0ou1.Suggestions (non bloquantes)
Échec de la relecture non épinglé (Q6). Trois sources disent qu'un échec de
readCategoriesAgains'affiche dans la fenêtre :useCategories.ts(lignes 417 et 452) ;docs/architecture.md:286;Aucun test ne le tient. Avec Q6, un échec à l'ouverture part au bandeau de la page, caché derrière la fenêtre. Le diagnostic tourne ensuite sur les anciennes catégories : c'est le défaut du point 1 de la revue 25437. Après un rattachement, la fenêtre ne dit rien.
Un test suffit.
getAllCategoriesWithCountsest rejeté une fois à l'ouverture, puis une fois après un rattachement réussi. Il vérifie quehierarchyCheck.errorvaut{ message }, questate.errorrestenullet queisSavingrepasse àfalse. C'est une épingle sur du code correct : elle relève d'une issue de suivi, pas d'un tour de plus.Corps de la PR à mettre à jour.
/pr-briefrelirait la question comme ouverte.Lignes d'ajustement hors de la règle, pour information : la décision du 2026-10-19 ne les nomme pas.
adjustment_entries.category_idn'est pas vérifié, mais l'effet reste limité. Seule la page Ajustements lit ces lignes (adjustmentService.ts, aucun rapport ni budget). Sur cette page, une ligne dont la catégorie n'est plus saisissable garde sa catégorie, avec un champ vide (#429). Rien à changer, sauf avis contraire de Max.Détail de doc (
architecture.md:664).grep -rl "test/hookHarness" srcrend 22 lignes : les 21 fichiers de test, plus le harnais lui-même (src/test/hookHarness.ts). Avec--include='*.test.*', la commande rend 21.Hors PR : deux avis RustSec sur le chemin de l'updater
L'étape
cargo auditdu run 601, informative, écriterror: 2 vulnerabilities found!:rustls-webpki0.103.15, corrigé en 0.103.16, daté du 2026-10-20 ;webpki-root-certs1.0.6, corrigé en 1.0.10, daté du 2026-10-19.Cette PR ne touche pas au lock.
rustls-webpkiest atteint partauri-plugin-updater→reqwest0.13 →rustls0.23.46 (cargo tree -i, cible Linux) : c'est le chemin de #447 et #455. Le run quotidien d'audit.ymldu 2026-10-20 (599, 06:00 UTC) est passé avant la parution des deux avis. Celui du 2026-10-21 lira la même base que le run 601 et rougira. Aucune issue n'est ouverte à ce jour.Vérifié sans constat
becomesParentlitis_inputabledans les lignes de la hiérarchie, lues par le handle (HIERARCHY_ROWS_SQL), avec la condition demarkAsParent(is_inputable = 1).EXISTING_PARENT, mutation D-M1 de l'auteur).deactivateCategorya retiré le dernier enfant redevient saisissable et se vérifie de nouveau.createCategory, la profondeur se lit avant la transaction, comme avant. Les lignes, le contenu, l'INSERTetmarkAsParentpassent ensuite par le handle d'un seulwithTransaction(Q1, Q2 et Q3 rouges).transactions(parent_transaction_id) : la vérification des transactions les compte.CategoryFormgarde son état local tant queinitialDatane change pas (useEffectsur[initialData]), etSET_CATEGORIESne touche paseditingCategory. SansSET_LOADING, le formulaire reste donc monté avec ses changements.saveAsTemplate,applyTemplateetdeleteTemplate, avec un bouton de suppression dansTemplateActions.0(une cellule) ou supprime la ligne (toute l'année) :amount <> 0couvre les deux cas.docs.categories.tipset les deux messages d'erreur.CLAUDE.mdest exact : 12 modules horsmod.rs, 10 avec des commandes, 45 entrées dansgenerate_handler!.auth_commands.rsporte 6 annotations pour 5 commandes enregistrées :handle_auth_callbackpasse par le lien profond.status:approved, et leur timeline porte unpull_refde #482 ennone.$1).node:sqliteest intégré à Node.Actions faites
status:approved, aucune n'est enstatus:review, et la PR n'en ferme aucune./tmp/pr482-review) et les fichiers/tmp/pr482-*sont supprimés en fin de revue.Troisième tour sur #482 : deux commits en tête de la branche (
a17c1b7..c50ed53), un test et une ligne de doc, pas encore relus. Ils ne touchent ni au code livré, ni au lock, ni au Rust, ni à un workflow.13dca87test(categories) : un échec de la relecture des catégories s'affiche dans la fenêtre « Vérifier la hiérarchie », à l'ouverture comme après un rattachement réussi. Le bandeau de la page reste vide et l'enregistrement se termine (revue 25974, suggestion 1 ; Q6 rougit).c50ed53docs : dansarchitecture.md, la commande qui liste les utilisateurs du harnais filtre les fichiers de test et rend les 21 annoncés (suggestion 4).Corps de la PR à jour. La question sur l'édition Gratuite est retirée, tranchée le 2026-10-19 (commentaire 25885). Les deux décisions de Max du 2026-10-20 y sont consignées : les lignes d'ajustement ne bloquent pas, et les avis RustSec sont suivis par #484, hors pile. Le tableau des preuves et les comptes sont mis à jour.
Preuves : sonde rc=1, build vert, 156 fichiers et 2560 tests verts.
CI lue par l'orchestrateur sur la tête
c50ed53: les deux contrôles sont verts.check-frontend(run 602) : 156 fichiers et 2560 tests verts.check-rust(run 603) :windows-sys:9 checks, exit 0;cargo test: 269 passés, 1 ignoré.cargo auditdecheck-rust, informative, écriterror: 2 vulnerabilities found!. Ce sont RUSTSEC-2026-0382 et RUSTSEC-2026-0383, présentes surmainet suivies par #484 (décision de Max du 2026-10-20 : issue seulement, hors de cette pile).Les deux commits du troisième tour (
13dca87test,c50ed53doc) n'ont pas eu de nouvelle revue, comme le proposait le commentaire 25974 pour ces épingles. Le reste de la PR est relu (commentaires 25921 et 25974, APPROVE).Mergée en avance rapide par
/merge-stackle 2026-10-20, avec les huit maillons de la pile (#468 → #475), sur la tête relue et validéec50ed53.mainest maintenant àc50ed53.npm cipropre :npm testdonne 156 fichiers et 2560 tests verts ;cargo check --lockedpasse, etcargo test --lockeddonne 269 tests réussis et 1 ignoré ;check-rust.ymlpassent : 2 vérifications sur les avis acceptés, 9 sur l'épinglagewindows-sys.13dca87(test Q6) etc50ed53(une ligne de doc). Ce sont des tests et de la doc seulement, classés triviaux.planned-2026-10-16-lot-v018fermée.