feat(onboarding): allow moving profile-building to background after 10s#1295
Conversation
After 10 seconds of pipeline runtime, a "Keep building in background & continue" link fades in. Clicking it completes onboarding and navigates to /home while the pipeline finishes asynchronously — the profile is saved once done, no user action needed. Closes tinyhumansai#1228
📝 WalkthroughWalkthroughThe PR introduces a "Keep building in background" UX option to ContextGatheringStep that appears after 10 seconds of pipeline execution. Users can click to proceed without waiting for the full pipeline, while profile building continues asynchronously in the background. ChangesProfile Building Background Option
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/src/pages/onboarding/steps/ContextGatheringStep.tsx (1)
237-246:⚠️ Potential issue | 🟠 Major | ⚡ Quick winMake the background-continue path truly single-shot and handle failures.
Line 239 only checks
backgroundClickedRefbefore the 800ms auto-advance timeout is scheduled. If the pipeline finishes first and the user clicks the link during that 800ms window, the pending timeout still fires andonNext()runs a second time. Lines 293-296 also drop rejectedonNext()promises entirely, so a failedcompleteAndExit()becomes a silent/unhandled failure.Suggested fix
const [finished, setFinished] = useState(false); const [hasError, setHasError] = useState(false); const [showBackgroundLink, setShowBackgroundLink] = useState(false); const backgroundClickedRef = useRef(false); + const autoAdvanceTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); const ranRef = useRef(false); // Auto-navigate on successful completion (skip if user already clicked background link) useEffect(() => { if (finished && !hasError && !backgroundClickedRef.current) { - const t = setTimeout(() => { - void Promise.resolve(onNext()).catch(e => { + autoAdvanceTimeoutRef.current = setTimeout(() => { + if (backgroundClickedRef.current) return; + backgroundClickedRef.current = true; + void Promise.resolve(onNext()).catch(e => { console.warn('[onboarding:context] auto-advance failed', e); setHasError(true); }); }, 800); - return () => clearTimeout(t); + return () => { + if (autoAdvanceTimeoutRef.current) { + clearTimeout(autoAdvanceTimeoutRef.current); + autoAdvanceTimeoutRef.current = null; + } + }; } }, [finished, hasError, onNext]); {showBackgroundLink && ( <button type="button" className="animate-fade-in text-sm text-ocean-600 hover:text-ocean-700 underline underline-offset-2 transition-colors mt-2" onClick={() => { + if (backgroundClickedRef.current) return; backgroundClickedRef.current = true; - void onNext(); + if (autoAdvanceTimeoutRef.current) { + clearTimeout(autoAdvanceTimeoutRef.current); + autoAdvanceTimeoutRef.current = null; + } + void Promise.resolve(onNext()).catch(e => { + console.warn('[onboarding:context] background continue failed', e); + setHasError(true); + }); }}> Keep building in background & continue </button> )}Also applies to: 289-296
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/pages/onboarding/steps/ContextGatheringStep.tsx` around lines 237 - 246, The auto-advance and background-continue paths can both trigger onNext()/completeAndExit() causing double-invocation and unhandled rejections; make the backgroundClickedRef a true single-shot guard by setting backgroundClickedRef.current = true immediately when the user clicks the background-continue link (the handler that calls completeAndExit/onNext), and before invoking onNext() inside the setTimeout also re-check backgroundClickedRef.current and return early if set (symbols: backgroundClickedRef, onNext, completeAndExit, finished). Additionally, ensure every call site that calls onNext() or completeAndExit() wraps the returned promise with a .catch that logs the error and calls setHasError(true) (or awaits in an async handler with try/catch) so failures are not dropped (references: setHasError). Finally, clear the pending timeout when backgroundClickedRef.current becomes true to avoid the timeout firing after a manual click.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@app/src/pages/onboarding/steps/ContextGatheringStep.tsx`:
- Around line 237-246: The auto-advance and background-continue paths can both
trigger onNext()/completeAndExit() causing double-invocation and unhandled
rejections; make the backgroundClickedRef a true single-shot guard by setting
backgroundClickedRef.current = true immediately when the user clicks the
background-continue link (the handler that calls completeAndExit/onNext), and
before invoking onNext() inside the setTimeout also re-check
backgroundClickedRef.current and return early if set (symbols:
backgroundClickedRef, onNext, completeAndExit, finished). Additionally, ensure
every call site that calls onNext() or completeAndExit() wraps the returned
promise with a .catch that logs the error and calls setHasError(true) (or awaits
in an async handler with try/catch) so failures are not dropped (references:
setHasError). Finally, clear the pending timeout when
backgroundClickedRef.current becomes true to avoid the timeout firing after a
manual click.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b321cc87-b55b-43ce-abb6-b718f03521f2
📒 Files selected for processing (2)
app/src/pages/onboarding/steps/ContextGatheringStep.tsxapp/src/pages/onboarding/steps/__tests__/ContextGatheringStep.test.tsx
Summary
/homewhile the pipeline finishes asynchronously — the profile is saved once doneonNextif pipeline finishes around the same time as the clickChanges
app/src/pages/onboarding/steps/ContextGatheringStep.tsxshowBackgroundLinkstate, 10s timer effect, background link JSX,backgroundClickedRefguardapp/src/pages/onboarding/steps/__tests__/ContextGatheringStep.test.tsxDesign decisions
animate-fade-inCSS class andocean-600colorbackgroundClickedRef(not state) prevents auto-navigate race without extra re-rendersTest plan
animate-fade-inonNextpnpm typecheckcleanpnpm lint0 errors (warnings are pre-existing)pnpm format:checkcleanpnpm buildcleanCloses #1228
Summary by CodeRabbit
Release Notes
New Features
Tests