REVIEW.md, outside « Niveaux »: a pitfall on the vitest tests that read src-tauri/ (a PR touching only src-tauri/** skips check-frontend, review 25592 point 6), and, in the Release zone, the --locked relay to re-prove when @tauri-apps/cli or cargo-xwin is bumped (review 25416 point 2). architecture.md: the same relay, the six cargo metadata calls coming from the Tauri CLI only (25416 point 3); the mount error of Transactions wiped by the first debounced page read, and the banner cleared by refreshFolder and browseFolder (25592 points 7 and 2); the catch-up measurement asserts success, not duration, and PriceFetchControl among the harness users (25443 points 1 and 5); the Ctrl+click note's reason as read in tauri 2.10.2 and wry 0.54.1 (25487 point 5). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
9fc942d295
commit
1a3fa1dcc9
2 changed files with 8 additions and 6 deletions
|
|
@ -127,6 +127,7 @@ Chaque règle cite sa source. Elles valent quel que soit le niveau : une PR norm
|
||||||
- `latest.json` porte une clé par paquet (`linux-x86_64-deb`, `linux-x86_64-rpm`, `windows-x86_64`), contrôlée par `scripts/check-latest-json.jq` avant le `DELETE` du registre ; `scripts/build-latest-json.test.mjs` tourne dans `npm test`. — `CLAUDE.md` « CI/CD », `docs/architecture.md` « `release.yml` — Build et publication »
|
- `latest.json` porte une clé par paquet (`linux-x86_64-deb`, `linux-x86_64-rpm`, `windows-x86_64`), contrôlée par `scripts/check-latest-json.jq` avant le `DELETE` du registre ; `scripts/build-latest-json.test.mjs` tourne dans `npm test`. — `CLAUDE.md` « CI/CD », `docs/architecture.md` « `release.yml` — Build et publication »
|
||||||
- `release.yml` signe les binaires et publie le JSON d'updater que lisent toutes les installations : une clé publique de `plugins.updater` changée sans bascule prévue coupe leurs mises à jour, comme un endpoint déplacé. — `CLAUDE.md` « CI/CD » et « Fonctionnalités principales »
|
- `release.yml` signe les binaires et publie le JSON d'updater que lisent toutes les installations : une clé publique de `plugins.updater` changée sans bascule prévue coupe leurs mises à jour, comme un endpoint déplacé. — `CLAUDE.md` « CI/CD » et « Fonctionnalités principales »
|
||||||
- Une release qui touche l'updater, ses dépendances, `release.yml` ou la config `updater` exige un cycle réel de mise à jour (étape 10 de `/release`, `docs/qa-update-cycle.md`) : la PR le signale pour la release suivante. `verify-release.sh` contrôle la release publiée. — `CLAUDE.md` « CI/CD », `.claude/skills/release/SKILL.md`
|
- Une release qui touche l'updater, ses dépendances, `release.yml` ou la config `updater` exige un cycle réel de mise à jour (étape 10 de `/release`, `docs/qa-update-cycle.md`) : la PR le signale pour la release suivante. `verify-release.sh` contrôle la release publiée. — `CLAUDE.md` « CI/CD », `.claude/skills/release/SKILL.md`
|
||||||
|
- Le `-- --locked` des deux `npx tauri build` n'arrive à `cargo build` que par la CLI de Tauri (`@tauri-apps/cli`, 2.10.0 dans le lock npm), qui le passe à son runner, et par cargo-xwin (0.23.1, épinglé), qui le passe à `cargo build` ; le journal du tag ne montre jamais la ligne de commande de cargo. Une PR qui monte l'une de ces deux versions rejoue la preuve de #449 sur les deux runners : un lock désaccordé fait échouer l'étape, `cargo` remplacé par un script qui journalise chaque appel et son appelant. — `docs/architecture.md` « `release.yml` — Build et publication », #449
|
||||||
|
|
||||||
### CI et gardes
|
### CI et gardes
|
||||||
|
|
||||||
|
|
@ -186,3 +187,4 @@ Les trois premiers viennent du skill global `pr-review`, corrigés pour ce dép
|
||||||
- **Booléen lié en texte.** tauri-plugin-sql stocke un booléen JS en texte `'true'`, qu'une condition `= 1` ne trouve pas : défaut resté invisible de la v0.1.0 à #390. — [ADR 0021](docs/adr/0021-migrations-profils-secondaires.md)
|
- **Booléen lié en texte.** tauri-plugin-sql stocke un booléen JS en texte `'true'`, qu'une condition `= 1` ne trouve pas : défaut resté invisible de la v0.1.0 à #390. — [ADR 0021](docs/adr/0021-migrations-profils-secondaires.md)
|
||||||
- **Transaction hors verrou.** Un `BEGIN` et son `COMMIT` envoyés par deux `db.execute` peuvent partir sur deux connexions du pool et laisser un verrou zombie (« database is locked », v0.10.0) : toujours `withTransaction` et son handle. — `docs/architecture.md` « Accès DB et transactions »
|
- **Transaction hors verrou.** Un `BEGIN` et son `COMMIT` envoyés par deux `db.execute` peuvent partir sur deux connexions du pool et laisser un verrou zombie (« database is locked », v0.10.0) : toujours `withTransaction` et son handle. — `docs/architecture.md` « Accès DB et transactions »
|
||||||
- **Dépôt public.** Aucune coordonnée d'un tiers (courriel, adresse, téléphone), aucune clé de licence ni aucun jeton dans un fichier commité, `STATE.md`, `docs/` et `.claude/skills/` compris ; un testeur se désigne par son identifiant Logto. — `.claude/skills/issue-license/SKILL.md`
|
- **Dépôt public.** Aucune coordonnée d'un tiers (courriel, adresse, téléphone), aucune clé de licence ni aucun jeton dans un fichier commité, `STATE.md`, `docs/` et `.claude/skills/` compris ; un testeur se désigne par son identifiant Logto. — `.claude/skills/issue-license/SKILL.md`
|
||||||
|
- **Tests vitest qui lisent `src-tauri/`.** Le couplage inverse de l'exception « CI d'une pile » : `src/hooks/useImportWizard.test.ts` lit les codes `SAMPLE_ERROR_*` de `fs_commands.rs` (contrat avec leur traduction), `src/services/categoryService.test.ts` et `src/__integration__/onboarding-samples.test.ts` lisent `consolidated_schema.sql`, `src/utils/changelog.test.ts` la version de `tauri.conf.json`. Une PR qui ne touche que `src-tauri/**` saute `check-frontend` : la dérive ne rougit que la PR suivante qui touche `src/`, sans rapport avec elle. La revue d'une telle PR lance ces tests (`npx vitest run <fichier>`). — `check-frontend.yml` (`paths-ignore`), #384
|
||||||
|
|
|
||||||
|
|
@ -353,7 +353,7 @@ Phase 1 de l'accueil des nouveaux profils (#351-#357 ; analyse d'origine dans `d
|
||||||
| Transactions `/transactions` | `showsEmptyState(state)` = `totalInDb === 0`, `totalInDb` via `getTransactionCount`, lu au montage : la carte vient dès que le compte est lu, sans attendre la première page (#384, D1) | `EmptyState` dans la page | « Importer des transactions » → `/import` |
|
| Transactions `/transactions` | `showsEmptyState(state)` = `totalInDb === 0`, `totalInDb` via `getTransactionCount`, lu au montage : la carte vient dès que le compte est lu, sans attendre la première page (#384, D1) | `EmptyState` dans la page | « Importer des transactions » → `/import` |
|
||||||
| Budget `/budget` | `showsEmptyState(state)` = `totalInDb === 0`, même compteur | `EmptyState` dans la page | « Importer des transactions » → `/import` |
|
| Budget `/budget` | `showsEmptyState(state)` = `totalInDb === 0`, même compteur | `EmptyState` dans la page | « Importer des transactions » → `/import` |
|
||||||
|
|
||||||
Sur ces deux pages, le compte se lit au montage à côté des autres lectures de la page (catégories et sources de Transactions, comptes du filtre de Budget), chaque lecture indépendante des autres (`Promise.allSettled`, #384) : un compte en échec laisse `totalInDb` à `null`, donc la mise en page normale, jamais l'état vide, sans priver la page de ses filtres ; le premier échec devient l'erreur de la page. Le compte n'est relu qu'au montage, aucun abonnement au canal d'événements de données : un import se fait sur `/import`, et revenir sur la page la remonte (un changement de profil remonte toutes les routes, `BrowserRouter key={refreshKey}`).
|
Sur ces deux pages, le compte se lit au montage à côté des autres lectures de la page (catégories et sources de Transactions, comptes du filtre de Budget), chaque lecture indépendante des autres (`Promise.allSettled`, #384) : un compte en échec laisse `totalInDb` à `null`, donc la mise en page normale, jamais l'état vide, sans priver la page de ses filtres ; le premier échec devient l'erreur de la page. Sur Transactions, elle n'y reste qu'environ 300 ms : la lecture de la première page que relance le délai des filtres (300 ms) l'efface (`fetchData` envoie `SET_ERROR(null)`), limite antérieure à #384, non corrigée. Le compte n'est relu qu'au montage, aucun abonnement au canal d'événements de données : un import se fait sur `/import`, et revenir sur la page la remonte (un changement de profil remonte toutes les routes, `BrowserRouter key={refreshKey}`).
|
||||||
|
|
||||||
**La liste de démarrage** (`OnboardingChecklist`, dans la carte d'accueil du tableau de bord vide, et dépliée en place par « Voir la liste » du bandeau de démarrage `OnboardingBanner`, que `AppShell` pose en haut de chaque page, #397) a huit lignes dans un ordre fixe (#425). Les six étapes de données se cochent par une **donnée**, jamais par un clic ; les deux étapes de visite par « J'ai compris » (et, pour Rapports, l'ouverture de la carte Tendances mise en évidence). La source de vérité est `computeChecklist(counters, { importFolderSet, budgetAllowed, skipped, acknowledged, visitStepsInitialized })` (`utils/onboardingChecklist.ts`, pur) :
|
**La liste de démarrage** (`OnboardingChecklist`, dans la carte d'accueil du tableau de bord vide, et dépliée en place par « Voir la liste » du bandeau de démarrage `OnboardingBanner`, que `AppShell` pose en haut de chaque page, #397) a huit lignes dans un ordre fixe (#425). Les six étapes de données se cochent par une **donnée**, jamais par un clic ; les deux étapes de visite par « J'ai compris » (et, pour Rapports, l'ouverture de la carte Tendances mise en évidence). La source de vérité est `computeChecklist(counters, { importFolderSet, budgetAllowed, skipped, acknowledged, visitStepsInitialized })` (`utils/onboardingChecklist.ts`, pur) :
|
||||||
|
|
||||||
|
|
@ -370,7 +370,7 @@ Sur ces deux pages, le compte se lit au montage à côté des autres lectures de
|
||||||
|
|
||||||
`computeChecklist` rend `{ items, done, total, complete }`. Chaque ligne porte `done` (la donnée), `skipped` (l'utilisateur l'a passée) et `locked` ; au niveau de la liste, `done` compte les lignes déverrouillées faites ou passées, une fois chacune, et `complete` vaut vrai quand toutes le sont. Une ligne verrouillée reste hors du total, quel que soit son état. Les deux lignes de visite sont verrouillées tant que le profil n'a pas initialisé ses étapes de visite : un profil au parcours fini ne passe pas un instant par « Étape suivante : Parcourir les catégories » ; la liste les montre alors « en attente », jamais « verrouillées » (le badge de l'édition Base ne les concerne pas), et son compteur attend `budgetReady && visitStepsInitialized`. Dans la liste, une ligne faite n'a plus de lien, une ligne passée montre le badge « Passée » et « Reprendre », une ligne verrouillée mène à la carte Licence ; chaque état sauf « à faire » est lu par un texte masqué visuellement (`onboarding.state.*`), les icônes étant cachées aux technologies d'assistance.
|
`computeChecklist` rend `{ items, done, total, complete }`. Chaque ligne porte `done` (la donnée), `skipped` (l'utilisateur l'a passée) et `locked` ; au niveau de la liste, `done` compte les lignes déverrouillées faites ou passées, une fois chacune, et `complete` vaut vrai quand toutes le sont. Une ligne verrouillée reste hors du total, quel que soit son état. Les deux lignes de visite sont verrouillées tant que le profil n'a pas initialisé ses étapes de visite : un profil au parcours fini ne passe pas un instant par « Étape suivante : Parcourir les catégories » ; la liste les montre alors « en attente », jamais « verrouillées » (le badge de l'édition Base ne les concerne pas), et son compteur attend `budgetReady && visitStepsInitialized`. Dans la liste, une ligne faite n'a plus de lien, une ligne passée montre le badge « Passée » et « Reprendre », une ligne verrouillée mène à la carte Licence ; chaque état sauf « à faire » est lu par un texte masqué visuellement (`onboarding.state.*`), les icônes étant cachées aux technologies d'assistance.
|
||||||
|
|
||||||
**Le dossier d'exemple** : sur un dossier d'import sans sous-dossier, `ImportEmptyState` propose « Créer un dossier d'exemple ». `useImportWizard.createSampleFolders` invoque `create_sample_import_folders` avec le dossier et la date du jour, rescanne, puis affiche `import.empty.sampleCreated` avec les noms rendus par la commande ; ce bandeau de réussite tombe au changement de dossier. Il ne vient qu'après un rescan réussi : `scanFolderInternal` rend `false` sur un échec, dont le bandeau d'erreur reste seul affiché (#384). La commande refuse d'écraser (voir son contrat dans « Commandes Tauri ») ; ses refus sont des codes stables que `sampleFolderError` (pur) traduit en clés `import.empty.sampleErrors.*`, le nom du dossier d'exemple passé par `errorValues` de l'état, que la page interpole au rendu ; tout autre message reste affiché brut. Le message brut part aussi au Journal en `logWarn` : un code, ou une erreur d'écriture qui nomme un dossier d'exemple, son fichier et l'erreur du système, jamais un chemin ; l'erreur du rescan, qui peut porter le chemin, n'y va pas. Les gabarits et leur test d'intégration sont décrits dans « Gabarits du dossier d'exemple ».
|
**Le dossier d'exemple** : sur un dossier d'import sans sous-dossier, `ImportEmptyState` propose « Créer un dossier d'exemple ». `useImportWizard.createSampleFolders` invoque `create_sample_import_folders` avec le dossier et la date du jour, rescanne, puis affiche `import.empty.sampleCreated` avec les noms rendus par la commande ; ce bandeau de réussite tombe au changement de dossier. Il ne vient qu'après un rescan réussi : `scanFolderInternal` rend `false` sur un échec, dont le bandeau d'erreur reste seul affiché (#384). `refreshFolder` (« Actualiser », et le rescan après un changement de l'historique des imports) et `browseFolder`, une fois un dossier choisi, effacent le bandeau d'erreur avant de relire le dossier, comme `selectSource` : le refus d'un dossier d'exemple déjà présent demande justement d'actualiser, et un rescan en échec affiche sa propre erreur ; un choix de dossier annulé ne change rien. La commande refuse d'écraser (voir son contrat dans « Commandes Tauri ») ; ses refus sont des codes stables que `sampleFolderError` (pur) traduit en clés `import.empty.sampleErrors.*`, le nom du dossier d'exemple passé par `errorValues` de l'état, que la page interpole au rendu ; tout autre message reste affiché brut. Le message brut part aussi au Journal en `logWarn` : un code, ou une erreur d'écriture qui nomme un dossier d'exemple, son fichier et l'erreur du système, jamais un chemin ; l'erreur du rescan, qui peut porter le chemin, n'y va pas. Les gabarits et leur test d'intégration sont décrits dans « Gabarits du dossier d'exemple ».
|
||||||
|
|
||||||
### Parcours guidé
|
### Parcours guidé
|
||||||
|
|
||||||
|
|
@ -411,7 +411,7 @@ Sur ces deux pages, le compte se lit au montage à côté des autres lectures de
|
||||||
|
|
||||||
**Les cartes d'étape.** `StepCard` (props seules) et `OnboardingStepCard` (connectée, seule pièce de la carte qui lit `useOnboarding()`). Chaque page qui porte une carte rend `OnboardingStepCard` en premier enfant, avant son en-tête, et ne lui passe que son modèle. La carte a un titre `h2` « Étape n sur N · <étape> », qui prend le focus après une arrivée par un lien d'étape (une fois par arrivée, garde sur `location.key`, puis l'état de navigation est consommé) ; la phrase du modèle, où les libellés des boutons viennent de leurs propres clés ; une zone `role="status"` toujours rendue, qui reçoit « Fait. » ; « J'ai compris » avant « Passer cette étape » pour une étape de visite (`onAcknowledge`, passé par `OnboardingStepCard` quand `VISIT_STEP_KEYS` contient l'étape de la carte ; `ACKNOWLEDGE` garde le verrou de carte, qui annonce « Fait. » puis l'étape suivante), « Passer cette étape » tant que l'étape n'est pas faite ; le rang « Étape n sur N » et la fin attendent `budgetReady && visitStepsInitialized` ; puis le lien vers l'étape suivante ou le message de fin. Sur la page Transactions, l'entrée guidée (`guided`) passe `GUIDED_ENTRY_FILTERS` à `useTransactions` : non catégorisées, sans borne de date, avec un repli unique sur toutes les transactions quand aucune ne reste à classer.
|
**Les cartes d'étape.** `StepCard` (props seules) et `OnboardingStepCard` (connectée, seule pièce de la carte qui lit `useOnboarding()`). Chaque page qui porte une carte rend `OnboardingStepCard` en premier enfant, avant son en-tête, et ne lui passe que son modèle. La carte a un titre `h2` « Étape n sur N · <étape> », qui prend le focus après une arrivée par un lien d'étape (une fois par arrivée, garde sur `location.key`, puis l'état de navigation est consommé) ; la phrase du modèle, où les libellés des boutons viennent de leurs propres clés ; une zone `role="status"` toujours rendue, qui reçoit « Fait. » ; « J'ai compris » avant « Passer cette étape » pour une étape de visite (`onAcknowledge`, passé par `OnboardingStepCard` quand `VISIT_STEP_KEYS` contient l'étape de la carte ; `ACKNOWLEDGE` garde le verrou de carte, qui annonce « Fait. » puis l'étape suivante), « Passer cette étape » tant que l'étape n'est pas faite ; le rang « Étape n sur N » et la fin attendent `budgetReady && visitStepsInitialized` ; puis le lien vers l'étape suivante ou le message de fin. Sur la page Transactions, l'entrée guidée (`guided`) passe `GUIDED_ENTRY_FILTERS` à `useTransactions` : non catégorisées, sans borne de date, avec un repli unique sur toutes les transactions quand aucune ne reste à classer.
|
||||||
|
|
||||||
**La mise en évidence.** Elle passe par props, jamais par une requête DOM. La page tire la cible de son modèle et ne la transmet que pendant que la carte est affichée (`showsStepCard`) : `onboardingTarget` sur l'action de `EmptyState` (par `ImportEmptyState`), sur la première carte de `SourceList`, sur le bouton suivant de `WizardNavigation`, sur la catégorie de la première ligne de `TransactionTable`, sur « Tout déplier » ou la cellule du mois courant de `BudgetTable`, sur « Tous les mots-clés » de `CategoriesPage` et sur la carte Tendances du hub (`HubReportNavCard`), une seule cible à la fois. Sur le hub, la carte Tendances reçoit aussi `onNavigate`, appelé au clic avant la navigation, tant que la carte d'étape s'affiche et que l'étape n'est pas faite : il reconnaît l'étape des rapports (`acknowledge("reports")`, D17), car suivre l'anneau quitte la page et relâche le verrou de carte. `HubReportNavCard` le passe au `onClick` du `Link`, que React tire aussi sur un Ctrl+clic (pas sur le clic du milieu, qui passe par `onAuxClick`) : un Ctrl+clic sur la carte Tendances coche donc l'étape « Parcourir les rapports », alors que le routeur ne navigue pas sur un clic modifié. Le webview n'ayant pas d'onglets, le cas est jugé théorique : documenté, pas corrigé (#446). Le contrôle rend `data-onboarding-target="<clé fixe>"` et `data-onboarding-highlight="true"` (`onboardingTargetAttributes`). `styles.css` dessine sur ce second attribut un contour `3px solid var(--accent)` décalé de 2 px et une pulsation `box-shadow` de 1,2 s jouée trois fois, retirée sous `prefers-reduced-motion: reduce`. `--accent` atteint 3:1 contre le fond de la page et contre les cartes, dans les deux thèmes (`styles.contrast.test.ts`). Une cible absente de l'écran laisse la carte sans anneau.
|
**La mise en évidence.** Elle passe par props, jamais par une requête DOM. La page tire la cible de son modèle et ne la transmet que pendant que la carte est affichée (`showsStepCard`) : `onboardingTarget` sur l'action de `EmptyState` (par `ImportEmptyState`), sur la première carte de `SourceList`, sur le bouton suivant de `WizardNavigation`, sur la catégorie de la première ligne de `TransactionTable`, sur « Tout déplier » ou la cellule du mois courant de `BudgetTable`, sur « Tous les mots-clés » de `CategoriesPage` et sur la carte Tendances du hub (`HubReportNavCard`), une seule cible à la fois. Sur le hub, la carte Tendances reçoit aussi `onNavigate`, appelé au clic avant la navigation, tant que la carte d'étape s'affiche et que l'étape n'est pas faite : il reconnaît l'étape des rapports (`acknowledge("reports")`, D17), car suivre l'anneau quitte la page et relâche le verrou de carte. `HubReportNavCard` le passe au `onClick` du `Link`, que React tire aussi sur un Ctrl+clic (pas sur le clic du milieu, qui passe par `onAuxClick`) : un Ctrl+clic sur la carte Tendances coche donc l'étape « Parcourir les rapports », alors que le routeur ne navigue pas sur un clic modifié. L'app ne pose aucun gestionnaire `on_new_window` (rien dans `src-tauri/src`) ; avec les versions du lock (tauri 2.10.2, wry 0.54.1), WebView2 marque alors une demande de nouvelle fenêtre traitée sans en ouvrir (`SetHandled(true)`), et wry ne branche le signal `create` de WebKitGTK que si un gestionnaire existe : le seul effet d'un Ctrl+clic est l'étape cochée. Lu dans le code des crates, pas observé dans l'application : le cas est jugé théorique, documenté, pas corrigé (#446). Le contrôle rend `data-onboarding-target="<clé fixe>"` et `data-onboarding-highlight="true"` (`onboardingTargetAttributes`). `styles.css` dessine sur ce second attribut un contour `3px solid var(--accent)` décalé de 2 px et une pulsation `box-shadow` de 1,2 s jouée trois fois, retirée sous `prefers-reduced-motion: reduce`. `--accent` atteint 3:1 contre le fond de la page et contre les cartes, dans les deux thèmes (`styles.contrast.test.ts`). Une cible absente de l'écran laisse la carte sans anneau.
|
||||||
|
|
||||||
**Paramètres → Utilisateurs** lit `hidden` dans `useOnboarding()` et montre « Afficher la liste de démarrage » tant que le parcours est masqué, y compris quand le bandeau de cette même page vient de le masquer. Le clic passe par `show()` du fournisseur, et une confirmation remplace le bouton (#397).
|
**Paramètres → Utilisateurs** lit `hidden` dans `useOnboarding()` et montre « Afficher la liste de démarrage » tant que le parcours est masqué, y compris quand le bandeau de cette même page vient de le masquer. Le clic passe par `show()` du fournisseur, et une confirmation remplace le bouton (#397).
|
||||||
|
|
||||||
|
|
@ -661,7 +661,7 @@ Sémantique simulée, chaque point prouvé par `src/test/hookHarness.test.ts` :
|
||||||
|
|
||||||
Non simulé : le nouveau rendu après un setter ou un dispatch, les enfants (un composant dans l'arbre rendu reste un élément), le DOM, le concurrent, la boucle de mises à jour pendant le rendu (le `render()` suivant la lit), et un reducer qui change d'un rendu à l'autre : le `dispatch` d'un `useReducer` garde le reducer du premier rendu (il est créé une fois, avec l'emplacement), là où React applique celui du dernier rendu. Un reducer défini au niveau du module, seul cas utilisé, n'est pas touché.
|
Non simulé : le nouveau rendu après un setter ou un dispatch, les enfants (un composant dans l'arbre rendu reste un élément), le DOM, le concurrent, la boucle de mises à jour pendant le rendu (le `render()` suivant la lit), et un reducer qui change d'un rendu à l'autre : le `dispatch` d'un `useReducer` garde le reducer du premier rendu (il est créé une fois, avec l'emplacement), là où React applique celui du dernier rendu. Un reducer défini au niveau du module, seul cas utilisé, n'est pas touché.
|
||||||
|
|
||||||
Utilisateurs : `useOnboardingChecklist.wiring.test.ts`, `UsersSettingsPage.test.tsx`, `BudgetTable.wiring.test.ts`, `BudgetPage.wiring.test.ts`, `AppShell.slot.test.tsx`, `OnboardingStepCard.test.tsx`, `PriceFetchConsentToggle.test.tsx` (#389). Hors périmètre de #409, `useImportWizard.mount.test.ts` et `useImportWizard.executeImport.test.ts` gardent leurs faux non persistants ; `useImportWizard.samples.test.ts` (« Créer un dossier d'exemple », #384) passe par le harnais partagé.
|
Utilisateurs : `useOnboardingChecklist.wiring.test.ts`, `UsersSettingsPage.test.tsx`, `BudgetTable.wiring.test.ts`, `BudgetPage.wiring.test.ts`, `AppShell.slot.test.tsx`, `OnboardingStepCard.test.tsx`, `PriceFetchConsentToggle.test.tsx` (#389), `PriceFetchControl.test.tsx` (#437). Hors périmètre de #409, `useImportWizard.mount.test.ts` et `useImportWizard.executeImport.test.ts` gardent leurs faux non persistants ; `useImportWizard.samples.test.ts` (« Créer un dossier d'exemple », #384) passe par le harnais partagé.
|
||||||
|
|
||||||
### Faux `t` marqué — `src/test/i18nMock.ts` (#389)
|
### Faux `t` marqué — `src/test/i18nMock.ts` (#389)
|
||||||
|
|
||||||
|
|
@ -691,7 +691,7 @@ Garde : `src/__integration__/i18n-mock-guard.test.ts` lit tous les `*.test.ts(x)
|
||||||
|
|
||||||
- **Emplacements préréglés du harnais.** Le harnais donne un emplacement à chaque appel de hook, tous types confondus (`useState`, `useReducer`, `useRef`, `useMemo`, `useCallback`, `useEffect`), dans l'ordre des appels : c'est le helper `slot` de `fakeReact`. Un test qui prérègle un emplacement par sa position (`harness.slots[0] = …`) dépend donc de l'ordre des hooks du composant : un hook ajouté avant, de n'importe quel type, décale les emplacements sans rien dire, et la valeur préréglée tombe sur un autre hook. Préréglages actuels, chacun commenté dans son fichier : `PriceFetchConsentToggle.test.tsx` (emplacements 0 à 2), `PriceFetchControl.test.tsx` (la fenêtre de consentement ouverte), `useOnboardingChecklist.wiring.test.ts` (`mountWith` et deux écritures directes) et `SystemsSettingsPage.test.tsx` ; `OnboardingStepCard.test.tsx` lit le `useRef` de l'emplacement 0. Qui ajoute un hook avant ces emplacements met le test à jour. Un test qui ouvre un état par un clic dans l'arbre rendu (`TransactionTable.wiring.test.ts`) n'a pas cette dépendance.
|
- **Emplacements préréglés du harnais.** Le harnais donne un emplacement à chaque appel de hook, tous types confondus (`useState`, `useReducer`, `useRef`, `useMemo`, `useCallback`, `useEffect`), dans l'ordre des appels : c'est le helper `slot` de `fakeReact`. Un test qui prérègle un emplacement par sa position (`harness.slots[0] = …`) dépend donc de l'ordre des hooks du composant : un hook ajouté avant, de n'importe quel type, décale les emplacements sans rien dire, et la valeur préréglée tombe sur un autre hook. Préréglages actuels, chacun commenté dans son fichier : `PriceFetchConsentToggle.test.tsx` (emplacements 0 à 2), `PriceFetchControl.test.tsx` (la fenêtre de consentement ouverte), `useOnboardingChecklist.wiring.test.ts` (`mountWith` et deux écritures directes) et `SystemsSettingsPage.test.tsx` ; `OnboardingStepCard.test.tsx` lit le `useRef` de l'emplacement 0. Qui ajoute un hook avant ces emplacements met le test à jour. Un test qui ouvre un état par un clic dans l'arbre rendu (`TransactionTable.wiring.test.ts`) n'a pas cette dépendance.
|
||||||
- **`withoutFolders`.** Ses arrêts et ses deux limites (chemin relatif sans début reconnu ; nom de dossier qui contient l'un des arrêts) sont dans l'[ADR 0021](adr/0021-migrations-profils-secondaires.md), § 7.
|
- **`withoutFolders`.** Ses arrêts et ses deux limites (chemin relatif sans début reconnu ; nom de dossier qui contient l'un des arrêts) sont dans l'[ADR 0021](adr/0021-migrations-profils-secondaires.md), § 7.
|
||||||
- **Mesure du rattrapage.** Le test `#[ignore]` `a_large_profile_catch_up_duration` (`profile_db.rs`) ne tourne pas en CI et n'asserte rien : il imprime une durée. Sa mesure, prise sur un SSD NVMe, et le risque qu'elle ne couvre pas (disque lent, antivirus qui inspecte la copie) sont dans l'[ADR 0021](adr/0021-migrations-profils-secondaires.md), « Négatives et points de vigilance », point « Le délai de démarrage couvre le rattrapage du profil actif ».
|
- **Mesure du rattrapage.** Le test `#[ignore]` `a_large_profile_catch_up_duration` (`profile_db.rs`) ne tourne pas en CI et n'asserte rien sur la durée : il vérifie que le rattrapage aboutit (aucune erreur, plancher 6), puis imprime la durée. Sa mesure, prise sur un SSD NVMe, et le risque qu'elle ne couvre pas (disque lent, antivirus qui inspecte la copie) sont dans l'[ADR 0021](adr/0021-migrations-profils-secondaires.md), « Négatives et points de vigilance », point « Le délai de démarrage couvre le rattrapage du profil actif ».
|
||||||
|
|
||||||
## CI/CD
|
## CI/CD
|
||||||
|
|
||||||
|
|
@ -744,7 +744,7 @@ Fonctionnalités :
|
||||||
|
|
||||||
**Node.js 24** ([#376](https://git.lacompagniemaximus.com/maximus/simpl-resultat/issues/376)), installé comme dans les trois autres workflows par le script NodeSource (`curl … | bash`). Ce script tourne en root dans le conteneur du job, et le Node.js qu'il installe lance ensuite `npx tauri build` avec `TAURI_SIGNING_PRIVATE_KEY` dans son environnement : le job confie à NodeSource (script et dépôt apt) la clé de signature de l'updater, et les jetons de publication des étapes suivantes. Risque accepté par Max le 2026-10-10, alternative écartée : un tarball Node.js épinglé. La chaîne Rust, installée de la même façon par `rustup`, est dans le même cas. Ce workflow ne tourne qu'au tag : la première release après #376 est la première sous Node 24, à vérifier par `node --version` dans le journal du run (étape 9 de `/release`).
|
**Node.js 24** ([#376](https://git.lacompagniemaximus.com/maximus/simpl-resultat/issues/376)), installé comme dans les trois autres workflows par le script NodeSource (`curl … | bash`). Ce script tourne en root dans le conteneur du job, et le Node.js qu'il installe lance ensuite `npx tauri build` avec `TAURI_SIGNING_PRIVATE_KEY` dans son environnement : le job confie à NodeSource (script et dépôt apt) la clé de signature de l'updater, et les jetons de publication des étapes suivantes. Risque accepté par Max le 2026-10-10, alternative écartée : un tarball Node.js épinglé. La chaîne Rust, installée de la même façon par `rustup`, est dans le même cas. Ce workflow ne tourne qu'au tag : la première release après #376 est la première sous Node 24, à vérifier par `node --version` dans le journal du run (étape 9 de `/release`).
|
||||||
|
|
||||||
**Lock commité, cargo-xwin épinglé** ([#449](https://git.lacompagniemaximus.com/maximus/simpl-resultat/issues/449)). Les deux `npx tauri build` passent `-- --locked` : la CLI de Tauri transmet ce qui suit `--` à son runner, cargo pour Linux, cargo-xwin pour Windows, qui le passe à `cargo build`. Les binaires livrés sont donc bâtis sur le `Cargo.lock` commité, ou le build échoue au lieu de le réécrire ; l'étape 2 de `/release`, qui édite le lock à la main sur `main` sans PR, vérifie ce lock par `cargo metadata --locked` avant le tag. Le journal du run n'imprime jamais la ligne de commande de cargo : la preuve que le drapeau arrive est un lock désaccordé qui fait échouer le build, faite en local dans la PR de #449 pour les deux runners (cargo-xwin 0.23.1 lance `cargo build … --locked …`, et les autres appels à cargo de la CLI et de cargo-xwin sont des `cargo metadata --no-deps`, qui ne touchent pas au lock). cargo-xwin, qui tourne avec `TAURI_SIGNING_PRIVATE_KEY` dans son environnement, est installé en version fixe (`cargo install --locked cargo-xwin --version 0.23.1`, celle qu'avait installée la release v0.17.2), et l'étape affiche `cargo-xwin --version` ; une nouvelle version se décide, après lecture de ce qu'elle change.
|
**Lock commité, cargo-xwin épinglé** ([#449](https://git.lacompagniemaximus.com/maximus/simpl-resultat/issues/449)). Les deux `npx tauri build` passent `-- --locked` : la CLI de Tauri transmet ce qui suit `--` à son runner, cargo pour Linux, cargo-xwin pour Windows, qui le passe à `cargo build`. Les binaires livrés sont donc bâtis sur le `Cargo.lock` commité, ou le build échoue au lieu de le réécrire ; l'étape 2 de `/release`, qui édite le lock à la main sur `main` sans PR, vérifie ce lock par `cargo metadata --locked` avant le tag. Le journal du run n'imprime jamais la ligne de commande de cargo : la preuve que le drapeau arrive est un lock désaccordé qui fait échouer le build, faite en local dans la PR de #449 pour les deux runners. D'après l'appelant que journalisait l'espion de la revue (commentaire 25416 de la PR #469), les six `cargo metadata --no-deps` viennent tous de la CLI de Tauri, sous les deux runners, et ne touchent pas au lock ; le `cargo build … --locked …` vient de la CLI pour Linux, et de cargo-xwin 0.23.1 pour Windows, qui ne lance rien d'autre. Ce relais dépend donc des versions de `@tauri-apps/cli` (2.10.0 dans le lock npm) et de cargo-xwin : une montée de l'une ou de l'autre rejoue cette preuve (lock désaccordé, `cargo` remplacé par un script qui journalise chaque appel et son appelant), puisque le journal du tag ne la montrerait pas. cargo-xwin, qui tourne avec `TAURI_SIGNING_PRIVATE_KEY` dans son environnement, est installé en version fixe (`cargo install --locked cargo-xwin --version 0.23.1`, celle qu'avait installée la release v0.17.2), et l'étape affiche `cargo-xwin --version` ; une nouvelle version se décide, après lecture de ce qu'elle change.
|
||||||
|
|
||||||
## Architecture Decision Records (ADRs)
|
## Architecture Decision Records (ADRs)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue