feat(tui): styled section headers with trailing rules in detail panel

Section labels (STATE CHANGES, LATENCY, RESPONSE TIME DISTRIBUTION,
PROBE RESULTS) now render in bold accent color with a trailing ───
rule that fills the width. Two-column info block separated by a full
divider below. Creates clear visual boundaries between sections.
This commit is contained in:
2026-06-24 20:56:21 -04:00
parent fb50184f37
commit 1268d99f5c
+16 -6
View File
@@ -47,6 +47,15 @@ func (m Model) viewDetailPanel() string {
return fmt.Sprintf(" %-16s %s", m.st.subtleStyle.Render(label), value) return fmt.Sprintf(" %-16s %s", m.st.subtleStyle.Render(label), value)
} }
divW := totalW - 4
if divW < 20 {
divW = 20
}
sectionDiv := m.st.subtleStyle.Render(strings.Repeat("─", divW))
sectionHead := func(title string) string {
return m.st.titleStyle.Render(" "+title) + " " + m.st.subtleStyle.Render(strings.Repeat("─", divW-len(title)-3))
}
// Left column: status + endpoint // Left column: status + endpoint
var left []string var left []string
left = append(left, row("Status", m.fmtStatus(site.Status, site.Paused, m.isMonitorInMaintenance(site.ID)))) left = append(left, row("Status", m.fmtStatus(site.Status, site.Paused, m.isMonitorInMaintenance(site.ID))))
@@ -69,7 +78,7 @@ func (m Model) viewDetailPanel() string {
} }
left = append(left, "") left = append(left, "")
left = append(left, m.st.subtleStyle.Render(" ENDPOINT")) left = append(left, m.st.titleStyle.Render(" ENDPOINT"))
left = append(left, row("Type", site.Type)) left = append(left, row("Type", site.Type))
if site.URL != "" { if site.URL != "" {
left = append(left, row("URL", limitStr(site.URL, colW-19))) left = append(left, row("URL", limitStr(site.URL, colW-19)))
@@ -95,7 +104,7 @@ func (m Model) viewDetailPanel() string {
if site.Type == "http" { if site.Type == "http" {
right = append(right, "") right = append(right, "")
right = append(right, m.st.subtleStyle.Render(" HTTP")) right = append(right, m.st.titleStyle.Render(" HTTP"))
codes := site.AcceptedCodes codes := site.AcceptedCodes
if codes == "" { if codes == "" {
codes = "200-299" codes = "200-299"
@@ -122,6 +131,7 @@ func (m Model) viewDetailPanel() string {
leftCol := lipgloss.NewStyle().Width(colW).Render(strings.Join(left, "\n")) leftCol := lipgloss.NewStyle().Width(colW).Render(strings.Join(left, "\n"))
rightCol := lipgloss.NewStyle().Width(colW).Render(strings.Join(right, "\n")) rightCol := lipgloss.NewStyle().Width(colW).Render(strings.Join(right, "\n"))
b.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, leftCol, rightCol) + "\n") b.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, leftCol, rightCol) + "\n")
b.WriteString(sectionDiv + "\n")
// Connection chain (full width, only on errors) // Connection chain (full width, only on errors)
if (site.Status == models.StatusDown || site.Status == models.StatusSSLExp) && site.LastError != "" { if (site.Status == models.StatusDown || site.Status == models.StatusSSLExp) && site.LastError != "" {
@@ -175,7 +185,7 @@ func (m Model) viewDetailPanel() string {
nodeIDs = append(nodeIDs, id) nodeIDs = append(nodeIDs, id)
} }
sort.Strings(nodeIDs) sort.Strings(nodeIDs)
b.WriteString("\n" + m.st.subtleStyle.Render(" PROBE RESULTS") + "\n") b.WriteString("\n" + sectionHead("PROBE RESULTS") + "\n")
for _, nodeID := range nodeIDs { for _, nodeID := range nodeIDs {
result := probeResults[nodeID] result := probeResults[nodeID]
status := m.st.specialStyle.Render("UP") status := m.st.specialStyle.Render("UP")
@@ -198,7 +208,7 @@ func (m Model) viewDetailPanel() string {
stateChanges = m.detailChanges stateChanges = m.detailChanges
} }
if len(stateChanges) > 0 { if len(stateChanges) > 0 {
b.WriteString("\n" + m.st.subtleStyle.Render(" STATE CHANGES") + "\n") b.WriteString("\n" + sectionHead("STATE CHANGES") + "\n")
for i, sc := range stateChanges { for i, sc := range stateChanges {
from := m.fmtStatusWord(string(sc.FromStatus)) from := m.fmtStatusWord(string(sc.FromStatus))
to := m.fmtStatusWord(string(sc.ToStatus)) to := m.fmtStatusWord(string(sc.ToStatus))
@@ -218,7 +228,7 @@ func (m Model) viewDetailPanel() string {
} }
// Sparkline + stats // Sparkline + stats
b.WriteString("\n") b.WriteString("\n" + sectionHead("LATENCY") + "\n")
if site.Type == "push" { if site.Type == "push" {
b.WriteString(" " + m.zones.Mark("spark-heartbeat", m.heartbeatSparkline(hist.Statuses, detailSparkWidth, nil))) b.WriteString(" " + m.zones.Mark("spark-heartbeat", m.heartbeatSparkline(hist.Statuses, detailSparkWidth, nil)))
if len(hist.Statuses) > 0 { if len(hist.Statuses) > 0 {
@@ -269,7 +279,7 @@ func (m Model) viewDetailPanel() string {
if histW < 30 { if histW < 30 {
histW = 30 histW = 30
} }
b.WriteString("\n\n" + m.st.subtleStyle.Render(" RESPONSE TIME DISTRIBUTION") + "\n") b.WriteString("\n" + sectionHead("RESPONSE TIME DISTRIBUTION") + "\n")
b.WriteString(m.latencyHistogram(hist.Latencies, hist.Statuses, histW)) b.WriteString(m.latencyHistogram(hist.Latencies, hist.Statuses, histW))
} }