Skip to content

Commit 3833b37

Browse files
cpcloudclaude
andauthored
Add 1s display tick so TUI elapsed counter updates every second (#527)
## Summary - The TUI elapsed counter only updated every 2 seconds because the display refresh was tied to the API polling interval (`tickIntervalActive = 2s`) - Adds a separate 1-second display tick (`tuiDisplayTickMsg`) that triggers a repaint without any API calls - API polling remains at 2s to avoid unnecessary daemon requests ## Test plan - [x] `TestTUIDisplayTickDoesNotTriggerRefresh` verifies the display tick reschedules itself and does not trigger job/status fetches - [x] Existing `TestTUITickInterval` and `TestTUITickNoRefreshWhileLoadingJobs` still pass - [ ] Manual: run `roborev tui` with a running job and verify the elapsed counter ticks every second 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e2b1d73 commit 3833b37

File tree

4 files changed

+35
-3
lines changed

4 files changed

+35
-3
lines changed

cmd/roborev/tui/fetch.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ func (m model) tick() tea.Cmd {
2828
})
2929
}
3030

31+
func (m model) displayTick() tea.Cmd {
32+
return tea.Tick(displayTickInterval, func(time.Time) tea.Msg {
33+
return displayTickMsg{}
34+
})
35+
}
36+
3137
// tickInterval returns the appropriate polling interval based on queue activity.
3238
// Uses faster polling when jobs are running or pending, slower when idle.
3339
func (m model) tickInterval() time.Duration {

cmd/roborev/tui/tui.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@ import (
2525
"github.com/roborev-dev/roborev/internal/streamfmt"
2626
)
2727

28-
// Tick intervals for adaptive polling
28+
// Tick intervals for local redraws and adaptive polling.
2929
const (
30-
tickIntervalActive = 2 * time.Second // Poll frequently when jobs are running/pending
31-
tickIntervalIdle = 10 * time.Second // Poll less when queue is idle
30+
displayTickInterval = 1 * time.Second // Repaint only (elapsed counters, flash expiry)
31+
tickIntervalActive = 2 * time.Second // Poll frequently when jobs are running/pending
32+
tickIntervalIdle = 10 * time.Second // Poll less when queue is idle
3233
)
3334

3435
// TUI styles using AdaptiveColor for light/dark terminal support.
@@ -566,6 +567,7 @@ func newModel(serverAddr string, opts ...option) model {
566567
func (m model) Init() tea.Cmd {
567568
return tea.Batch(
568569
tea.WindowSize(), // request initial window size
570+
m.displayTick(),
569571
m.tick(),
570572
m.fetchJobs(),
571573
m.fetchStatus(),
@@ -712,6 +714,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
712714
result, cmd = m.handleMouseMsg(msg)
713715
case tea.WindowSizeMsg:
714716
result, cmd = m.handleWindowSizeMsg(msg)
717+
case displayTickMsg:
718+
return m, m.displayTick()
715719
case tickMsg:
716720
result, cmd = m.handleTickMsg(msg)
717721
case logTickMsg:

cmd/roborev/tui/tui_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,25 @@ func TestTUITickNoRefreshWhileLoadingJobs(t *testing.T) {
330330
}
331331
}
332332

333+
func TestTUIDisplayTickDoesNotTriggerRefresh(t *testing.T) {
334+
m := newModel("http://localhost")
335+
m.loadingJobs = false
336+
m.loadingMore = false
337+
338+
updated, cmd := m.Update(displayTickMsg{})
339+
m2 := updated.(model)
340+
341+
if m2.loadingJobs {
342+
t.Error("display tick should not mark jobs as loading")
343+
}
344+
if m2.loadingMore {
345+
t.Error("display tick should not start pagination loads")
346+
}
347+
if cmd == nil {
348+
t.Error("display tick should reschedule itself")
349+
}
350+
}
351+
333352
func TestTUITickInterval(t *testing.T) {
334353
tests := []struct {
335354
name string

cmd/roborev/tui/types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ type logOutputMsg struct {
115115
// logTickMsg triggers a refresh of the log output
116116
type logTickMsg struct{}
117117

118+
// displayTickMsg triggers a local repaint without polling the daemon.
119+
type displayTickMsg struct{}
120+
118121
type tickMsg time.Time
119122
type jobsMsg struct {
120123
jobs []storage.ReviewJob

0 commit comments

Comments
 (0)