From 0badc2ddf5cf10ca437050ca75a1d29f2c250dfe Mon Sep 17 00:00:00 2001 From: Tyler Koenig Date: Tue, 30 Jun 2026 20:18:39 -0400 Subject: [PATCH] fix(tui): cap detail sidebar height to match monitors panel SLA daily breakdown no longer overflows past the monitors panel. Detail sidebar height is capped to monitors panel height via titledPanelH. SLA daily rows are limited to fit within available space. Logs strip stays visible in all detail modes. --- internal/tui/panel.go | 12 ++++++++++++ internal/tui/update_test.go | 2 +- internal/tui/view_dashboard.go | 5 +++-- internal/tui/view_detail_inline.go | 30 ++++++++++++++++++++++-------- 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/internal/tui/panel.go b/internal/tui/panel.go index 6935334..a8480f1 100644 --- a/internal/tui/panel.go +++ b/internal/tui/panel.go @@ -6,6 +6,18 @@ import ( "github.com/charmbracelet/lipgloss" ) +func (m Model) titledPanelH(title, content string, width, height int, focused bool) string { + s := m.titledPanel(title, content, width, focused) + if height <= 0 { + return s + } + lines := strings.Split(s, "\n") + if len(lines) <= height { + return s + } + return strings.Join(lines[:height-1], "\n") + "\n" + lines[len(lines)-1] +} + func (m Model) titledPanel(title, content string, width int, focused bool) string { borderColor := m.theme.Border titleColor := m.theme.Muted diff --git a/internal/tui/update_test.go b/internal/tui/update_test.go index d98d564..9d8aae0 100644 --- a/internal/tui/update_test.go +++ b/internal/tui/update_test.go @@ -142,7 +142,7 @@ func TestDetailLoad_CachesAndViewDoesNoIO(t *testing.T) { } for i := 0; i < 3; i++ { - _ = m.viewDetailInline(80) + _ = m.viewDetailInline(80, 30) } if ms.stateChangeCalls != 1 { t.Errorf("View performed DB IO: store hit %d times (want 1, from the Cmd only)", ms.stateChangeCalls) diff --git a/internal/tui/view_dashboard.go b/internal/tui/view_dashboard.go index 5999fbb..24fd8eb 100644 --- a/internal/tui/view_dashboard.go +++ b/internal/tui/view_dashboard.go @@ -190,8 +190,9 @@ func (m Model) viewMonitorsLayout() string { case detailHistory: title = "History · " + title } - detail := m.viewDetailInline(detailW - 2) - detailPanel := m.zones.Mark("panel-detail", m.titledPanel(title, detail, detailW, m.focusedPanel == panelDetail)) + monHeight := lipgloss.Height(monPanel) + detail := m.viewDetailInline(detailW-2, monHeight) + detailPanel := m.zones.Mark("panel-detail", m.titledPanelH(title, detail, detailW, monHeight, m.focusedPanel == panelDetail)) topParts = append(topParts, detailPanel) } diff --git a/internal/tui/view_detail_inline.go b/internal/tui/view_detail_inline.go index dbb4127..22e5834 100644 --- a/internal/tui/view_detail_inline.go +++ b/internal/tui/view_detail_inline.go @@ -10,23 +10,23 @@ import ( "github.com/charmbracelet/lipgloss" ) -func (m Model) viewDetailInline(width int) string { +func (m Model) viewDetailInline(width, height int) string { if m.cursor >= len(m.sites) { return "" } switch m.detailMode { case detailSLA: - return m.viewSLASidebar(width) + return m.viewSLASidebar(width, height) case detailHistory: - return m.viewHistorySidebar(width) + return m.viewHistorySidebar(width, height) default: site := m.sites[m.cursor] hist, _ := m.engine.GetHistory(site.ID) - return m.viewDetailSidebar(site, hist, width) + return m.viewDetailSidebar(site, hist, width, height) } } -func (m Model) viewDetailSidebar(site models.Site, hist monitor.SiteHistory, width int) string { +func (m Model) viewDetailSidebar(site models.Site, hist monitor.SiteHistory, width, _ int) string { dot := m.st.subtleStyle.Render(" · ") label := m.st.subtleStyle var b strings.Builder @@ -235,7 +235,7 @@ func (m Model) fmtStatusWord(status string) string { } } -func (m Model) viewSLASidebar(width int) string { +func (m Model) viewSLASidebar(width, height int) string { var b strings.Builder label := m.st.subtleStyle innerW := width - 4 @@ -277,7 +277,21 @@ func (m Model) viewSLASidebar(width int) string { if dayBarW < 10 { dayBarW = 10 } - for _, day := range m.slaDailyBreakdown { + slaChrome := 14 + if r.OutageCount > 0 { + slaChrome += 3 + } + maxDaily := len(m.slaDailyBreakdown) + if height > 0 && height-slaChrome < maxDaily { + maxDaily = height - slaChrome + if maxDaily < 1 { + maxDaily = 1 + } + } + for i, day := range m.slaDailyBreakdown { + if i >= maxDaily { + break + } dateStr := day.Date.Format("Jan 02") dayBar := m.uptimeBar(day.UptimePct, dayBarW) pctStr := fmtPct(day.UptimePct) + "%" @@ -309,7 +323,7 @@ func (m Model) viewSLASidebar(width int) string { return lipgloss.NewStyle().Width(width).MaxWidth(width).Render(b.String()) } -func (m Model) viewHistorySidebar(width int) string { +func (m Model) viewHistorySidebar(width, _ int) string { var b strings.Builder label := m.st.subtleStyle innerW := width - 4