diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-05 19:42:22 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-05 19:42:22 +0200 |
| commit | 1318a3a927ea3bae77a7560e149070583f215982 (patch) | |
| tree | fde86b0250643da119ca75e051028e3e92466d32 /internal | |
| parent | 47c53c0d9f06451972fa32d6d74ebe572757c639 (diff) | |
feat(tui): pause dashboard refresh on terminal blur
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/tui/dashboard/model.go | 16 | ||||
| -rw-r--r-- | internal/tui/tui.go | 14 | ||||
| -rw-r--r-- | internal/tui/tui_test.go | 34 |
3 files changed, 64 insertions, 0 deletions
diff --git a/internal/tui/dashboard/model.go b/internal/tui/dashboard/model.go index a4cd4a5..f097da7 100644 --- a/internal/tui/dashboard/model.go +++ b/internal/tui/dashboard/model.go @@ -48,6 +48,7 @@ type Model struct { streamModel eventstream.Model showHelp bool isDark bool + focused bool } // NewModel creates a dashboard model with default refresh cadence. @@ -68,6 +69,7 @@ func NewModelWithConfig(engine SnapshotSource, streamSource *eventstream.RingBuf pidFilter: -1, streamModel: eventstream.NewModel(streamSource), isDark: true, + focused: true, } m.SetDarkMode(true) return m @@ -88,6 +90,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.streamModel.SetViewport(streamWidth, streamHeight) return m, nil case refreshTickMsg: + if !m.focused { + return m, nil + } snap := m.snapshot() return m, tea.Batch( tickCmd(m.refreshEvery), @@ -292,6 +297,17 @@ func (m *Model) SetDarkMode(isDark bool) { m.streamModel.SetDarkMode(isDark) } +// SetFocused controls whether periodic refresh ticks are processed. +func (m *Model) SetFocused(focused bool) { + m.focused = focused +} + +// SnapshotCmd returns a command that fetches and emits a fresh dashboard snapshot. +func (m Model) SnapshotCmd() tea.Cmd { + snap := m.snapshot() + return func() tea.Msg { return messages.StatsTickMsg{Snap: snap} } +} + // SetPidFilter updates the active PID filter used by tab render hints. func (m *Model) SetPidFilter(pid int) { m.pidFilter = pid diff --git a/internal/tui/tui.go b/internal/tui/tui.go index a12554a..f1de224 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -174,6 +174,7 @@ type Model struct { pidFilter int exportEnabled bool isDark bool + focused bool } // NewModel creates the top-level TUI model. @@ -218,6 +219,7 @@ func newModelWithRuntimeConfig(initialPID, startupPidFilter int, exportEnabled b pidFilter: pidFilter, exportEnabled: exportEnabled, isDark: true, + focused: true, } if initialPID > 0 { @@ -255,6 +257,17 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.BackgroundColorMsg: m.applyTheme(msg.IsDark()) return m, nil + case tea.FocusMsg: + m.focused = true + m.dashboard.SetFocused(true) + if m.screen == ScreenDashboard && !m.attaching { + return m, tea.Batch(m.dashboard.Init(), m.dashboard.SnapshotCmd()) + } + return m, nil + case tea.BlurMsg: + m.focused = false + m.dashboard.SetFocused(false) + return m, nil case tea.KeyPressMsg: if key.Matches(msg, m.keys.Quit) { m.quitting = true @@ -682,5 +695,6 @@ func placeToViewport(width, height int, content string) string { func altScreenView(content string) tea.View { view := tea.NewView(content) view.AltScreen = true + view.ReportFocus = true return view } diff --git a/internal/tui/tui_test.go b/internal/tui/tui_test.go index 5c1ea5f..e705f41 100644 --- a/internal/tui/tui_test.go +++ b/internal/tui/tui_test.go @@ -5,6 +5,7 @@ import ( "errors" "ior/internal/probemanager" "ior/internal/statsengine" + dashboardui "ior/internal/tui/dashboard" "ior/internal/tui/eventstream" tuiexport "ior/internal/tui/export" "ior/internal/tui/messages" @@ -606,3 +607,36 @@ func TestProbeModalViewDoesNotStackDashboardContent(t *testing.T) { t.Fatalf("expected probe modal to render as standalone view, got stacked dashboard content") } } + +func TestBlurPausesDashboardRefreshAndFocusResumesIt(t *testing.T) { + m := NewModel(-1, func(context.Context) error { return nil }) + m.screen = ScreenDashboard + m.attaching = false + m.dashboard = dashboardui.NewModelWithConfig(nil, nil, 1, m.keys) + m.focused = true + + next, _ := m.Update(tea.BlurMsg{}) + m = next.(Model) + if m.focused { + t.Fatalf("expected focused=false after blur") + } + + tickMsg := m.dashboard.Init()() + next, tickCmd := m.Update(tickMsg) + m = next.(Model) + if tickCmd != nil { + t.Fatalf("expected no follow-up tick command while blurred") + } + + next, focusCmd := m.Update(tea.FocusMsg{}) + m = next.(Model) + if !m.focused { + t.Fatalf("expected focused=true after focus") + } + if focusCmd == nil { + t.Fatalf("expected focus to resume refresh with a command batch") + } + if _, ok := focusCmd().(tea.BatchMsg); !ok { + t.Fatalf("expected focus command to be a batch") + } +} |
