From 115adebe89e0166c2a15f323bb901a5f5151d208 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 27 Jan 2026 11:38:57 -0500 Subject: [PATCH] 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. --- internal/ui/model/onboarding.go | 3 +- internal/ui/model/ui.go | 63 +++++++++++++++++---------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/internal/ui/model/onboarding.go b/internal/ui/model/onboarding.go index d18469ee822460e60544a304afebb37dac7fa0d9..9d7e9ea0882a2d0cf8508ed75f8e5d4b5edb2b03 100644 --- a/internal/ui/model/onboarding.go +++ b/internal/ui/model/onboarding.go @@ -68,8 +68,7 @@ func (m *UI) initializeProject() tea.Cmd { // skipInitializeProject skips project initialization and transitions to the landing view. func (m *UI) skipInitializeProject() tea.Cmd { // TODO: initialize the project - m.state = uiLanding - m.focus = uiFocusEditor + m.setState(uiLanding, uiFocusEditor) // mark the project as initialized return m.markProjectInitialized } diff --git a/internal/ui/model/ui.go b/internal/ui/model/ui.go index 7895f323ba8c8a24de1eb5895e377cbeef761d40..e6e54af525cb72c61a942e408dcbda9203de6cf2 100644 --- a/internal/ui/model/ui.go +++ b/internal/ui/model/ui.go @@ -258,8 +258,6 @@ func New(com *common.Common) *UI { com: com, dialog: dialog.NewOverlay(), keyMap: keyMap, - focus: uiFocusNone, - state: uiOnboarding, textarea: ta, chat: ch, completions: comp, @@ -271,18 +269,6 @@ func New(com *common.Common) *UI { status := NewStatus(com, ui) - // set onboarding state defaults - ui.onboarding.yesInitializeSelected = true - - if !com.Config().IsConfigured() { - ui.state = uiOnboarding - } else if n, _ := config.ProjectNeedsInitialization(); n { - ui.state = uiInitialize - } else { - ui.state = uiLanding - ui.focus = uiFocusEditor - } - ui.setEditorPrompt(false) ui.randomizePlaceholders() ui.textarea.Placeholder = ui.readyPlaceholder @@ -291,6 +277,20 @@ func New(com *common.Common) *UI { // Initialize compact mode from config ui.forceCompactMode = com.Config().Options.TUI.CompactMode + // set onboarding state defaults + ui.onboarding.yesInitializeSelected = true + + desiredState := uiLanding + desiredFocus := uiFocusEditor + if !com.Config().IsConfigured() { + desiredState = uiOnboarding + } else if n, _ := config.ProjectNeedsInitialization(); n { + desiredState = uiInitialize + } + + // set initial state + ui.setState(desiredState, desiredFocus) + return ui } @@ -310,6 +310,14 @@ func (m *UI) Init() tea.Cmd { return tea.Batch(cmds...) } +// setState changes the UI state and focus. +func (m *UI) setState(state uiState, focus uiFocusState) { + m.state = state + m.focus = focus + // Changing the state may change layout, so update it. + m.updateLayoutAndSize() +} + // loadCustomCommands loads the custom commands asynchronously. func (m *UI) loadCustomCommands() tea.Cmd { return func() tea.Msg { @@ -360,10 +368,10 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, timage.RequestCapabilities(m.imgCaps.Env)) } case loadSessionMsg: - m.state = uiChat if m.forceCompactMode { m.isCompact = true } + m.setState(uiChat, m.focus) m.session = msg.session m.sessionFiles = msg.files 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) { return m, nil case tea.WindowSizeMsg: m.width, m.height = msg.Width, msg.Height - m.handleCompactMode(m.width, m.height) m.updateLayoutAndSize() // XXX: We need to store cell dimensions for image rendering. m.imgCaps.Columns, m.imgCaps.Rows = msg.Width, msg.Height @@ -1212,9 +1219,7 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd { m.dialog.CloseDialog(dialog.ModelsID) if isOnboarding { - m.state = uiLanding - m.focus = uiFocusEditor - + m.setState(uiLanding, uiFocusEditor) m.com.Config().SetupAgents() if err := m.com.App.InitCoderAgent(context.TODO()); err != nil { cmds = append(cmds, uiutil.ReportError(err)) @@ -1507,7 +1512,7 @@ func (m *UI) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd { m.newSession() case key.Matches(msg, m.keyMap.Tab): if m.state != uiLanding { - m.focus = uiFocusMain + m.setState(m.state, uiFocusMain) m.textarea.Blur() m.chat.Focus() m.chat.SetSelected(m.chat.Len() - 1) @@ -2054,29 +2059,26 @@ func (m *UI) toggleCompactMode() tea.Cmd { return uiutil.ReportError(err) } - m.handleCompactMode(m.width, m.height) m.updateLayoutAndSize() return nil } -// handleCompactMode updates the UI state based on window size and compact mode setting. -func (m *UI) handleCompactMode(newWidth, newHeight int) { +// updateLayoutAndSize updates the layout and sizes of UI components. +func (m *UI) updateLayoutAndSize() { + // Determine if we should be in compact mode if m.state == uiChat { if m.forceCompactMode { m.isCompact = true return } - if newWidth < compactModeWidthBreakpoint || newHeight < compactModeHeightBreakpoint { + if m.width < compactModeWidthBreakpoint || m.height < compactModeHeightBreakpoint { m.isCompact = true } else { m.isCompact = false } } -} -// updateLayoutAndSize updates the layout and sizes of UI components. -func (m *UI) updateLayoutAndSize() { m.layout = m.generateLayout(m.width, m.height) m.updateSize() } @@ -2121,7 +2123,7 @@ func (m *UI) generateLayout(w, h int) layout { const landingHeaderHeight = 4 var helpKeyMap help.KeyMap = m - if m.status.ShowingAll() { + if m.status != nil && m.status.ShowingAll() { for _, row := range helpKeyMap.FullHelp() { helpHeight = max(helpHeight, len(row)) } @@ -2527,7 +2529,6 @@ func (m *UI) sendMessage(content string, attachments ...message.Attachment) tea. if err != nil { return uiutil.ReportError(err) } - m.state = uiChat if m.forceCompactMode { m.isCompact = true } @@ -2535,6 +2536,7 @@ func (m *UI) sendMessage(content string, attachments ...message.Attachment) tea. m.session = &newSession cmds = append(cmds, m.loadSession(newSession.ID)) } + m.setState(uiChat, m.focus) } // Capture session ID to avoid race with main goroutine updating m.session. @@ -2782,8 +2784,7 @@ func (m *UI) newSession() { m.session = nil m.sessionFiles = nil - m.state = uiLanding - m.focus = uiFocusEditor + m.setState(uiLanding, uiFocusEditor) m.textarea.Focus() m.chat.Blur() m.chat.ClearMessages()