feat(import): warn about the CSV files placed directly in the import folder (#427) #440

Merged
maximus merged 2 commits from issue-427-root-files-warning into main 2026-10-16 00:37:10 +00:00
Owner

Generated autonomously by /autopilot run of 2026-10-13

Milestone planned-2026-10-12-retours-onboarding-v0171, link 3 of 8 (T3). Stacked on #439 (issue-422-import-per-account).

What

A CSV/TXT file placed directly in the import folder was skipped without a word. The scan now returns those files, and the Import page names them in a warning.

  • Rust scan_import_folder returns ImportFolderScan { sources, root_files }. root_files lists the non-hidden files at the root with a csv/txt extension (any case, the same has_statement_extension rule as the subfolders), sorted by name. The command is now documented.
  • TS type ImportFolderScan; SET_SCANNED_SOURCES takes the whole scan and sets scannedSources and rootFiles together; RESET keeps both (review caveat of the plan); a new scan replaces both. Exposed as state.rootFiles.
  • formatRootFiles(names, max = 5) → { shown, more } (src/utils/rootFiles.ts).
  • RootFilesWarning: AlertTriangle (aria-hidden), no live region, message import.rootFiles.message_one/_other with {{folder}} = last segment of the folder (never the path) and {{names}} = five names then import.rootFiles.more.
  • ImportPage: above the source list when there are sources, under the "no source" empty state otherwise, never without a folder, never on the wizard steps, never before the folder is read.
  • Journal: no line written; the mount test captures the real log console and checks that neither a file name nor the folder path lands in it.
  • Docs: docs.import.tips (FR/EN) + guide (Import tips), architecture.md (command line, useImportWizard row), three QA cases in the "Retours de la v0.17.1" section of docs/qa-onboarding.html. CHANGELOG Added / Ajouté in both files.

Files

src-tauri/src/commands/fs_commands.rs (command + 5 tests), src/shared/types/index.ts, src/hooks/useImportWizard.ts + .test.ts + .mount.test.ts, src/utils/rootFiles.ts + test, src/components/import/RootFilesWarning.tsx + .test.tsx + .locales.test.tsx, src/pages/ImportPage.tsx + .render.test.tsx, src/i18n/locales/fr.json, en.json, docs/architecture.md, docs/guide-utilisateur.md, docs/qa-onboarding.html, CHANGELOG.md, CHANGELOG.fr.md.

Decisions

  • [HIGH] rootFiles exposed through state, like scannedSources.
  • [HIGH] Extension rule factored into has_statement_extension, shared by root and subfolders.
  • [MEDIUM] Warning styled like the duplicate-rows notice (accent border), not the error banner: nothing failed.
  • [MEDIUM] Extra test through the real locales (RootFilesWarning.locales.test.tsx): the marked fake t cannot see the _one/_other resolution nor the quotes.
  • [HIGH] CHANGELOG under a new ### Added section, first under [Unreleased].

Acceptance criteria → proof

Criterion Test Mutation Result
a.csv at root + visa/ → source + warning naming a.csv Rust lists_the_root_statement_files_sorted_by_name; ImportPage.render "with sources" M5 warning moved under the list red (rc=1)
No subfolder, three root CSV → "no source" + warning naming the three ImportPage.render "without source" M6 folder guard removed (no-folder case) red (rc=1)
Sorted by name Rust sorted test R1 root_files.sort() removed red (rc=101)
Hidden files and other extensions ignored, case rule Rust ignores_other_extensions_and_hidden_files_at_the_root R2 hidden kept / R3 any extension / R4 case-sensitive red (rc=101) x3
One action sets both reducer "sets the sources and the root files together" M1 reducer ignores root_files red (rc=1)
"Cancel"/"Done" keep the warning reducer "RESET keeps both" M2 RESET drops rootFiles red (rc=1)
"Refresh" re-reads and updates reducer "a new scan replaces both" (covered by M1) M1 red
Five names then "and 2 more" formatRootFiles 0/1/5/7; warning tests M3 max 5→6; M4 suffix dropped; M8 more_other collapsed to singular red (rc=1) x3
No Journal line with file name or path mount test with real log capture M7 logInfo of the root files added red (rc=1)

Validation: npm run build green, npm test 125 files / 2112 tests, cargo test 267 passed (1 ignored). No migration, no network call.

🤖 Generated with Claude Code

Generated autonomously by /autopilot run of 2026-10-13 Milestone `planned-2026-10-12-retours-onboarding-v0171`, link 3 of 8 (T3). Stacked on #439 (`issue-422-import-per-account`). ## What A CSV/TXT file placed directly in the import folder was skipped without a word. The scan now returns those files, and the Import page names them in a warning. - **Rust** `scan_import_folder` returns `ImportFolderScan { sources, root_files }`. `root_files` lists the non-hidden files at the root with a `csv`/`txt` extension (any case, the same `has_statement_extension` rule as the subfolders), sorted by name. The command is now documented. - **TS** type `ImportFolderScan`; `SET_SCANNED_SOURCES` takes the whole scan and sets `scannedSources` and `rootFiles` together; `RESET` keeps both (review caveat of the plan); a new scan replaces both. Exposed as `state.rootFiles`. - `formatRootFiles(names, max = 5)` → `{ shown, more }` (`src/utils/rootFiles.ts`). - `RootFilesWarning`: `AlertTriangle` (`aria-hidden`), no live region, message `import.rootFiles.message_one/_other` with `{{folder}}` = last segment of the folder (never the path) and `{{names}}` = five names then `import.rootFiles.more`. - `ImportPage`: above the source list when there are sources, under the "no source" empty state otherwise, never without a folder, never on the wizard steps, never before the folder is read. - Journal: no line written; the mount test captures the real log console and checks that neither a file name nor the folder path lands in it. - Docs: `docs.import.tips` (FR/EN) + guide (Import tips), `architecture.md` (command line, `useImportWizard` row), three QA cases in the "Retours de la v0.17.1" section of `docs/qa-onboarding.html`. CHANGELOG Added / Ajouté in both files. ## Files `src-tauri/src/commands/fs_commands.rs` (command + 5 tests), `src/shared/types/index.ts`, `src/hooks/useImportWizard.ts` + `.test.ts` + `.mount.test.ts`, `src/utils/rootFiles.ts` + test, `src/components/import/RootFilesWarning.tsx` + `.test.tsx` + `.locales.test.tsx`, `src/pages/ImportPage.tsx` + `.render.test.tsx`, `src/i18n/locales/fr.json`, `en.json`, `docs/architecture.md`, `docs/guide-utilisateur.md`, `docs/qa-onboarding.html`, `CHANGELOG.md`, `CHANGELOG.fr.md`. ## Decisions - [HIGH] `rootFiles` exposed through `state`, like `scannedSources`. - [HIGH] Extension rule factored into `has_statement_extension`, shared by root and subfolders. - [MEDIUM] Warning styled like the duplicate-rows notice (accent border), not the error banner: nothing failed. - [MEDIUM] Extra test through the real locales (`RootFilesWarning.locales.test.tsx`): the marked fake `t` cannot see the `_one`/`_other` resolution nor the quotes. - [HIGH] CHANGELOG under a new `### Added` section, first under [Unreleased]. ## Acceptance criteria → proof | Criterion | Test | Mutation | Result | |---|---|---|---| | `a.csv` at root + `visa/` → source + warning naming `a.csv` | Rust `lists_the_root_statement_files_sorted_by_name`; `ImportPage.render` "with sources" | M5 warning moved under the list | red (rc=1) | | No subfolder, three root CSV → "no source" + warning naming the three | `ImportPage.render` "without source" | M6 folder guard removed (no-folder case) | red (rc=1) | | Sorted by name | Rust sorted test | R1 `root_files.sort()` removed | red (rc=101) | | Hidden files and other extensions ignored, case rule | Rust `ignores_other_extensions_and_hidden_files_at_the_root` | R2 hidden kept / R3 any extension / R4 case-sensitive | red (rc=101) x3 | | One action sets both | reducer "sets the sources and the root files together" | M1 reducer ignores `root_files` | red (rc=1) | | "Cancel"/"Done" keep the warning | reducer "RESET keeps both" | M2 RESET drops `rootFiles` | red (rc=1) | | "Refresh" re-reads and updates | reducer "a new scan replaces both" (covered by M1) | M1 | red | | Five names then "and 2 more" | `formatRootFiles` 0/1/5/7; warning tests | M3 max 5→6; M4 suffix dropped; M8 `more_other` collapsed to singular | red (rc=1) x3 | | No Journal line with file name or path | mount test with real log capture | M7 `logInfo` of the root files added | red (rc=1) | Validation: `npm run build` green, `npm test` 125 files / 2112 tests, `cargo test` 267 passed (1 ignored). No migration, no network call. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
maximus added 2 commits 2026-10-14 01:02:45 +00:00
scan_import_folder now returns ImportFolderScan { sources, root_files }:
the CSV/TXT files at the root, which no source reads, are named in a
warning on the Import page (above the source list, or under the "no
source" empty state). One action sets sources and root files, RESET keeps
both. Names stay on screen, never in the Journal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs(import): changelog, guide, architecture and QA cases of the root files warning
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m56s
PR Check — Rust / rust (pull_request) Successful in 3m41s
2de4d9d255
Resolves #427

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
maximus added the
autopilot:pending-human
label 2026-10-14 01:02:52 +00:00
Author
Owner

Self-review (autopilot worker, not an independent review) — verdict: APPROVE

Diff read in full against issue-422-import-per-account (head 2de4d9d).

Acceptance criteria

  • a.csv at the root + visa/: source visa and the warning naming a.csv, above the list (Rust test + ImportPage.render, mutation M5 red).
  • No subfolder, three root CSV: "no source" empty state, warning under it naming the three (render test, M6 red for the no-folder guard).
  • No root file → no warning; a new scan ("Refresh") replaces both fields; RESET ("Cancel", "Done") keeps both (reducer tests, M1/M2 red).
  • No Journal line with a file name or the folder path: nothing is logged; pinned by the mount test reading the real log capture (M7 red).
  • cargo test (267 passed, 1 ignored), npm run build, npm test (125 files / 2112 tests) green.

Security — names and folder stay on screen; {{folder}} is folderNameFromPath, never the path (render test asserts folder=/home is absent). No new Tauri command, no new capability, no network call. The scan error text (with the path) still goes to the on-screen error banner, unchanged from before.

Correctness / regressions — single caller of scan_import_folder (scanFolderInternal), updated; the reducer test fixtures of #356/#384 moved to the new payload shape with no assertion changed. The extension rule is one function for root and subfolders, so the case rule cannot drift. Sorting is byte order, as for the sources.

i18n — four keys FR/EN word for word from the plan, plural forms checked through the real locales (RootFilesWarning.locales.test.tsx, M8 red on more_other); docs.import.tips appended last in both locales and the same tip in the guide.

CHANGELOG / docs — Added / Ajouté in both files; architecture.md command line as worded in the plan + useImportWizard row; three QA cases appended to "Retours de la v0.17.1". No migration touched.

For the reviewer — the warning uses the accent border of the duplicate-rows notice rather than the error banner's negative border (nothing failed); under the empty state it is wrapped in mt-4. The Rust sort test relies on read_dir order differing from name order to prove the sort (R1 went red on ext4 here).

## Self-review (autopilot worker, not an independent review) — verdict: APPROVE Diff read in full against `issue-422-import-per-account` (head `2de4d9d`). **Acceptance criteria** - [x] `a.csv` at the root + `visa/`: source `visa` and the warning naming `a.csv`, above the list (Rust test + `ImportPage.render`, mutation M5 red). - [x] No subfolder, three root CSV: "no source" empty state, warning under it naming the three (render test, M6 red for the no-folder guard). - [x] No root file → no warning; a new scan ("Refresh") replaces both fields; `RESET` ("Cancel", "Done") keeps both (reducer tests, M1/M2 red). - [x] No Journal line with a file name or the folder path: nothing is logged; pinned by the mount test reading the real log capture (M7 red). - [x] `cargo test` (267 passed, 1 ignored), `npm run build`, `npm test` (125 files / 2112 tests) green. **Security** — names and folder stay on screen; `{{folder}}` is `folderNameFromPath`, never the path (render test asserts `folder=/home` is absent). No new Tauri command, no new capability, no network call. The scan error text (with the path) still goes to the on-screen error banner, unchanged from before. **Correctness / regressions** — single caller of `scan_import_folder` (`scanFolderInternal`), updated; the reducer test fixtures of #356/#384 moved to the new payload shape with no assertion changed. The extension rule is one function for root and subfolders, so the case rule cannot drift. Sorting is byte order, as for the sources. **i18n** — four keys FR/EN word for word from the plan, plural forms checked through the real locales (`RootFilesWarning.locales.test.tsx`, M8 red on `more_other`); `docs.import.tips` appended last in both locales and the same tip in the guide. **CHANGELOG / docs** — Added / Ajouté in both files; `architecture.md` command line as worded in the plan + `useImportWizard` row; three QA cases appended to "Retours de la v0.17.1". No migration touched. **For the reviewer** — the warning uses the accent border of the duplicate-rows notice rather than the error banner's negative border (nothing failed); under the empty state it is wrapped in `mt-4`. The Rust sort test relies on `read_dir` order differing from name order to prove the sort (R1 went red on ext4 here).
Author
Owner

CI lue par l'orchestrateur sur la tête 2de4d9d :

  • PR Check — Frontend run 545 vert : build OK, vitest 125 fichiers / 2112 tests (comptes égaux à ceux du worker).
  • PR Check — Rust run 546 vert : cache restauré, garde windows-sys 9 vérifications à 0, cargo test 267 passés et 1 ignoré (comptes égaux à ceux du worker).

Generated autonomously by /autopilot run of 2026-10-13

CI lue par l'orchestrateur sur la tête `2de4d9d` : - **PR Check — Frontend** run 545 vert : build OK, vitest 125 fichiers / 2112 tests (comptes égaux à ceux du worker). - **PR Check — Rust** run 546 vert : cache restauré, garde `windows-sys` 9 vérifications à 0, `cargo test` 267 passés et 1 ignoré (comptes égaux à ceux du worker). Generated autonomously by /autopilot run of 2026-10-13
Author
Owner

/pr-review — APPROVE

Résumé : le scan rend les CSV/TXT de la racine, une seule action pose sources et rootFiles, RESET garde les deux, et l'avertissement s'affiche aux deux endroits prévus, avec le dernier segment du dossier et sans rien écrire au Journal. Conforme au corps de #427, textes FR/EN mot pour mot.

Exécuté par le relecteur (worktree npm ci propre sur 2de4d9d)

  • vitest run : 125 fichiers, 2112 tests verts. cargo test --lib scan_import_folder_tests : 5 verts. CI de la tête verte (frontend et rust, statut agrégé success).
  • Mutations du relecteur, une à la fois, arbre restauré après chacune :
Mutation Résultat
count = nombre de noms affichés au lieu du total 3 rouges
chemin complet passé en folderName 2 rouges
espace avant « et N autres » retiré 4 rouges
avertissement déplacé sous la liste des sources 1 rouge
logInfo des noms de la racine après le scan 1 rouge
logInfo du chemin du dossier après le scan 1 rouge
fichiers des sous-dossiers ajoutés aussi à root_files 2 rouges (Rust)
path.is_file() retiré de la garde de la racine vert, mutation équivalente : la branche est déjà sous !path.is_dir(), seul un lien cassé ou une entrée spéciale la distingue

Une première version de la mutation du Journal appelait logWarn, non importé : l'erreur partait dans le catch et le test restait vert. Refaite avec logInfo, déjà importé : rouge. Le test du Journal tient donc.

Suggestions (non bloquantes)

  1. Fermeture de #427 : Resolves #427 n'est que dans le message du commit 2de4d9d, pas dans le corps de la PR ; la timeline de #427 porte ref_action: "none" pour cette PR. Un merge ff (commits poussés sur main) fermera l'issue ; l'ajouter au corps rend la fermeture indépendante du chemin de merge.
  2. Erreur de scan après un changement de dossier : SET_ERROR garde les rootFiles du scan précédent, alors que importFolder a déjà changé, donc l'avertissement peut nommer d'anciens fichiers sous le nom du nouveau dossier. Même comportement que scannedSources avant cette PR, et le test « a scan error leaves the root files of the last scan in place » l'épingle exprès : à garder en tête plutôt qu'à corriger ici.

Aucune migration, aucun secret, pas de skip/only, commits au format conventionnel.

## /pr-review — APPROVE **Résumé** : le scan rend les CSV/TXT de la racine, une seule action pose sources et `rootFiles`, `RESET` garde les deux, et l'avertissement s'affiche aux deux endroits prévus, avec le dernier segment du dossier et sans rien écrire au Journal. Conforme au corps de #427, textes FR/EN mot pour mot. ### Exécuté par le relecteur (worktree `npm ci` propre sur `2de4d9d`) - `vitest run` : 125 fichiers, 2112 tests verts. `cargo test --lib scan_import_folder_tests` : 5 verts. CI de la tête verte (frontend et rust, statut agrégé `success`). - Mutations du relecteur, une à la fois, arbre restauré après chacune : | Mutation | Résultat | |---|---| | `count` = nombre de noms affichés au lieu du total | 3 rouges | | chemin complet passé en `folderName` | 2 rouges | | espace avant « et N autres » retiré | 4 rouges | | avertissement déplacé sous la liste des sources | 1 rouge | | `logInfo` des noms de la racine après le scan | 1 rouge | | `logInfo` du chemin du dossier après le scan | 1 rouge | | fichiers des sous-dossiers ajoutés aussi à `root_files` | 2 rouges (Rust) | | `path.is_file()` retiré de la garde de la racine | vert, mutation équivalente : la branche est déjà sous `!path.is_dir()`, seul un lien cassé ou une entrée spéciale la distingue | Une première version de la mutation du Journal appelait `logWarn`, non importé : l'erreur partait dans le `catch` et le test restait vert. Refaite avec `logInfo`, déjà importé : rouge. Le test du Journal tient donc. ### Suggestions (non bloquantes) 1. **Fermeture de #427** : `Resolves #427` n'est que dans le message du commit `2de4d9d`, pas dans le corps de la PR ; la timeline de #427 porte `ref_action: "none"` pour cette PR. Un merge ff (commits poussés sur `main`) fermera l'issue ; l'ajouter au corps rend la fermeture indépendante du chemin de merge. 2. **Erreur de scan après un changement de dossier** : `SET_ERROR` garde les `rootFiles` du scan précédent, alors que `importFolder` a déjà changé, donc l'avertissement peut nommer d'anciens fichiers sous le nom du nouveau dossier. Même comportement que `scannedSources` avant cette PR, et le test « a scan error leaves the root files of the last scan in place » l'épingle exprès : à garder en tête plutôt qu'à corriger ici. Aucune migration, aucun secret, pas de `skip`/`only`, commits au format conventionnel.
maximus changed target branch from issue-422-import-per-account to main 2026-10-16 00:37:00 +00:00
maximus merged commit 2de4d9d255 into main 2026-10-16 00:37:10 +00:00
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#440
No description provided.