@@ -25,6 +25,10 @@ type Splash interface {
2525 util.Model
2626 layout.Sizeable
2727 layout.Help
28+ // SetOnboarding controls whether the splash shows model selection UI
29+ SetOnboarding(bool)
30+ // SetProjectInit controls whether the splash shows project initialization prompt
31+ SetProjectInit(bool)
2832}
2933
3034const (
@@ -32,14 +36,6 @@ const (
3236 SplashScreenPaddingY = 1 // Padding Y for the splash screen
3337)
3438
35−type SplashScreenState string
36−
37−const (
38− SplashScreenStateOnboarding SplashScreenState = "onboarding"
39− SplashScreenStateInitialize SplashScreenState = "initialize"
40− SplashScreenStateReady SplashScreenState = "ready"
41−)
42−
4339// OnboardingCompleteMsg is sent when onboarding is complete
4440type OnboardingCompleteMsg struct{}
4541
@@ -47,10 +43,14 @@ type splashCmp struct {
4743 width, height int
4844 keyMap KeyMap
4945 logoRendered string
50− state SplashScreenState
46+
47+ // State
48+ isOnboarding bool
49+ needsProjectInit bool
50+ selectedNo bool
51+
5152 modelList *models.ModelListComponent
5253 cursorRow, cursorCol int
53− selectedNo bool // true if "No" button is selected in initialize state
5454}
5555
5656func New() Splash {
@@ -69,33 +69,21 @@ func New() Splash {
6969 inputStyle := t.S().Base.Padding(0, 1, 0, 1)
7070 modelList := models.NewModelListComponent(listKeyMap, inputStyle, "Find your fave")
7171 return &splashCmp{
72− width: 0,
73− height: 0,
74− keyMap: keyMap,
75− state: SplashScreenStateOnboarding,
76− logoRendered: "",
77− modelList: modelList,
78− selectedNo: false,
72+ width: 0,
73+ height: 0,
74+ keyMap: keyMap,
75+ logoRendered: "",
76+ modelList: modelList,
77+ selectedNo: false,
7978 }
8079}
8180
82−// GetSize implements SplashPage.
83−func (s *splashCmp) GetSize() (int, int) {
84− return s.width, s.height
85−}
86−
87−// Init implements SplashPage.
88−func (s *splashCmp) Init() tea.Cmd {
89− if config.HasInitialDataConfig() {
90− if b, _ := config.ProjectNeedsInitialization(); b {
91− s.state = SplashScreenStateInitialize
92− } else {
93− s.state = SplashScreenStateReady
94− }
95− } else {
81+func (s *splashCmp) SetOnboarding(onboarding bool) {
82+ s.isOnboarding = onboarding
83+ if onboarding {
9684 providers, err := config.Providers()
9785 if err != nil {
98− return util.ReportError(err)
86+ return
9987 }
10088 filteredProviders := []provider.Provider{}
10189 simpleProviders := []string{
@@ -111,9 +99,21 @@ func (s *splashCmp) Init() tea.Cmd {
11199 }
112100 }
113101 s.modelList.SetProviders(filteredProviders)
114− return s.modelList.Init()
115102 }
116− return nil
103+}
104+
105+func (s *splashCmp) SetProjectInit(needsInit bool) {
106+ s.needsProjectInit = needsInit
107+}
108+
109+// GetSize implements SplashPage.
110+func (s *splashCmp) GetSize() (int, int) {
111+ return s.width, s.height
112+}
113+
114+// Init implements SplashPage.
115+func (s *splashCmp) Init() tea.Cmd {
116+ return s.modelList.Init()
117117}
118118
119119// SetSize implements SplashPage.
@@ -137,42 +137,34 @@ func (s *splashCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
137137 case tea.KeyPressMsg:
138138 switch {
139139 case key.Matches(msg, s.keyMap.Select):
140− if s.state == SplashScreenStateOnboarding {
140+ if s.isOnboarding {
141141 modelInx := s.modelList.SelectedIndex()
142142 items := s.modelList.Items()
143143 selectedItem := items[modelInx].(completions.CompletionItem).Value().(models.ModelOption)
144144 if s.isProviderConfigured(string(selectedItem.Provider.ID)) {
145145 cmd := s.setPreferredModel(selectedItem)
146− s.state = SplashScreenStateReady
147− if b, err := config.ProjectNeedsInitialization(); err != nil {
148− return s, tea.Batch(cmd, util.ReportError(err))
149− } else if b {
150− s.state = SplashScreenStateInitialize
151− return s, cmd
152− } else {
153− s.state = SplashScreenStateReady
154− return s, tea.Batch(cmd, util.CmdHandler(OnboardingCompleteMsg{}))
155− }
146+ s.isOnboarding = false
147+ return s, tea.Batch(cmd, util.CmdHandler(OnboardingCompleteMsg{}))
156148 }
157− } else if s.state == SplashScreenStateInitialize {
149+ } else if s.needsProjectInit {
158150 return s, s.initializeProject()
159151 }
160152 case key.Matches(msg, s.keyMap.Tab, s.keyMap.LeftRight):
161− if s.state == SplashScreenStateInitialize {
153+ if s.needsProjectInit {
162154 s.selectedNo = !s.selectedNo
163155 return s, nil
164156 }
165157 case key.Matches(msg, s.keyMap.Yes):
166− if s.state == SplashScreenStateInitialize {
158+ if s.needsProjectInit {
167159 return s, s.initializeProject()
168160 }
169161 case key.Matches(msg, s.keyMap.No):
170− if s.state == SplashScreenStateInitialize {
171− s.state = SplashScreenStateReady
162+ if s.needsProjectInit {
163+ s.needsProjectInit = false
172164 return s, util.CmdHandler(OnboardingCompleteMsg{})
173165 }
174166 default:
175− if s.state == SplashScreenStateOnboarding {
167+ if s.isOnboarding {
176168 u, cmd := s.modelList.Update(msg)
177169 s.modelList = u
178170 return s, cmd
@@ -183,7 +175,7 @@ func (s *splashCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
183175}
184176
185177func (s *splashCmp) initializeProject() tea.Cmd {
186− s.state = SplashScreenStateReady
178+ s.needsProjectInit = false
187179 prompt := `Please analyze this codebase and create a CRUSH.md file containing:
1881801. Build/lint/test commands - especially for running a single test
1891812. Code style guidelines including imports, formatting, types, naming conventions, error handling, etc.
@@ -193,7 +185,6 @@ If there's already a CRUSH.md, improve it.
193185If there are Cursor rules (in .cursor/rules/ or .cursorrules) or Copilot rules (in .github/copilot-instructions.md), make sure to include them.
194186Add the .crush directory to the .gitignore file if it's not already there.`
195187
196− // Mark the project as initialized
197188 if err := config.MarkProjectInitialized(); err != nil {
198189 return util.ReportError(err)
199190 }
@@ -293,9 +284,7 @@ func (s *splashCmp) View() tea.View {
293284 var cursor *tea.Cursor
294285
295286 var content string
296− switch s.state {
297− case SplashScreenStateOnboarding:
298− // Show logo and model selector
287+ if s.isOnboarding {
299288 remainingHeight := s.height - lipgloss.Height(s.logoRendered) - (SplashScreenPaddingY * 2)
300289 modelListView := s.modelList.View()
301290 cursor = s.moveCursor(modelListView.Cursor())
@@ -312,7 +301,7 @@ func (s *splashCmp) View() tea.View {
312301 s.logoRendered,
313302 modelSelector,
314303 )
315− case SplashScreenStateInitialize:
304+ } else if s.needsProjectInit {
316305 t := styles.CurrentTheme()
317306
318307 titleStyle := t.S().Base.Foreground(t.FgBase)
@@ -361,9 +350,7 @@ func (s *splashCmp) View() tea.View {
361350 s.logoRendered,
362351 initContent,
363352 )
364−
365− default:
366− // Show just the logo for other states
353+ } else {
367354 content = s.logoRendered
368355 }
369356
@@ -407,13 +394,13 @@ func (m *splashCmp) moveCursor(cursor *tea.Cursor) *tea.Cursor {
407394
408395// Bindings implements SplashPage.
409396func (s *splashCmp) Bindings() []key.Binding {
410− if s.state == SplashScreenStateOnboarding {
397+ if s.isOnboarding {
411398 return []key.Binding{
412399 s.keyMap.Select,
413400 s.keyMap.Next,
414401 s.keyMap.Previous,
415402 }
416− } else if s.state == SplashScreenStateInitialize {
403+ } else if s.needsProjectInit {
417404 return []key.Binding{
418405 s.keyMap.Select,
419406 s.keyMap.Yes,