🎯 Use case
Tracks onboarding state for new projects. On the API surface it exposes Step, getSteps, isProjectOnboardingComplete, maybeMarkProjectOnboardingComplete, and shouldShowProjectOnboarding (and more) — mainly functions, hooks, or classes. Dependencies touch lodash-es and Node path helpers. It composes internal code from utils (relative imports).
Generated from folder role, exports, dependency roots, and inline comments — not hand-reviewed for every path.
🧠 Inline summary
import memoize from 'lodash-es/memoize.js' import { join } from 'path' import { getCurrentProjectConfig, saveCurrentProjectConfig,
📤 Exports (heuristic)
StepgetStepsisProjectOnboardingCompletemaybeMarkProjectOnboardingCompleteshouldShowProjectOnboardingincrementProjectOnboardingSeenCount
📚 External import roots
Package roots from from "…" (relative paths omitted).
lodash-espath
🖥️ Source preview
import memoize from 'lodash-es/memoize.js'
import { join } from 'path'
import {
getCurrentProjectConfig,
saveCurrentProjectConfig,
} from './utils/config.js'
import { getCwd } from './utils/cwd.js'
import { isDirEmpty } from './utils/file.js'
import { getFsImplementation } from './utils/fsOperations.js'
export type Step = {
key: string
text: string
isComplete: boolean
isCompletable: boolean
isEnabled: boolean
}
export function getSteps(): Step[] {
const hasClaudeMd = getFsImplementation().existsSync(
join(getCwd(), 'CLAUDE.md'),
)
const isWorkspaceDirEmpty = isDirEmpty(getCwd())
return [
{
key: 'workspace',
text: 'Ask Claude to create a new app or clone a repository',
isComplete: false,
isCompletable: true,
isEnabled: isWorkspaceDirEmpty,
},
{
key: 'claudemd',
text: 'Run /init to create a CLAUDE.md file with instructions for Claude',
isComplete: hasClaudeMd,
isCompletable: true,
isEnabled: !isWorkspaceDirEmpty,
},
]
}
export function isProjectOnboardingComplete(): boolean {
return getSteps()
.filter(({ isCompletable, isEnabled }) => isCompletable && isEnabled)
.every(({ isComplete }) => isComplete)
}
export function maybeMarkProjectOnboardingComplete(): void {
// Short-circuit on cached config — isProjectOnboardingComplete() hits
// the filesystem, and REPL.tsx calls this on every prompt submit.
if (getCurrentProjectConfig().hasCompletedProjectOnboarding) {
return
}
if (isProjectOnboardingComplete()) {
saveCurrentProjectConfig(current => ({
...current,
hasCompletedProjectOnboarding: true,
}))
}
}
export const shouldShowProjectOnboarding = memoize((): boolean => {
const projectConfig = getCurrentProjectConfig()
// Short-circuit on cached config before isProjectOnboardingComplete()
// hits the filesystem — this runs during first render.
if (
projectConfig.hasCompletedProjectOnboarding ||
projectConfig.projectOnboardingSeenCount >= 4 ||
process.env.IS_DEMO
) {
return false
}
return !isProjectOnboardingComplete()
})
export function incrementProjectOnboardingSeenCount(): void {
saveCurrentProjectConfig(current => ({
...current,
projectOnboardingSeenCount: current.projectOnboardingSeenCount + 1,
}))
}