feat(onboarding): visit steps foundation, eight rows, "Got it" and one-time initialization (#425) #444

Merged
maximus merged 3 commits from issue-425-visit-steps into main 2026-10-16 00:37:50 +00:00
Owner

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

Link 7 of 8 of the milestone planned-2026-10-12-retours-onboarding-v0171 (task T7). Stacked on #443 (issue-431-stale-help).

What it does

The getting-started path goes from six to eight rows: "Browse categories and keywords" (5th, before categorizing) and "Browse the reports" (8th). Nothing in the database counts them: they are done by "Got it" on their card (onboarding_acknowledged_<key> = "1" in user_preferences, no migration). A profile whose six data steps were all done or skipped gets them done once, at its first opening under this version (onboarding_visit_steps_initialized), so it keeps "Getting started complete".

The cards of /categories and /reports come with #432 (next link). Until then the path stops at step 5, as the issue admits: the stack merges as one block before any release.

Changes

  • Types: ChecklistItemKey + categories, reports; CHECKLIST_ITEM_KEYS in the new order; VISIT_STEP_KEYS.
  • computeChecklist: options acknowledged and visitStepsInitialized; the two visit rows are locked (out of the total, of complete and of nextStep) until initialization, their to kept. New pure shouldAcknowledgeVisitSteps.
  • onboardingService: acknowledgedStepKey, acknowledgeStep, ONBOARDING_VISIT_STEPS_INITIALIZED_KEY, snapshot acknowledged (one LIKE query, filtered on the visit keys and "1") and visitStepsInitialized; initializeVisitSteps(acknowledge) without a transaction (D16): the acknowledgements, then the flag, idempotent. Nothing notifies the data channel.
  • Provider (useOnboardingChecklist): ACKNOWLEDGE (wins over an older snapshot like SKIP, keeps the card latch), acknowledge(key), visitStepsInitialized exposed; createVisitStepsInitializer (shared promise against StrictMode's double mount, then a re-read) started by an effect when needsVisitStepsInitialization (successful load, budgetReady, flag unset). deriveOnboardingView passes budgetReady && visitStepsInitialized to bannerMode.
  • StepCard: onAcknowledge → "Got it" before "Skip this step", never on a done step. OnboardingStepCard passes it for a visit step; its rank and cardNext wait for both flags.
  • OnboardingBanner, OnboardingChecklist, DashboardEmptyState: a visitStepsInitialized prop; counters wait for both flags; the visit rows show "pending" (never "locked", no edition badge) before initialization; the Budget row's pending state keeps budgetReady alone.
  • i18n: onboarding.checklist.items.categories / .reports, onboarding.card.acknowledge (FR, EN).
  • Docs: guide §1 and docs.gettingStarted / docs.dashboard.features[6] (eight steps, 3/8, 7/7, "J'ai compris", finished profiles); ADR 0022 amended (§2 non-notifying writes, §4 local actions, §5 the one-time initialization admitted like D9); architecture.md (eight rows, reworded "never by a click" invariant, the two preferences in both tables, provider and service rows); QA list (step numbers moved to eight, five new cases under "Retours de la v0.17.1").
  • CHANGELOG: Added, both files.
  • Tests recalibrated per the template rule (finished templates and templates stopped after the import acknowledge both visit steps and are initialized): AppShell render and slot, DashboardEmptyState, Dashboard, Import, Transactions, Budget pages. BudgetTable.wiring.test.ts and UsersSettingsPage.test.tsx needed no change (no typed OnboardingValue, no totals).

Files

src/shared/types/index.ts, src/utils/onboardingChecklist.ts, src/services/onboardingService.ts, src/hooks/useOnboardingChecklist.ts, src/utils/onboardingPath.ts (comments), src/components/onboarding/{StepCard,OnboardingStepCard,OnboardingBanner}.tsx, src/components/dashboard/{OnboardingChecklist,DashboardEmptyState}.tsx, src/components/layout/AppShell.tsx, src/pages/DashboardPage.tsx, locales, 18 test files, docs/adr/0022-canal-evenements-donnees.md, docs/architecture.md, docs/guide-utilisateur.md, docs/qa-onboarding.html, CHANGELOG.md, CHANGELOG.fr.md. src/contexts/OnboardingContext.tsx unchanged: it shares the hook's value as is (its render test now checks acknowledge and visitStepsInitialized).

Decisions (details in the run's decisions log)

  • [MEDIUM] Separate visitStepsInitialized prop on the banner and the list rather than a combined budgetReady, so the Budget row's "pending" keeps budgetReady alone.
  • [MEDIUM] deriveOnboardingView computes the conjunction itself; bannerMode / cardNext signatures unchanged.
  • [MEDIUM] "Got it" is a filled primary button; "Skip this step" stays a text button.
  • [MEDIUM] The "Got it" wiring of OnboardingStepCard is tested through a mocked cardStep on a stand-in route (/categories guides no step before #432).
  • [MEDIUM] ADR 0016 not amended (it lists no onboarding key; the new keys follow its rule); ADR 0022 amended.
  • [MEDIUM] QA list: existing guided-path cases renumbered to eight steps, the walk goes through the Categories and Reports cards of #432.
  • [MEDIUM] Guide sentence « chaque étape se coche d'elle-même » left apart from six → huit (plan's word-for-word list); the note after the list carries "J'ai compris".
  • [HIGH] One-time initialization logged with a fixed line only when it acknowledges; a failure is logged, not retried in the same provider, retried at the next opening.

Proof table (mutations, one at a time, restored by git checkout, all on the committed implementation)

# Mutation Tests Result
M1 categories row not locked before init onboardingChecklist red (2)
M2 reports row ignores acknowledged onboardingChecklist red (5)
M3 shouldAcknowledgeVisitSteps counts the visit rows onboardingChecklist red (1)
M4 shouldAcknowledgeVisitSteps ignores skipped rows onboardingChecklist red (1)
M5 acknowledged steps not filtered on "1" onboardingService red (1)
M6 acknowledged steps filtered on all eight keys onboardingService red (1)
M7 flag written before the acknowledgements onboardingService red (2)
M8 initialization goes through withTransaction onboardingService red (1)
M9 flag set for any value onboardingService red (1)
M10 ACKNOWLEDGE releases the card latch hook + wiring red (3)
M11 ACKNOWLEDGE does not win over an older snapshot hook red (1)
M12 initializer not shared (runs at each call) hook + wiring red (2)
M13 initializer does not re-read hook + wiring red (2)
M14 initialization before budgetReady hook + wiring red (2)
M15 view does not wait for the initialization hook + wiring + AppShell red (4)
M16 "Got it" on a done step StepCard red (1)
M17 "Got it" after "Skip" StepCard red (1)
M18 "Got it" passed for every step OnboardingStepCard red (1)
M19 card rank ignores the initialization OnboardingStepCard red (1)
M20 visit rows not "pending" before init OnboardingChecklist red (1)
M21 list counter ignores the initialization OnboardingChecklist red (1)
M22 banner counter ignores the initialization banner + AppShell red (2)
M23 banner hands the combined flag to the list's Budget row banner red (1)
M24 categories after manualCategory in CHECKLIST_ITEM_KEYS checklist + hook + service survived, then a test pinning the constant to the row order was added (eff77e4): red (1)
M25 hook passes visitStepsInitialized: true to computeChecklist wiring red (1)

Validation

npm run build green (tsc covers the test files), npm test 127 files / 2185 tests green. No Rust change. No migration (v1 to v17 unchanged), no network call, no Journal line with a path.

🤖 Generated with Claude Code

Generated autonomously by /autopilot run of 2026-10-13 Link 7 of 8 of the milestone `planned-2026-10-12-retours-onboarding-v0171` (task T7). Stacked on #443 (`issue-431-stale-help`). ## What it does The getting-started path goes from six to eight rows: "Browse categories and keywords" (5th, before categorizing) and "Browse the reports" (8th). Nothing in the database counts them: they are done by "Got it" on their card (`onboarding_acknowledged_<key>` = "1" in `user_preferences`, no migration). A profile whose six data steps were all done or skipped gets them done once, at its first opening under this version (`onboarding_visit_steps_initialized`), so it keeps "Getting started complete". The cards of `/categories` and `/reports` come with #432 (next link). Until then the path stops at step 5, as the issue admits: the stack merges as one block before any release. ## Changes - **Types**: `ChecklistItemKey` + `categories`, `reports`; `CHECKLIST_ITEM_KEYS` in the new order; `VISIT_STEP_KEYS`. - **`computeChecklist`**: options `acknowledged` and `visitStepsInitialized`; the two visit rows are locked (out of the total, of `complete` and of `nextStep`) until initialization, their `to` kept. New pure `shouldAcknowledgeVisitSteps`. - **`onboardingService`**: `acknowledgedStepKey`, `acknowledgeStep`, `ONBOARDING_VISIT_STEPS_INITIALIZED_KEY`, snapshot `acknowledged` (one `LIKE` query, filtered on the visit keys and "1") and `visitStepsInitialized`; `initializeVisitSteps(acknowledge)` **without a transaction** (D16): the acknowledgements, then the flag, idempotent. Nothing notifies the data channel. - **Provider** (`useOnboardingChecklist`): `ACKNOWLEDGE` (wins over an older snapshot like `SKIP`, **keeps** the card latch), `acknowledge(key)`, `visitStepsInitialized` exposed; `createVisitStepsInitializer` (shared promise against StrictMode's double mount, then a re-read) started by an effect when `needsVisitStepsInitialization` (successful load, `budgetReady`, flag unset). `deriveOnboardingView` passes `budgetReady && visitStepsInitialized` to `bannerMode`. - **`StepCard`**: `onAcknowledge` → "Got it" before "Skip this step", never on a done step. `OnboardingStepCard` passes it for a visit step; its rank and `cardNext` wait for both flags. - **`OnboardingBanner`, `OnboardingChecklist`, `DashboardEmptyState`**: a `visitStepsInitialized` prop; counters wait for both flags; the visit rows show "pending" (never "locked", no edition badge) before initialization; the Budget row's pending state keeps `budgetReady` alone. - **i18n**: `onboarding.checklist.items.categories` / `.reports`, `onboarding.card.acknowledge` (FR, EN). - **Docs**: guide §1 and `docs.gettingStarted` / `docs.dashboard.features[6]` (eight steps, 3/8, 7/7, "J'ai compris", finished profiles); ADR 0022 amended (§2 non-notifying writes, §4 local actions, §5 the one-time initialization admitted like D9); `architecture.md` (eight rows, reworded "never by a click" invariant, the two preferences in both tables, provider and service rows); QA list (step numbers moved to eight, five new cases under "Retours de la v0.17.1"). - **CHANGELOG**: Added, both files. - **Tests recalibrated** per the template rule (finished templates and templates stopped after the import acknowledge both visit steps and are initialized): AppShell render and slot, DashboardEmptyState, Dashboard, Import, Transactions, Budget pages. `BudgetTable.wiring.test.ts` and `UsersSettingsPage.test.tsx` needed no change (no typed `OnboardingValue`, no totals). ## Files `src/shared/types/index.ts`, `src/utils/onboardingChecklist.ts`, `src/services/onboardingService.ts`, `src/hooks/useOnboardingChecklist.ts`, `src/utils/onboardingPath.ts` (comments), `src/components/onboarding/{StepCard,OnboardingStepCard,OnboardingBanner}.tsx`, `src/components/dashboard/{OnboardingChecklist,DashboardEmptyState}.tsx`, `src/components/layout/AppShell.tsx`, `src/pages/DashboardPage.tsx`, locales, 18 test files, `docs/adr/0022-canal-evenements-donnees.md`, `docs/architecture.md`, `docs/guide-utilisateur.md`, `docs/qa-onboarding.html`, `CHANGELOG.md`, `CHANGELOG.fr.md`. `src/contexts/OnboardingContext.tsx` unchanged: it shares the hook's value as is (its render test now checks `acknowledge` and `visitStepsInitialized`). ## Decisions (details in the run's decisions log) - [MEDIUM] Separate `visitStepsInitialized` prop on the banner and the list rather than a combined `budgetReady`, so the Budget row's "pending" keeps `budgetReady` alone. - [MEDIUM] `deriveOnboardingView` computes the conjunction itself; `bannerMode` / `cardNext` signatures unchanged. - [MEDIUM] "Got it" is a filled primary button; "Skip this step" stays a text button. - [MEDIUM] The "Got it" wiring of `OnboardingStepCard` is tested through a mocked `cardStep` on a stand-in route (`/categories` guides no step before #432). - [MEDIUM] ADR 0016 not amended (it lists no onboarding key; the new keys follow its rule); ADR 0022 amended. - [MEDIUM] QA list: existing guided-path cases renumbered to eight steps, the walk goes through the Categories and Reports cards of #432. - [MEDIUM] Guide sentence « chaque étape se coche d'elle-même » left apart from six → huit (plan's word-for-word list); the note after the list carries "J'ai compris". - [HIGH] One-time initialization logged with a fixed line only when it acknowledges; a failure is logged, not retried in the same provider, retried at the next opening. ## Proof table (mutations, one at a time, restored by `git checkout`, all on the committed implementation) | # | Mutation | Tests | Result | |---|---|---|---| | M1 | categories row not locked before init | onboardingChecklist | red (2) | | M2 | reports row ignores `acknowledged` | onboardingChecklist | red (5) | | M3 | `shouldAcknowledgeVisitSteps` counts the visit rows | onboardingChecklist | red (1) | | M4 | `shouldAcknowledgeVisitSteps` ignores skipped rows | onboardingChecklist | red (1) | | M5 | acknowledged steps not filtered on "1" | onboardingService | red (1) | | M6 | acknowledged steps filtered on all eight keys | onboardingService | red (1) | | M7 | flag written before the acknowledgements | onboardingService | red (2) | | M8 | initialization goes through `withTransaction` | onboardingService | red (1) | | M9 | flag set for any value | onboardingService | red (1) | | M10 | `ACKNOWLEDGE` releases the card latch | hook + wiring | red (3) | | M11 | `ACKNOWLEDGE` does not win over an older snapshot | hook | red (1) | | M12 | initializer not shared (runs at each call) | hook + wiring | red (2) | | M13 | initializer does not re-read | hook + wiring | red (2) | | M14 | initialization before `budgetReady` | hook + wiring | red (2) | | M15 | view does not wait for the initialization | hook + wiring + AppShell | red (4) | | M16 | "Got it" on a done step | StepCard | red (1) | | M17 | "Got it" after "Skip" | StepCard | red (1) | | M18 | "Got it" passed for every step | OnboardingStepCard | red (1) | | M19 | card rank ignores the initialization | OnboardingStepCard | red (1) | | M20 | visit rows not "pending" before init | OnboardingChecklist | red (1) | | M21 | list counter ignores the initialization | OnboardingChecklist | red (1) | | M22 | banner counter ignores the initialization | banner + AppShell | red (2) | | M23 | banner hands the combined flag to the list's Budget row | banner | red (1) | | M24 | `categories` after `manualCategory` in `CHECKLIST_ITEM_KEYS` | checklist + hook + service | survived, then a test pinning the constant to the row order was added (eff77e4): red (1) | | M25 | hook passes `visitStepsInitialized: true` to `computeChecklist` | wiring | red (1) | ## Validation `npm run build` green (tsc covers the test files), `npm test` 127 files / 2185 tests green. No Rust change. No migration (v1 to v17 unchanged), no network call, no Journal line with a path. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
maximus added 3 commits 2026-10-14 01:38:31 +00:00
Two visit steps join the getting-started path: "Browse categories and
keywords" (5th) and "Browse the reports" (8th). Nothing in the database
counts them: they are done by "Got it" on their card, stored as
onboarding_acknowledged_<key> in user_preferences (no migration).

- computeChecklist: eight rows; the visit rows are locked until the
  profile's visit steps are initialized, so a finished path does not
  flash a "Next step" before they are marked done.
- initializeVisitSteps: once per profile, after the first successful
  load with the Budget entitlement known; acknowledges both steps when
  every other step is done or skipped, then sets the flag. No
  transaction (D16): idempotent writes, the flag last.
- Reducer: ACKNOWLEDGE wins over an older snapshot like SKIP but keeps
  the card latch, so the card says "Done." and offers the next step.
- StepCard: "Got it" before "Skip this step" when onAcknowledge is
  given; OnboardingStepCard passes it for a visit step.
- Banner counter, card rank and end of path wait for
  budgetReady && visitStepsInitialized; the list shows the visit rows
  "pending" until then.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A mutation that moved "categories" after "manualCategory" in the
constant survived: the rows come from computeChecklist, the constant
only orders the skipped and acknowledged steps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs(onboarding): eight steps, visit steps and their one-time initialization
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m58s
a064043c20
- Guide and docs.gettingStarted / docs.dashboard keys (FR, EN): eight
  steps, the two visit steps in the quick-start list, "Got it", 3/8 and
  7/7, a finished path finds them done.
- ADR 0022: acknowledgeStep and initializeVisitSteps do not notify (§2),
  ACKNOWLEDGE among the local actions (§4), the one-time initialization
  admitted like rule D9 (§5).
- architecture.md: eight rows, the amended "never by a click" invariant,
  the two new preferences, the provider's new action and initializer.
- QA list: step numbers moved to eight, five cases for the visit steps.
- CHANGELOG: Added entry in both files.

Resolves #425

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

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

Reviewed the full diff of #444 against issue-431-stale-help (head a064043).

Acceptance criteria of #425

  • New profile: "Démarrage n/8" (Base+), "n/7" (Free), once initialized: computeChecklist totals 8/7 (tests), counters wait for budgetReady && visitStepsInitialized (M21, M22).
  • Profile whose six old steps were done or skipped keeps "Démarrage terminé" without "Étape suivante": the visit rows are locked until initialization (no next step, mode none while it runs), then initializeVisitSteps(true) and a re-read give complete (hook test "first render of a profile whose path was complete", wiring test with initializeVisitSteps(true); M14, M15).
  • Incomplete profile keeps both visit steps to do, even after finishing the six others (shouldAcknowledgeVisitSteps false, hook walk test; M3, M4).
  • Between this link and #432 the path stops at step 5: CARD_PAGES / PAGE_STEPS untouched, so /categories shows the banner, no card.
  • npm run build and npm test green (127 files, 2185 tests).

Security / correctness

  • initializeVisitSteps opens no transaction (D16), pinned by a test that asserts no withTransaction and no BEGIN (M8); writes are idempotent, flag last (M7).
  • Snapshot filters acknowledged keys on VISIT_STEP_KEYS and "1" (M5, M6); LIKE wildcard caveat handled like the skipped steps.
  • No Journal line with a path (fixed lines, step key only). No migration, no network call, nothing on the data channel (test).
  • ACKNOWLEDGE keeps the card latch (M10) and wins over an older snapshot (M11).

i18n / docs / CHANGELOG

  • Three new keys FR + EN, texts word for word; guide, docs.* keys, ADR 0022 (§2, §4, §5), architecture.md, QA list, CHANGELOG Added in both files.

Points for the independent reviewer

  1. A finished profile shows nothing (mode none) for the time of the initialization at its first opening under this version, instead of the end banner: the end waits for the initialization, as the spec's resolution says. Never a "Next step".
  2. A failed initialization is logged and not retried by the same provider (retried at the next opening); until then the visit rows stay "pending" and out of the total.
  3. The "Got it" wiring of OnboardingStepCard is tested through a mocked cardStep on a stand-in route, since /categories guides no step before #432.
  4. The QA list renumbers cases written by earlier links (5/5 → 7/7, step 5 → 6, step 6 → 7), and the guided walk now goes through the Categories and Reports cards of #432.
  5. One mutation survived the first pass (M24, CHECKLIST_ITEM_KEYS order); a test now pins it (eff77e4).
## Self-review (autopilot worker, not independent) — verdict: APPROVE Reviewed the full diff of #444 against `issue-431-stale-help` (head `a064043`). **Acceptance criteria of #425** - [x] New profile: "Démarrage n/8" (Base+), "n/7" (Free), once initialized: `computeChecklist` totals 8/7 (tests), counters wait for `budgetReady && visitStepsInitialized` (M21, M22). - [x] Profile whose six old steps were done or skipped keeps "Démarrage terminé" without "Étape suivante": the visit rows are locked until initialization (no next step, mode `none` while it runs), then `initializeVisitSteps(true)` and a re-read give `complete` (hook test "first render of a profile whose path was complete", wiring test with `initializeVisitSteps(true)`; M14, M15). - [x] Incomplete profile keeps both visit steps to do, even after finishing the six others (`shouldAcknowledgeVisitSteps` false, hook walk test; M3, M4). - [x] Between this link and #432 the path stops at step 5: `CARD_PAGES` / `PAGE_STEPS` untouched, so `/categories` shows the banner, no card. - [x] `npm run build` and `npm test` green (127 files, 2185 tests). **Security / correctness** - `initializeVisitSteps` opens no transaction (D16), pinned by a test that asserts no `withTransaction` and no `BEGIN` (M8); writes are idempotent, flag last (M7). - Snapshot filters acknowledged keys on `VISIT_STEP_KEYS` and "1" (M5, M6); `LIKE` wildcard caveat handled like the skipped steps. - No Journal line with a path (fixed lines, step key only). No migration, no network call, nothing on the data channel (test). - `ACKNOWLEDGE` keeps the card latch (M10) and wins over an older snapshot (M11). **i18n / docs / CHANGELOG** - Three new keys FR + EN, texts word for word; guide, `docs.*` keys, ADR 0022 (§2, §4, §5), `architecture.md`, QA list, CHANGELOG Added in both files. **Points for the independent reviewer** 1. A finished profile shows nothing (mode `none`) for the time of the initialization at its first opening under this version, instead of the end banner: the end waits for the initialization, as the spec's resolution says. Never a "Next step". 2. A failed initialization is logged and not retried by the same provider (retried at the next opening); until then the visit rows stay "pending" and out of the total. 3. The "Got it" wiring of `OnboardingStepCard` is tested through a mocked `cardStep` on a stand-in route, since `/categories` guides no step before #432. 4. The QA list renumbers cases written by earlier links (5/5 → 7/7, step 5 → 6, step 6 → 7), and the guided walk now goes through the Categories and Reports cards of #432. 5. One mutation survived the first pass (M24, `CHECKLIST_ITEM_KEYS` order); a test now pins it (`eff77e4`).
Author
Owner

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

Revue /pr-review — PR #444 (#425, maillon 7/8)

Verdict : APPROVE

Résumé. La fondation des étapes de visite est correcte : les deux lignes restent verrouillées (hors total, hors complete, hors nextStep) jusqu'à l'initialisation, l'initialisation unique attend un chargement réussi et budgetReady, sa promesse partagée tient contre le double montage de StrictMode, et l'ordre « accusés puis drapeau » sans transaction laisse un arrêt entre deux écritures rejouable à l'ouverture suivante. Diff relu contre sa base issue-431-stale-help.

Ce qui a été exécuté (copie hors dépôt, git archive de a064043, npm ci propre)

  • vitest run : 127 fichiers, 2185 tests verts. CI de la tête : PR Check — Frontend en success (statut agrégé).
  • Mutations du relecteur, une à la fois, restaurées par git checkout sur la copie :
# Mutation Résultat
R1 initializeVisitSteps n'accuse que categories rouge (3)
R2 shouldAcknowledgeVisitSteps compte aussi les lignes verrouillées (Budget en Gratuite) rouge (1)
R3 ligne de visite « verrouillée » avant « en attente » dans rowState (badge d'édition) rouge (1)
R4 needsVisitStepsInitialization ignore le drapeau rouge (4)
R8 acknowledge met l'état à jour sans écrire la préférence rouge (2)
R10 l'initialisation accuse toujours (profil en cours compris) rouge (1)
R11 cardNext de la carte reçoit budgetReady seul rouge (1)
R12 drapeau collant (state ‖ snapshot) dans LOAD_SUCCESS vert, mutant équivalent : le drapeau ne repasse jamais de 1 à 0 dans un même profil
R13 point séparateur du bandeau sous budgetReady au lieu de totalKnown vert, voir suggestion 1

Copie supprimée en fin de revue.

Points vérifiés

  • Nouveau fournisseur par profil (BrowserRouter key={refreshKey} remonte AppShell) : la durée de vie « une initialisation par ouverture de profil » de createVisitStepsInitializer tient.
  • Course avec un rechargement de route : un chargement commencé avant le refresh de l'initialiseur porte un loadSeq plus ancien et son résultat est jeté ; tout chargement commencé après lit le drapeau.
  • Profil terminé avant l'initialisation : bannerMode rend none (et non complete) tant que totalKnown est faux, donc ni « Étape suivante » ni message de fin en avance ; après l'initialisation, complete avec les deux visites accusées.
  • Gratuite terminée à 5/5 : Budget verrouillé exclu de shouldAcknowledgeVisitSteps, donc les visites sont accusées et le parcours finit à 7/7 (R2 l'épingle).
  • Fermeture : la timeline de #425 ne porte aucun closes venu du corps de la PR (pas de mot-clé), mais le commit a064043 contient Resolves #425 : un merge ff de la pile fermera #425, ce qui est voulu puisque la pile se merge d'un bloc avec #432.
  • Aucune migration (v1→v17), aucune requête réseau, i18n FR/EN complète, CHANGELOG dans les deux fichiers.

Suggestions (non bloquantes)

  1. OnboardingBanner.tsx, point séparateur : R13 survit. Le code est correct (totalKnown && nextStep), mais aucun test n'épingle l'absence du point quand budgetReady est vrai et visitStepsInitialized faux. Un cas dans OnboardingBanner.render.test.tsx suffit.
  2. Bref clignotement de l'étape suivante : avant l'initialisation, le bandeau affiche nextStep (le compteur attend, pas le libellé). Pour un profil en cours dont l'import est fait, il nomme « Classer une transaction à la main » quelques millisecondes, puis « Parcourir les catégories » une fois le drapeau lu. Même ordre de grandeur que l'attente de budgetReady déjà admise ; à garder en tête si la QA le remarque.
  3. Texte docs.dashboard.features[6] (FR/EN) : « huit étapes qui se cochent d'elles-mêmes selon vos données » n'est plus exact pour les deux visites, cochées par « J'ai compris ». La note de docs.gettingStarted le corrige déjà ; reformuler ici aussi (« huit étapes, cochées selon vos données ou par « J'ai compris » »).
  4. Corps de la PR : ajouter Resolves #425 au corps rendrait la fermeture visible dans la timeline quel que soit le chemin de merge (aujourd'hui elle ne repose que sur le message de commit).
## Revue `/pr-review` — PR #444 (#425, maillon 7/8) **Verdict : APPROVE** **Résumé.** La fondation des étapes de visite est correcte : les deux lignes restent verrouillées (hors total, hors `complete`, hors `nextStep`) jusqu'à l'initialisation, l'initialisation unique attend un chargement réussi et `budgetReady`, sa promesse partagée tient contre le double montage de StrictMode, et l'ordre « accusés puis drapeau » sans transaction laisse un arrêt entre deux écritures rejouable à l'ouverture suivante. Diff relu contre sa base `issue-431-stale-help`. ### Ce qui a été exécuté (copie hors dépôt, `git archive` de `a064043`, `npm ci` propre) - `vitest run` : 127 fichiers, **2185 tests verts**. CI de la tête : `PR Check — Frontend` en `success` (statut agrégé). - **Mutations du relecteur**, une à la fois, restaurées par `git checkout` sur la copie : | # | Mutation | Résultat | |---|---|---| | R1 | `initializeVisitSteps` n'accuse que `categories` | rouge (3) | | R2 | `shouldAcknowledgeVisitSteps` compte aussi les lignes verrouillées (Budget en Gratuite) | rouge (1) | | R3 | ligne de visite « verrouillée » avant « en attente » dans `rowState` (badge d'édition) | rouge (1) | | R4 | `needsVisitStepsInitialization` ignore le drapeau | rouge (4) | | R8 | `acknowledge` met l'état à jour sans écrire la préférence | rouge (2) | | R10 | l'initialisation accuse toujours (profil en cours compris) | rouge (1) | | R11 | `cardNext` de la carte reçoit `budgetReady` seul | rouge (1) | | R12 | drapeau collant (`state ‖ snapshot`) dans `LOAD_SUCCESS` | vert, **mutant équivalent** : le drapeau ne repasse jamais de 1 à 0 dans un même profil | | R13 | point séparateur du bandeau sous `budgetReady` au lieu de `totalKnown` | **vert**, voir suggestion 1 | Copie supprimée en fin de revue. ### Points vérifiés - Nouveau fournisseur par profil (`BrowserRouter key={refreshKey}` remonte `AppShell`) : la durée de vie « une initialisation par ouverture de profil » de `createVisitStepsInitializer` tient. - Course avec un rechargement de route : un chargement commencé avant le `refresh` de l'initialiseur porte un `loadSeq` plus ancien et son résultat est jeté ; tout chargement commencé après lit le drapeau. - Profil terminé avant l'initialisation : `bannerMode` rend `none` (et non `complete`) tant que `totalKnown` est faux, donc ni « Étape suivante » ni message de fin en avance ; après l'initialisation, `complete` avec les deux visites accusées. - Gratuite terminée à 5/5 : Budget verrouillé exclu de `shouldAcknowledgeVisitSteps`, donc les visites sont accusées et le parcours finit à 7/7 (R2 l'épingle). - Fermeture : la timeline de #425 ne porte aucun `closes` venu du corps de la PR (pas de mot-clé), mais le commit `a064043` contient `Resolves #425` : un merge ff de la pile fermera #425, ce qui est voulu puisque la pile se merge d'un bloc avec #432. - Aucune migration (v1→v17), aucune requête réseau, i18n FR/EN complète, CHANGELOG dans les deux fichiers. ### Suggestions (non bloquantes) 1. **`OnboardingBanner.tsx`, point séparateur** : R13 survit. Le code est correct (`totalKnown && nextStep`), mais aucun test n'épingle l'absence du point quand `budgetReady` est vrai et `visitStepsInitialized` faux. Un cas dans `OnboardingBanner.render.test.tsx` suffit. 2. **Bref clignotement de l'étape suivante** : avant l'initialisation, le bandeau affiche `nextStep` (le compteur attend, pas le libellé). Pour un profil en cours dont l'import est fait, il nomme « Classer une transaction à la main » quelques millisecondes, puis « Parcourir les catégories » une fois le drapeau lu. Même ordre de grandeur que l'attente de `budgetReady` déjà admise ; à garder en tête si la QA le remarque. 3. **Texte `docs.dashboard.features[6]`** (FR/EN) : « huit étapes qui se cochent d'elles-mêmes selon vos données » n'est plus exact pour les deux visites, cochées par « J'ai compris ». La note de `docs.gettingStarted` le corrige déjà ; reformuler ici aussi (« huit étapes, cochées selon vos données ou par « J'ai compris » »). 4. **Corps de la PR** : ajouter `Resolves #425` au corps rendrait la fermeture visible dans la timeline quel que soit le chemin de merge (aujourd'hui elle ne repose que sur le message de commit).
maximus changed target branch from issue-431-stale-help to main 2026-10-16 00:37:41 +00:00
maximus merged commit a064043c20 into main 2026-10-16 00:37:50 +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#444
No description provided.