feat(tui): stable detail panel height with scroll support
CI / test (pull_request) Successful in 1m56s
CI / lint (pull_request) Successful in 1m12s
CI / vulncheck (pull_request) Successful in 56s

Detail sidebar now maintains consistent height matching the monitors
panel across all modes (default, SLA, history). Content scrolls within
the fixed frame via mouse wheel or j/k when the detail panel is
focused. Scroll offset resets on mode/monitor/period changes. Logs
strip stays visible in all states.
This commit is contained in:
2026-06-30 20:36:25 -04:00
parent 0badc2ddf5
commit 631f07c242
5 changed files with 101 additions and 29 deletions
+60 -7
View File
@@ -6,16 +6,69 @@ 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)
func (m Model) titledPanelH(title, content string, width, height, scrollOffset int, focused bool) string {
if height <= 0 {
return s
return m.titledPanel(title, content, width, focused)
}
lines := strings.Split(s, "\n")
if len(lines) <= height {
return s
borderColor := m.theme.Border
titleColor := m.theme.Muted
if focused {
borderColor = m.theme.Accent
titleColor = m.theme.Accent
}
return strings.Join(lines[:height-1], "\n") + "\n" + lines[len(lines)-1]
bc := lipgloss.NewStyle().Foreground(borderColor)
tc := lipgloss.NewStyle().Foreground(titleColor).Bold(true)
innerW := width - 2
if innerW < 10 {
innerW = 10
}
titleRendered := tc.Render(" " + title + " ")
titleLen := len([]rune(title)) + 2
fillLen := innerW - titleLen - 1
if fillLen < 0 {
fillLen = 0
}
top := bc.Render("╭─") + titleRendered + bc.Render(strings.Repeat("─", fillLen)+"╮")
bottom := bc.Render("╰" + strings.Repeat("─", innerW) + "╯")
contentStyle := lipgloss.NewStyle().Width(innerW).MaxWidth(innerW)
inner := contentStyle.Render(content)
contentLines := strings.Split(inner, "\n")
bodyH := height - 2
if bodyH < 1 {
bodyH = 1
}
if scrollOffset > len(contentLines)-bodyH {
scrollOffset = len(contentLines) - bodyH
}
if scrollOffset < 0 {
scrollOffset = 0
}
end := scrollOffset + bodyH
if end > len(contentLines) {
end = len(contentLines)
}
visible := contentLines[scrollOffset:end]
var lines []string
lines = append(lines, top)
for _, line := range visible {
lines = append(lines, bc.Render("│")+line+strings.Repeat(" ", max(0, innerW-lipgloss.Width(line)))+bc.Render("│"))
}
for len(lines) < height-1 {
lines = append(lines, bc.Render("│")+strings.Repeat(" ", innerW)+bc.Render("│"))
}
lines = append(lines, bottom)
return strings.Join(lines, "\n")
}
func (m Model) titledPanel(title, content string, width int, focused bool) string {