summaryrefslogtreecommitdiff
path: root/internal/tui/dashboard
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-25 22:58:40 +0200
committerPaul Buetow <paul@buetow.org>2026-02-25 22:58:40 +0200
commit4c34b9efcd539c819648c927d7e3f53220df8ad2 (patch)
treef9de9fd650a2d16316ba2c159990d891c9de5189 /internal/tui/dashboard
parent67e10f34c92e93343adbd690b3b21e455e863bd3 (diff)
Fix stream paused scrolling and apply pending TUI/probe updates
Diffstat (limited to 'internal/tui/dashboard')
-rw-r--r--internal/tui/dashboard/model.go13
-rw-r--r--internal/tui/dashboard/model_test.go85
2 files changed, 95 insertions, 3 deletions
diff --git a/internal/tui/dashboard/model.go b/internal/tui/dashboard/model.go
index 8b2c814..9b425b1 100644
--- a/internal/tui/dashboard/model.go
+++ b/internal/tui/dashboard/model.go
@@ -73,6 +73,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.WindowSizeMsg:
m.width = msg.Width
m.height = msg.Height
+ m.streamModel.SetViewport(msg.Width, msg.Height)
return m, nil
case refreshTickMsg:
snap := m.snapshot()
@@ -104,7 +105,10 @@ func (m Model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
prevActiveTab := m.activeTab
var cmd tea.Cmd
keyStr := msg.String()
- handled := m.handleArrowTabKey(keyStr) || m.handleScrollKey(keyStr)
+ handled := m.handleArrowTabKey(keyStr) || m.handleScrollKey(msg)
+ if handled && m.activeTab == TabStream && (keyStr == " " || keyStr == "space") && !m.streamModel.Paused() {
+ cmd = streamTickCmd()
+ }
if !handled {
switch {
@@ -171,7 +175,8 @@ func (m *Model) handleArrowTabKey(keyStr string) bool {
}
}
-func (m *Model) handleScrollKey(keyStr string) bool {
+func (m *Model) handleScrollKey(msg tea.KeyMsg) bool {
+ keyStr := msg.String()
switch m.activeTab {
case TabSyscalls:
return scrollOffset(keyStr, &m.syscallsOffset, m.maxSyscallsRows())
@@ -183,7 +188,9 @@ func (m *Model) handleScrollKey(keyStr string) bool {
case TabProcesses:
return scrollOffset(keyStr, &m.processesOffset, m.maxProcessesRows())
case TabStream:
- return m.streamModel.HandleKey(keyStr)
+ streamWidth, streamHeight := common.EffectiveViewport(m.width, m.height)
+ m.streamModel.SetViewport(streamWidth, streamHeight)
+ return m.streamModel.HandleTeaKey(msg)
default:
return false
}
diff --git a/internal/tui/dashboard/model_test.go b/internal/tui/dashboard/model_test.go
index c1b2e1d..1e54b27 100644
--- a/internal/tui/dashboard/model_test.go
+++ b/internal/tui/dashboard/model_test.go
@@ -1,11 +1,15 @@
package dashboard
import (
+ "fmt"
+ "regexp"
+ "strconv"
"strings"
"testing"
"ior/internal/statsengine"
common "ior/internal/tui/common"
+ "ior/internal/tui/eventstream"
"ior/internal/tui/messages"
tea "github.com/charmbracelet/bubbletea"
@@ -161,6 +165,87 @@ func TestFilesTabGroupedScrollUsesDirectoryOffset(t *testing.T) {
}
}
+func TestStreamSpaceUnpauseSchedulesStreamTick(t *testing.T) {
+ rb := eventstream.NewRingBuffer()
+ m := NewModelWithConfig(nil, rb, 250, common.DefaultKeyMap())
+ m.activeTab = TabStream
+ m.streamModel.HandleKey("space") // pause
+
+ next, cmd := m.Update(tea.KeyMsg{Type: tea.KeySpace})
+ _ = next
+ if cmd == nil {
+ t.Fatalf("expected stream tick command when unpausing stream")
+ }
+}
+
+func TestStreamPausedSupportsJKArrowsAndPageKeys(t *testing.T) {
+ rb := eventstream.NewRingBuffer()
+ for i := 0; i < 300; i++ {
+ rb.Push(eventstream.StreamEvent{
+ Seq: uint64(i + 1),
+ Syscall: "read",
+ Comm: "proc",
+ PID: 1000,
+ TID: uint32(2000 + i),
+ FileName: fmt.Sprintf("/tmp/file-%03d", i),
+ })
+ }
+
+ m := NewModelWithConfig(nil, rb, 250, common.DefaultKeyMap())
+ m.activeTab = TabStream
+ next, _ := m.Update(tea.WindowSizeMsg{Width: 120, Height: 30})
+ m = next.(Model)
+
+ m.streamModel.Refresh()
+ _ = m.View()
+
+ next, _ = m.Update(tea.KeyMsg{Type: tea.KeySpace}) // pause
+ m = next.(Model)
+ before := rowFromStreamView(t, m.View())
+
+ next, _ = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'k'}})
+ m = next.(Model)
+ afterK := rowFromStreamView(t, m.View())
+ if afterK >= before {
+ t.Fatalf("expected k to scroll up while paused: before=%d afterK=%d", before, afterK)
+ }
+
+ next, _ = m.Update(tea.KeyMsg{Type: tea.KeyDown})
+ m = next.(Model)
+ afterDown := rowFromStreamView(t, m.View())
+ if afterDown <= afterK {
+ t.Fatalf("expected down arrow to scroll down while paused: afterK=%d afterDown=%d", afterK, afterDown)
+ }
+
+ next, _ = m.Update(tea.KeyMsg{Type: tea.KeyPgUp})
+ m = next.(Model)
+ afterPgUp := rowFromStreamView(t, m.View())
+ if afterPgUp >= afterDown {
+ t.Fatalf("expected pgup to scroll up while paused: afterDown=%d afterPgUp=%d", afterDown, afterPgUp)
+ }
+
+ next, _ = m.Update(tea.KeyMsg{Type: tea.KeyPgDown})
+ m = next.(Model)
+ afterPgDown := rowFromStreamView(t, m.View())
+ if afterPgDown <= afterPgUp {
+ t.Fatalf("expected pgdown to scroll down while paused: afterPgUp=%d afterPgDown=%d", afterPgUp, afterPgDown)
+ }
+}
+
+func rowFromStreamView(t *testing.T, view string) int {
+ t.Helper()
+ re := regexp.MustCompile(`Row ([0-9]+)/([0-9]+)`)
+ m := re.FindStringSubmatch(view)
+ if len(m) != 3 {
+ t.Fatalf("stream row status not found in view")
+ }
+ row, err := strconv.Atoi(m[1])
+ if err != nil {
+ t.Fatalf("invalid row value %q: %v", m[1], err)
+ }
+ return row
+}
+
func TestDirGroupKeyTogglesOnlyOnFilesTab(t *testing.T) {
m := NewModelWithConfig(nil, nil, 250, common.DefaultKeyMap())
m.activeTab = TabFiles