ProjectOnboarding.tsx
1 import * as React from 'react' 2 import { OrderedList } from '@inkjs/ui' 3 import { Box, Text } from 'ink' 4 import { 5 getCurrentProjectConfig, 6 getGlobalConfig, 7 saveCurrentProjectConfig, 8 saveGlobalConfig, 9 } from './utils/config.js' 10 import { existsSync } from 'fs' 11 import { join } from 'path' 12 import { homedir } from 'os' 13 import terminalSetup from './commands/terminalSetup.js' 14 import { getTheme } from './utils/theme.js' 15 import { RELEASE_NOTES } from './constants/releaseNotes.js' 16 import { gt } from 'semver' 17 import { isDirEmpty } from './utils/file.js' 18 19 // Function to mark onboarding as complete 20 export function markProjectOnboardingComplete(): void { 21 const projectConfig = getCurrentProjectConfig() 22 if (!projectConfig.hasCompletedProjectOnboarding) { 23 saveCurrentProjectConfig({ 24 ...projectConfig, 25 hasCompletedProjectOnboarding: true, 26 }) 27 } 28 } 29 30 function markReleaseNotesSeen(): void { 31 const config = getGlobalConfig() 32 saveGlobalConfig({ 33 ...config, 34 lastReleaseNotesSeen: MACRO.VERSION, 35 }) 36 } 37 38 type Props = { 39 workspaceDir: string 40 } 41 42 export default function ProjectOnboarding({ 43 workspaceDir, 44 }: Props): React.ReactNode { 45 // Check if project onboarding has already been completed 46 const projectConfig = getCurrentProjectConfig() 47 const showOnboarding = !projectConfig.hasCompletedProjectOnboarding 48 49 // Get previous version from config 50 const config = getGlobalConfig() 51 const previousVersion = config.lastReleaseNotesSeen 52 53 // Get release notes to show 54 let releaseNotesToShow: string[] = [] 55 if (!previousVersion || gt(MACRO.VERSION, previousVersion)) { 56 releaseNotesToShow = RELEASE_NOTES[MACRO.VERSION] || [] 57 } 58 const hasReleaseNotes = releaseNotesToShow.length > 0 59 60 // Mark release notes as seen when they're displayed without onboarding 61 React.useEffect(() => { 62 if (hasReleaseNotes && !showOnboarding) { 63 markReleaseNotesSeen() 64 } 65 }, [hasReleaseNotes, showOnboarding]) 66 67 // We only want to show either onboarding OR release notes (with preference for onboarding) 68 // If there's no onboarding to show and no release notes, return null 69 if (!showOnboarding && !hasReleaseNotes) { 70 return null 71 } 72 73 // Load what we need for onboarding 74 // NOTE: This whole component is staticly rendered Once 75 const hasClaudeMd = existsSync(join(workspaceDir, 'CLAUDE.md')) 76 const isWorkspaceDirEmpty = isDirEmpty(workspaceDir) 77 const needsClaudeMd = !hasClaudeMd && !isWorkspaceDirEmpty 78 const showTerminalTip = 79 terminalSetup.isEnabled && !getGlobalConfig().shiftEnterKeyBindingInstalled 80 81 const theme = getTheme() 82 83 return ( 84 <Box flexDirection="column" gap={1} padding={1} paddingBottom={0}> 85 {showOnboarding && ( 86 <> 87 <Text color={theme.secondaryText}>Tips for getting started:</Text> 88 <OrderedList> 89 {/* Collect all the items that should be displayed */} 90 {(() => { 91 const items = [] 92 93 if (isWorkspaceDirEmpty) { 94 items.push( 95 <OrderedList.Item key="workspace"> 96 <Text color={theme.secondaryText}> 97 Ask Claude to create a new app or clone a repository. 98 </Text> 99 </OrderedList.Item>, 100 ) 101 } 102 if (needsClaudeMd) { 103 items.push( 104 <OrderedList.Item key="claudemd"> 105 <Text color={theme.secondaryText}> 106 Run <Text color={theme.text}>/init</Text> to create a 107 CLAUDE.md file with instructions for Claude. 108 </Text> 109 </OrderedList.Item>, 110 ) 111 } 112 113 if (showTerminalTip) { 114 items.push( 115 <OrderedList.Item key="terminal"> 116 <Text color={theme.secondaryText}> 117 Run <Text color={theme.text}>/terminal-setup</Text> 118 <Text bold={false}> to set up terminal integration</Text> 119 </Text> 120 </OrderedList.Item>, 121 ) 122 } 123 124 items.push( 125 <OrderedList.Item key="questions"> 126 <Text color={theme.secondaryText}> 127 Ask Claude questions about your codebase. 128 </Text> 129 </OrderedList.Item>, 130 ) 131 132 items.push( 133 <OrderedList.Item key="changes"> 134 <Text color={theme.secondaryText}> 135 Ask Claude to implement changes to your codebase. 136 </Text> 137 </OrderedList.Item>, 138 ) 139 140 return items 141 })()} 142 </OrderedList> 143 </> 144 )} 145 146 {!showOnboarding && hasReleaseNotes && ( 147 <Box 148 borderColor={getTheme().secondaryBorder} 149 flexDirection="column" 150 marginRight={1} 151 > 152 <Box flexDirection="column" gap={0}> 153 <Box marginBottom={1}> 154 <Text>🆕 What's new in v{MACRO.VERSION}:</Text> 155 </Box> 156 <Box flexDirection="column" marginLeft={1}> 157 {releaseNotesToShow.map((note, noteIndex) => ( 158 <Text key={noteIndex} color={getTheme().secondaryText}> 159 • {note} 160 </Text> 161 ))} 162 </Box> 163 </Box> 164 </Box> 165 )} 166 167 {workspaceDir === homedir() && ( 168 <Text color={getTheme().warning}> 169 Note: You have launched <Text bold>claude</Text> in your home 170 directory. For the best experience, launch it in a project directory 171 instead. 172 </Text> 173 )} 174 </Box> 175 ) 176 }