@@ -25,6 +25,10 @@ type Splash interface {
25 util.Model
25 util.Model
26 layout.Sizeable
26 layout.Sizeable
27 layout.Help
27 layout.Help
28+ // SetOnboarding controls whether the splash shows model selection UI
30+ // SetProjectInit controls whether the splash shows project initialization prompt
@@ -32,14 +36,6 @@ const (
32 SplashScreenPaddingY = 1 // Padding Y for the splash screen
36 SplashScreenPaddingY = 1 // Padding Y for the splash screen
35−type SplashScreenState string
38− SplashScreenStateOnboarding SplashScreenState = "onboarding"
39− SplashScreenStateInitialize SplashScreenState = "initialize"
40− SplashScreenStateReady SplashScreenState = "ready"
43// OnboardingCompleteMsg is sent when onboarding is complete
39// OnboardingCompleteMsg is sent when onboarding is complete
44type OnboardingCompleteMsg struct{}
40type OnboardingCompleteMsg struct{}
@@ -47,10 +43,14 @@ type splashCmp struct {
47 width, height int
43 width, height int
48 keyMap KeyMap
44 keyMap KeyMap
49 logoRendered string
45 logoRendered string
50− state SplashScreenState
46+
49+ needsProjectInit bool
51 modelList *models.ModelListComponent
52 modelList *models.ModelListComponent
52 cursorRow, cursorCol int
53 cursorRow, cursorCol int
53− selectedNo bool // true if "No" button is selected in initialize state
56func New() Splash {
56func New() Splash {
@@ -69,33 +69,21 @@ func New() Splash {
69 inputStyle := t.S().Base.Padding(0, 1, 0, 1)
69 inputStyle := t.S().Base.Padding(0, 1, 0, 1)
70 modelList := models.NewModelListComponent(listKeyMap, inputStyle, "Find your fave")
70 modelList := models.NewModelListComponent(listKeyMap, inputStyle, "Find your fave")
71 return &splashCmp{
71 return &splashCmp{
72− width: 0,
72+ width: 0,
73− height: 0,
73+ height: 0,
74− keyMap: keyMap,
74+ keyMap: keyMap,
75− state: SplashScreenStateOnboarding,
76− logoRendered: "",
75+ logoRendered: "",
77− modelList: modelList,
76+ modelList: modelList,
78− selectedNo: false,
77+ selectedNo: false,
82−// GetSize implements SplashPage.
83−func (s *splashCmp) GetSize() (int, int) {
81+func (s *splashCmp) SetOnboarding(onboarding bool) {
84− return s.width, s.height
82+ s.isOnboarding = onboarding
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
93− s.state = SplashScreenStateReady
96 providers, err := config.Providers()
84 providers, err := config.Providers()
97 if err != nil {
85 if err != nil {
98− return util.ReportError(err)
86+ return
100 filteredProviders := []provider.Provider{}
88 filteredProviders := []provider.Provider{}
101 simpleProviders := []string{
89 simpleProviders := []string{
@@ -111,9 +99,21 @@ func (s *splashCmp) Init() tea.Cmd {
113 s.modelList.SetProviders(filteredProviders)
101 s.modelList.SetProviders(filteredProviders)
114− return s.modelList.Init()
105+func (s *splashCmp) SetProjectInit(needsInit bool) {
106+ s.needsProjectInit = needsInit
109+// GetSize implements SplashPage.
110+func (s *splashCmp) GetSize() (int, int) {
116− return nil
111+ return s.width, s.height
114+// Init implements SplashPage.
115+func (s *splashCmp) Init() tea.Cmd {
116+ return s.modelList.Init()
119// SetSize implements SplashPage.
119// SetSize implements SplashPage.
@@ -137,42 +137,34 @@ func (s *splashCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
137 case tea.KeyPressMsg:
137 case tea.KeyPressMsg:
139 case key.Matches(msg, s.keyMap.Select):
139 case key.Matches(msg, s.keyMap.Select):
140− if s.state == SplashScreenStateOnboarding {
140+ if s.isOnboarding {
141 modelInx := s.modelList.SelectedIndex()
141 modelInx := s.modelList.SelectedIndex()
142 items := s.modelList.Items()
142 items := s.modelList.Items()
143 selectedItem := items[modelInx].(completions.CompletionItem).Value().(models.ModelOption)
143 selectedItem := items[modelInx].(completions.CompletionItem).Value().(models.ModelOption)
144 if s.isProviderConfigured(string(selectedItem.Provider.ID)) {
144 if s.isProviderConfigured(string(selectedItem.Provider.ID)) {
145 cmd := s.setPreferredModel(selectedItem)
145 cmd := s.setPreferredModel(selectedItem)
146− s.state = SplashScreenStateReady
146+ s.isOnboarding = false
147− if b, err := config.ProjectNeedsInitialization(); err != nil {
148− return s, tea.Batch(cmd, util.ReportError(err))
150− s.state = SplashScreenStateInitialize
153− s.state = SplashScreenStateReady
154− return s, tea.Batch(cmd, util.CmdHandler(OnboardingCompleteMsg{}))
147+ return s, tea.Batch(cmd, util.CmdHandler(OnboardingCompleteMsg{}))
157− } else if s.state == SplashScreenStateInitialize {
149+ } else if s.needsProjectInit {
158 return s, s.initializeProject()
150 return s, s.initializeProject()
160 case key.Matches(msg, s.keyMap.Tab, s.keyMap.LeftRight):
152 case key.Matches(msg, s.keyMap.Tab, s.keyMap.LeftRight):
161− if s.state == SplashScreenStateInitialize {
153+ if s.needsProjectInit {
162 s.selectedNo = !s.selectedNo
154 s.selectedNo = !s.selectedNo
163 return s, nil
155 return s, nil
165 case key.Matches(msg, s.keyMap.Yes):
157 case key.Matches(msg, s.keyMap.Yes):
166− if s.state == SplashScreenStateInitialize {
158+ if s.needsProjectInit {
167 return s, s.initializeProject()
159 return s, s.initializeProject()
169 case key.Matches(msg, s.keyMap.No):
161 case key.Matches(msg, s.keyMap.No):
170− if s.state == SplashScreenStateInitialize {
162+ if s.needsProjectInit {
171− s.state = SplashScreenStateReady
163+ s.needsProjectInit = false
172 return s, util.CmdHandler(OnboardingCompleteMsg{})
164 return s, util.CmdHandler(OnboardingCompleteMsg{})
175− if s.state == SplashScreenStateOnboarding {
167+ if s.isOnboarding {
176 u, cmd := s.modelList.Update(msg)
168 u, cmd := s.modelList.Update(msg)
177 s.modelList = u
169 s.modelList = u
178 return s, cmd
170 return s, cmd
@@ -183,7 +175,7 @@ func (s *splashCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
185func (s *splashCmp) initializeProject() tea.Cmd {
177func (s *splashCmp) initializeProject() tea.Cmd {
186− s.state = SplashScreenStateReady
178+ s.needsProjectInit = false
187 prompt := `Please analyze this codebase and create a CRUSH.md file containing:
179 prompt := `Please analyze this codebase and create a CRUSH.md file containing:
1881. Build/lint/test commands - especially for running a single test
1801. Build/lint/test commands - especially for running a single test
1892. Code style guidelines including imports, formatting, types, naming conventions, error handling, etc.
1812. 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.
193If there are Cursor rules (in .cursor/rules/ or .cursorrules) or Copilot rules (in .github/copilot-instructions.md), make sure to include them.
185If there are Cursor rules (in .cursor/rules/ or .cursorrules) or Copilot rules (in .github/copilot-instructions.md), make sure to include them.
194Add the .crush directory to the .gitignore file if it's not already there.`
186Add the .crush directory to the .gitignore file if it's not already there.`
196− // Mark the project as initialized
197 if err := config.MarkProjectInitialized(); err != nil {
188 if err := config.MarkProjectInitialized(); err != nil {
198 return util.ReportError(err)
189 return util.ReportError(err)
@@ -293,9 +284,7 @@ func (s *splashCmp) View() tea.View {
293 var cursor *tea.Cursor
284 var cursor *tea.Cursor
295 var content string
286 var content string
296− switch s.state {
287+ if s.isOnboarding {
297− case SplashScreenStateOnboarding:
298− // Show logo and model selector
299 remainingHeight := s.height - lipgloss.Height(s.logoRendered) - (SplashScreenPaddingY * 2)
288 remainingHeight := s.height - lipgloss.Height(s.logoRendered) - (SplashScreenPaddingY * 2)
300 modelListView := s.modelList.View()
289 modelListView := s.modelList.View()
301 cursor = s.moveCursor(modelListView.Cursor())
290 cursor = s.moveCursor(modelListView.Cursor())
@@ -312,7 +301,7 @@ func (s *splashCmp) View() tea.View {
312 s.logoRendered,
301 s.logoRendered,
313 modelSelector,
302 modelSelector,
315− case SplashScreenStateInitialize:
304+ } else if s.needsProjectInit {
316 t := styles.CurrentTheme()
305 t := styles.CurrentTheme()
318 titleStyle := t.S().Base.Foreground(t.FgBase)
307 titleStyle := t.S().Base.Foreground(t.FgBase)
@@ -361,9 +350,7 @@ func (s *splashCmp) View() tea.View {
361 s.logoRendered,
350 s.logoRendered,
362 initContent,
351 initContent,
366− // Show just the logo for other states
367 content = s.logoRendered
354 content = s.logoRendered
@@ -407,13 +394,13 @@ func (m *splashCmp) moveCursor(cursor *tea.Cursor) *tea.Cursor {
408// Bindings implements SplashPage.
395// Bindings implements SplashPage.
409func (s *splashCmp) Bindings() []key.Binding {
396func (s *splashCmp) Bindings() []key.Binding {
410− if s.state == SplashScreenStateOnboarding {
397+ if s.isOnboarding {
411 return []key.Binding{
398 return []key.Binding{
412 s.keyMap.Select,
399 s.keyMap.Select,
413 s.keyMap.Next,
400 s.keyMap.Next,
414 s.keyMap.Previous,
401 s.keyMap.Previous,
416− } else if s.state == SplashScreenStateInitialize {
403+ } else if s.needsProjectInit {
417 return []key.Binding{
404 return []key.Binding{
418 s.keyMap.Select,
405 s.keyMap.Select,
419 s.keyMap.Yes,
406 s.keyMap.Yes,