Skip to content

feat: paginate the eval --rich dashboard with left/right arrow keys#1912

Merged
mikasenghaas merged 2 commits into
mainfrom
feat/eval-rich-arrow-pagination
Jul 2, 2026
Merged

feat: paginate the eval --rich dashboard with left/right arrow keys#1912
mikasenghaas merged 2 commits into
mainfrom
feat/eval-rich-arrow-pagination

Conversation

@mikasenghaas

@mikasenghaas mikasenghaas commented Jul 2, 2026

Copy link
Copy Markdown
Member

Summary

  • When rollout rows overflow the eval --rich dashboard, the left/right arrow keys now page through them.
  • Auto-advance is preserved: the 5s timer drives paging until the first arrow press, then hands off seamlessly (continuing from the page currently on screen) and stays where the user leaves it. The page count only grows as rollouts start, and the manual page is clamped so a terminal resize can't leave it out of range.
  • Arrows are inert until there's more than one page: the Pager tracks the page count (set each render by _paginate) and ignores presses while a single page fits, so a stray arrow before rollouts overflow can't disable auto-advance or offset the starting page once paging begins.
  • Key reading lives in the shared live_view engine (dashboard/base.py) behind an optional on_key handler: stdin is put in cbreak mode and read via the asyncio event loop (no extra thread), buffering a partial escape across reads (cbreak can split a key's 3 bytes over callbacks). Each press redraws immediately (rather than waiting for the next 0.25s refresh tick) so there's no lag. Because updates go through the same live.update(..., refresh=True) path as the timer, there's no extra flicker. setcbreak runs inside the try, so the terminal is always restored on exit even if setup raises.
  • No-op when stdin isn't a terminal (POSIX-only), so non-interactive/legacy runs are unaffected. validate_dashboard, which shares live_view, is untouched (it passes no on_key).
  • The page indicator gains a discreet ◄ ► hint ((page 2/6 ◄ ►)) so the controls are discoverable.

Verification

Drove the real dashboard/live_view pipeline inside a pseudo-terminal (real cbreak, real asyncio add_reader, real arrow-sequence parsing, Pager + _paginate, rich Live) with real arrow-key bytes:

  • initial (no keypress): auto-advance active (manual=False), page rotates on the timer
  • read within 0.12s (< the 0.25s tick): page already advanced — confirms the immediate redraw (no lag)
  • → →, : step correctly through pages, continuing from the on-screen auto page
  • ×9 and ×20: clamp at page 1 and the last page (no wrap, no out-of-range)
  • split escape: ESC and [C sent in separate writes with a 0.35s gap (past a refresh tick) — still pages
  • single-page gate: with rows that fit one page, right/right/left leave manual=False and the page at 1/1
  • restore-after-raise: forcing add_reader to raise after setcbreak in a real pty — ICANON and ECHO are restored afterward
  • stdin = /dev/null (non-tty): key reader no-ops, auto-advance still runs, clean exit — no crash

ruff check, ruff format, and ty check verifiers all pass.

🤖 Generated with Claude Code

Note

Add left/right arrow key pagination to the eval rich dashboard

  • Adds a Pager class in eval.py that tracks pagination state: auto-advances pages every _PAGE_SECONDS until the user presses an arrow key, after which it follows manual input and clamps within bounds.
  • Adds a _key_reader context manager in base.py that, on POSIX TTY stdin, uses cbreak mode and an asyncio reader to detect left/right arrow escape sequences and dispatch them to an on_key callback with an immediate redraw.
  • The page indicator in the stats line now shows (page X/Y ◄ ►) when pagination is active.
  • Behavioral Change: key reading is a no-op when on_key is not provided, termios is unavailable, or stdin is not a TTY, so non-interactive environments are unaffected.

Macroscope summarized 54acea6.

When rollout rows overflow the eval `--rich` dashboard, the left/right
arrows now page through them. Auto-advance is preserved: it drives paging
until the first arrow press, then hands off seamlessly (continuing from the
page on screen) and stays where the user leaves it.

Key reading lives in the shared `live_view` engine behind an optional
`on_key` handler — stdin goes to cbreak and is read via the event loop
(no extra thread), redrawing immediately on each press so there's no lag.
A no-op when stdin isn't a terminal, so non-interactive runs are unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread verifiers/v1/cli/dashboard/base.py
Comment thread verifiers/v1/cli/dashboard/eval.py

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 8a43f69. Configure here.

Comment thread verifiers/v1/cli/dashboard/base.py
Comment thread verifiers/v1/cli/dashboard/base.py
Comment thread verifiers/v1/cli/dashboard/eval.py
@macroscopeapp

macroscopeapp Bot commented Jul 2, 2026

Copy link
Copy Markdown

Approvability

Verdict: Approved

Self-contained CLI UX enhancement adding arrow key pagination to the eval dashboard. Changes are isolated to display logic, don't affect eval processing, and the author is the original maintainer of this dashboard code.

You can customize Macroscope's approvability policy. Learn more.

- _key_reader now buffers a partial escape across reads: cbreak can split a
  key's 3 bytes over separate os.read callbacks (common over SSH), which the
  per-chunk scan dropped. Unconsumed tail bytes carry to the next read.
- setcbreak moved inside the try/finally so the terminal is always restored,
  even if setcbreak or add_reader raises after it (previously it could leave
  stdin in cbreak with echo off).
- Pager arrows are now inert until there's more than one page: it tracks the
  page count (set each render by _paginate) and ignores presses while a single
  page fits, so a stray arrow before rollouts overflow can't disable
  auto-advance or offset the starting page once paging begins.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mikasenghaas mikasenghaas requested a review from snimu July 2, 2026 02:38
@mikasenghaas mikasenghaas merged commit 9bd2ab8 into main Jul 2, 2026
13 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.

2 participants