feat(onboarding): visit steps foundation, eight rows, "Got it" and one-time initialization (#425) #444
No reviewers
Labels
No labels
autopilot:pending-human
source:analyste
source:defenseur
source:human
source:medic
status:approved
status:blocked
status:in-progress
status:needs-clarification
status:needs-fix
status:ready
status:review
status:triage
type:bug
type:feature
type:infra
type:refactor
type:schema
type:security
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: maximus/Simpl-Resultat#444
Loading…
Reference in a new issue
No description provided.
Delete branch "issue-425-visit-steps"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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" inuser_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
/categoriesand/reportscome 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
ChecklistItemKey+categories,reports;CHECKLIST_ITEM_KEYSin the new order;VISIT_STEP_KEYS.computeChecklist: optionsacknowledgedandvisitStepsInitialized; the two visit rows are locked (out of the total, ofcompleteand ofnextStep) until initialization, theirtokept. New pureshouldAcknowledgeVisitSteps.onboardingService:acknowledgedStepKey,acknowledgeStep,ONBOARDING_VISIT_STEPS_INITIALIZED_KEY, snapshotacknowledged(oneLIKEquery, filtered on the visit keys and "1") andvisitStepsInitialized;initializeVisitSteps(acknowledge)without a transaction (D16): the acknowledgements, then the flag, idempotent. Nothing notifies the data channel.useOnboardingChecklist):ACKNOWLEDGE(wins over an older snapshot likeSKIP, keeps the card latch),acknowledge(key),visitStepsInitializedexposed;createVisitStepsInitializer(shared promise against StrictMode's double mount, then a re-read) started by an effect whenneedsVisitStepsInitialization(successful load,budgetReady, flag unset).deriveOnboardingViewpassesbudgetReady && visitStepsInitializedtobannerMode.StepCard:onAcknowledge→ "Got it" before "Skip this step", never on a done step.OnboardingStepCardpasses it for a visit step; its rank andcardNextwait for both flags.OnboardingBanner,OnboardingChecklist,DashboardEmptyState: avisitStepsInitializedprop; counters wait for both flags; the visit rows show "pending" (never "locked", no edition badge) before initialization; the Budget row's pending state keepsbudgetReadyalone.onboarding.checklist.items.categories/.reports,onboarding.card.acknowledge(FR, EN).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").BudgetTable.wiring.test.tsandUsersSettingsPage.test.tsxneeded no change (no typedOnboardingValue, 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.tsxunchanged: it shares the hook's value as is (its render test now checksacknowledgeandvisitStepsInitialized).Decisions (details in the run's decisions log)
visitStepsInitializedprop on the banner and the list rather than a combinedbudgetReady, so the Budget row's "pending" keepsbudgetReadyalone.deriveOnboardingViewcomputes the conjunction itself;bannerMode/cardNextsignatures unchanged.OnboardingStepCardis tested through a mockedcardStepon a stand-in route (/categoriesguides no step before #432).Proof table (mutations, one at a time, restored by
git checkout, all on the committed implementation)acknowledgedshouldAcknowledgeVisitStepscounts the visit rowsshouldAcknowledgeVisitStepsignores skipped rowswithTransactionACKNOWLEDGEreleases the card latchACKNOWLEDGEdoes not win over an older snapshotbudgetReadycategoriesaftermanualCategoryinCHECKLIST_ITEM_KEYSeff77e4): red (1)visitStepsInitialized: truetocomputeChecklistValidation
npm run buildgreen (tsc covers the test files),npm test127 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
Self-review (autopilot worker, not independent) — verdict: APPROVE
Reviewed the full diff of #444 against
issue-431-stale-help(heada064043).Acceptance criteria of #425
computeChecklisttotals 8/7 (tests), counters wait forbudgetReady && visitStepsInitialized(M21, M22).nonewhile it runs), theninitializeVisitSteps(true)and a re-read givecomplete(hook test "first render of a profile whose path was complete", wiring test withinitializeVisitSteps(true); M14, M15).shouldAcknowledgeVisitStepsfalse, hook walk test; M3, M4).CARD_PAGES/PAGE_STEPSuntouched, so/categoriesshows the banner, no card.npm run buildandnpm testgreen (127 files, 2185 tests).Security / correctness
initializeVisitStepsopens no transaction (D16), pinned by a test that asserts nowithTransactionand noBEGIN(M8); writes are idempotent, flag last (M7).VISIT_STEP_KEYSand "1" (M5, M6);LIKEwildcard caveat handled like the skipped steps.ACKNOWLEDGEkeeps the card latch (M10) and wins over an older snapshot (M11).i18n / docs / CHANGELOG
docs.*keys, ADR 0022 (§2, §4, §5),architecture.md, QA list, CHANGELOG Added in both files.Points for the independent reviewer
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".OnboardingStepCardis tested through a mockedcardStepon a stand-in route, since/categoriesguides no step before #432.CHECKLIST_ITEM_KEYSorder); a test now pins it (eff77e4).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
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, horsnextStep) jusqu'à l'initialisation, l'initialisation unique attend un chargement réussi etbudgetReady, 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 baseissue-431-stale-help.Ce qui a été exécuté (copie hors dépôt,
git archivedea064043,npm cipropre)vitest run: 127 fichiers, 2185 tests verts. CI de la tête :PR Check — Frontendensuccess(statut agrégé).git checkoutsur la copie :initializeVisitStepsn'accuse quecategoriesshouldAcknowledgeVisitStepscompte aussi les lignes verrouillées (Budget en Gratuite)rowState(badge d'édition)needsVisitStepsInitializationignore le drapeauacknowledgemet l'état à jour sans écrire la préférencecardNextde la carte reçoitbudgetReadyseulstate ‖ snapshot) dansLOAD_SUCCESSbudgetReadyau lieu detotalKnownCopie supprimée en fin de revue.
Points vérifiés
BrowserRouter key={refreshKey}remonteAppShell) : la durée de vie « une initialisation par ouverture de profil » decreateVisitStepsInitializertient.refreshde l'initialiseur porte unloadSeqplus ancien et son résultat est jeté ; tout chargement commencé après lit le drapeau.bannerModerendnone(et noncomplete) tant quetotalKnownest faux, donc ni « Étape suivante » ni message de fin en avance ; après l'initialisation,completeavec les deux visites accusées.shouldAcknowledgeVisitSteps, donc les visites sont accusées et le parcours finit à 7/7 (R2 l'épingle).closesvenu du corps de la PR (pas de mot-clé), mais le commita064043contientResolves #425: un merge ff de la pile fermera #425, ce qui est voulu puisque la pile se merge d'un bloc avec #432.Suggestions (non bloquantes)
OnboardingBanner.tsx, point séparateur : R13 survit. Le code est correct (totalKnown && nextStep), mais aucun test n'épingle l'absence du point quandbudgetReadyest vrai etvisitStepsInitializedfaux. Un cas dansOnboardingBanner.render.test.tsxsuffit.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 debudgetReadydéjà admise ; à garder en tête si la QA le remarque.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 dedocs.gettingStartedle corrige déjà ; reformuler ici aussi (« huit étapes, cochées selon vos données ou par « J'ai compris » »).Resolves #425au 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).