feat(dashboard): auto-focus dashboard pane + proposals on new arrival
test / unit (pull_request) Successful in 18s
test / integration (pull_request) Successful in 1m10s

When a fresh proposal arrives, the dashboard now also:

  - Runs `tmux select-pane -t \$TMUX_PANE` (the dashboard's own
    pane id, captured at startup) so tmux focus jumps to the
    dashboard from wherever the operator was (typically claude
    in the right pane).
  - Flips internal focus to PANE_PROPOSALS so j/k navigates the
    queued items immediately.
  - Lands the selected cursor on the first new proposal —
    proposals are sorted by arrival ascending, so the earliest
    new arrival in the batch gets the cursor.

Stacks with the bell + label highlight from the previous
commit. The operator gets:
  1. Audible bell (or tmux activity marker)
  2. Tmux focus on the dashboard pane
  3. Dashboard's internal focus on the proposals list
  4. Cursor on the actual new proposal
  5. Pane label flashing `(new!)` in bold green

— all without leaving the keyboard.
This commit is contained in:
2026-05-26 16:04:23 -04:00
parent 9ac05c1a63
commit 3a7b7d054b
+22 -4
View File
@@ -1356,6 +1356,11 @@ def _main_loop(stdscr: "curses._CursesWindow") -> None:
# pre-existing queue entries on its first poll; those # pre-existing queue entries on its first poll; those
# shouldn't ring the bell as if they just arrived. # shouldn't ring the bell as if they just arrived.
saw_first_tick = False saw_first_tick = False
# The dashboard's own tmux pane id (tmux sets `$TMUX_PANE`
# per-pane). Captured at startup so a new-proposal arrival
# can `tmux select-pane` back to the dashboard from
# whatever pane the operator is currently in.
dashboard_pane_id = os.environ.get("TMUX_PANE", "")
while True: while True:
pending = discover_pending() pending = discover_pending()
if selected >= len(pending): if selected >= len(pending):
@@ -1367,16 +1372,29 @@ def _main_loop(stdscr: "curses._CursesWindow") -> None:
now = time.monotonic() now = time.monotonic()
live_ids = {qp.proposal.id for qp in pending} live_ids = {qp.proposal.id for qp in pending}
# Detect proposals we've never seen before — used to ring # Detect proposals we've never seen before. Triggers:
# the terminal bell (so tmux's monitor-bell or the # - terminal bell (`curses.beep` → tmux's monitor-bell)
# terminal's own bell-on-activity surface a notice when # - tmux focus jump to the dashboard pane (so the
# the operator isn't looking at the dashboard pane). # operator notices even if they were typing at claude)
# - dashboard's internal focus flip to the proposals
# pane (so j/k navigates the queued items immediately)
newly_arrived = live_ids - first_seen.keys() newly_arrived = live_ids - first_seen.keys()
if saw_first_tick and newly_arrived: if saw_first_tick and newly_arrived:
try: try:
curses.beep() curses.beep()
except curses.error: except curses.error:
pass pass
if dashboard_pane_id and _in_tmux():
_tmux_select_pane(dashboard_pane_id)
focus = PANE_PROPOSALS
# Land the cursor on the first new proposal so the
# operator can act immediately. Proposals are sorted
# by arrival_timestamp ascending; find the lowest
# index whose id is in `newly_arrived`.
for i, qp in enumerate(pending):
if qp.proposal.id in newly_arrived:
selected = i
break
for proposal_id in live_ids: for proposal_id in live_ids:
first_seen.setdefault(proposal_id, now) first_seen.setdefault(proposal_id, now)
for stale_id in list(first_seen.keys() - live_ids): for stale_id in list(first_seen.keys() - live_ids):