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"
)
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
+1 -1
View File
@@ -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)
+3 -2
View File
@@ -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)
}
+22 -8
View File
@@ -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