···832832func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
833833 switch msg := msg.(type) {
834834835835+ case tea.MouseMsg:
836836+ if m.state == stateInbox && msg.Action == tea.MouseActionPress && msg.Button == tea.MouseButtonLeft && msg.Y == 0 {
837837+ // Click on tab bar — compute zones and match.
838838+ _, zones := folderTabs(m.folders, "", m.folderCounts)
839839+ offX := 0
840840+ if len(m.accounts) > 1 {
841841+ offX = len(" "+m.activeAccount().Name+" ·") + 2
842842+ }
843843+ clickX := msg.X - offX
844844+ for _, z := range zones {
845845+ if clickX >= z.xStart && clickX < z.xEnd {
846846+ if z.folderIndex == m.activeFolderI && m.offTabFolder == "" {
847847+ return m, nil // already on this tab
848848+ }
849849+ m.activeFolderI = z.folderIndex
850850+ m.offTabFolder = ""
851851+ m.loading = true
852852+ return m, tea.Batch(m.spinner.Tick, m.fetchFolderCmd(m.activeFolder()))
853853+ }
854854+ }
855855+ }
856856+ // Fall through — let other components handle mouse events (scroll, etc.)
857857+835858 case tea.WindowSizeMsg:
836859 m.width = msg.Width
837860 m.height = msg.Height
···27472770 if m.offTabFolder != "" {
27482771 activeTab = "" // deselect all tabs; off-tab folder shown separately
27492772 }
27502750- header := folderTabs(m.folders, activeTab, m.folderCounts)
27732773+ header, _ := folderTabs(m.folders, activeTab, m.folderCounts)
27512774 if m.offTabFolder != "" {
27522775 header += styleSeparator.Render(" │ ") + styleHeader.Render(m.offTabFolder)
27532776 }
+35-7
internal/ui/styles.go
···113113 Bold(true)
114114)
115115116116-// folderTabs renders the folder switcher bar.
117117-func folderTabs(folders []string, active string, counts map[string]int) string {
118118- var tabs []string
119119- for _, f := range folders {
120120- label := f
116116+// tabZone records the X range for a clickable folder tab.
117117+type tabZone struct {
118118+ xStart, xEnd int // character range [xStart, xEnd)
119119+ folderIndex int
120120+}
121121+122122+// folderTabs renders the folder switcher bar and returns click zones.
123123+func folderTabs(folders []string, active string, counts map[string]int) (string, []tabZone) {
124124+ // Compute raw label for each tab (before styling) to track character positions.
125125+ labels := make([]string, len(folders))
126126+ for i, f := range folders {
127127+ labels[i] = f
121128 if n, ok := counts[f]; ok && n > 0 {
122122- label = fmt.Sprintf("%s (%d)", f, n)
129129+ labels[i] = fmt.Sprintf("%s (%d)", f, n)
123130 }
131131+ }
132132+133133+ // styleHeader and styleFolder both add Padding(0,1) = 1 space each side.
134134+ const padLeft = 1
135135+ const padRight = 1
136136+ const sepWidth = 3 // " │ " rendered width
137137+138138+ var zones []tabZone
139139+ var tabs []string
140140+ x := 0
141141+ for i, f := range folders {
142142+ label := labels[i]
143143+ start := x + padLeft
144144+ end := start + len(label)
145145+ zones = append(zones, tabZone{xStart: x, xEnd: end + padRight, folderIndex: i})
146146+124147 if f == active {
125148 tabs = append(tabs, styleHeader.Render(label))
126149 } else {
127150 tabs = append(tabs, styleFolder.Render(label))
128151 }
152152+ x = end + padRight
153153+ if i < len(folders)-1 {
154154+ x += sepWidth
155155+ }
129156 }
157157+130158 sep := styleSeparator.Render(" │ ")
131159 result := ""
132160 for i, t := range tabs {
···135163 }
136164 result += t
137165 }
138138- return result
166166+ return result, zones
139167}