docs: architecture, ADR 0019, user guide and changelog for the import format #342

Closed
maximus wants to merge 4 commits from issue-332-docs-adr-changelog into issue-331-sref-import-sources
Owner

Link 10 of 10 — the last one — of the import-format stack. Based on issue-331-sref-import-sources, not on main. Review the top commit only.

Resolves #332

Links 1-9 wrote no changelog and no documentation on purpose (spec-plan-import-csv-format.md: a per-PR entry would conflict at every level of a ten-deep linear pile). This link owes all of it. The nine PR bodies were read to write the entries, rather than the issue bodies — several links revised their own issue's framing along the way.

ADR 0019 — the import format is a persisted value, never re-inferred

The chantier's three failure paths were independent, which is the point of giving them one ADR rather than three fixes:

  1. Hardcoded restore — reopening a configured source re-derived the mode and wrote signConvention: "negative_expense" outright (#324).
  2. Header drift — the bank moves a column, the stored mapping keeps pointing at positions that no longer mean the same thing (#330).
  3. Data exportDELETE FROM import_sources on restore, replaced by a synthetic "Data Import" source (#331).

The ADR records why the completeness guarantee comes from the codec and its test, not from a composed type: ImportSource.has_header is boolean and ImportConfigTemplate.has_header is a number, so a shared shape is structurally impossible. FORMAT_FIELD_PAIRS typed Record<keyof ImportFormat, keyof ImportFormatRow> fails to build on an unlisted field; the test fails on a field listed but not wired. It also records the decision that template_id is a provenance label, never re-read as format — deriving the format from it would hand the format back to a mutable carrier, which is the thing being removed.

Status Accepted. The Spec: line points at the two root spec files — both already tracked (force-added in b30c9fa, the base of this stack), verified with git ls-files before writing the line, so the dead-Spec:-line precedent from ADR 0016 / #295 does not repeat here. No git add -f was needed.

Counts — measured against the tree, not derived by arithmetic

Fact Documented Measured Action
Migrations 16 (v1→v16) 17 (Migration { version: 17 } + V17_SQL) corrected in both files
src/components/import/ 13 14 corrected in both files
Tables / indexes 20 / 24 20 / 24 not stale — left alone
src/utils/ 4 13 corrected in architecture.md

The tables/indexes line is the one worth flagging: v17 is an ALTER TABLE only, so it adds neither. The counts were re-measured on consolidated_schema.sql rather than assumed to have moved with the migration, and the reason they did not is now written into CLAUDE.md so the next reader does not re-derive it.

CLAUDE.md's tree also said lib.rs # ... 7 migrations inline — not named in the issue, but it is the same fact as the migration count 60 lines below, so leaving it would have made the file contradict itself inside this very edit. Corrected.

ADR 0018 was missing from architecture.md's ADR table (the file exists, the row did not). Added alongside 0019.

Deliberately not fixed — the rest of the count drift in CLAUDE.md

Measured while verifying: 120 components (documented 53), 27 hooks (13), 20 pages (11), 24 services (14), 6 Tauri command modules / 36 commands (3 / 17). All stale, none of it caused by this chantier. Rewriting that tree is a separate change and would bury the import edits in noise, so only the two numbers this chantier actually moved were touched. Flagged here so the measurement is not lost.

Changelog

Grouped by user-visible outcome, not one bullet per issue — a user reads "my amounts import correctly now", not nine internal refactors. #326 (fixtures) and #323 (migration) have no user-visible behaviour at all and get no bullet of their own; their numbers ride on the entries whose behaviour they enable, so nothing becomes untraceable. Added / Changed / Fixed; no Security section (no advisory here).

Parity between the two files was verified mechanically, not by eye: same sections, same bullet counts, and the same issue references in the same order, section by section — 5 Added / 2 Changed / 7 Fixed / 2 Security on both sides. The public/ copies sync via syncChangelogs() in vite.config.ts (they are gitignored; the build regenerated them and the new French text is present).

Guide and docs.*

docs/guide-utilisateur.md §4 and docs.import.* in both locales carry the same 9 features / 8 steps / 9 tips — the in-app guide renders from the JSON, the markdown is its printable twin, and divergence between them is the failure mode. Locale files were edited through a JSON round-trip proven byte-identical on the untouched files first, so the diff is 26 lines in each and nothing was reformatted.

Two things a user needs to find in the guide rather than only in a toast: the absolute_indicator refusal (the one case where the app now stops instead of importing) and the repair path (delete the faulty import, then replay — re-importing on top doubles the rows).

Verification

  • npm test1176 passed (60 files), unchanged from link 9's baseline. Docs-only PR: it adds no test and must move none.
  • npm run build — tsc + vite clean.
  • cd src-tauri && cargo check — clean. No Rust touched, no DB migration (v1→v17 as link 2 left it).

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

Link **10 of 10** — the last one — of the import-format stack. **Based on `issue-331-sref-import-sources`, not on `main`.** Review the top commit only. Resolves #332 Links 1-9 wrote no changelog and no documentation on purpose (`spec-plan-import-csv-format.md`: a per-PR entry would conflict at every level of a ten-deep linear pile). This link owes all of it. The nine PR bodies were read to write the entries, rather than the issue bodies — several links revised their own issue's framing along the way. ## ADR 0019 — the import format is a persisted value, never re-inferred The chantier's three failure paths were independent, which is the point of giving them one ADR rather than three fixes: 1. **Hardcoded restore** — reopening a configured source re-derived the mode and wrote `signConvention: "negative_expense"` outright (#324). 2. **Header drift** — the bank moves a column, the stored mapping keeps pointing at positions that no longer mean the same thing (#330). 3. **Data export** — `DELETE FROM import_sources` on restore, replaced by a synthetic "Data Import" source (#331). The ADR records **why the completeness guarantee comes from the codec and its test, not from a composed type**: `ImportSource.has_header` is `boolean` and `ImportConfigTemplate.has_header` is a `number`, so a shared shape is structurally impossible. `FORMAT_FIELD_PAIRS` typed `Record<keyof ImportFormat, keyof ImportFormatRow>` fails to *build* on an unlisted field; the test fails on a field listed but not wired. It also records the decision that **`template_id` is a provenance label, never re-read as format** — deriving the format from it would hand the format back to a mutable carrier, which is the thing being removed. Status Accepted. The `Spec:` line points at the two root spec files — **both already tracked** (force-added in `b30c9fa`, the base of this stack), verified with `git ls-files` *before* writing the line, so the dead-`Spec:`-line precedent from ADR 0016 / #295 does not repeat here. No `git add -f` was needed. ## Counts — measured against the tree, not derived by arithmetic | Fact | Documented | Measured | Action | |---|---|---|---| | Migrations | 16 (`v1→v16`) | **17** (`Migration { version: 17 }` + `V17_SQL`) | corrected in both files | | `src/components/import/` | 13 | **14** | corrected in both files | | Tables / indexes | 20 / 24 | **20 / 24** | **not stale — left alone** | | `src/utils/` | 4 | **13** | corrected in `architecture.md` | The tables/indexes line is the one worth flagging: v17 is an `ALTER TABLE` only, so it adds neither. The counts were re-measured on `consolidated_schema.sql` rather than assumed to have moved with the migration, and the reason they did not is now written into `CLAUDE.md` so the next reader does not re-derive it. `CLAUDE.md`'s tree also said **`lib.rs # ... 7 migrations inline`** — not named in the issue, but it is the same fact as the migration count 60 lines below, so leaving it would have made the file contradict itself inside this very edit. Corrected. **ADR 0018 was missing from `architecture.md`'s ADR table** (the file exists, the row did not). Added alongside 0019. ## Deliberately not fixed — the rest of the count drift in `CLAUDE.md` Measured while verifying: **120 components** (documented 53), **27 hooks** (13), **20 pages** (11), **24 services** (14), **6 Tauri command modules / 36 commands** (3 / 17). All stale, none of it caused by this chantier. Rewriting that tree is a separate change and would bury the import edits in noise, so only the two numbers this chantier actually moved were touched. Flagged here so the measurement is not lost. ## Changelog Grouped by user-visible outcome, not one bullet per issue — a user reads "my amounts import correctly now", not nine internal refactors. **#326 (fixtures) and #323 (migration) have no user-visible behaviour at all** and get no bullet of their own; their numbers ride on the entries whose behaviour they enable, so nothing becomes untraceable. Added / Changed / Fixed; no Security section (no advisory here). Parity between the two files was **verified mechanically**, not by eye: same sections, same bullet counts, and the same issue references in the same order, section by section — 5 Added / 2 Changed / 7 Fixed / 2 Security on both sides. The `public/` copies sync via `syncChangelogs()` in `vite.config.ts` (they are gitignored; the build regenerated them and the new French text is present). ## Guide and `docs.*` `docs/guide-utilisateur.md` §4 and `docs.import.*` in both locales carry the **same** 9 features / 8 steps / 9 tips — the in-app guide renders from the JSON, the markdown is its printable twin, and divergence between them is the failure mode. Locale files were edited through a JSON round-trip proven byte-identical on the untouched files first, so the diff is 26 lines in each and nothing was reformatted. Two things a user needs to find in the guide rather than only in a toast: the **`absolute_indicator` refusal** (the one case where the app now stops instead of importing) and the **repair path** (delete the faulty import, then replay — re-importing on top doubles the rows). ## Verification - `npm test` — **1176 passed** (60 files), unchanged from link 9's baseline. Docs-only PR: it adds no test and must move none. - `npm run build` — tsc + vite clean. - `cd src-tauri && cargo check` — clean. No Rust touched, no DB migration (v1→v17 as link 2 left it). --- Generated autonomously by /autopilot run of 2026-08-13
maximus added 1 commit 2026-08-14 15:14:15 +00:00
docs: architecture, ADR 0019, user guide and changelog for the import format
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m38s
e2b8eb8b22
Last link of the ten-link import-format stack. Links 1-9 deliberately wrote
no changelog and no documentation, to avoid a conflict at every level of a
linear pile; this link owes all of it.

- ADR 0019 records the structuring decision: the import format is a fully
  persisted value, never re-inferred. It documents the three independent paths
  by which it used to be lost (hardcoded restore, header drift, data export),
  and why a single codec with a completeness test closes the class rather than
  a composed type -- the two carriers are structurally incompatible
  (`has_header` is boolean on one and number on the other). `template_id` is
  recorded as a provenance label, never re-read as format.
- `docs/architecture.md` gains a dedicated "Import CSV" section covering the
  codec, the lexical detection layer and its separate dictionary module, the
  bank signatures, the now-mandatory preview step, and sources/templates in
  the SREF envelope. Migration v17 and its four CHECK-guarded columns are
  listed in the migrations table.
- Stale counts corrected against the tree, not by arithmetic: 16 -> 17
  migrations (both files), `src/components/import/` 13 -> 14, `src/utils/`
  4 -> 13. Tables (20) and indexes (24) were re-measured and are NOT stale --
  v17 is an ALTER TABLE only -- so they are left as they are, with the reason
  written down. ADR 0018, missing from the ADR table, is added.
- The user guide and the `docs.*` keys in both locales carry the same new
  import journey: automatic detection, confidence score, mandatory preview
  with its signed recap, sign inversion, recognised bank formats, drift panel,
  and the safe repair path.
- Both changelogs carry the same entries, translated, verified section by
  section including issue references. The `public/` copies sync automatically
  via `syncChangelogs()`.

1176 vitest green, build clean, cargo check clean. No DB migration.

Resolves #332

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

Revue adversariale — PR #342 (maillon 10/10, documentation)

Verdict : APPROVE

Résumé

PR docs-only conforme : 8 fichiers, aucun fichier source ni test touché (les deux locales le sont, mais c'est le contrat — le guide in-app se rend depuis docs.*). J'ai re-mesuré chaque compteur revendiqué contre l'arbre plutôt que de relire la table du corps de PR, résolu chaque lien de l'ADR, comparé les deux CHANGELOG mécaniquement, et sondé une vingtaine d'affirmations techniques contre le code du stack. Aucune n'est fausse. Rien de bloquant.

Ce que j'ai re-mesuré moi-même

Fait Documenté Mesuré
Migrations 17 (v1→v17) 17 entrées Migration { version: n }, v17 dernière
src/components/import/ 14 14 fichiers exactement
src/utils/ 13 13 hors tests (23 avec)
Tables / index 20 / 24, inchangés 20 CREATE TABLE + 24 CREATE INDEX dans consolidated_schema.sql

Le point le plus facile à rater est le bon : v17 ne contient que quatre ALTER TABLE + un UPDATE, donc laisser 20/24 est exact, et la justification écrite en CLAUDE.md:104 est vérifiable par un lecteur futur au lieu d'être à re-dériver. La correction du « 7 migrations inline » de l'arbre (CLAUDE.md:84) supprime une contradiction interne réelle avec la ligne 60 lignes plus bas.

  • Le delta de composants s'explique : FilePreviewModal.tsx supprimé, FormatDriftPanel.tsx + RepairPathNotice.tsx ajoutés → 13 − 1 + 2 = 14.
  • v1→v16 sont intactes dans lib.rs (le diff main..HEAD n'ajoute que v17 et des constantes de test).

Liens de l'ADR

spec-decisions-import-csv-format.md et spec-plan-import-csv-format.md sont bien dans git ls-tree de la branche malgré .gitignore:71-72les deux liens Spec: résolvent, le précédent ADR 0016 / #295 ne se répète pas. Les trois autres liens de l'ADR (0003, 0004, 0017) existent, ainsi que les deux lignes ajoutées à la table des ADR de architecture.md (0018, 0019) ; la date 2026-07-27 portée à la ligne 0018 est celle de son propre en-tête.

Parité CHANGELOG et i18n

  • 5 Added / 2 Changed / 7 Fixed / 2 Security des deux côtés, et surtout les mêmes références d'issue dans le même ordre, bullet par bullet : #327+#328 | #329 | #330 | #331 | #259 puis #323+#324+#328 | #297-#301 puis #327 | #325 | #325 | #325 | #327 | #325+#327 | #331 puis #310 | #311. Ordre Keep a Changelog respecté (Fixed avant Security).
  • i18n en lockstep : le diff des chemins de scalaires entre en.json et fr.json est vide (structures strictement identiques), 9 features / 8 steps / 9 tips dans chaque locale, et guide-utilisateur.md §4 porte 9/8/9 aussi. Le mode d'échec connu (divergence des compteurs entre le JSON et son jumeau imprimable) n'est pas présent.

Fidélité de la prose au code (sondage)

mapRow calcule bien crédit − débit sur magnitudes et n'échoue que si les deux colonnes sont illisibles — la doc dit « dans les deux colonnes », pas plus ; parseFrenchAmount est ancré (100,00 CAD → NaN, (50,00) et 50,00- → −50) et oui, la doc énonce que ce cas produit désormais une ligne en erreur, dans les deux CHANGELOG et dans architecture.md ; séparateur décimal arbitré par colonne ; garde !existing (useImportWizard.ts:503-508) ; file-preview est sur l'unique chemin avant (le seul SET_STEP → duplicate-check part de checkDuplicates, donc « traversée à chaque import » est exact, pas rhétorique) ; config écrite à executeImport seul ; flipSignFormat échange les indices en debit_credit ; sélecteur de convention masqué et non réinitialisé (SourceConfigPanel.tsx:358) ; header_signature = tableau JSON de libellés normalisés ; MIN_SIGNATURE_LABELS = 4, MIN_HEADER_ROLE_MATCHES = 2, CONFIDENCE_THRESHOLD = 0.9, SREF_FORMAT_VERSION = 2 ; modèles restaurés avant les sources, upsert par nom, remap template_id, whitelist AMOUNT_MODES/SIGN_CONVENTIONS, withTransaction ; 15 fixtures CSV, exactement celles énumérées ; VALUE_HEADER_KEYWORDS contient bien montant (la raison invoquée pour séparer headerDictionary.ts est réelle) ; « éditer le modèle n'altère aucune source liée » est un vrai test d'intégration.

Le récit des trois voies de perte tient : la voie 1 de l'ADR correspond au code de main mot pour mot (amountMode: mapping.debitAmount !== undefined ? "debit_credit" : "single" puis signConvention: "negative_expense", useImportWizard.ts:321-323), la voie 2 est câblée dans bankSignatures.ts + FormatDriftPanel, la voie 3 dans dataExportService.ts.

Blocages

Aucun.

Suggestions (aucune bloquante)

  1. CHANGELOG.md:15 / CHANGELOG.fr.md:15 — classement de l'entrée #323/#324. C'est le correctif racine du chantier (une source réglée en dépenses positives revenait au défaut au 2e import → dépenses importées en revenus) et il est sous Changed/Modifié. Le bullet est mixte — nouveau modèle de persistance, sélecteur masqué, écriture différée — donc le choix se défend, mais le lecteur des notes de version qui cherche « mon bug de signes est-il réglé ? » ouvre Fixed en premier. Option : scinder, la moitié « re-déduisait le mode / remettait silencieusement la convention sur négatif » sous Fixed, le reste sous Changed.
  2. CHANGELOG.md:16 / CHANGELOG.fr.md:16 — une ligne vide a été insérée entre les deux bullets de Changed/Modifié, seule section où c'est le cas. Markdown en fait une liste loose (chaque item enveloppé dans un <p>), donc un rendu différent des autres sections sur /changelog. À retirer.
  3. docs/guide-utilisateur.md:493 et docs.settings.features — §11 énonce encore « Export des données (transactions, catégories, ou les deux) ». Depuis #331 l'enveloppe emporte aussi les sources et les modèles. L'information existe en §4 (astuce, ligne 133), mais l'utilisateur qui veut savoir ce que contient sa sauvegarde lit §11.
  4. docs/guide-utilisateur.md:124-137 — le rejet d'un montant à suffixe est un changement visible pour l'utilisateur : une ligne qui passait sans bruit (à 10 000,00) échoue désormais. C'est dans les deux CHANGELOG et dans architecture.md, pas dans le guide, alors que le refus D/C y est (ligne 130). Une astuce de plus suffirait.
  5. Dérive de compteurs pré-existante, dans les blocs mêmes que la PR édite : docs/architecture.md:29 (58 composants → 127 hors tests), :44 (14 pages → 25 fichiers), :45 (14 services → 24 hors tests, alors que sa propre table en liste 17) ; CLAUDE.md:41,54,55,56,65 ; et CLAUDE.md:14 « Version actuelle : 0.6.3 » alors que le dernier tag est v0.14.0. Rien de tout cela n'est causé par ce chantier et le corps de PR le mesure et le déclare — c'est le bon réflexe. Ouvrir une issue de suivi pour que la mesure ne se perde pas.
  6. Hors portée docs, trouvé en vérifiant la prose de mapRow : en mode débit/crédit, une cellule illisible d'un côté et un 0,00 lisible de l'autre donnent 0 − 0 = 0, donc une transaction à 0,00 écrite en silence — le cas même que la doc décrit comme fermé, mais elle le décrit correctement (« illisible dans les deux colonnes »). C'est un angle mort résiduel de #325, pas un défaut de documentation.

Non vérifiable sans muter l'arbre : l'ADR affirme que retirer sign_convention de formatToRow « fait tomber 14 tests ». Le fichier porte 83 tests et le double mécanisme (FORMAT_FIELD_PAIRS typé Record<keyof ImportFormat, …> + test de comparaison des clés) est bien en place ; seul le chiffre exact reste sur parole.

## Revue adversariale — PR #342 (maillon 10/10, documentation) **Verdict : APPROVE** ### Résumé PR docs-only conforme : 8 fichiers, aucun fichier source ni test touché (les deux locales le sont, mais c'est le contrat — le guide in-app se rend depuis `docs.*`). J'ai re-mesuré chaque compteur revendiqué contre l'arbre plutôt que de relire la table du corps de PR, résolu chaque lien de l'ADR, comparé les deux CHANGELOG mécaniquement, et sondé une vingtaine d'affirmations techniques contre le code du stack. **Aucune n'est fausse.** Rien de bloquant. ### Ce que j'ai re-mesuré moi-même | Fait | Documenté | Mesuré | | |---|---|---|---| | Migrations | 17 (`v1→v17`) | 17 entrées `Migration { version: n }`, v17 dernière | ✅ | | `src/components/import/` | 14 | 14 fichiers exactement | ✅ | | `src/utils/` | 13 | 13 hors tests (23 avec) | ✅ | | Tables / index | 20 / 24, **inchangés** | 20 `CREATE TABLE` + 24 `CREATE INDEX` dans `consolidated_schema.sql` | ✅ | Le point le plus facile à rater est le bon : v17 ne contient que quatre `ALTER TABLE` + un `UPDATE`, donc laisser 20/24 est exact, et la justification écrite en `CLAUDE.md:104` est vérifiable par un lecteur futur au lieu d'être à re-dériver. La correction du « 7 migrations inline » de l'arbre (`CLAUDE.md:84`) supprime une contradiction interne réelle avec la ligne 60 lignes plus bas. - Le delta de composants s'explique : `FilePreviewModal.tsx` supprimé, `FormatDriftPanel.tsx` + `RepairPathNotice.tsx` ajoutés → 13 − 1 + 2 = 14. - v1→v16 sont intactes dans `lib.rs` (le diff `main..HEAD` n'ajoute que v17 et des constantes de test). ### Liens de l'ADR `spec-decisions-import-csv-format.md` et `spec-plan-import-csv-format.md` sont bien dans `git ls-tree` de la branche malgré `.gitignore:71-72` → **les deux liens `Spec:` résolvent**, le précédent ADR 0016 / #295 ne se répète pas. Les trois autres liens de l'ADR (0003, 0004, 0017) existent, ainsi que les deux lignes ajoutées à la table des ADR de `architecture.md` (0018, 0019) ; la date 2026-07-27 portée à la ligne 0018 est celle de son propre en-tête. ### Parité CHANGELOG et i18n - **5 Added / 2 Changed / 7 Fixed / 2 Security des deux côtés**, et surtout les **mêmes références d'issue dans le même ordre, bullet par bullet** : `#327+#328 | #329 | #330 | #331 | #259` puis `#323+#324+#328 | #297-#301` puis `#327 | #325 | #325 | #325 | #327 | #325+#327 | #331` puis `#310 | #311`. Ordre Keep a Changelog respecté (Fixed avant Security). - **i18n en lockstep** : le diff des chemins de scalaires entre `en.json` et `fr.json` est **vide** (structures strictement identiques), 9 features / 8 steps / 9 tips dans chaque locale, et `guide-utilisateur.md` §4 porte 9/8/9 aussi. Le mode d'échec connu (divergence des compteurs entre le JSON et son jumeau imprimable) n'est pas présent. ### Fidélité de la prose au code (sondage) `mapRow` calcule bien `crédit − débit` sur magnitudes et n'échoue que si les **deux** colonnes sont illisibles — la doc dit « dans les deux colonnes », pas plus ; `parseFrenchAmount` est ancré (`100,00 CAD` → NaN, `(50,00)` et `50,00-` → −50) et **oui, la doc énonce que ce cas produit désormais une ligne en erreur**, dans les deux CHANGELOG et dans `architecture.md` ; séparateur décimal arbitré par colonne ; garde `!existing` (`useImportWizard.ts:503-508`) ; `file-preview` est sur l'**unique** chemin avant (le seul `SET_STEP → duplicate-check` part de `checkDuplicates`, donc « traversée à chaque import » est exact, pas rhétorique) ; config écrite à `executeImport` seul ; `flipSignFormat` échange les indices en `debit_credit` ; sélecteur de convention masqué et non réinitialisé (`SourceConfigPanel.tsx:358`) ; `header_signature` = tableau JSON de libellés normalisés ; `MIN_SIGNATURE_LABELS = 4`, `MIN_HEADER_ROLE_MATCHES = 2`, `CONFIDENCE_THRESHOLD = 0.9`, `SREF_FORMAT_VERSION = 2` ; modèles restaurés avant les sources, upsert par nom, remap `template_id`, whitelist `AMOUNT_MODES`/`SIGN_CONVENTIONS`, `withTransaction` ; **15** fixtures CSV, exactement celles énumérées ; `VALUE_HEADER_KEYWORDS` contient bien `montant` (la raison invoquée pour séparer `headerDictionary.ts` est réelle) ; « éditer le modèle n'altère aucune source liée » est un vrai test d'intégration. Le récit des trois voies de perte tient : la voie 1 de l'ADR correspond au code de `main` mot pour mot (`amountMode: mapping.debitAmount !== undefined ? "debit_credit" : "single"` puis `signConvention: "negative_expense"`, `useImportWizard.ts:321-323`), la voie 2 est câblée dans `bankSignatures.ts` + `FormatDriftPanel`, la voie 3 dans `dataExportService.ts`. ### Blocages Aucun. ### Suggestions (aucune bloquante) 1. **`CHANGELOG.md:15` / `CHANGELOG.fr.md:15` — classement de l'entrée #323/#324.** C'est le correctif racine du chantier (une source réglée en dépenses positives revenait au défaut au 2e import → dépenses importées en revenus) et il est sous `Changed`/`Modifié`. Le bullet est mixte — nouveau modèle de persistance, sélecteur masqué, écriture différée — donc le choix se défend, mais le lecteur des notes de version qui cherche « mon bug de signes est-il réglé ? » ouvre `Fixed` en premier. Option : scinder, la moitié « re-déduisait le mode / remettait silencieusement la convention sur négatif » sous `Fixed`, le reste sous `Changed`. 2. **`CHANGELOG.md:16` / `CHANGELOG.fr.md:16`** — une ligne vide a été insérée entre les deux bullets de `Changed`/`Modifié`, seule section où c'est le cas. Markdown en fait une liste *loose* (chaque item enveloppé dans un `<p>`), donc un rendu différent des autres sections sur `/changelog`. À retirer. 3. **`docs/guide-utilisateur.md:493` et `docs.settings.features`** — §11 énonce encore « Export des données (transactions, catégories, ou les deux) ». Depuis #331 l'enveloppe emporte aussi les sources et les modèles. L'information existe en §4 (astuce, ligne 133), mais l'utilisateur qui veut savoir ce que contient sa sauvegarde lit §11. 4. **`docs/guide-utilisateur.md:124-137`** — le rejet d'un montant à suffixe est un changement visible pour l'utilisateur : une ligne qui passait sans bruit (à 10 000,00) échoue désormais. C'est dans les deux CHANGELOG et dans `architecture.md`, pas dans le guide, alors que le refus `D`/`C` y est (ligne 130). Une astuce de plus suffirait. 5. **Dérive de compteurs pré-existante, dans les blocs mêmes que la PR édite** : `docs/architecture.md:29` (58 composants → 127 hors tests), `:44` (14 pages → 25 fichiers), `:45` (14 services → 24 hors tests, alors que sa propre table en liste 17) ; `CLAUDE.md:41,54,55,56,65` ; et `CLAUDE.md:14` « Version actuelle : 0.6.3 » alors que le dernier tag est v0.14.0. Rien de tout cela n'est causé par ce chantier et le corps de PR le mesure et le déclare — c'est le bon réflexe. Ouvrir une issue de suivi pour que la mesure ne se perde pas. 6. **Hors portée docs, trouvé en vérifiant la prose de `mapRow`** : en mode débit/crédit, une cellule illisible d'un côté et un `0,00` lisible de l'autre donnent `0 − 0 = 0`, donc une transaction à 0,00 écrite en silence — le cas même que la doc décrit comme fermé, mais elle le décrit correctement (« illisible dans les **deux** colonnes »). C'est un angle mort résiduel de #325, pas un défaut de documentation. *Non vérifiable sans muter l'arbre* : l'ADR affirme que retirer `sign_convention` de `formatToRow` « fait tomber 14 tests ». Le fichier porte 83 tests et le double mécanisme (`FORMAT_FIELD_PAIRS` typé `Record<keyof ImportFormat, …>` + test de comparaison des clés) est bien en place ; seul le chiffre exact reste sur parole.
maximus added 3 commits 2026-08-14 16:03:13 +00:00
Review finding on #325. The debit/credit rule tested isNaN(debit) && isNaN(credit),
which only caught the case where BOTH sides fell. The unused column carries 0,00
in exactly the files this rule exists to fix, so an unreadable debit beside a
0,00 credit computed 0 - 0 = 0 and imported silently — the very bug, one cell
over. Replayed on the PR's own unused-column-zero fixture with a currency
suffix: 6 transactions imported at 0,00 with no error row.

A mapped cell that is not empty but does not parse now fails the row whatever
its sibling holds. An EMPTY cell keeps meaning 'this column does not apply to
this row' and contributes zero, which is the normal shape of the format.

Refs #325

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review finding on #327. detectDescriptionColumn returned the lexically
preferred column with no check on the data, unlike the date (replayed at 0.8)
and the amount (constrained to the shape candidates). The dictionary lists
'transaction' as a description keyword, and Tangerine exports
Date,Transaction,Name,Memo,Amount where Transaction holds DEBIT/CREDIT — so the
description moved off the merchant name and keyword categorisation died.

Cardinality tells free text from an enum: a description repeats almost nothing,
an enum repeats almost everything. Average length does not — Note and Libelle
are both short, so a length veto would reject legitimate columns.

This fixes the cause. #330 had rescued the case through the Tangerine signature
alone, leaving every unrecognised file with a Transaction column broken; that
test now asserts the correct mapping with and without a signature.

Refs #327

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fix(import): stop a generic bank signature from claiming, and misreading, a richer file
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m46s
37b832e084
Review finding on #330, two defects with one root cause.

Desjardins' fingerprint is date/description/montant/solde — four labels any
Canadian bank could emit — and MIN_SIGNATURE_LABELS = 4 did not deliver the
property it promised, because matching was by SUBSET. Any
Date;Description;Montant;Solde file was announced 'Format Desjardins reconnu'.
A variant built only from generic labels must now describe the header exactly;
one carrying a discriminating label (chequenumber, categorie, memo) keeps
subset matching, so extra columns stay fine once something identifies the bank.

Worse, a matched signature's single amount column short-circuited the
sparse-complementary scan instead of being arbitrated against it. A
Date;Description;Debit;Credit;Montant;Solde file reads correctly as debit/credit
before #330 and became one unsigned column after, importing every deposit as an
expense. The scan now runs first; a signature's amount column only wins when the
pair contains it — which is what RBC's genuine Cheque Number / CAD$ case needs,
and it still passes.

Refs #330

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Author
Owner

Mergée dans main en fast-forward avec le reste de la pile (tip 37b832e).

Forgejo ne détecte pas un merge local comme merged — la PR est donc fermée à la main, et l'issue liée s'est fermée automatiquement via son Resolves #N.

Tip cumulé validé avant push : 1181 vitest, 111 tests Rust, build tsc + vite. La CI ne tourne pas sur push main, cette validation locale était donc le seul filet.

Mergée dans `main` en fast-forward avec le reste de la pile (tip `37b832e`). Forgejo ne détecte pas un merge local comme *merged* — la PR est donc fermée à la main, et l'issue liée s'est fermée automatiquement via son `Resolves #N`. Tip cumulé validé avant push : **1181 vitest**, **111 tests Rust**, build tsc + vite. La CI ne tourne pas sur push `main`, cette validation locale était donc le seul filet.
maximus closed this pull request 2026-08-14 16:14:02 +00:00
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m46s

Pull request closed

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#342
No description provided.