From c9efbeca39889eeee910c05323e233aa5435a642 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Mon, 15 Jun 2026 16:42:04 -0700 Subject: [PATCH] [eric] onboarding: restart-tour reload + cursor yields silently on off-script click --- .../app/components/Onboarding/OnboardingDirector.ts | 13 +++++++++++++ .../src/app/components/Onboarding/ac/acRuntime.ts | 13 +++++++++++++ .../Settings/sections/general/GeneralAdvanced.tsx | 4 ++++ 3 files changed, 30 insertions(+) diff --git a/frontend/src/app/components/Onboarding/OnboardingDirector.ts b/frontend/src/app/components/Onboarding/OnboardingDirector.ts index 6522adf3..2dbaa7e7 100644 --- a/frontend/src/app/components/Onboarding/OnboardingDirector.ts +++ b/frontend/src/app/components/Onboarding/OnboardingDirector.ts @@ -98,8 +98,20 @@ class OnboardingDirector { controller.abort(); } }; + // Yield to the user: the runtime fires this when, during a wait for a + // SPECIFIC click target, the user instead clicks somewhere off-script. Back + // off silently (reason 'user-cancel' suppresses acRuntime's recovery popup) + // rather than nagging or auto-performing the action. It is scoped to + // click-target waits in the runtime, so it can't cancel free-interaction + // waits (e.g. connecting a model in Settings, where the user must click + // non-tour controls). + const onUserOffscript = () => { + report('step_aborted_user_offscript', { step_id: stepId }); + controller.abort('user-cancel'); + }; window.addEventListener('openswarm:onboarding:lost_target', onLost); window.addEventListener('hashchange', onRouteChange); + window.addEventListener('openswarm:onboarding:user_offscript', onUserOffscript); try { await runStep({ @@ -115,6 +127,7 @@ class OnboardingDirector { } finally { window.removeEventListener('openswarm:onboarding:lost_target', onLost); window.removeEventListener('hashchange', onRouteChange); + window.removeEventListener('openswarm:onboarding:user_offscript', onUserOffscript); if (this.currentAbort === controller) { this.currentAbort = null; } diff --git a/frontend/src/app/components/Onboarding/ac/acRuntime.ts b/frontend/src/app/components/Onboarding/ac/acRuntime.ts index 92e9c955..eea5e174 100644 --- a/frontend/src/app/components/Onboarding/ac/acRuntime.ts +++ b/frontend/src/app/components/Onboarding/ac/acRuntime.ts @@ -860,12 +860,25 @@ function waitForCondition( ) ) { finish(false); + return; } + // Off-script click during a wait for a specific target: if it's not any + // tour control and not the cursor/popup, the user has gone their own + // way, so tell the director to back off (it aborts the step silently). + // Scoped here to click-target waits so free-interaction waits + // (redux_predicate / event_bus) never cancel on a stray click. + if (!(el instanceof Element)) return; + if (el.closest('[data-onboarding], [data-select-type]')) return; + for (let n: Element | null = el; n; n = n.parentElement) { + if (parseInt(window.getComputedStyle(n).zIndex || '0', 10) >= 10500) return; + } + window.dispatchEvent(new CustomEvent('openswarm:onboarding:user_offscript', { detail: { target: cond.target } })); }; document.addEventListener('click', handler, true); cleanup = () => document.removeEventListener('click', handler, true); return; } + case 'redux_predicate': { const check = () => { const value = cond.selector(store.getState()); diff --git a/frontend/src/app/pages/Settings/sections/general/GeneralAdvanced.tsx b/frontend/src/app/pages/Settings/sections/general/GeneralAdvanced.tsx index 62803460..01ce5b94 100644 --- a/frontend/src/app/pages/Settings/sections/general/GeneralAdvanced.tsx +++ b/frontend/src/app/pages/Settings/sections/general/GeneralAdvanced.tsx @@ -117,6 +117,10 @@ const GeneralAdvanced: React.FC<{ dispatch(resetTour()); dispatch(closeSettingsModal()); onboardingBus.emit('settings:closed'); + // In-place reset can't re-arm the welcome cursor's once-per-mount + // guard, so the tour never re-fired without a reload; reload from the + // now-cleared storage is the reliable restart (matches the workaround). + window.location.reload(); }} sx={{ color: c.text.secondary,