From 4acdfeaa87b55eea2494af0f3211d2ac586aa97d Mon Sep 17 00:00:00 2001 From: SW Date: Thu, 10 Sep 2026 13:52:55 +0200 Subject: [PATCH] feat: mouse wheel scrolling in errors tab --- internal/tui/app.go | 49 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/internal/tui/app.go b/internal/tui/app.go index 2b1032f..313433c 100644 --- a/internal/tui/app.go +++ b/internal/tui/app.go @@ -132,6 +132,8 @@ type model struct { width int height int err error + + errorsScrollOffset int } func initialModel(client *sshclient.Client, logPath string) model { @@ -233,6 +235,18 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.width = msg.Width m.height = msg.Height + case tea.MouseMsg: + if m.activeTab == tabErrors { + switch msg.Button { + case tea.MouseButtonWheelUp: + if m.errorsScrollOffset > 0 { + m.errorsScrollOffset-- + } + case tea.MouseButtonWheelDown: + m.errorsScrollOffset++ + } + } + case tea.KeyMsg: switch msg.String() { case "q", "ctrl+c": @@ -240,14 +254,18 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, tea.Quit case "1": m.activeTab = tabLive + m.errorsScrollOffset = 0 case "2": m.activeTab = tabDay + m.errorsScrollOffset = 0 return m, m.ensureHistory(tabDay) case "3": m.activeTab = tabWeek + m.errorsScrollOffset = 0 return m, m.ensureHistory(tabWeek) case "4": m.activeTab = tabErrors + m.errorsScrollOffset = 0 return m, m.ensureHistory(tabDay) } @@ -290,6 +308,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { h.stats = msg.agg.ToStats() h.buckets = msg.agg.ToBuckets(tabBucketSize(msg.tab)) h.errors = msg.agg.RecentErrors(100) + m.errorsScrollOffset = 0 } case errMsg: @@ -506,8 +525,34 @@ func (m model) renderErrors() string { ipW, "REMOTE IP", "URI", ) + + // Fixed-height overhead: header + tab bar + title/header rows + footer/borders. + const overhead = 10 + maxRows := m.height - overhead + if maxRows < 3 { + maxRows = 3 + } + + // Clamp scroll offset. + if m.errorsScrollOffset > len(errs)-maxRows { + if len(errs) > maxRows { + m.errorsScrollOffset = len(errs) - maxRows + } else { + m.errorsScrollOffset = 0 + } + } + if m.errorsScrollOffset < 0 { + m.errorsScrollOffset = 0 + } + + end := m.errorsScrollOffset + maxRows + if end > len(errs) { + end = len(errs) + } + visible := errs[m.errorsScrollOffset:end] + rows := []string{styleHeader.Render(title), "", styleHeader.Render(header)} - for _, e := range errs { + for _, e := range visible { statusStr := fmt.Sprintf("%d", e.Status) var statusStyled string if e.Status >= 500 { @@ -732,7 +777,7 @@ func bucketSizeLabel(d time.Duration) string { // -- entry point -------------------------------------------------------------- func Run(client *sshclient.Client, logPath string) error { - p := tea.NewProgram(initialModel(client, logPath), tea.WithAltScreen()) + p := tea.NewProgram(initialModel(client, logPath), tea.WithAltScreen(), tea.WithMouseCellMotion()) _, err := p.Run() return err }