on main help sign in
fix(ui): use setState method to change UI state and focus (#1994)
115adebAyman Bagabas
internal/ui/model/onboarding.go+1−2
@@ -68,8 +68,7 @@ func (m *UI) initializeProject() tea.Cmd {
68// skipInitializeProject skips project initialization and transitions to the landing view.
68// skipInitializeProject skips project initialization and transitions to the landing view.
69func (m *UI) skipInitializeProject() tea.Cmd {
69func (m *UI) skipInitializeProject() tea.Cmd {
70 // TODO: initialize the project
70 // TODO: initialize the project
71− m.state = uiLanding
72− m.focus = uiFocusEditor
71+ m.setState(uiLanding, uiFocusEditor)
73 // mark the project as initialized
72 // mark the project as initialized
74 return m.markProjectInitialized
73 return m.markProjectInitialized
75}
74}
internal/ui/model/ui.go+32−31
@@ -258,8 +258,6 @@ func New(com *common.Common) *UI {
258 com: com,
258 com: com,
259 dialog: dialog.NewOverlay(),
259 dialog: dialog.NewOverlay(),
260 keyMap: keyMap,
260 keyMap: keyMap,
261− focus: uiFocusNone,
262− state: uiOnboarding,
263 textarea: ta,
261 textarea: ta,
264 chat: ch,
262 chat: ch,
265 completions: comp,
263 completions: comp,
@@ -271,18 +269,6 @@ func New(com *common.Common) *UI {
271
269
272 status := NewStatus(com, ui)
270 status := NewStatus(com, ui)
273
271
274− // set onboarding state defaults
275− ui.onboarding.yesInitializeSelected = true
276−
277− if !com.Config().IsConfigured() {
278− ui.state = uiOnboarding
279− } else if n, _ := config.ProjectNeedsInitialization(); n {
280− ui.state = uiInitialize
281− } else {
282− ui.state = uiLanding
283− ui.focus = uiFocusEditor
284− }
285−
286 ui.setEditorPrompt(false)
272 ui.setEditorPrompt(false)
287 ui.randomizePlaceholders()
273 ui.randomizePlaceholders()
288 ui.textarea.Placeholder = ui.readyPlaceholder
274 ui.textarea.Placeholder = ui.readyPlaceholder
@@ -291,6 +277,20 @@ func New(com *common.Common) *UI {
291 // Initialize compact mode from config
277 // Initialize compact mode from config
292 ui.forceCompactMode = com.Config().Options.TUI.CompactMode
278 ui.forceCompactMode = com.Config().Options.TUI.CompactMode
293
279
280+ // set onboarding state defaults
281+ ui.onboarding.yesInitializeSelected = true
282+
283+ desiredState := uiLanding
284+ desiredFocus := uiFocusEditor
285+ if !com.Config().IsConfigured() {
286+ desiredState = uiOnboarding
287+ } else if n, _ := config.ProjectNeedsInitialization(); n {
288+ desiredState = uiInitialize
289+ }
290+
291+ // set initial state
292+ ui.setState(desiredState, desiredFocus)
293+
294 return ui
294 return ui
295}
295}
296
296
@@ -310,6 +310,14 @@ func (m *UI) Init() tea.Cmd {
310 return tea.Batch(cmds...)
310 return tea.Batch(cmds...)
311}
311}
312
312
313+// setState changes the UI state and focus.
314+func (m *UI) setState(state uiState, focus uiFocusState) {
315+ m.state = state
316+ m.focus = focus
317+ // Changing the state may change layout, so update it.
318+ m.updateLayoutAndSize()
319+}
320+
313// loadCustomCommands loads the custom commands asynchronously.
321// loadCustomCommands loads the custom commands asynchronously.
314func (m *UI) loadCustomCommands() tea.Cmd {
322func (m *UI) loadCustomCommands() tea.Cmd {
315 return func() tea.Msg {
323 return func() tea.Msg {
@@ -360,10 +368,10 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
360 cmds = append(cmds, timage.RequestCapabilities(m.imgCaps.Env))
368 cmds = append(cmds, timage.RequestCapabilities(m.imgCaps.Env))
361 }
369 }
362 case loadSessionMsg:
370 case loadSessionMsg:
363− m.state = uiChat
364 if m.forceCompactMode {
371 if m.forceCompactMode {
365 m.isCompact = true
372 m.isCompact = true
366 }
373 }
374+ m.setState(uiChat, m.focus)
367 m.session = msg.session
375 m.session = msg.session
368 m.sessionFiles = msg.files
376 m.sessionFiles = msg.files
369 msgs, err := m.com.App.Messages.List(context.Background(), m.session.ID)
377 msgs, err := m.com.App.Messages.List(context.Background(), m.session.ID)
@@ -493,7 +501,6 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
493 return m, nil
501 return m, nil
494 case tea.WindowSizeMsg:
502 case tea.WindowSizeMsg:
495 m.width, m.height = msg.Width, msg.Height
503 m.width, m.height = msg.Width, msg.Height
496− m.handleCompactMode(m.width, m.height)
497 m.updateLayoutAndSize()
504 m.updateLayoutAndSize()
498 // XXX: We need to store cell dimensions for image rendering.
505 // XXX: We need to store cell dimensions for image rendering.
499 m.imgCaps.Columns, m.imgCaps.Rows = msg.Width, msg.Height
506 m.imgCaps.Columns, m.imgCaps.Rows = msg.Width, msg.Height
@@ -1212,9 +1219,7 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd {
1212 m.dialog.CloseDialog(dialog.ModelsID)
1219 m.dialog.CloseDialog(dialog.ModelsID)
1213
1220
1214 if isOnboarding {
1221 if isOnboarding {
1215− m.state = uiLanding
1216− m.focus = uiFocusEditor
1222+ m.setState(uiLanding, uiFocusEditor)
1217−
1218 m.com.Config().SetupAgents()
1223 m.com.Config().SetupAgents()
1219 if err := m.com.App.InitCoderAgent(context.TODO()); err != nil {
1224 if err := m.com.App.InitCoderAgent(context.TODO()); err != nil {
1220 cmds = append(cmds, uiutil.ReportError(err))
1225 cmds = append(cmds, uiutil.ReportError(err))
@@ -1507,7 +1512,7 @@ func (m *UI) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
1507 m.newSession()
1512 m.newSession()
1508 case key.Matches(msg, m.keyMap.Tab):
1513 case key.Matches(msg, m.keyMap.Tab):
1509 if m.state != uiLanding {
1514 if m.state != uiLanding {
1510− m.focus = uiFocusMain
1515+ m.setState(m.state, uiFocusMain)
1511 m.textarea.Blur()
1516 m.textarea.Blur()
1512 m.chat.Focus()
1517 m.chat.Focus()
1513 m.chat.SetSelected(m.chat.Len() - 1)
1518 m.chat.SetSelected(m.chat.Len() - 1)
@@ -2054,29 +2059,26 @@ func (m *UI) toggleCompactMode() tea.Cmd {
2054 return uiutil.ReportError(err)
2059 return uiutil.ReportError(err)
2055 }
2060 }
2056
2061
2057− m.handleCompactMode(m.width, m.height)
2058 m.updateLayoutAndSize()
2062 m.updateLayoutAndSize()
2059
2063
2060 return nil
2064 return nil
2061}
2065}
2062
2066
2063−// handleCompactMode updates the UI state based on window size and compact mode setting.
2067+// updateLayoutAndSize updates the layout and sizes of UI components.
2064−func (m *UI) handleCompactMode(newWidth, newHeight int) {
2068+func (m *UI) updateLayoutAndSize() {
2069+ // Determine if we should be in compact mode
2065 if m.state == uiChat {
2070 if m.state == uiChat {
2066 if m.forceCompactMode {
2071 if m.forceCompactMode {
2067 m.isCompact = true
2072 m.isCompact = true
2068 return
2073 return
2069 }
2074 }
2070− if newWidth < compactModeWidthBreakpoint || newHeight < compactModeHeightBreakpoint {
2075+ if m.width < compactModeWidthBreakpoint || m.height < compactModeHeightBreakpoint {
2071 m.isCompact = true
2076 m.isCompact = true
2072 } else {
2077 } else {
2073 m.isCompact = false
2078 m.isCompact = false
2074 }
2079 }
2075 }
2080 }
2076−}
2077
2081
2078−// updateLayoutAndSize updates the layout and sizes of UI components.
2079−func (m *UI) updateLayoutAndSize() {
2080 m.layout = m.generateLayout(m.width, m.height)
2082 m.layout = m.generateLayout(m.width, m.height)
2081 m.updateSize()
2083 m.updateSize()
2082}
2084}
@@ -2121,7 +2123,7 @@ func (m *UI) generateLayout(w, h int) layout {
2121 const landingHeaderHeight = 4
2123 const landingHeaderHeight = 4
2122
2124
2123 var helpKeyMap help.KeyMap = m
2125 var helpKeyMap help.KeyMap = m
2124− if m.status.ShowingAll() {
2126+ if m.status != nil && m.status.ShowingAll() {
2125 for _, row := range helpKeyMap.FullHelp() {
2127 for _, row := range helpKeyMap.FullHelp() {
2126 helpHeight = max(helpHeight, len(row))
2128 helpHeight = max(helpHeight, len(row))
2127 }
2129 }
@@ -2527,7 +2529,6 @@ func (m *UI) sendMessage(content string, attachments ...message.Attachment) tea.
2527 if err != nil {
2529 if err != nil {
2528 return uiutil.ReportError(err)
2530 return uiutil.ReportError(err)
2529 }
2531 }
2530− m.state = uiChat
2531 if m.forceCompactMode {
2532 if m.forceCompactMode {
2532 m.isCompact = true
2533 m.isCompact = true
2533 }
2534 }
@@ -2535,6 +2536,7 @@ func (m *UI) sendMessage(content string, attachments ...message.Attachment) tea.
2535 m.session = &newSession
2536 m.session = &newSession
2536 cmds = append(cmds, m.loadSession(newSession.ID))
2537 cmds = append(cmds, m.loadSession(newSession.ID))
2537 }
2538 }
2539+ m.setState(uiChat, m.focus)
2538 }
2540 }
2539
2541
2540 // Capture session ID to avoid race with main goroutine updating m.session.
2542 // Capture session ID to avoid race with main goroutine updating m.session.
@@ -2782,8 +2784,7 @@ func (m *UI) newSession() {
2782
2784
2783 m.session = nil
2785 m.session = nil
2784 m.sessionFiles = nil
2786 m.sessionFiles = nil
2785− m.state = uiLanding
2786− m.focus = uiFocusEditor
2787+ m.setState(uiLanding, uiFocusEditor)
2787 m.textarea.Focus()
2788 m.textarea.Focus()
2788 m.chat.Blur()
2789 m.chat.Blur()
2789 m.chat.ClearMessages()
2790 m.chat.ClearMessages()