mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-02 22:58:50 +02:00
[eric] onboarding: drop linear step-gating so every roadmap step is freely explorable
This commit is contained in:
@@ -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<string, (s: RootState) => boolean> = {
|
||||
use_browser: hasAnyBrowserSpawned,
|
||||
agent_use_browser: hasAnyBrowserSpawned,
|
||||
install_skill: hasAnySkillInstalled,
|
||||
};
|
||||
|
||||
const HINTS: Record<string, string> = {
|
||||
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<string> {
|
||||
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]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user