Skip to content

Commit 3de4cf5

Browse files
authored
Fix table viewport panic on small terminal windows (#141) (#141)
The table height calculation subtracts 35 lines of overhead from the terminal height, producing a negative value for any terminal under 36 lines (including standard 24-line terminals). This was clamped to 1, but table.SetHeight(1) internally subtracts the 2-line header height (text + bottom border), giving the viewport a height of -1. This caused either invisible table rows or a panic in viewport.visibleLines ("slice bounds out of range [2:1]") depending on timing. Raise the minimum tableHeight from 1 to 4 so the viewport always gets at least 2 lines of content space after the header is subtracted. Add TestWindowSizeMsgHandler_SmallWindow covering standard 80x24, tiny, minimum, and zero-height terminals. Created with assistance from Claude 🤖 <claude@anthropic.com> Signed-off-by: Christopher Collins <collins.christopher@gmail.com>
1 parent 51881bb commit 3de4cf5

2 files changed

Lines changed: 55 additions & 3 deletions

File tree

pkg/tui/model_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -908,3 +908,53 @@ func TestSelectedIncidentSurvivesListUpdate(t *testing.T) {
908908
assert.Equal(t, "Original Title", m.selectedIncident.Title,
909909
"selectedIncident should retain original data after list reallocation")
910910
}
911+
912+
func TestWindowSizeMsgHandler_SmallWindow(t *testing.T) {
913+
tests := []struct {
914+
name string
915+
width int
916+
height int
917+
}{
918+
{"standard 80x24 terminal", 80, 24},
919+
{"tiny window", 40, 10},
920+
{"minimum height", 20, 1},
921+
{"zero height", 80, 0},
922+
}
923+
924+
for _, tt := range tests {
925+
t.Run(tt.name, func(t *testing.T) {
926+
m := model{
927+
table: newTableWithStyles(),
928+
actionLogTable: newActionLogTable(),
929+
incidentViewer: newIncidentViewer(),
930+
help: newHelp(),
931+
incidentCache: make(map[string]*cachedIncidentData),
932+
}
933+
934+
// First call sets columns (simulates initial startup WindowSizeMsg)
935+
normalSize := tea.WindowSizeMsg{Width: 120, Height: 50}
936+
result, _ := m.windowSizeMsgHandler(normalSize)
937+
m = result.(model)
938+
939+
// Incidents arrive and populate the table
940+
m.table.SetRows([]table.Row{
941+
{".", "P123ABC", "Test incident", "test-service"},
942+
{".", "P456DEF", "Another incident", "test-service-2"},
943+
{".", "P789GHI", "Third incident", "test-service-3"},
944+
})
945+
946+
// Second call with small window: columns are already set, so
947+
// table.SetHeight will subtract a 2-line header from the height.
948+
// Before the fix, this panicked with "slice bounds out of range"
949+
// when the resulting viewport height went negative.
950+
msg := tea.WindowSizeMsg{Width: tt.width, Height: tt.height}
951+
assert.NotPanics(t, func() {
952+
result, _ = m.windowSizeMsgHandler(msg)
953+
})
954+
955+
m = result.(model)
956+
assert.GreaterOrEqual(t, m.table.Height(), 1,
957+
"viewport height must be positive to avoid panic in visibleLines")
958+
})
959+
}
960+
}

pkg/tui/msgHandlers.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,11 @@ func (m model) windowSizeMsgHandler(msg tea.Msg) (tea.Model, tea.Cmd) {
8181
actionLogReservedLines = 11
8282
}
8383
tableHeight := windowSize.Height - verticalScratchWidth - tableVerticalScratchWidth - rowCount - estimatedExtraLinesFromComponents - actionLogReservedLines - inputReservedLines - additionalSpacing
84-
// Ensure table height is never negative (can happen with very small terminal windows)
85-
if tableHeight < 1 {
86-
tableHeight = 1
84+
// table.SetHeight subtracts the rendered header height (2 lines: text + bottom border)
85+
// from the value we pass, so the minimum must exceed the header height to keep the
86+
// internal viewport height positive and avoid a panic in viewport.visibleLines
87+
if tableHeight < 4 {
88+
tableHeight = 4
8789
}
8890

8991
m.table.SetHeight(tableHeight)

0 commit comments

Comments
 (0)