[eric] onboarding: per-popup dwell override so the welcome cursor holds ~3s then clicks (not the tour's 6s floor)

This commit is contained in:
ciregenz
2026-06-14 03:20:38 -07:00
parent e9c8774ad7
commit fbbc57c810
3 changed files with 16 additions and 7 deletions
@@ -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<void> {
async function ensurePopupDwell(ctx: RunContext): Promise<void> {
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<void> {
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<void> {
findStep,
highlightCleanup,
popupShownAt,
popupDwellMs,
};
try {
@@ -119,6 +124,7 @@ export async function runStep(args: RunStepArgs): Promise<void> {
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<void> {
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<void> {
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<void> {
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<void> {
});
ac.showPopup("Didn't seem to go through. Try again?");
ctx.popupShownAt.current = performance.now();
ctx.popupDwellMs.current = null;
await waitForCondition(
op.condition,
signal,
@@ -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' },
],
};
@@ -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 }
| {