fix(ui): Sprint 9 — dismiss X / Skip tour did not hide the dialog

Root cause: the OnboardingTour early-return is gated on
isComplete=true, but App.tsx was calling onboarding.reset() on
onComplete. reset() does the inverse: clears the localStorage key
and flips isComplete to FALSE. The user clicked X, the localStorage
key got written, but the App-level flag flipped to false, so the
tour re-rendered and the early-return did not fire — the dialog
stayed visible.

Fix: split the dismiss and reset paths into two distinct callbacks
onComplete (dismiss) and onReset (re-show). Added markComplete to
useOnboarding: flips isComplete to true. App wires:
  onComplete -> onboarding.markComplete()
  onReset    -> onboarding.reset()
The tour itself still calls writeComplete() before invoking
onComplete, so the localStorage key is written once on dismiss.

Also cleaned markComplete: it now only flips state (the tour already
wrote the key), removing a redundant double-write.

Verified npm run build green on docker-willester. No regression
expected; all other Sprint 9 code paths untouched.
This commit is contained in:
2026-06-05 13:40:51 -07:00
parent 0b6c5dcfb7
commit 1562929f6b
2 changed files with 36 additions and 7 deletions
+10 -3
View File
@@ -102,9 +102,16 @@ function App() {
<OnboardingTour
isComplete={onboarding.isComplete}
onComplete={() => {
// The tour already wrote the localStorage key via
// writeComplete(); flip the App-level flag so it stays
// hidden after the next render.
// Dismiss path (X / Skip / Esc / "Got it"). The tour
// already wrote the localStorage key; flip the
// App-level flag to true so the early-return fires and
// the dialog disappears.
onboarding.markComplete()
}}
onReset={() => {
// Re-show path (?reset-tour=1). The tour already
// cleared the localStorage key; flip the App-level
// flag to false so the tour re-appears.
onboarding.reset()
}}
/>