[eric] onboarding: Get started shows busy state + finish double-fire guard

This commit is contained in:
ciregenz
2026-07-27 12:08:30 -07:00
parent ea63a7a388
commit 56ec809ca5
2 changed files with 9 additions and 2 deletions
@@ -52,6 +52,8 @@ const BeatCard: React.FC<{
}> = ({ c, identity, onFinish, onBack }) => {
const { accent, gradient } = useThemeAccent();
const [name, setName] = useState(() => nameFromIdentity(identity));
// finish() can legitimately wait up to PREP_WAIT_CAP_MS on prep; the button must say so, once.
const [submitting, setSubmitting] = useState(false);
const seed = useMemo(() => Math.floor(Math.random() * EPITHETS.length), []);
const [roll, setRoll] = useState(0);
const epithet = EPITHETS[(seed + roll) % EPITHETS.length];
@@ -184,8 +186,9 @@ const BeatCard: React.FC<{
c={c}
title={name ? `Welcome to OpenSwarm, ${name}` : 'Welcome to OpenSwarm'}
body={"Here's your Swarm Card. Show it off to the world or keep it to yourself.\n\nAnd with that, you're ready to run your new OS."}
nextLabel="Get started"
onNext={() => onFinish(name.trim() || null)}
nextLabel={submitting ? 'Setting up...' : 'Get started'}
nextDisabled={submitting}
onNext={() => { if (submitting) return; setSubmitting(true); onFinish(name.trim() || null); }}
onBack={onBack}
stageDark
>
@@ -29,6 +29,7 @@ export function useOnboardingV3Pipeline() {
// greeting/starters the instant they're ready (the common case, prep finishes during the beats) without
// awaiting, so the curtain never blocks behind a spinner.
const prepReadyRef = useRef<PrepResponse | null>(null);
const finishingRef = useRef(false);
const scanResultRef = useRef<ScanResult | null>(null);
const usageSummaryRef = useRef<string>('');
const usageReadRef = useRef<Promise<void> | null>(null);
@@ -72,6 +73,9 @@ export function useOnboardingV3Pipeline() {
}, []);
const finish = useCallback(async (outcome: 'done' | 'skipped') => {
// Double-fire guard: repeated Get-started clicks during the prep wait must not stage twice.
if (finishingRef.current) return;
finishingRef.current = true;
if (outcome === 'skipped') {
dispatch(setFlowActive(false));
dispatch(updateSettingsPatch({ onboarding_v3: 'skipped', accent_color: accent, accent_gradient: gradient, theme: mode }));