fix: onboarding phase 1 follow-ups: pins, Transactions card, Import messages, docs (#384) #474
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#474
Loading…
Reference in a new issue
No description provided.
Delete branch "issue-384-onboarding-followups"
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 #384
Generated autonomously by /autopilot run of 2026-10-18
Résumé
Maillon 7 sur 8 de la pile
planned-2026-10-16-lot-v018, basé surissue-437-test-pins(PR #473). Les quatre parties du corps réécrit par/analyzele 2026-10-16, avec les décisions D1 et D2 de Max :{{M1}}= mois le plus récent, ligne à ligne contre le gabarit), le commentaire « priority 0 » deonboarding-samples.test.tscorrigé, le cas « profil sans transaction » deTransactionsPagerendu.totalInDb === 0(même règle que Budget) ; lectures du montage deuseTransactionsetuseBudgetenPromise.allSettled.create_sample_import_foldersrefuse par des codes stables (sample_folder_exists:<nom>,import_folder_missing,invalid_date) que le front traduit (import.empty.sampleErrors.*, nom du dossier interpolé) ; tout autre message reste brut ; le message brut part au Journal enlogWarn. Le bandeau de réussite n'est posé qu'après un rescan réussi.ImportEmptyStaten'a plus quefolderName.generate_handler!(45, était 39), les cinq absentes documentées dans deux nouvelles sections de module,fetch_pricedécrite au présent ; hooks (29) ;CLAUDE.md(hooks seulement) ; guide sans « seed ».Aucune migration (v1→v17), aucun appel réseau ajouté ni modifié.
Fichiers
src-tauri/src/commands/fs_commands.rs(constantesSAMPLE_ERROR_*, refus codés, docstring ; module de tests : codes assertés à l'identique,refuses_when_tangerine_folder_exists_and_writes_nothing,assert_months_follow_tokensappelée pour 2026-09-25 et pour le passage d'année). Les deux CSV desrc/__fixtures__/samples/compilés parinclude_str!sont intacts.src/hooks/useImportWizard.ts(sampleFolderError,WizardState.errorValues,SET_ERRORavecvaluesfacultatif,scanFolderInternalrend un booléen,createSampleFolders),src/pages/ImportPage.tsx(bandeau d'erreur avec les valeurs, appel d'ImportEmptyState),src/components/import/ImportEmptyState.tsx.src/hooks/useTransactions.ts(showsEmptyState, lectures du montage),src/hooks/useBudget.ts(lectures du montage).import.empty.sampleErrors.{folderExists,importFolderMissing,invalidDate}etdocs.gettingStarted.steps.5, FR et EN.useImportWizard.samples.test.ts,useTransactions.wiring.test.ts,useBudget.wiring.test.ts(nouveaux, harnais partagé) ;useImportWizard.test.ts,useTransactions.test.ts,TransactionsPage.render.test.tsx,ImportPage.render.test.tsx,ImportEmptyState.test.tsx,onboarding-samples.test.ts.docs/architecture.md,docs/guide-utilisateur.md,CLAUDE.md,CHANGELOG.md,CHANGELOG.fr.md.Décisions
SET_ERRORprend unvaluesfacultatif, rangé dansWizardState.errorValues(vidé par toutSET_ERRORsans valeurs et parRESET) ; la page rendt(state.error, { ...errorValues, defaultValue: state.error }), donc elle traduit toujours au rendu (suit un changement de langue) et un texte inconnu reste brut. Écartés : traduire dans le hook paruseTranslationcommeuseDataImport(texte figé dans la langue du moment, puis repassé àtcomme clé), une clé par dossier d'exemple (le corps demande un nom interpolé), un type d'erreur structuré (tous lesSET_ERRORet la page changent).useImportWizard.test.tslitfs_commands.rs, en extrait lesconst SAMPLE_ERROR_*(canari : exactement trois codes) et vérifie que chacun est traduit. Angle mort, écrit dans le test : une PR qui ne touche quesrc-tauri/**sautecheck-frontend, la dérive se voit au prochain run frontend. Écartés : pas de contrôle croisé (un code renommé côté Rust arriverait brut à l'utilisateur, en silence) ; un test Rust qui lirait le TS parinclude_str!(élargirait le piège REVIEW.md des fichiers compilés parinclude_str!).ImportEmptyStateseul : les « deux sites d'appel » du corps sontImportPage.tsxl. 77-78 (entrée d'importCardModel, typeImportCardInput) et l. 153-155 (<ImportEmptyState>) ; seul le second appelle le composant.ImportCardInput.hasFolderest gardé : type partagé,onboardingPath.tset ses tests hors de la liste de fichiers et du critère («ImportEmptyStaten'a plus quefolderName»). Suite possible, voir plus bas."": l'équivalence du corps (folderName !== null ⇔ hasFolder) ne tient pas pour""(hasFoldervrai,folderNamenul avecimportFolder ? … : null). La page passe désormaisimportFolder !== null ? folderNameFromPath(importFolder) : null(""donne"") : la sortie du composant est inchangée pour toute entrée et reste d'accord avec la carte de démarrage. Épinglé côté composant et côté page.architecture.mdau-delà de la liste : pour que chaque compteur dise sa liste (la règle du corps pour les commandes) : trois hooks non documentés ajoutés au tableau (useCategoryMigration,useCategoryTaxonomy,useFeedback, donc 28 lignes +useCollapsibleGroups= 29) ; la lignecommands/de l'arborescence (6 modules) passe à 10 modules, 45 commandes ; les phrases « Phase 5 » sur les cours hors de la section des commandes (point 4 du service Bilan, « Les commandes Rust sont réservées… ») mises au présent.SAMPLE_ERROR_*, assertés à l'identique (assert_eq!) : le code est épinglé et le chemin ne peut plus y figurer. Les erreurs d'écriture et les textes « Cannot compute… » (inatteignables) gardent leur anglais.{{M1}}: la « première ligne de données » du corps porte{{M2}}dans les deux gabarits (lignes 2 à 21 en M2, 22 à 41 en M1), d'où une comparaison ligne à ligne avec le gabarit plutôt qu'une ligne. Côté vitest,onboarding-samples.test.tsrend les gabarits par son proprerenderSampleTemplateet ne lit aucune ligne précise : pas d'épingle là (règle du corps), la propriété est épinglée côté Rust, où vit la commande.logWarn("[import] Sample folders not created: <brut>"). Textes bruts possibles, lus dans le code : les trois codes (nom du dossier d'exemple seulement), « Cannot compute… »,Cannot create folder <exemple>: <erreur io>,Cannot write <exemple>/releve-AAAA-MM.csv: <erreur io>(leDisplaydestd::io::Errorne porte aucun chemin). Le chemin ou le nom du dossier d'import ne peut pas y figurer ; l'erreur du rescan (Folder does not exist: <chemin>) reste à l'écran, jamais au Journal.Promise.allSettled: chaque lecture réussie est dispatchée, le premier échec dans l'ordre du tableau (catégories, sources, compte ; comptes, compte) part enSET_ERROR, un compte en échec laissetotalInDbànull. Testé par le harnais partagé.allSettled(hors liste du corps ; unCOUNTen échec n'est pas un parcours utilisateur).REVIEW.mdintact : aucun fait qu'il cite ne change.D1 —
totalInDbaprès un import (vérifié à la lecture)totalInDbn'est lu qu'au montage de la page : seuluseOnboardingChecklists'abonne au canal d'événements de données (ADR 0022). Après un import, l'utilisateur revient de/import, ce qui remonte la page et relit le compte ; un changement de profil remonte toutes les routes (BrowserRouter key={refreshKey}). Aucune écriture de transactions ne peut se faire pendant que/transactionsest montée (import sur/import, restauration dans Paramètres) : la carte ne peut donc pas rester affichée après un import aujourd'hui. Limite, non corrigée : si une écriture de transactions devenait possible depuis la page elle-même, la carte resterait jusqu'au prochain montage.Preuves — mutations
Chaque mutation appliquée seule sur le code commité, restaurée par
git checkout -- <fichier>, arbre propre vérifié après chacune (21 sur 21). Rust :cargo test --locked --all-targets commands::fs_commands(rc 101 = échec de test, aucune erreur de compilation) ; vitest : suite complète.fs_commands.rsrefuses_when_tangerine_folder_exists_and_writes_nothing,…desjardins…)fs_commands.rsm1_tokenetm2_tokenéchangés dans les deux.replacewrites_both_folders_for_the_two_previous_months,crosses_the_year_boundary)fs_commands.rsimport_folder_missing→Folder does not exist: <chemin>fs_commands.rssample_folder_exists:<nom>→Folder already exists: <nom>fs_commands.rsinvalid_date→Invalid date '…': …useTransactions.ts&& !isLoading)TransactionsPage.tsxisEmptyProfileforcé àfalseuseTransactions.tsPromise.allexactuseTransactions.tsuseBudget.tsPromise.allexactuseImportWizard.tsSET_SAMPLE_CREATEDquel que soit le rescan (ancien code)useImportWizard.tsscanFolderInternalrendtruesur un échecuseImportWizard.tssampleFolderErrorne traduit rienuseImportWizard.tsuseImportWizard.tsImportPage.tsxuseImportWizard.tsSET_ERRORgarde les valeurs d'un refus précédentImportEmptyState.tsx!folderNameau lieu defolderName === nullfs_commands.rsdate_invalid), test de contratuseImportWizard.tsImportPage.tsximportFolder ? … : null)R1 : avant cette PR, la même mutation laissait les cinq tests Rust verts (constat de la revue de #380) ; le test Desjardins rougit aussi désormais, par son code asserté à l'identique.
Comptes
dad81fa: 152, 2465), sonde rc=1 (src/zz_tmp_probe.test.ts, unexpectfaux, supprimé ensuite).cargo test --locked --all-targets: 268 réussis, 1 ignoré (base : 267 et 1).npm run buildvert.generate_handler!danssrc-tauri/src/lib.rs(45 : fs 7, export_import 5, profile 8, license 11, auth 5, token_store 1, entitlements 1, feedback 2, backup 3, balance 2) ; hooks =ls src/hooks/*.ts | grep -v '\.test\.ts$' | wc -l(29).Vérifications manuelles dues (prochaine release)
Exemple Desjardins(sans extension : ni source ni fichier de relevé, l'état vide reste), cliquer « Actualiser » (le fichier n'apparaît pas) puis « Créer un dossier d'exemple » : « « Exemple Desjardins » existe déjà dans le dossier d'import : aucun dossier d'exemple n'a été créé. Actualisez la liste ; s'il n'y apparaît pas, renommez-le avant de réessayer. » (EN : « "Exemple Desjardins" already exists in the import folder: … »), et dans Paramètres → Systèmes → Journaux une seule ligne d'avertissement[import] Sample folders not created: sample_folder_exists:Exemple Desjardins, sans chemin ;useImportWizard.samples.test.ts. À tenter seulement si une manipulation se présente.À regarder par le relecteur
1ed6a67(le message « existe déjà » disait « Actualisez la liste pour le voir », faux quand l'entrée existante est un fichier, que la liste ne montre jamais : il dit désormais d'actualiser puis, si l'entrée n'apparaît pas, de la renommer ; CHANGELOG suivi) et08c2f57(formulations : paragraphe des lectures du montage, commentaire Rust des erreurs d'écriture, qui nomment le dossier d'exemple et son fichier mais jamais le chemin du dossier d'import). Suite complète, build etcargo testrejoués après eux.main) :fs_commands.rsest dans la liste « restent au moins normaux » ; aucun appel réseau ajouté ni modifié, aucun consentement touché. La lignelogWarnajoutée peut partir avec une rétroaction quand l'utilisateur coche les journaux (les lignes noninfo) : elle ne porte qu'un code ou une erreur d'écriture sur un dossier d'exemple, jamais de donnée de l'utilisateur ni de chemin (même lecture que la lignelogWarnde #372, maillon 3).SET_ERROR(null)efface l'erreur posée par les lectures du montage. Mesuré par une sonde sous le harnais, minuteries simulées (supprimée) :"count failed"après le montage,null300 ms plus tard, deux lectures de page. Avant comme après cette PR, cette erreur ne reste donc qu'un instant à l'écran sur Transactions (sur Budget elle reste) ; le critère du corps (les catégories et les sources se chargent) n'en dépend pas. Suite possible.ImportCardInput.hasFolder: même redondance que celle retirée d'ImportEmptyState, laissée (voir Décisions) ; suite possible.CLAUDE.md: seule la ligne des hooks est touchée (consigne du maillon : #446 recompte les composants dans la même arborescence). La lignecommands/y reste périmée (« 3 modules, 17 commandes Tauri », et les comptes par fichier) ;docs/architecture.mdporte les bons comptes.refuses_when_tangerine_folder_exists_and_writes_nothing: il vérifie aussi qu'aucune autre entrée n'est créée dans le dossier d'import.🤖 Generated with Claude Code
create_sample_import_folders refuses with stable codes instead of English sentences: sample_folder_exists:<name>, import_folder_missing (no longer carrying the folder's path) and invalid_date. The wizard maps them to import.empty.sampleErrors.* through the pure sampleFolderError, the folder name interpolated from the new errorValues of the state; any other message stays raw. The raw refusal also goes to the Journal as a warning: a code, or a write error naming a sample folder and the OS error, never a path. scanFolderInternal now tells whether the scan went through, and the success banner only follows a rescan that did: a failed rescan leaves its error alone on screen. ImportEmptyState keeps folderName alone (null without a folder); the page passes "" for a folder stored as "", as hasFolder did. Rust tests: the three codes asserted exactly, the case of the second sample folder already there (nothing written), and {{M1}} pinned as the most recent month line by line against the templates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>Niveau : normal
Verdict : APPROVE
Auto-revue de l'auteur (worker
/autopilot), pas une revue indépendante. Checklist depr-reviewappliquée au diff complet contreissue-437-test-pins(tête relue :08c2f57), à la profondeur du niveau normal.Niveau (REVIEW.md de
main, fichier par fichier) :src-tauri/src/commands/fs_commands.rsest dans la liste « restent au moins normaux » ;CLAUDE.mdetdocs/architecture.mdaussi ; code et tests desrc/**sans appel réseau ajouté ni modifié, sans consentement touché ; traductions normales ;CHANGELOG*.mdetdocs/guide-utilisateur.mdbas. La lignelogWarnajoutée (src/hooks/useImportWizard.ts:506) peut partir avec une rétroaction quand l'utilisateur coche les journaux : elle ne porte qu'un code ou une erreur d'écriture sur un dossier d'exemple, jamais de donnée de l'utilisateur ni le chemin du dossier d'import ; même lecture que la lignelogWarnde #372. Niveau de la PR : normal.Résumé
Les critères d'acceptation de #384, un par un :
fs_commands.rs:442,fs_commands.rs:391) et épingleTransactionsPage: rouges sous leur mutation (R1, R2, T2) ;useTransactions.ts:216) : carte dès que le compte est lu, test réécrit, rendu de page ;Promise.allSettled(useTransactions.ts:237,useBudget.ts:437) : un compte en échec ne prive plus la page de ses filtres (T3, T5) ;fs_commands.rs:228-230), traduction pure (useImportWizard.ts:88), nom interpolé parerrorValues(useImportWizard.ts:251,ImportPage.tsx:110), texte inconnu brut ;SET_SAMPLE_CREATEDaprès un rescan réussi seulement (useImportWizard.ts:501, T6, T7) ;ImportEmptyStaten'a plus quefolderName(ImportPage.tsx:155, cas""épinglé, T12, T15) ;generate_handler!, les cinq absentes documentées,fetch_priceau présent, 29 hooks = 28 lignes +useCollapsibleGroups(vérifiés par script),CLAUDE.md, guide etdocs.gettingStarted.steps.5sans « seed » ;Rejoué : 21 mutations, toutes rouges (tableau du corps) ; sonde vitest rc=1 ;
npm test155 fichiers, 2498 tests ;npm run buildvert ;cargo test --locked --all-targets268 réussis, 1 ignoré ; arbre propre.Passe vie privée faite quand même sur le Journal : chaque texte brut possible relu dans le code (trois codes, « Cannot compute… »,
Cannot create folder <exemple>: <erreur io>,Cannot write <exemple>/releve-AAAA-MM.csv: <erreur io>) ; l'erreur du rescan, qui porte le chemin, reste à l'écran ; test à quatre refus et mutation T14 (dossier d'import dans la ligne : 4 rouges). Aucun secret, aucune injection, aucune migration.Constats (corrigés en tête de PR)
src/i18n/locales/fr.json:339eten.json(import.empty.sampleErrors.folderExists) : le message disait « Actualisez la liste pour le voir », faux quand l'entrée existante est un fichier (la commande refuse surexists(), fichier compris, que la liste ne montre jamais). Corrigé dans1ed6a67: actualiser, puis renommer si l'entrée n'apparaît pas ; CHANGELOG suivi.src-tauri/src/commands/fs_commands.rs(doc decreate_sample_import_folders) : « never a path » alors qu'une erreur d'écriture nommeExemple …/releve-AAAA-MM.csv. Corrigé dans08c2f57: jamais le chemin du dossier d'import.docs/architecture.md, paragraphe des lectures du montage : jeu de mots sur « compte ». Corrigé dans08c2f57.Suggestions (non bloquantes, hors du corps)
SET_ERROR(null)de la première lecture différée de la recherche (useTransactions.ts:293), mesuré par une sonde sous le harnais ; comportement antérieur, identique avant et après. Suite possible : ne pas effacer une erreur de montage depuisfetchData.ImportCardInput.hasFolder(src/shared/types/index.ts) garde la redondance retirée d'ImportEmptyState; à retirer aveconboardingPath.tset ses tests.CLAUDE.md, lignecommands/de l'arborescence : « 3 modules, 17 commandes Tauri » et les comptes par fichier restent périmés (consigne du maillon : seule la ligne des hooks est touchée ici).useImportWizard.test.ts) ne tourne pas sur une PR qui ne touche quesrc-tauri/**(check-frontendsautée) : angle mort écrit dans le test.Vérifications manuelles dues à la prochaine release : liste dans le corps de la PR.
CI lue par l'orchestrateur du run
/autopilotdu 2026-10-18 sur la tête08c2f57: les deux contrôles sont verts.check-frontend(run 589) : 155 fichiers, 2498 tests, tous verts. Ont tourné entre autresuseImportWizard.samples.test.ts(bandeaux et les trois codes de refus),TransactionsPage.render.test.tsx(20),ImportPage.render.test.tsx(25),ImportEmptyState.test.tsx(14) etonboarding-samples.test.ts(9).check-rust(run 590) :Cache restored successfully;webkit2gtkprésent ;windows-sys:9 checks, exit 0;cargo test: 268 passés, 1 ignoré (un de plus que surmain, le nouveau test de la boucle de vérification préalable) ;cargo auditinformatif : 663 crates, aucune vulnérabilité, 18 avertissements autorisés.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.