fix(import): name a source after its subfolder, read-only (#426) #441

Merged
maximus merged 3 commits from issue-426-source-name-folder into main 2026-10-16 00:37:20 +00:00
Owner

Generated autonomously by /autopilot run of 2026-10-13 (milestone planned-2026-10-12-retours-onboarding-v0171, link 4 of 8). Stacked on #440 (issue-427-root-files-warning).

Issue #426: the closing keyword is in the last commit only.

What

A source was saved under the name typed in its configuration, but the next import looks the source, and its imported files, up by the subfolder name. A renamed source therefore looked new: its format was guessed again (against ADR 0019), every file was pre-checked, and a second source was created under the folder name.

  • SourceConfigPanel: the editable "Source name" field becomes SourceNameDisplay, a read-only display of source.folder_name with the hint import.config.sourceNameHint.
  • executeImport: the guard at the top is now if (!state.duplicateResult || !state.selectedSource) return;, and the saved name is state.selectedSource.folder_name, on create and on update, whatever config.name holds. state.selectedSource was added to the callback deps. config.name stays in place for its other readers (set to the folder name at its three write points).
  • Card sentence onboarding.card.import.sourceConfig: "Vérifiez les fichiers cochés, puis cliquez « {{button}} »." / "Check the selected files, then click “{{button}}”."
  • Last docs.import.tips entry and guide tip: "Le nom d'une source est celui de son sous-dossier" / "A source is named after its subfolder".

No migration (v1→v17 unchanged), no Rust change, no new network call, no Journal line.

Files

  • src/components/import/SourceNameDisplay.tsx (new) and SourceNameDisplay.test.tsx (new)
  • src/components/import/SourceConfigPanel.tsx, SourceConfigPanel.test.tsx (new)
  • src/hooks/useImportWizard.ts, src/hooks/useImportWizard.executeImport.test.ts
  • src/i18n/locales/fr.json, src/i18n/locales/en.json
  • CHANGELOG.md, CHANGELOG.fr.md (Fixed / Corrigé)
  • docs/guide-utilisateur.md, docs/architecture.md, docs/qa-onboarding.html (cases v0171-import-source-name, v0171-import-source-reopen)

Decisions

  • [MEDIUM] The label stays as a <span> and the name is a <p>: no <input> at all, even disabled or read-only, as the issue asks.
  • [MEDIUM] A SourceConfigPanel render test is added (beyond the plan), so the panel wiring is pinned and not only the small component.
  • [MEDIUM] docs/architecture.md: the import components count goes 16 → 18 (it was already off by one on the base, #427 did not bump it for RootFilesWarning); the global "61 composants" total, already drifted, is left as is.
  • [HIGH] Texts written word for word from the plan.

Proofs (one mutation at a time, restored by git checkout --)

# Mutation Red test(s) vitest exit
M1 createSource({ name: config.name }) creates a new source under the subfolder name 1
M2 updateSource(id, { name: config.name }) updates an existing source under the subfolder name 1
M3 guard back to if (!state.duplicateResult) return; writes nothing without a selected source 1
M4 name rendered as <input> in SourceNameDisplay has no input field; panel: shows the subfolder name with its hint 1
M5 hint removed SourceNameDisplay: shows the name, label and hint; panel: shows the subfolder name with its hint 1
M6 old editable input restored in SourceConfigPanel panel: shows the subfolder name with its hint; has no field holding the configured name 1

Validation in the worktree: npm run build green, npx vitest run 127 files / 2119 tests green. No Rust touched.

For the reviewer

  • Out of scope, as planned: sources already renamed in existing databases (testers only) keep the defect; renaming the subfolder still creates a new source.
  • The criterion "reopening finds the source, format restored, files not pre-checked" follows from the saved name now equal to the lookup key (getSourceByName(source.folder_name), importedFilesBySource keyed by import_sources.name); the lookup path itself is unchanged and is checked by hand in the QA case v0171-import-source-reopen.

🤖 Generated with Claude Code

Generated autonomously by /autopilot run of 2026-10-13 (milestone `planned-2026-10-12-retours-onboarding-v0171`, link 4 of 8). Stacked on #440 (`issue-427-root-files-warning`). Issue #426: the closing keyword is in the last commit only. ## What A source was saved under the name typed in its configuration, but the next import looks the source, and its imported files, up by the subfolder name. A renamed source therefore looked new: its format was guessed again (against ADR 0019), every file was pre-checked, and a second source was created under the folder name. - `SourceConfigPanel`: the editable "Source name" field becomes `SourceNameDisplay`, a read-only display of `source.folder_name` with the hint `import.config.sourceNameHint`. - `executeImport`: the guard at the top is now `if (!state.duplicateResult || !state.selectedSource) return;`, and the saved name is `state.selectedSource.folder_name`, on create and on update, whatever `config.name` holds. `state.selectedSource` was added to the callback deps. `config.name` stays in place for its other readers (set to the folder name at its three write points). - Card sentence `onboarding.card.import.sourceConfig`: "Vérifiez les fichiers cochés, puis cliquez « {{button}} »." / "Check the selected files, then click “{{button}}”." - Last `docs.import.tips` entry and guide tip: "Le nom d'une source est celui de son sous-dossier" / "A source is named after its subfolder". No migration (v1→v17 unchanged), no Rust change, no new network call, no Journal line. ## Files - `src/components/import/SourceNameDisplay.tsx` (new) and `SourceNameDisplay.test.tsx` (new) - `src/components/import/SourceConfigPanel.tsx`, `SourceConfigPanel.test.tsx` (new) - `src/hooks/useImportWizard.ts`, `src/hooks/useImportWizard.executeImport.test.ts` - `src/i18n/locales/fr.json`, `src/i18n/locales/en.json` - `CHANGELOG.md`, `CHANGELOG.fr.md` (Fixed / Corrigé) - `docs/guide-utilisateur.md`, `docs/architecture.md`, `docs/qa-onboarding.html` (cases `v0171-import-source-name`, `v0171-import-source-reopen`) ## Decisions - [MEDIUM] The label stays as a `<span>` and the name is a `<p>`: no `<input>` at all, even disabled or read-only, as the issue asks. - [MEDIUM] A `SourceConfigPanel` render test is added (beyond the plan), so the panel wiring is pinned and not only the small component. - [MEDIUM] `docs/architecture.md`: the import components count goes 16 → 18 (it was already off by one on the base, #427 did not bump it for `RootFilesWarning`); the global "61 composants" total, already drifted, is left as is. - [HIGH] Texts written word for word from the plan. ## Proofs (one mutation at a time, restored by `git checkout --`) | # | Mutation | Red test(s) | vitest exit | |---|---|---|---| | M1 | `createSource({ name: config.name })` | creates a new source under the subfolder name | 1 | | M2 | `updateSource(id, { name: config.name })` | updates an existing source under the subfolder name | 1 | | M3 | guard back to `if (!state.duplicateResult) return;` | writes nothing without a selected source | 1 | | M4 | name rendered as `<input>` in `SourceNameDisplay` | has no input field; panel: shows the subfolder name with its hint | 1 | | M5 | hint removed | SourceNameDisplay: shows the name, label and hint; panel: shows the subfolder name with its hint | 1 | | M6 | old editable input restored in `SourceConfigPanel` | panel: shows the subfolder name with its hint; has no field holding the configured name | 1 | Validation in the worktree: `npm run build` green, `npx vitest run` 127 files / 2119 tests green. No Rust touched. ## For the reviewer - Out of scope, as planned: sources already renamed in existing databases (testers only) keep the defect; renaming the subfolder still creates a new source. - The criterion "reopening finds the source, format restored, files not pre-checked" follows from the saved name now equal to the lookup key (`getSourceByName(source.folder_name)`, `importedFilesBySource` keyed by `import_sources.name`); the lookup path itself is unchanged and is checked by hand in the QA case `v0171-import-source-reopen`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
maximus added 3 commits 2026-10-14 01:08:41 +00:00
The source name typed during configuration was saved, but the next import
looks the source and its imported files up by the subfolder name, so a
renamed source looked new: format guessed again, every file pre-checked,
and a second source created under the folder name.

The name field becomes a read-only SourceNameDisplay showing the subfolder
name with a hint, and executeImport writes selectedSource.folder_name on
create and update, whatever config.name holds, behind a selectedSource
guard. The import card sentence no longer asks to check the source name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs(import): changelog, guide, architecture and QA cases of the read-only source name
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m56s
e177a7561f
Resolves #426

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

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

Diff read against issue-427-root-files-warning (13 files).

Acceptance criteria

  • No input field for the name anymore: SourceConfigPanel renders SourceNameDisplay (source.folder_name in a <p>, label as <span>, hint import.config.sourceNameHint). Pinned by SourceNameDisplay.test.tsx (no <input>/<textarea>/contenteditable) and SourceConfigPanel.test.tsx (the configured name Mon compte chèques appears nowhere; mutation M6, old input restored, goes red).
  • Reopening finds the source: the saved name is now selectedSource.folder_name on create and update (M1, M2 red), the same key as getSourceByName(source.folder_name) and importedFilesBySource. The lookup path is unchanged; the end-to-end reopening is the manual QA case v0171-import-source-reopen.
  • Guard !state.selectedSource: nothing written, no dispatch (M3 red). state.selectedSource added to the callback deps.
  • npm run build green, vitest 127 files / 2119 tests green. No Rust touched.

Checks

  • Security: no new input, no network, no Journal line (no folder name logged).
  • i18n: every new string in FR and EN, texts of the plan word for word; the card sentence keeps {{button}} through StepCardText.labels with « » / “ ”; onboardingPath.locales.test.ts stays green.
  • CHANGELOG: one Fixed / Corrigé entry in both files under [Unreleased].
  • No SQL migration touched. Docs: guide tip, docs.import.tips last entry (after #427's), architecture (component count, executeImport write), two QA cases.

For the human reviewer

  • Legacy data, out of scope as planned: a source renamed before this fix keeps its typed name. Edge case: if a source of folder A had been renamed B and a folder B now exists, getSourceByName("B") returns that old source as the existing source of folder B (same behaviour as before this PR; testers only).
  • config.name is still filled (at its three write points) and read by ImportPage; it is no longer written to the database.
## Self-review (autopilot worker, not an independent review): APPROVE Diff read against `issue-427-root-files-warning` (13 files). **Acceptance criteria** - [x] No input field for the name anymore: `SourceConfigPanel` renders `SourceNameDisplay` (`source.folder_name` in a `<p>`, label as `<span>`, hint `import.config.sourceNameHint`). Pinned by `SourceNameDisplay.test.tsx` (no `<input>`/`<textarea>`/`contenteditable`) and `SourceConfigPanel.test.tsx` (the configured name `Mon compte chèques` appears nowhere; mutation M6, old input restored, goes red). - [x] Reopening finds the source: the saved name is now `selectedSource.folder_name` on create and update (M1, M2 red), the same key as `getSourceByName(source.folder_name)` and `importedFilesBySource`. The lookup path is unchanged; the end-to-end reopening is the manual QA case `v0171-import-source-reopen`. - [x] Guard `!state.selectedSource`: nothing written, no dispatch (M3 red). `state.selectedSource` added to the callback deps. - [x] `npm run build` green, vitest 127 files / 2119 tests green. No Rust touched. **Checks** - Security: no new input, no network, no Journal line (no folder name logged). - i18n: every new string in FR and EN, texts of the plan word for word; the card sentence keeps `{{button}}` through `StepCardText.labels` with « » / “ ”; `onboardingPath.locales.test.ts` stays green. - CHANGELOG: one Fixed / Corrigé entry in both files under `[Unreleased]`. - No SQL migration touched. Docs: guide tip, `docs.import.tips` last entry (after #427's), architecture (component count, `executeImport` write), two QA cases. **For the human reviewer** - Legacy data, out of scope as planned: a source renamed before this fix keeps its typed name. Edge case: if a source of folder `A` had been renamed `B` and a folder `B` now exists, `getSourceByName("B")` returns that old source as the existing source of folder `B` (same behaviour as before this PR; testers only). - `config.name` is still filled (at its three write points) and read by `ImportPage`; it is no longer written to the database.
Author
Owner

CI lue par l'orchestrateur sur la tête e177a75 : PR Check — Frontend run 547 vert (build OK, vitest 127 fichiers / 2119 tests, comptes égaux à ceux du worker). Pas de job Rust attendu.

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

CI lue par l'orchestrateur sur la tête `e177a75` : **PR Check — Frontend** run 547 vert (build OK, vitest 127 fichiers / 2119 tests, comptes égaux à ceux du worker). Pas de job Rust attendu. Generated autonomously by /autopilot run of 2026-10-13
Author
Owner

Verdict : APPROVE

Résumé. Le correctif tient : executeImport écrit selectedSource.folder_name à la création comme à la mise à jour, le champ éditable disparaît au profit de SourceNameDisplay, et la clé d'écriture devient celle de la recherche (getSourceByName(source.folder_name), importedFilesBySource.get(folder_name)). Les autres lecteurs de config.name (bouton Suivant désactivé, sourceName de la confirmation, applyConfigTemplate) reçoivent toujours le nom du dossier : fresh le pose, restored reprend existing.name, égal au dossier par construction de la recherche.

Preuves rejouées par le relecteur (copie hors dépôt de e177a75, supprimée)

  • tsc --noEmit vert, vitest run : 127 fichiers, 2119 tests verts. CI de la tête : PR Check — Frontend en success (statut agrégé).
  • Mutations du relecteur, une à la fois :
    Mutation Résultat
    SourceNameDisplay folderName={config.name} dans le panneau 2 rouges (les deux tests de SourceConfigPanel.test.tsx)
    sourceName = state.existingSource?.name ?? state.sourceConfig.name 1 rouge (création) ; la mise à jour reste verte, sans défaut réel : existingSource.name vaut le dossier par la recherche
    sourceNameHint retiré de en.json 0 rouge (voir suggestion 1)

Suggestions (non bloquantes)

  1. Aucune garde ne détecte une clé i18n manquante dans une seule locale : retirer import.config.sourceNameHint de en.json laisse les 2119 tests verts (le faux t marqué rend la clé, pas sa valeur). Défaut préexistant, hors périmètre ; une garde de parité des clés FR/EN serait un suivi possible (#437 ou issue dédiée).
  2. Fermeture : la timeline de #426 ne porte aucun pull_ref en closes ; seul le commit e177a75 (« Resolves #426 ») la fermera, à son arrivée sur main. Cohérent avec le merge ff de la pile ; à vérifier après le merge.
  3. Le <span> du libellé n'est rattaché à rien (pas de <label for> possible sans champ) ; un aria-describedby n'apporte rien sur du texte statique, rien à faire, constat seulement.

Hors périmètre (annoncé par la PR, confirmé)

Une source déjà renommée dans une base existante n'est plus retrouvée : le prochain import en crée une seconde sous le nom du dossier. Touche les testeurs seulement.

Actions faites

  • Copie hors dépôt créée dans le scratchpad, suite et 3 mutations rejouées, copie supprimée.
  • Label de #426 : status:review → status:approved.
## Verdict : APPROVE **Résumé.** Le correctif tient : `executeImport` écrit `selectedSource.folder_name` à la création comme à la mise à jour, le champ éditable disparaît au profit de `SourceNameDisplay`, et la clé d'écriture devient celle de la recherche (`getSourceByName(source.folder_name)`, `importedFilesBySource.get(folder_name)`). Les autres lecteurs de `config.name` (bouton Suivant désactivé, `sourceName` de la confirmation, `applyConfigTemplate`) reçoivent toujours le nom du dossier : `fresh` le pose, `restored` reprend `existing.name`, égal au dossier par construction de la recherche. ### Preuves rejouées par le relecteur (copie hors dépôt de `e177a75`, supprimée) - `tsc --noEmit` vert, `vitest run` : 127 fichiers, 2119 tests verts. CI de la tête : `PR Check — Frontend` en `success` (statut agrégé). - Mutations du relecteur, une à la fois : | Mutation | Résultat | |---|---| | `SourceNameDisplay folderName={config.name}` dans le panneau | 2 rouges (les deux tests de `SourceConfigPanel.test.tsx`) | | `sourceName = state.existingSource?.name ?? state.sourceConfig.name` | 1 rouge (création) ; la mise à jour reste verte, sans défaut réel : `existingSource.name` vaut le dossier par la recherche | | `sourceNameHint` retiré de `en.json` | **0 rouge** (voir suggestion 1) | ### Suggestions (non bloquantes) 1. Aucune garde ne détecte une clé i18n manquante dans une seule locale : retirer `import.config.sourceNameHint` de `en.json` laisse les 2119 tests verts (le faux `t` marqué rend la clé, pas sa valeur). Défaut préexistant, hors périmètre ; une garde de parité des clés FR/EN serait un suivi possible (#437 ou issue dédiée). 2. Fermeture : la timeline de #426 ne porte aucun `pull_ref` en `closes` ; seul le commit `e177a75` (« Resolves #426 ») la fermera, à son arrivée sur `main`. Cohérent avec le merge ff de la pile ; à vérifier après le merge. 3. Le `<span>` du libellé n'est rattaché à rien (pas de `<label for>` possible sans champ) ; un `aria-describedby` n'apporte rien sur du texte statique, rien à faire, constat seulement. ### Hors périmètre (annoncé par la PR, confirmé) Une source déjà renommée dans une base existante n'est plus retrouvée : le prochain import en crée une seconde sous le nom du dossier. Touche les testeurs seulement. ### Actions faites - Copie hors dépôt créée dans le scratchpad, suite et 3 mutations rejouées, copie supprimée. - Label de #426 : `status:review` → `status:approved`.
maximus changed target branch from issue-427-root-files-warning to main 2026-10-16 00:37:11 +00:00
maximus merged commit e177a7561f into main 2026-10-16 00:37:20 +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#441
No description provided.