feat(tui): stable detail panel with scroll, pinned footer, and 60/40 split #165

Merged
lerko merged 6 commits from fix/sla-sidebar-height into main 2026-07-01 01:27:52 +00:00
3 changed files with 48 additions and 23 deletions
Showing only changes of commit 1d14f640f4 - Show all commits
+19 -5
View File
@@ -6,7 +6,7 @@ import (
"github.com/charmbracelet/lipgloss"
)
func (m Model) titledPanelH(title, content string, width, height, scrollOffset int, focused bool) string {
func (m Model) titledPanelH(title, content, footer string, width, height, scrollOffset int, focused bool) string {
if height <= 0 {
return m.titledPanel(title, content, width, focused)
}
@@ -40,7 +40,13 @@ func (m Model) titledPanelH(title, content string, width, height, scrollOffset i
inner := contentStyle.Render(content)
contentLines := strings.Split(inner, "\n")
bodyH := height - 2
var footerLines []string
if footer != "" {
footerRendered := contentStyle.Render(footer)
footerLines = strings.Split(footerRendered, "\n")
}
bodyH := height - 2 - len(footerLines)
if bodyH < 1 {
bodyH = 1
}
@@ -58,13 +64,21 @@ func (m Model) titledPanelH(title, content string, width, height, scrollOffset i
}
visible := contentLines[scrollOffset:end]
borderLine := func(line string) string {
return bc.Render("│") + line + strings.Repeat(" ", max(0, innerW-lipgloss.Width(line))) + bc.Render("│")
}
emptyLine := borderLine(strings.Repeat(" ", innerW))
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("│"))
lines = append(lines, borderLine(line))
}
for len(lines) < height-1 {
lines = append(lines, bc.Render("│")+strings.Repeat(" ", innerW)+bc.Render("│"))
for len(lines) < height-1-len(footerLines) {
lines = append(lines, emptyLine)
}
for _, line := range footerLines {
lines = append(lines, borderLine(line))
}
lines = append(lines, bottom)
+2 -1
View File
@@ -192,7 +192,8 @@ func (m Model) viewMonitorsLayout() string {
}
monHeight := lipgloss.Height(monPanel)
detail := m.viewDetailInline(detailW-2, monHeight)
detailPanel := m.zones.Mark("panel-detail", m.titledPanelH(title, detail, detailW, monHeight, m.detailScrollOffset, m.focusedPanel == panelDetail))
footer := m.detailFooter(detailW - 2)
detailPanel := m.zones.Mark("panel-detail", m.titledPanelH(title, detail, footer, detailW, monHeight, m.detailScrollOffset, m.focusedPanel == panelDetail))
topParts = append(topParts, detailPanel)
}
+27 -17
View File
@@ -143,8 +143,6 @@ func (m Model) viewDetailSidebar(site models.Site, hist monitor.SiteHistory, wid
b.WriteString(" " + label.Render("No state changes") + "\n")
}
b.WriteString("\n " + m.detailKeys() + "\n")
return lipgloss.NewStyle().Width(width).MaxWidth(width).Render(b.String())
}
@@ -216,6 +214,33 @@ func (m Model) detailKeys() string {
return m.st.subtleStyle.Render("[e] Edit [h] History [s] SLA [Esc] Back")
}
func (m Model) detailFooter(width int) string {
label := m.st.subtleStyle
var lines []string
switch m.detailMode {
case detailSLA:
var keys []string
for i, p := range slaPeriods {
k := fmt.Sprintf("[%s] %s", p.key, p.label)
if i == m.slaPeriodIdx {
keys = append(keys, m.st.titleStyle.Render(k))
} else {
keys = append(keys, label.Render(k))
}
}
lines = append(lines, " "+strings.Join(keys, " "))
lines = append(lines, " "+label.Render("[Esc] Back"))
case detailHistory:
lines = append(lines, " "+label.Render("[Esc] Back"))
default:
lines = append(lines, " "+m.detailKeys())
}
content := strings.Join(lines, "\n")
return lipgloss.NewStyle().Width(width).MaxWidth(width).Render(content)
}
func (m Model) fmtStatusWord(status string) string {
switch status {
case "DOWN", "SSL EXP":
@@ -292,20 +317,6 @@ func (m Model) viewSLASidebar(width, _ int) string {
}
}
b.WriteString("\n")
var keys []string
for i, p := range slaPeriods {
k := fmt.Sprintf("[%s] %s", p.key, p.label)
if i == m.slaPeriodIdx {
keys = append(keys, m.st.titleStyle.Render(k))
} else {
keys = append(keys, label.Render(k))
}
}
b.WriteString(" " + strings.Join(keys, " ") + "\n")
b.WriteString(" " + label.Render("[Esc] Back") + "\n")
return lipgloss.NewStyle().Width(width).MaxWidth(width).Render(b.String())
}
@@ -368,7 +379,6 @@ func (m Model) viewHistorySidebar(width, _ int) string {
statParts = append(statParts, "avg "+fmtDuration(avg))
}
b.WriteString(" " + label.Render(strings.Join(statParts, " │ ")) + "\n")
b.WriteString(" " + label.Render("[Esc] Back") + "\n")
return lipgloss.NewStyle().Width(width).MaxWidth(width).Render(b.String())
}