fix(tui): cap detail sidebar height to match monitors panel
CI / test (pull_request) Successful in 1m51s
CI / lint (pull_request) Successful in 1m17s
CI / vulncheck (pull_request) Successful in 56s

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.
This commit is contained in:
2026-06-30 20:18:39 -04:00
parent be14436701
commit 0badc2ddf5
4 changed files with 38 additions and 11 deletions
+12
View File
@@ -6,6 +6,18 @@ import (
"github.com/charmbracelet/lipgloss" "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 { func (m Model) titledPanel(title, content string, width int, focused bool) string {
borderColor := m.theme.Border borderColor := m.theme.Border
titleColor := m.theme.Muted titleColor := m.theme.Muted
+1 -1
View File
@@ -142,7 +142,7 @@ func TestDetailLoad_CachesAndViewDoesNoIO(t *testing.T) {
} }
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
_ = m.viewDetailInline(80) _ = m.viewDetailInline(80, 30)
} }
if ms.stateChangeCalls != 1 { if ms.stateChangeCalls != 1 {
t.Errorf("View performed DB IO: store hit %d times (want 1, from the Cmd only)", ms.stateChangeCalls) t.Errorf("View performed DB IO: store hit %d times (want 1, from the Cmd only)", ms.stateChangeCalls)
+3 -2
View File
@@ -190,8 +190,9 @@ func (m Model) viewMonitorsLayout() string {
case detailHistory: case detailHistory:
title = "History · " + title title = "History · " + title
} }
detail := m.viewDetailInline(detailW - 2) monHeight := lipgloss.Height(monPanel)
detailPanel := m.zones.Mark("panel-detail", m.titledPanel(title, detail, detailW, m.focusedPanel == panelDetail)) 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) topParts = append(topParts, detailPanel)
} }
+22 -8
View File
@@ -10,23 +10,23 @@ import (
"github.com/charmbracelet/lipgloss" "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) { if m.cursor >= len(m.sites) {
return "" return ""
} }
switch m.detailMode { switch m.detailMode {
case detailSLA: case detailSLA:
return m.viewSLASidebar(width) return m.viewSLASidebar(width, height)
case detailHistory: case detailHistory:
return m.viewHistorySidebar(width) return m.viewHistorySidebar(width, height)
default: default:
site := m.sites[m.cursor] site := m.sites[m.cursor]
hist, _ := m.engine.GetHistory(site.ID) 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(" · ") dot := m.st.subtleStyle.Render(" · ")
label := m.st.subtleStyle label := m.st.subtleStyle
var b strings.Builder 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 var b strings.Builder
label := m.st.subtleStyle label := m.st.subtleStyle
innerW := width - 4 innerW := width - 4
@@ -277,7 +277,21 @@ func (m Model) viewSLASidebar(width int) string {
if dayBarW < 10 { if dayBarW < 10 {
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") dateStr := day.Date.Format("Jan 02")
dayBar := m.uptimeBar(day.UptimePct, dayBarW) dayBar := m.uptimeBar(day.UptimePct, dayBarW)
pctStr := fmtPct(day.UptimePct) + "%" 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()) 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 var b strings.Builder
label := m.st.subtleStyle label := m.st.subtleStyle
innerW := width - 4 innerW := width - 4