feat(tui): responsive table columns — expand headers with terminal width
CI / test (pull_request) Successful in 2m48s
CI / lint (pull_request) Successful in 1m17s
CI / vulncheck (pull_request) Successful in 51s

Replace hardcoded column widths with dynamic layout system:
- Each column has short/full header and min/max width
- At narrow terminals: LAT, UP%, RT, compact widths
- At wide terminals: LATENCY, UPTIME, RETRIES, expanded widths
- Surplus space distributed left-to-right across expandable columns
- Headers switch between short/full based on actual column width

Column definitions:
  # (4-6)  TYPE (8-10)  STATUS (8-10)  LAT/LATENCY (5-10)
  UP%/UPTIME (5-8)  SSL (5-7)  RT/RETRIES (5-9)
This commit is contained in:
2026-05-28 13:28:08 -04:00
parent af5246e777
commit 82d7b2942b
+92 -13
View File
@@ -334,15 +334,50 @@ func fmtDuration(d time.Duration) string {
return fmt.Sprintf("%dd", days) return fmt.Sprintf("%dd", days)
} }
func (m Model) dynamicWidths() (nameW, sparkW int) { type tableLayout struct {
fixed := 6 + 10 + 10 + 8 + 8 + 7 + 9 // #, TYPE, STATUS, LATENCY, UPTIME, SSL, RETRY nameW, sparkW int
overhead := 30 // cell padding + borders headers []string
avail := m.termWidth - chromePadH - 2 - fixed - overhead colWidths []int
if avail < 30 { }
avail = 30
func (m Model) computeLayout() tableLayout {
type colDef struct {
short string
full string
minWidth int
maxWidth int
} }
nameW = avail / 2
sparkW = avail - nameW - 2 // -2 for spark column padding cols := []colDef{
{"#", "#", 4, 6},
{"", "", 0, 0}, // NAME (dynamic)
{"TYPE", "TYPE", 8, 10},
{"STATUS", "STATUS", 8, 10},
{"LAT", "LATENCY", 5, 10},
{"UP%", "UPTIME", 5, 8},
{"", "", 0, 0}, // HISTORY (dynamic)
{"SSL", "SSL", 5, 7},
{"RT", "RETRIES", 5, 9},
}
overhead := 30
usable := m.termWidth - chromePadH - 2 - overhead
if usable < 80 {
usable = 80
}
fixedMin := 0
for i, c := range cols {
if i == 1 || i == 6 {
continue
}
fixedMin += c.minWidth
}
avail := usable - fixedMin
nameW := avail / 2
sparkW := avail - nameW - 2
if nameW < 13 { if nameW < 13 {
nameW = 13 nameW = 13
} }
@@ -355,7 +390,50 @@ func (m Model) dynamicWidths() (nameW, sparkW int) {
if sparkW > 60 { if sparkW > 60 {
sparkW = 60 sparkW = 60
} }
return
surplus := usable - fixedMin - nameW - sparkW - 2
if surplus < 0 {
surplus = 0
}
headers := make([]string, len(cols))
widths := make([]int, len(cols))
for i, c := range cols {
if i == 1 {
headers[i] = "NAME"
widths[i] = 0
continue
}
if i == 6 {
headers[i] = "HISTORY"
widths[i] = sparkW + 2
continue
}
w := c.minWidth
expand := c.maxWidth - c.minWidth
if surplus >= expand {
w = c.maxWidth
surplus -= expand
} else if surplus > 0 {
w += surplus
surplus = 0
}
if w >= len(c.full)+2 {
headers[i] = c.full
} else {
headers[i] = c.short
}
widths[i] = w
}
return tableLayout{
nameW: nameW,
sparkW: sparkW,
headers: headers,
colWidths: widths,
}
} }
func (m Model) viewSitesTab() string { func (m Model) viewSitesTab() string {
@@ -373,12 +451,13 @@ func (m Model) viewSitesTab() string {
return "\n" + welcome return "\n" + welcome
} }
nameW, sparkWidth := m.dynamicWidths() layout := m.computeLayout()
colWidths := []int{6, 0, 10, 10, 8, 8, sparkWidth + 2, 7, 9} nameW := layout.nameW
sparkWidth := layout.sparkW
var groupRows map[int]bool var groupRows map[int]bool
return m.renderTable( return m.renderTable(
[]string{"#", "NAME", "TYPE", "STATUS", "LAT", "UPTIME", "HISTORY", "SSL", "RETRY"}, layout.headers,
len(m.sites), len(m.sites),
func(start, end int) [][]string { func(start, end int) [][]string {
groupRows = make(map[int]bool) groupRows = make(map[int]bool)
@@ -444,7 +523,7 @@ func (m Model) viewSitesTab() string {
} }
return rows return rows
}, },
colWidths, layout.colWidths,
func(row, col int) *lipgloss.Style { func(row, col int) *lipgloss.Style {
if groupRows[row] { if groupRows[row] {
s := siteGroupStyle s := siteGroupStyle