projectOnboardingState.ts 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. import memoize from 'lodash-es/memoize.js'
  2. import { join } from 'path'
  3. import {
  4. getCurrentProjectConfig,
  5. saveCurrentProjectConfig,
  6. } from './utils/config.js'
  7. import { getCwd } from './utils/cwd.js'
  8. import { isDirEmpty } from './utils/file.js'
  9. import { getFsImplementation } from './utils/fsOperations.js'
  10. export type Step = {
  11. key: string
  12. text: string
  13. isComplete: boolean
  14. isCompletable: boolean
  15. isEnabled: boolean
  16. }
  17. export function getSteps(): Step[] {
  18. const hasClaudeMd = getFsImplementation().existsSync(
  19. join(getCwd(), 'CLAUDE.md'),
  20. )
  21. const isWorkspaceDirEmpty = isDirEmpty(getCwd())
  22. return [
  23. {
  24. key: 'workspace',
  25. text: 'Ask Claude to create a new app or clone a repository',
  26. isComplete: false,
  27. isCompletable: true,
  28. isEnabled: isWorkspaceDirEmpty,
  29. },
  30. {
  31. key: 'claudemd',
  32. text: 'Run /init to create a CLAUDE.md file with instructions for Claude',
  33. isComplete: hasClaudeMd,
  34. isCompletable: true,
  35. isEnabled: !isWorkspaceDirEmpty,
  36. },
  37. ]
  38. }
  39. export function isProjectOnboardingComplete(): boolean {
  40. return getSteps()
  41. .filter(({ isCompletable, isEnabled }) => isCompletable && isEnabled)
  42. .every(({ isComplete }) => isComplete)
  43. }
  44. export function maybeMarkProjectOnboardingComplete(): void {
  45. // Short-circuit on cached config — isProjectOnboardingComplete() hits
  46. // the filesystem, and REPL.tsx calls this on every prompt submit.
  47. if (getCurrentProjectConfig().hasCompletedProjectOnboarding) {
  48. return
  49. }
  50. if (isProjectOnboardingComplete()) {
  51. saveCurrentProjectConfig(current => ({
  52. ...current,
  53. hasCompletedProjectOnboarding: true,
  54. }))
  55. }
  56. }
  57. export const shouldShowProjectOnboarding = memoize((): boolean => {
  58. const projectConfig = getCurrentProjectConfig()
  59. // Short-circuit on cached config before isProjectOnboardingComplete()
  60. // hits the filesystem — this runs during first render.
  61. if (
  62. projectConfig.hasCompletedProjectOnboarding ||
  63. projectConfig.projectOnboardingSeenCount >= 4 ||
  64. process.env.IS_DEMO
  65. ) {
  66. return false
  67. }
  68. return !isProjectOnboardingComplete()
  69. })
  70. export function incrementProjectOnboardingSeenCount(): void {
  71. saveCurrentProjectConfig(current => ({
  72. ...current,
  73. projectOnboardingSeenCount: current.projectOnboardingSeenCount + 1,
  74. }))
  75. }