From 7a91abba8d260efa88dd7a7254a91233d09c4be5 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Mon, 15 Jun 2026 16:43:55 -0700 Subject: [PATCH] [eric] onboarding: drop linear step-gating so every roadmap step is freely explorable --- .../components/Onboarding/steps/stepUnlock.ts | 72 ++++--------------- 1 file changed, 12 insertions(+), 60 deletions(-) diff --git a/frontend/src/app/components/Onboarding/steps/stepUnlock.ts b/frontend/src/app/components/Onboarding/steps/stepUnlock.ts index eef57122..209b87be 100644 --- a/frontend/src/app/components/Onboarding/steps/stepUnlock.ts +++ b/frontend/src/app/components/Onboarding/steps/stepUnlock.ts @@ -1,73 +1,25 @@ -// Soft, earned unlocks for the onboarding panel. A locked step is still fully -// usable in the app, this only gates the guided spotlight + shows a lock icon -// with a one-line teaser, so the tour reveals things ONE AT A TIME instead of -// dumping the whole feature surface at once. -// -// Tiers: -// - get_started (launch an agent, connect a model): unlocked from the start. -// - Tier 1 "the basics": the FIRST feature unlocks on your first agent win. -// - Tier 2 "going further": a CHAIN, each feature unlocks once you finish the -// previous tour step, so the panel only ever surfaces the NEXT thing. -// -// Off-script still counts: doing a thing yourself (opening a browser, installing -// a skill) unlocks its step immediately, so exploring is never punished. +// Onboarding is a playground, not homework: every roadmap step is freely +// explorable in any order. A linear FEATURE_CHAIN used to gate each step on +// finishing the one above it (the 🔒 "Finish the step above" teasers); that read +// as a chore, so the gating is gone and nothing is locked. The exported shapes +// are kept so the panel/roadmap callers don't change. import { useMemo } from 'react'; import type { RootState } from '@/shared/state/store'; import { useAppSelector } from '@/shared/hooks'; -import { - hasAnyAgentLaunched, - hasAnyBrowserSpawned, - hasAnySkillInstalled, -} from './skipPredicates'; import { STEPS } from './index'; -// Order features reveal in. Index 0 is tier 1 (first thing after the win); the -// rest are the tier-2 chain, each gated on finishing the one before it. -const FEATURE_CHAIN = [ - 'enable_actions', - 'use_browser', - 'agent_use_browser', - 'agent_control_agents', - 'install_skill', - 'make_app', -]; - -// A feature can ALSO unlock when its real-world milestone is met off-script. -const OFF_SCRIPT: Record boolean> = { - use_browser: hasAnyBrowserSpawned, - agent_use_browser: hasAnyBrowserSpawned, - install_skill: hasAnySkillInstalled, -}; - -const HINTS: Record = { - enable_actions: 'Run your first agent', - use_browser: 'Finish the step above', - agent_use_browser: 'Finish the step above', - agent_control_agents: 'Finish the step above', - install_skill: 'Finish the step above', - make_app: 'Finish the step above', -}; - -export function isStepUnlocked(stepId: string, s: RootState): boolean { - const idx = FEATURE_CHAIN.indexOf(stepId); - if (idx === -1) return true; // get_started entry points are always open - if (idx === 0) return hasAnyAgentLaunched(s); // tier 1 opens on the first win - const prevDone = (s.onboardingProgress?.completedSteps ?? []).includes( - FEATURE_CHAIN[idx - 1], - ); - return prevDone || (OFF_SCRIPT[stepId]?.(s) ?? false); +export function isStepUnlocked(_stepId: string, _s: RootState): boolean { + return true; } -export function unlockHintFor(stepId: string): string | null { - return HINTS[stepId] ?? null; +export function unlockHintFor(_stepId: string): string | null { + return null; } -/** Set of currently-unlocked step ids. Keyed on a stable string so the selector - * only re-renders when the unlock set actually changes. */ +/** Set of currently-unlocked step ids: every step, always. Selector form kept + * so callers' memoization is unchanged. */ export function useUnlockedStepIds(): Set { - const key = useAppSelector((s) => - STEPS.filter((st) => isStepUnlocked(st.id, s)).map((st) => st.id).join('|'), - ); + const key = useAppSelector(() => STEPS.map((st) => st.id).join('|')); return useMemo(() => new Set(key ? key.split('|') : []), [key]); }