From fbbc57c8105a110587f27028d9241d6aa90b0e5e Mon Sep 17 00:00:00 2001 From: ciregenz Date: Sun, 14 Jun 2026 03:20:38 -0700 Subject: [PATCH] [eric] onboarding: per-popup dwell override so the welcome cursor holds ~3s then clicks (not the tour's 6s floor) --- .../src/app/components/Onboarding/ac/acRuntime.ts | 12 +++++++++++- .../Onboarding/steps/step00_welcomeNudge.ts | 9 ++++----- .../src/app/components/Onboarding/steps/types.ts | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/components/Onboarding/ac/acRuntime.ts b/frontend/src/app/components/Onboarding/ac/acRuntime.ts index 960125c7..073a6c62 100644 --- a/frontend/src/app/components/Onboarding/ac/acRuntime.ts +++ b/frontend/src/app/components/Onboarding/ac/acRuntime.ts @@ -37,6 +37,8 @@ interface RunContext { findStep: (id: string) => OnboardingStep | undefined; highlightCleanup: { current: (() => void) | null }; popupShownAt: { current: number | null }; + // Per-popup dwell override; null falls back to MIN_POPUP_DWELL_MS. Lets a short one-liner (welcome nudge) move on fast instead of sitting the full 6s. + popupDwellMs: { current: number | null }; } // 6s = streaming typewriter cadence + ~3s post-stream read time; floor for popups that auto-transition without an explicit user action. @@ -61,8 +63,9 @@ function abortableSleep(ms: number, signal: AbortSignal): Promise { async function ensurePopupDwell(ctx: RunContext): Promise { const shownAt = ctx.popupShownAt.current; if (shownAt == null) return; + const dwell = ctx.popupDwellMs.current ?? MIN_POPUP_DWELL_MS; const elapsed = performance.now() - shownAt; - const remaining = MIN_POPUP_DWELL_MS - elapsed; + const remaining = dwell - elapsed; if (remaining > 0) await abortableSleep(remaining, ctx.signal); } @@ -89,6 +92,7 @@ export async function runStep(args: RunStepArgs): Promise { const highlightCleanup: { current: (() => void) | null } = { current: null }; const popupShownAt: { current: number | null } = { current: null }; + const popupDwellMs: { current: number | null } = { current: null }; const ctx: RunContext = { ac, store, @@ -100,6 +104,7 @@ export async function runStep(args: RunStepArgs): Promise { findStep, highlightCleanup, popupShownAt, + popupDwellMs, }; try { @@ -119,6 +124,7 @@ export async function runStep(args: RunStepArgs): Promise { report('dependency_walk', { step_id: step.id, dep_id: dep.stepId }); ac.showPopup('Quick setup before we continue.'); ctx.popupShownAt.current = performance.now(); + ctx.popupDwellMs.current = null; await sleep(700); // Non-silent dep-walk so each move_to has a label; telemetry stays per-step to avoid double-count. await runOps(depStep.ops, { ...ctx, silent: false, stepId: depStep.id }); @@ -276,6 +282,7 @@ async function runOp(op: ACOp, ctx: RunContext): Promise { await ensurePopupDwell(ctx); ac.hidePopup(); ctx.popupShownAt.current = null; + ctx.popupDwellMs.current = null; ac.stopTracking(); if (ctx.highlightCleanup.current) { ctx.highlightCleanup.current(); @@ -363,6 +370,7 @@ async function runOp(op: ACOp, ctx: RunContext): Promise { await ensurePopupDwell(ctx); ac.showPopup(op.text); ctx.popupShownAt.current = performance.now(); + ctx.popupDwellMs.current = op.dwellMs ?? null; return; } case 'multi_choice': { @@ -398,6 +406,7 @@ async function runOp(op: ACOp, ctx: RunContext): Promise { await ensurePopupDwell(ctx); ac.showPopup(op.popup); ctx.popupShownAt.current = performance.now(); + ctx.popupDwellMs.current = null; } await sleep(op.durationMs ?? 600); return; @@ -580,6 +589,7 @@ async function runOp(op: ACOp, ctx: RunContext): Promise { }); ac.showPopup("Didn't seem to go through. Try again?"); ctx.popupShownAt.current = performance.now(); + ctx.popupDwellMs.current = null; await waitForCondition( op.condition, signal, diff --git a/frontend/src/app/components/Onboarding/steps/step00_welcomeNudge.ts b/frontend/src/app/components/Onboarding/steps/step00_welcomeNudge.ts index c892e80b..282e97f1 100644 --- a/frontend/src/app/components/Onboarding/steps/step00_welcomeNudge.ts +++ b/frontend/src/app/components/Onboarding/steps/step00_welcomeNudge.ts @@ -11,11 +11,10 @@ export const welcomeOpenStep: OnboardingStep = { title: 'Welcome', description: '', ops: [ - { kind: 'delay', ms: 700 }, // let the big POP land - { kind: 'popup', text: 'Let me open up a chat for you.' }, // say it first - { kind: 'delay', ms: 900 }, // read, then go click it - { kind: 'move_to', target: S.newAgentButton }, // travel to the chat bubble - { kind: 'click', target: S.newAgentButton, simulate: true }, // click -> spawns the welcome chat + { kind: 'delay', ms: 700 }, // let the big POP land + { kind: 'popup', text: 'Let me open up a chat for you.', dwellMs: 3000 }, // say it, hold ~3s (not the tour's 6s floor) + { kind: 'move_to', target: S.newAgentButton }, // dwell elapses, then travel to the chat bubble + { kind: 'click', target: S.newAgentButton, simulate: true }, // click -> spawns the welcome chat { kind: 'outro' }, ], }; diff --git a/frontend/src/app/components/Onboarding/steps/types.ts b/frontend/src/app/components/Onboarding/steps/types.ts index 08bb085b..9d869719 100644 --- a/frontend/src/app/components/Onboarding/steps/types.ts +++ b/frontend/src/app/components/Onboarding/steps/types.ts @@ -14,7 +14,7 @@ export type ACMultiChoiceOption = { export type ACOp = | { kind: 'move_to'; target: Selector; offset?: { x: number; y: number } } - | { kind: 'popup'; text: string; cta?: string } + | { kind: 'popup'; text: string; cta?: string; dwellMs?: number } | { kind: 'multi_choice'; opId: string; question: string; options: ACMultiChoiceOption[] } | { kind: 'highlight_section'; target: Selector; popup?: string; durationMs?: number } | {