on main help sign in
fix(ui): use setState method to change UI state and focus (#1994)

This change introduces a setState method in the UI model to encapsulate the logic for changing the UI state and focus. This ensures that any time the state or focus is changed, the layout and size are updated accordingly.

commit115adebe89e0166c2a15f323bb901a5f5151d208 authorAyman Bagabas <ayman.bagabas@gmail.com> dateTue 27 Jan 2026, 11:38 (9 months ago) parent699ae40
2 files changed+33−33
internal/ui/model/onboarding.go+1−2 internal/ui/model/ui.go+32−31
internal/ui/model/onboarding.go+1−2
@@ -68,8 +68,7 @@ func (m *UI) initializeProject() tea.Cmd {
6868// skipInitializeProject skips project initialization and transitions to the landing view.
6969func (m *UI) skipInitializeProject() tea.Cmd {
7070 // TODO: initialize the project
71− m.state = uiLanding
72− m.focus = uiFocusEditor
71+ m.setState(uiLanding, uiFocusEditor)
7372 // mark the project as initialized
7473 return m.markProjectInitialized
7574}
internal/ui/model/ui.go+32−31
@@ -258,8 +258,6 @@ func New(com *common.Common) *UI {
258258 com: com,
259259 dialog: dialog.NewOverlay(),
260260 keyMap: keyMap,
261− focus: uiFocusNone,
262− state: uiOnboarding,
263261 textarea: ta,
264262 chat: ch,
265263 completions: comp,
@@ -271,18 +269,6 @@ func New(com *common.Common) *UI {
271269
272270 status := NewStatus(com, ui)
273271
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−
286272 ui.setEditorPrompt(false)
287273 ui.randomizePlaceholders()
288274 ui.textarea.Placeholder = ui.readyPlaceholder
@@ -291,6 +277,20 @@ func New(com *common.Common) *UI {
291277 // Initialize compact mode from config
292278 ui.forceCompactMode = com.Config().Options.TUI.CompactMode
293279
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+
294294 return ui
295295}
296296
@@ -310,6 +310,14 @@ func (m *UI) Init() tea.Cmd {
310310 return tea.Batch(cmds...)
311311}
312312
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+
313321// loadCustomCommands loads the custom commands asynchronously.
314322func (m *UI) loadCustomCommands() tea.Cmd {
315323 return func() tea.Msg {
@@ -360,10 +368,10 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
360368 cmds = append(cmds, timage.RequestCapabilities(m.imgCaps.Env))
361369 }
362370 case loadSessionMsg:
363− m.state = uiChat
364371 if m.forceCompactMode {
365372 m.isCompact = true
366373 }
374+ m.setState(uiChat, m.focus)
367375 m.session = msg.session
368376 m.sessionFiles = msg.files
369377 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) {
493501 return m, nil
494502 case tea.WindowSizeMsg:
495503 m.width, m.height = msg.Width, msg.Height
496− m.handleCompactMode(m.width, m.height)
497504 m.updateLayoutAndSize()
498505 // XXX: We need to store cell dimensions for image rendering.
499506 m.imgCaps.Columns, m.imgCaps.Rows = msg.Width, msg.Height
@@ -1212,9 +1219,7 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd {
12121219 m.dialog.CloseDialog(dialog.ModelsID)
12131220
12141221 if isOnboarding {
1215− m.state = uiLanding
1216− m.focus = uiFocusEditor
1217−
1222+ m.setState(uiLanding, uiFocusEditor)
12181223 m.com.Config().SetupAgents()
12191224 if err := m.com.App.InitCoderAgent(context.TODO()); err != nil {
12201225 cmds = append(cmds, uiutil.ReportError(err))
@@ -1507,7 +1512,7 @@ func (m *UI) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
15071512 m.newSession()
15081513 case key.Matches(msg, m.keyMap.Tab):
15091514 if m.state != uiLanding {
1510− m.focus = uiFocusMain
1515+ m.setState(m.state, uiFocusMain)
15111516 m.textarea.Blur()
15121517 m.chat.Focus()
15131518 m.chat.SetSelected(m.chat.Len() - 1)
@@ -2054,29 +2059,26 @@ func (m *UI) toggleCompactMode() tea.Cmd {
20542059 return uiutil.ReportError(err)
20552060 }
20562061
2057− m.handleCompactMode(m.width, m.height)
20582062 m.updateLayoutAndSize()
20592063
20602064 return nil
20612065}
20622066
2063−// handleCompactMode updates the UI state based on window size and compact mode setting.
2064−func (m *UI) handleCompactMode(newWidth, newHeight int) {
2067+// updateLayoutAndSize updates the layout and sizes of UI components.
2068+func (m *UI) updateLayoutAndSize() {
2069+ // Determine if we should be in compact mode
20652070 if m.state == uiChat {
20662071 if m.forceCompactMode {
20672072 m.isCompact = true
20682073 return
20692074 }
2070− if newWidth < compactModeWidthBreakpoint || newHeight < compactModeHeightBreakpoint {
2075+ if m.width < compactModeWidthBreakpoint || m.height < compactModeHeightBreakpoint {
20712076 m.isCompact = true
20722077 } else {
20732078 m.isCompact = false
20742079 }
20752080 }
2076−}
20772081
2078−// updateLayoutAndSize updates the layout and sizes of UI components.
2079−func (m *UI) updateLayoutAndSize() {
20802082 m.layout = m.generateLayout(m.width, m.height)
20812083 m.updateSize()
20822084}
@@ -2121,7 +2123,7 @@ func (m *UI) generateLayout(w, h int) layout {
21212123 const landingHeaderHeight = 4
21222124
21232125 var helpKeyMap help.KeyMap = m
2124− if m.status.ShowingAll() {
2126+ if m.status != nil && m.status.ShowingAll() {
21252127 for _, row := range helpKeyMap.FullHelp() {
21262128 helpHeight = max(helpHeight, len(row))
21272129 }
@@ -2527,7 +2529,6 @@ func (m *UI) sendMessage(content string, attachments ...message.Attachment) tea.
25272529 if err != nil {
25282530 return uiutil.ReportError(err)
25292531 }
2530− m.state = uiChat
25312532 if m.forceCompactMode {
25322533 m.isCompact = true
25332534 }
@@ -2535,6 +2536,7 @@ func (m *UI) sendMessage(content string, attachments ...message.Attachment) tea.
25352536 m.session = &newSession
25362537 cmds = append(cmds, m.loadSession(newSession.ID))
25372538 }
2539+ m.setState(uiChat, m.focus)
25382540 }
25392541
25402542 // Capture session ID to avoid race with main goroutine updating m.session.
@@ -2782,8 +2784,7 @@ func (m *UI) newSession() {
27822784
27832785 m.session = nil
27842786 m.sessionFiles = nil
2785− m.state = uiLanding
2786− m.focus = uiFocusEditor
2787+ m.setState(uiLanding, uiFocusEditor)
27872788 m.textarea.Focus()
27882789 m.chat.Blur()
27892790 m.chat.ClearMessages()