Skip to content

perf(watch): re-sort on push + skip redundant idle redraws#55

Merged
jiunbae merged 1 commit into
mainfrom
feat/watch-resort-on-push
Jun 16, 2026
Merged

perf(watch): re-sort on push + skip redundant idle redraws#55
jiunbae merged 1 commit into
mainfrom
feat/watch-resort-on-push

Conversation

@jiunbae

@jiunbae jiunbae commented Jun 16, 2026

Copy link
Copy Markdown
Member

Problem

muxa watch felt like it updated "a beat late" (한 박자 늦게), especially with sort = ["state", "latest"].

Root cause: a streaming SingleAgent transition updates the row in place but the loop never re-sorts on a push — ordering only refreshes on the 5 s Full fallback tick (STREAMING_FALLBACK_INTERVAL). When the sort key is a pushed field (state), the badge changed instantly but the row's position lagged up to 5 s.

Separately, the run loop repainted unconditionally every input-poll tick (~62 fps) even on a fully idle UI — wasted CPU.

Changes

  1. Re-sort on pushapply_outcome now calls resort_rows_preserving_selection() after applying a SingleAgent. The merge is surgical and sort_by is stable, so only the changed row moves (no "all rows jumped" jitter); selection is pinned by pane_id so the cursor stays on the same agent. State-sorted views now reflect transitions immediately.
  2. Dirty-gated rendering — repaint only when something changed (input / refresh outcome / preview recapture) or a 1 s idle cadence elapses. The cadence keeps the Activity column's second-granularity relative_time current without the constant full re-render.

Notes / ruled out

  • Reconciler (30 s) only handles closed-pane reaping / codex caps / stuck detection; snapshot debounce (200 ms) is disk-only; streaming is present in the running daemon — none were the cause.
  • Transition broadcast is synchronous in Store::apply (no debounce), so a pushed state change reaches the stream in ms; the lag was entirely client-side sort staleness.

Test

  • New unit test push_resorts_rows_so_a_state_change_moves_immediately.
  • cargo fmt --all -- --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test -p muxa-cli all green (151 watch unit + 8 e2e).
  • TUI not smoke-tested headlessly (needs a TTY); logic covered by the unit test + review.

🤖 Generated with Claude Code

Two fixes for the "watch updates a beat late" / wasted-CPU behaviour.

1. Re-sort on push. A streaming `SingleAgent` transition updated the row in
   place but never re-sorted, so the ordering only refreshed on the 5 s `Full`
   fallback tick. With `sort = ["state", …]` — where the sort key is itself a
   pushed field — the state badge changed instantly while the row stayed put
   for up to 5 s. `apply_outcome` now calls `resort_rows_preserving_selection`
   after a push. The merge is surgical and `sort_by` is stable, so only the
   changed row moves (no "all rows jumped" jitter), and selection is pinned by
   pane_id so the cursor stays on the same agent.

2. Dirty-gated rendering. The run loop repainted unconditionally once per
   input-poll tick (~62 fps) even on a fully idle UI. It now repaints only
   when something changed (input, a refresh outcome, a preview recapture) or
   the 1 s idle cadence elapses — the cadence keeps the Activity column's
   second-granularity `relative_time` current without the constant full
   re-render.

Adds a unit test asserting a pushed Error transition reorders a
state-sorted list immediately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jiunbae jiunbae merged commit 161217d into main Jun 16, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant