on main help sign in
Merge pull request #144 from charmbracelet/fix-sidebar-height
58e2f72Kujtim Hoxha
CRUSH.md+1−1
@@ -4,7 +4,7 @@
4
4
5- **Build**: `go build .` or `go run .`
5- **Build**: `go build .` or `go run .`
6- **Test**: `task test` or `go test ./...` (run single test: `go test ./internal/llm/prompt -run TestGetContextFromPaths`)
6- **Test**: `task test` or `go test ./...` (run single test: `go test ./internal/llm/prompt -run TestGetContextFromPaths`)
7−- **Lint**: `task lint` (golangci-lint run) or `task lint-fix` (with --fix)
7+- **Lint**: `task lint-fix`
8- **Format**: `task fmt` (gofumpt -w .)
8- **Format**: `task fmt` (gofumpt -w .)
9- **Dev**: `task dev` (runs with profiling enabled)
9- **Dev**: `task dev` (runs with profiling enabled)
10
10
internal/tui/components/chat/header/header.go+6
@@ -21,6 +21,7 @@ type Header interface {
21 SetSession(session session.Session) tea.Cmd
21 SetSession(session session.Session) tea.Cmd
22 SetWidth(width int) tea.Cmd
22 SetWidth(width int) tea.Cmd
23 SetDetailsOpen(open bool)
23 SetDetailsOpen(open bool)
24+ ShowingDetails() bool
24}
25}
25
26
26type header struct {
27type header struct {
@@ -137,3 +138,8 @@ func (h *header) SetWidth(width int) tea.Cmd {
137 h.width = width
138 h.width = width
138 return nil
139 return nil
139}
140}
141+
142+// ShowingDetails implements Header.
143+func (h *header) ShowingDetails() bool {
144+ return h.detailsOpen
145+}
internal/tui/components/chat/sidebar/sidebar.go+427−11
@@ -33,6 +33,16 @@ type FileHistory struct {
33 latestVersion history.File
33 latestVersion history.File
34}
34}
35
35
36+const LogoHeightBreakpoint = 40
37+
38+// Default maximum number of items to show in each section
39+const (
40+ DefaultMaxFilesShown = 10
41+ DefaultMaxLSPsShown = 8
42+ DefaultMaxMCPsShown = 8
43+ MinItemsPerSection = 2 // Minimum items to show per section
44+)
45+
36type SessionFile struct {
46type SessionFile struct {
37 History FileHistory
47 History FileHistory
38 FilePath string
48 FilePath string
@@ -100,8 +110,14 @@ func (m *sidebarCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
100func (m *sidebarCmp) View() string {
110func (m *sidebarCmp) View() string {
101 t := styles.CurrentTheme()
111 t := styles.CurrentTheme()
102 parts := []string{}
112 parts := []string{}
113+
103 if !m.compactMode {
114 if !m.compactMode {
115+ if m.height > LogoHeightBreakpoint {
104− parts = append(parts, m.logo)
116+ parts = append(parts, m.logo)
117+ } else {
118+ // Use a smaller logo for smaller screens
119+ parts = append(parts, m.smallerScreenLogo(), "")
120+ }
105 }
121 }
106
122
107 if !m.compactMode && m.session.ID != "" {
123 if !m.compactMode && m.session.ID != "" {
@@ -119,15 +135,26 @@ func (m *sidebarCmp) View() string {
119 parts = append(parts,
135 parts = append(parts,
120 m.currentModelBlock(),
136 m.currentModelBlock(),
121 )
137 )
138+
139+ // Check if we should use horizontal layout for sections
140+ if m.compactMode && m.width > m.height {
141+ // Horizontal layout for compact mode when width > height
142+ sectionsContent := m.renderSectionsHorizontal()
143+ if sectionsContent != "" {
144+ parts = append(parts, "", sectionsContent)
145+ }
146+ } else {
147+ // Vertical layout (default)
122− if m.session.ID != "" {
148+ if m.session.ID != "" {
123− parts = append(parts, "", m.filesBlock())
149+ parts = append(parts, "", m.filesBlock())
150+ }
151+ parts = append(parts,
152+ "",
153+ m.lspBlock(),
154+ "",
155+ m.mcpBlock(),
156+ )
124 }
157 }
125− parts = append(parts,
126− "",
127− m.lspBlock(),
128− "",
129− m.mcpBlock(),
130− )
131
158
132 style := t.S().Base.
159 style := t.S().Base.
133 Width(m.width).
160 Width(m.width).
@@ -258,6 +285,328 @@ func (m *sidebarCmp) getMaxWidth() int {
258 return min(m.width-2, 58) // -2 for padding
285 return min(m.width-2, 58) // -2 for padding
259}
286}
260
287
288+// calculateAvailableHeight estimates how much height is available for dynamic content
289+func (m *sidebarCmp) calculateAvailableHeight() int {
290+ usedHeight := 0
291+
292+ if !m.compactMode {
293+ if m.height > LogoHeightBreakpoint {
294+ usedHeight += 7 // Approximate logo height
295+ } else {
296+ usedHeight += 2 // Smaller logo height
297+ }
298+ usedHeight += 1 // Empty line after logo
299+ }
300+
301+ if m.session.ID != "" {
302+ usedHeight += 1 // Title line
303+ usedHeight += 1 // Empty line after title
304+ }
305+
306+ if !m.compactMode {
307+ usedHeight += 1 // CWD line
308+ usedHeight += 1 // Empty line after CWD
309+ }
310+
311+ usedHeight += 2 // Model info
312+
313+ usedHeight += 6 // 3 sections × 2 lines each (header + empty line)
314+
315+ // Base padding
316+ usedHeight += 2 // Top and bottom padding
317+
318+ return max(0, m.height-usedHeight)
319+}
320+
321+// getDynamicLimits calculates how many items to show in each section based on available height
322+func (m *sidebarCmp) getDynamicLimits() (maxFiles, maxLSPs, maxMCPs int) {
323+ availableHeight := m.calculateAvailableHeight()
324+
325+ // If we have very little space, use minimum values
326+ if availableHeight < 10 {
327+ return MinItemsPerSection, MinItemsPerSection, MinItemsPerSection
328+ }
329+
330+ // Distribute available height among the three sections
331+ // Give priority to files, then LSPs, then MCPs
332+ totalSections := 3
333+ heightPerSection := availableHeight / totalSections
334+
335+ // Calculate limits for each section, ensuring minimums
336+ maxFiles = max(MinItemsPerSection, min(DefaultMaxFilesShown, heightPerSection))
337+ maxLSPs = max(MinItemsPerSection, min(DefaultMaxLSPsShown, heightPerSection))
338+ maxMCPs = max(MinItemsPerSection, min(DefaultMaxMCPsShown, heightPerSection))
339+
340+ // If we have extra space, give it to files first
341+ remainingHeight := availableHeight - (maxFiles + maxLSPs + maxMCPs)
342+ if remainingHeight > 0 {
343+ extraForFiles := min(remainingHeight, DefaultMaxFilesShown-maxFiles)
344+ maxFiles += extraForFiles
345+ remainingHeight -= extraForFiles
346+
347+ if remainingHeight > 0 {
348+ extraForLSPs := min(remainingHeight, DefaultMaxLSPsShown-maxLSPs)
349+ maxLSPs += extraForLSPs
350+ remainingHeight -= extraForLSPs
351+
352+ if remainingHeight > 0 {
353+ maxMCPs += min(remainingHeight, DefaultMaxMCPsShown-maxMCPs)
354+ }
355+ }
356+ }
357+
358+ return maxFiles, maxLSPs, maxMCPs
359+}
360+
361+// renderSectionsHorizontal renders the files, LSPs, and MCPs sections horizontally
362+func (m *sidebarCmp) renderSectionsHorizontal() string {
363+ // Calculate available width for each section
364+ totalWidth := m.width - 4 // Account for padding and spacing
365+ sectionWidth := min(50, totalWidth/3)
366+
367+ // Get the sections content with limited height
368+ var filesContent, lspContent, mcpContent string
369+
370+ filesContent = m.filesBlockCompact(sectionWidth)
371+ lspContent = m.lspBlockCompact(sectionWidth)
372+ mcpContent = m.mcpBlockCompact(sectionWidth)
373+
374+ return lipgloss.JoinHorizontal(lipgloss.Top, filesContent, " ", lspContent, " ", mcpContent)
375+}
376+
377+// filesBlockCompact renders the files block with limited width and height for horizontal layout
378+func (m *sidebarCmp) filesBlockCompact(maxWidth int) string {
379+ t := styles.CurrentTheme()
380+
381+ section := t.S().Subtle.Render("Modified Files")
382+
383+ files := make([]SessionFile, 0)
384+ m.files.Range(func(key, value any) bool {
385+ file := value.(SessionFile)
386+ files = append(files, file)
387+ return true
388+ })
389+
390+ if len(files) == 0 {
391+ content := lipgloss.JoinVertical(
392+ lipgloss.Left,
393+ section,
394+ "",
395+ t.S().Base.Foreground(t.Border).Render("None"),
396+ )
397+ return lipgloss.NewStyle().Width(maxWidth).Render(content)
398+ }
399+
400+ fileList := []string{section, ""}
401+ sort.Slice(files, func(i, j int) bool {
402+ return files[i].History.latestVersion.CreatedAt > files[j].History.latestVersion.CreatedAt
403+ })
404+
405+ // Limit items for horizontal layout - use less space
406+ maxItems := min(5, len(files))
407+ availableHeight := m.height - 8 // Reserve space for header and other content
408+ if availableHeight > 0 {
409+ maxItems = min(maxItems, availableHeight)
410+ }
411+
412+ filesShown := 0
413+ for _, file := range files {
414+ if file.Additions == 0 && file.Deletions == 0 {
415+ continue
416+ }
417+ if filesShown >= maxItems {
418+ break
419+ }
420+
421+ var statusParts []string
422+ if file.Additions > 0 {
423+ statusParts = append(statusParts, t.S().Base.Foreground(t.Success).Render(fmt.Sprintf("+%d", file.Additions)))
424+ }
425+ if file.Deletions > 0 {
426+ statusParts = append(statusParts, t.S().Base.Foreground(t.Error).Render(fmt.Sprintf("-%d", file.Deletions)))
427+ }
428+
429+ extraContent := strings.Join(statusParts, " ")
430+ cwd := config.Get().WorkingDir() + string(os.PathSeparator)
431+ filePath := file.FilePath
432+ filePath = strings.TrimPrefix(filePath, cwd)
433+ filePath = fsext.DirTrim(fsext.PrettyPath(filePath), 2)
434+ filePath = ansi.Truncate(filePath, maxWidth-lipgloss.Width(extraContent)-2, "…")
435+
436+ fileList = append(fileList,
437+ core.Status(
438+ core.StatusOpts{
439+ IconColor: t.FgMuted,
440+ NoIcon: true,
441+ Title: filePath,
442+ ExtraContent: extraContent,
443+ },
444+ maxWidth,
445+ ),
446+ )
447+ filesShown++
448+ }
449+
450+ // Add "..." indicator if there are more files
451+ totalFilesWithChanges := 0
452+ for _, file := range files {
453+ if file.Additions > 0 || file.Deletions > 0 {
454+ totalFilesWithChanges++
455+ }
456+ }
457+ if totalFilesWithChanges > maxItems {
458+ fileList = append(fileList, t.S().Base.Foreground(t.FgMuted).Render("…"))
459+ }
460+
461+ content := lipgloss.JoinVertical(lipgloss.Left, fileList...)
462+ return lipgloss.NewStyle().Width(maxWidth).Render(content)
463+}
464+
465+// lspBlockCompact renders the LSP block with limited width and height for horizontal layout
466+func (m *sidebarCmp) lspBlockCompact(maxWidth int) string {
467+ t := styles.CurrentTheme()
468+
469+ section := t.S().Subtle.Render("LSPs")
470+
471+ lspList := []string{section, ""}
472+
473+ lsp := config.Get().LSP.Sorted()
474+ if len(lsp) == 0 {
475+ content := lipgloss.JoinVertical(
476+ lipgloss.Left,
477+ section,
478+ "",
479+ t.S().Base.Foreground(t.Border).Render("None"),
480+ )
481+ return lipgloss.NewStyle().Width(maxWidth).Render(content)
482+ }
483+
484+ // Limit items for horizontal layout
485+ maxItems := min(5, len(lsp))
486+ availableHeight := m.height - 8
487+ if availableHeight > 0 {
488+ maxItems = min(maxItems, availableHeight)
489+ }
490+
491+ for i, l := range lsp {
492+ if i >= maxItems {
493+ break
494+ }
495+
496+ iconColor := t.Success
497+ if l.LSP.Disabled {
498+ iconColor = t.FgMuted
499+ }
500+
501+ lspErrs := map[protocol.DiagnosticSeverity]int{
502+ protocol.SeverityError: 0,
503+ protocol.SeverityWarning: 0,
504+ protocol.SeverityHint: 0,
505+ protocol.SeverityInformation: 0,
506+ }
507+ if client, ok := m.lspClients[l.Name]; ok {
508+ for _, diagnostics := range client.GetDiagnostics() {
509+ for _, diagnostic := range diagnostics {
510+ if severity, ok := lspErrs[diagnostic.Severity]; ok {
511+ lspErrs[diagnostic.Severity] = severity + 1
512+ }
513+ }
514+ }
515+ }
516+
517+ errs := []string{}
518+ if lspErrs[protocol.SeverityError] > 0 {
519+ errs = append(errs, t.S().Base.Foreground(t.Error).Render(fmt.Sprintf("%s %d", styles.ErrorIcon, lspErrs[protocol.SeverityError])))
520+ }
521+ if lspErrs[protocol.SeverityWarning] > 0 {
522+ errs = append(errs, t.S().Base.Foreground(t.Warning).Render(fmt.Sprintf("%s %d", styles.WarningIcon, lspErrs[protocol.SeverityWarning])))
523+ }
524+ if lspErrs[protocol.SeverityHint] > 0 {
525+ errs = append(errs, t.S().Base.Foreground(t.FgHalfMuted).Render(fmt.Sprintf("%s %d", styles.HintIcon, lspErrs[protocol.SeverityHint])))
526+ }
527+ if lspErrs[protocol.SeverityInformation] > 0 {
528+ errs = append(errs, t.S().Base.Foreground(t.FgHalfMuted).Render(fmt.Sprintf("%s %d", styles.InfoIcon, lspErrs[protocol.SeverityInformation])))
529+ }
530+
531+ lspList = append(lspList,
532+ core.Status(
533+ core.StatusOpts{
534+ IconColor: iconColor,
535+ Title: l.Name,
536+ Description: l.LSP.Command,
537+ ExtraContent: strings.Join(errs, " "),
538+ },
539+ maxWidth,
540+ ),
541+ )
542+ }
543+
544+ // Add "..." indicator if there are more LSPs
545+ if len(lsp) > maxItems {
546+ lspList = append(lspList, t.S().Base.Foreground(t.FgMuted).Render("…"))
547+ }
548+
549+ content := lipgloss.JoinVertical(lipgloss.Left, lspList...)
550+ return lipgloss.NewStyle().Width(maxWidth).Render(content)
551+}
552+
553+// mcpBlockCompact renders the MCP block with limited width and height for horizontal layout
554+func (m *sidebarCmp) mcpBlockCompact(maxWidth int) string {
555+ t := styles.CurrentTheme()
556+
557+ section := t.S().Subtle.Render("MCPs")
558+
559+ mcpList := []string{section, ""}
560+
561+ mcps := config.Get().MCP.Sorted()
562+ if len(mcps) == 0 {
563+ content := lipgloss.JoinVertical(
564+ lipgloss.Left,
565+ section,
566+ "",
567+ t.S().Base.Foreground(t.Border).Render("None"),
568+ )
569+ return lipgloss.NewStyle().Width(maxWidth).Render(content)
570+ }
571+
572+ // Limit items for horizontal layout
573+ maxItems := min(5, len(mcps))
574+ availableHeight := m.height - 8
575+ if availableHeight > 0 {
576+ maxItems = min(maxItems, availableHeight)
577+ }
578+
579+ for i, l := range mcps {
580+ if i >= maxItems {
581+ break
582+ }
583+
584+ iconColor := t.Success
585+ if l.MCP.Disabled {
586+ iconColor = t.FgMuted
587+ }
588+
589+ mcpList = append(mcpList,
590+ core.Status(
591+ core.StatusOpts{
592+ IconColor: iconColor,
593+ Title: l.Name,
594+ Description: l.MCP.Command,
595+ },
596+ maxWidth,
597+ ),
598+ )
599+ }
600+
601+ // Add "..." indicator if there are more MCPs
602+ if len(mcps) > maxItems {
603+ mcpList = append(mcpList, t.S().Base.Foreground(t.FgMuted).Render("…"))
604+ }
605+
606+ content := lipgloss.JoinVertical(lipgloss.Left, mcpList...)
607+ return lipgloss.NewStyle().Width(maxWidth).Render(content)
608+}
609+
261func (m *sidebarCmp) filesBlock() string {
610func (m *sidebarCmp) filesBlock() string {
262 t := styles.CurrentTheme()
611 t := styles.CurrentTheme()
263
612
@@ -286,10 +635,19 @@ func (m *sidebarCmp) filesBlock() string {
286 return files[i].History.latestVersion.CreatedAt > files[j].History.latestVersion.CreatedAt
635 return files[i].History.latestVersion.CreatedAt > files[j].History.latestVersion.CreatedAt
287 })
636 })
288
637
638+ // Limit the number of files shown
639+ maxFiles, _, _ := m.getDynamicLimits()
640+ maxFiles = min(len(files), maxFiles)
641+ filesShown := 0
642+
289 for _, file := range files {
643 for _, file := range files {
290 if file.Additions == 0 && file.Deletions == 0 {
644 if file.Additions == 0 && file.Deletions == 0 {
291 continue // skip files with no changes
645 continue // skip files with no changes
292 }
646 }
647+ if filesShown >= maxFiles {
648+ break
649+ }
650+
293 var statusParts []string
651 var statusParts []string
294 if file.Additions > 0 {
652 if file.Additions > 0 {
295 statusParts = append(statusParts, t.S().Base.Foreground(t.Success).Render(fmt.Sprintf("+%d", file.Additions)))
653 statusParts = append(statusParts, t.S().Base.Foreground(t.Success).Render(fmt.Sprintf("+%d", file.Additions)))
@@ -315,6 +673,21 @@ func (m *sidebarCmp) filesBlock() string {
315 m.getMaxWidth(),
673 m.getMaxWidth(),
316 ),
674 ),
317 )
675 )
676+ filesShown++
677+ }
678+
679+ // Add indicator if there are more files
680+ totalFilesWithChanges := 0
681+ for _, file := range files {
682+ if file.Additions > 0 || file.Deletions > 0 {
683+ totalFilesWithChanges++
684+ }
685+ }
686+ if totalFilesWithChanges > maxFiles {
687+ remaining := totalFilesWithChanges - maxFiles
688+ fileList = append(fileList,
689+ t.S().Base.Foreground(t.FgMuted).Render(fmt.Sprintf("… and %d more", remaining)),
690+ )
318 }
691 }
319
692
320 return lipgloss.JoinVertical(
693 return lipgloss.JoinVertical(
@@ -342,7 +715,14 @@ func (m *sidebarCmp) lspBlock() string {
342 )
715 )
343 }
716 }
344
717
718+ // Limit the number of LSPs shown
719+ _, maxLSPs, _ := m.getDynamicLimits()
720+ maxLSPs = min(len(lsp), maxLSPs)
345− for _, l := range lsp {
721+ for i, l := range lsp {
722+ if i >= maxLSPs {
723+ break
724+ }
725+
346 iconColor := t.Success
726 iconColor := t.Success
347 if l.LSP.Disabled {
727 if l.LSP.Disabled {
348 iconColor = t.FgMuted
728 iconColor = t.FgMuted
@@ -390,6 +770,14 @@ func (m *sidebarCmp) lspBlock() string {
390 )
770 )
391 }
771 }
392
772
773+ // Add indicator if there are more LSPs
774+ if len(lsp) > maxLSPs {
775+ remaining := len(lsp) - maxLSPs
776+ lspList = append(lspList,
777+ t.S().Base.Foreground(t.FgMuted).Render(fmt.Sprintf("… and %d more", remaining)),
778+ )
779+ }
780+
393 return lipgloss.JoinVertical(
781 return lipgloss.JoinVertical(
394 lipgloss.Left,
782 lipgloss.Left,
395 lspList...,
783 lspList...,
@@ -415,7 +803,14 @@ func (m *sidebarCmp) mcpBlock() string {
415 )
803 )
416 }
804 }
417
805
806+ // Limit the number of MCPs shown
807+ _, _, maxMCPs := m.getDynamicLimits()
808+ maxMCPs = min(len(mcps), maxMCPs)
418− for _, l := range mcps {
809+ for i, l := range mcps {
810+ if i >= maxMCPs {
811+ break
812+ }
813+
419 iconColor := t.Success
814 iconColor := t.Success
420 if l.MCP.Disabled {
815 if l.MCP.Disabled {
421 iconColor = t.FgMuted
816 iconColor = t.FgMuted
@@ -432,6 +827,14 @@ func (m *sidebarCmp) mcpBlock() string {
432 )
827 )
433 }
828 }
434
829
830+ // Add indicator if there are more MCPs
831+ if len(mcps) > maxMCPs {
832+ remaining := len(mcps) - maxMCPs
833+ mcpList = append(mcpList,
834+ t.S().Base.Foreground(t.FgMuted).Render(fmt.Sprintf("… and %d more", remaining)),
835+ )
836+ }
837+
435 return lipgloss.JoinVertical(
838 return lipgloss.JoinVertical(
436 lipgloss.Left,
839 lipgloss.Left,
437 mcpList...,
840 mcpList...,
@@ -504,6 +907,19 @@ func (s *sidebarCmp) currentModelBlock() string {
504 )
907 )
505}
908}
506
909
910+func (m *sidebarCmp) smallerScreenLogo() string {
911+ t := styles.CurrentTheme()
912+ title := t.S().Base.Foreground(t.Secondary).Render("Charm™")
913+ title += " " + styles.ApplyBoldForegroundGrad("CRUSH", t.Secondary, t.Primary)
914+ remainingWidth := m.width - lipgloss.Width(title) - 3
915+ if remainingWidth > 0 {
916+ char := "╱"
917+ lines := strings.Repeat(char, remainingWidth)
918+ title += " " + t.S().Base.Foreground(t.Primary).Render(lines)
919+ }
920+ return title
921+}
922+
507// SetSession implements Sidebar.
923// SetSession implements Sidebar.
508func (m *sidebarCmp) SetSession(session session.Session) tea.Cmd {
924func (m *sidebarCmp) SetSession(session session.Session) tea.Cmd {
509 m.session = session
925 m.session = session
internal/tui/page/chat/chat.go+14−10
@@ -51,11 +51,12 @@ const (
51)
51)
52
52
53const (
53const (
54− CompactModeBreakpoint = 120 // Width at which the chat page switches to compact mode
54+ CompactModeWidthBreakpoint = 120 // Width at which the chat page switches to compact mode
55+ CompactModeHeightBreakpoint = 30 // Height at which the chat page switches to compact mode
55− EditorHeight = 5 // Height of the editor input area including padding
56+ EditorHeight = 5 // Height of the editor input area including padding
56− SideBarWidth = 31 // Width of the sidebar
57+ SideBarWidth = 31 // Width of the sidebar
57− SideBarDetailsPadding = 1 // Padding for the sidebar details section
58+ SideBarDetailsPadding = 1 // Padding for the sidebar details section
58− HeaderHeight = 1 // Height of the header
59+ HeaderHeight = 1 // Height of the header
59
60
60 // Layout constants for borders and padding
61 // Layout constants for borders and padding
61 BorderWidth = 1 // Width of component borders
62 BorderWidth = 1 // Width of component borders
@@ -177,7 +178,7 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
177 if p.forceCompact {
178 if p.forceCompact {
178 p.setCompactMode(true)
179 p.setCompactMode(true)
179 cmd = p.updateCompactConfig(true)
180 cmd = p.updateCompactConfig(true)
180− } else if p.width >= CompactModeBreakpoint {
181+ } else if p.width >= CompactModeWidthBreakpoint && p.height >= CompactModeHeightBreakpoint {
181 p.setCompactMode(false)
182 p.setCompactMode(false)
182 cmd = p.updateCompactConfig(false)
183 cmd = p.updateCompactConfig(false)
183 }
184 }
@@ -313,6 +314,9 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
313}
314}
314
315
315func (p *chatPage) Cursor() *tea.Cursor {
316func (p *chatPage) Cursor() *tea.Cursor {
317+ if p.header.ShowingDetails() {
318+ return nil
319+ }
316 switch p.focusedPane {
320 switch p.focusedPane {
317 case PanelTypeEditor:
321 case PanelTypeEditor:
318 return p.editor.Cursor()
322 return p.editor.Cursor()
@@ -420,20 +424,20 @@ func (p *chatPage) setCompactMode(compact bool) {
420 }
424 }
421}
425}
422
426
423−func (p *chatPage) handleCompactMode(newWidth int) {
427+func (p *chatPage) handleCompactMode(newWidth int, newHeight int) {
424 if p.forceCompact {
428 if p.forceCompact {
425 return
429 return
426 }
430 }
427− if newWidth < CompactModeBreakpoint && !p.compact {
431+ if (newWidth < CompactModeWidthBreakpoint || newHeight < CompactModeHeightBreakpoint) && !p.compact {
428 p.setCompactMode(true)
432 p.setCompactMode(true)
429 }
433 }
430− if newWidth >= CompactModeBreakpoint && p.compact {
434+ if (newWidth >= CompactModeWidthBreakpoint && newHeight >= CompactModeHeightBreakpoint) && p.compact {
431 p.setCompactMode(false)
435 p.setCompactMode(false)
432 }
436 }
433}
437}
434
438
435func (p *chatPage) SetSize(width, height int) tea.Cmd {
439func (p *chatPage) SetSize(width, height int) tea.Cmd {
436− p.handleCompactMode(width)
440+ p.handleCompactMode(width, height)
437 p.width = width
441 p.width = width
438 p.height = height
442 p.height = height
439 var cmds []tea.Cmd
443 var cmds []tea.Cmd