fix(cli): remove supervise queue highlight
test / unit (pull_request) Successful in 43s
test / integration (pull_request) Successful in 52s

This commit is contained in:
2026-06-03 17:31:19 +00:00
parent 50ec920243
commit d3bc463295
3 changed files with 13 additions and 79 deletions
+5 -31
View File
@@ -17,7 +17,6 @@ import os
import subprocess import subprocess
import sys import sys
import tempfile import tempfile
import time
import traceback import traceback
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime, timezone from datetime import datetime, timezone
@@ -59,7 +58,6 @@ from ._common import PROG
_REFRESH_INTERVAL_MS = 1000 _REFRESH_INTERVAL_MS = 1000
_NEW_PROPOSAL_HIGHLIGHT_SEC = 5.0
@dataclass(frozen=True) @dataclass(frozen=True)
@@ -99,20 +97,6 @@ def _approval_status(qp: QueuedProposal, verb: str) -> str:
return base return base
def _is_recent(
proposal_id: str,
first_seen: dict[str, float] | None,
now: float | None,
) -> bool:
"""True if `proposal_id` was first seen within the highlight window."""
if first_seen is None or now is None:
return False
started = first_seen.get(proposal_id)
if started is None:
return False
return (now - started) < _NEW_PROPOSAL_HIGHLIGHT_SEC
def _detail_lines( def _detail_lines(
qp: QueuedProposal, qp: QueuedProposal,
*, *,
@@ -388,21 +372,19 @@ def _main_loop(stdscr: "curses._CursesWindow") -> None:
curses.curs_set(0) curses.curs_set(0)
stdscr.timeout(_REFRESH_INTERVAL_MS) stdscr.timeout(_REFRESH_INTERVAL_MS)
green_attr = _try_init_green() green_attr = _try_init_green()
first_seen: dict[str, float] = {}
selected = 0 selected = 0
status_line = "" status_line = ""
saw_first_tick = False
supervise_pane_id = os.environ.get("TMUX_PANE", "") supervise_pane_id = os.environ.get("TMUX_PANE", "")
seen_ids: set[str] = set()
while True: while True:
pending = discover_pending() pending = discover_pending()
if selected >= len(pending): if selected >= len(pending):
selected = max(0, len(pending) - 1) selected = max(0, len(pending) - 1)
now = time.monotonic()
live_ids = {qp.proposal.id for qp in pending} live_ids = {qp.proposal.id for qp in pending}
newly_arrived = live_ids - first_seen.keys() newly_arrived = live_ids - seen_ids
if saw_first_tick and newly_arrived: if seen_ids and newly_arrived:
try: try:
curses.beep() curses.beep()
except curses.error: except curses.error:
@@ -413,15 +395,11 @@ def _main_loop(stdscr: "curses._CursesWindow") -> None:
if qp.proposal.id in newly_arrived: if qp.proposal.id in newly_arrived:
selected = i selected = i
break break
for proposal_id in live_ids: seen_ids = live_ids
first_seen.setdefault(proposal_id, now)
for stale_id in list(first_seen.keys() - live_ids):
del first_seen[stale_id]
saw_first_tick = True
_render( _render(
stdscr, pending, selected, status_line, stdscr, pending, selected, status_line,
first_seen=first_seen, now=now, green_attr=green_attr, green_attr=green_attr,
) )
try: try:
@@ -478,8 +456,6 @@ def _render(
selected: int, selected: int,
status_line: str, status_line: str,
*, *,
first_seen: dict[str, float] | None = None,
now: float | None = None,
green_attr: int = 0, green_attr: int = 0,
) -> None: ) -> None:
stdscr.erase() stdscr.erase()
@@ -512,8 +488,6 @@ def _render(
f"{_proposed_payload_label(p.tool)}" f"{_proposed_payload_label(p.tool)}"
) )
attr = curses.A_REVERSE if i == selected else curses.A_NORMAL attr = curses.A_REVERSE if i == selected else curses.A_NORMAL
if _is_recent(p.id, first_seen, now):
attr |= green_attr
stdscr.addnstr(row, 0, line, w - 1, attr) stdscr.addnstr(row, 0, line, w - 1, attr)
row += 1 row += 1
if row >= h - 3: if row >= h - 3:
@@ -1,5 +1,5 @@
- **Status:** Active - **Status:** Draft
- **Author:** didericis - **Author:** didericis
- **Created:** 2026-06-03 - **Created:** 2026-06-03
- **Issue:** #174 - **Issue:** #174
@@ -127,8 +127,8 @@ problem is everything that got bolted onto that core after.
dashboard.py` to `bot_bottle/cli/supervise.py`. The dispatcher dashboard.py` to `bot_bottle/cli/supervise.py`. The dispatcher
in `bot_bottle/cli/__init__.py` and the help text both update. in `bot_bottle/cli/__init__.py` and the help text both update.
- **Strip the curses loop to proposal-only.** The remaining - **Strip the curses loop to proposal-only.** The remaining
surface is: list pending proposals (with the new-arrival bell + surface is: list pending proposals (with the new-arrival bell
green highlight from PRD 0013), Enter for detail view, from PRD 0013), Enter for detail view,
`a`/`m`/`r` for approve / modify / reject, `q` to quit. No `a`/`m`/`r` for approve / modify / reject, `q` to quit. No
agents pane, no Tab, no agent picker, no `n`/`x`/`e`/`p`, no agents pane, no Tab, no agent picker, no `n`/`x`/`e`/`p`, no
tmux dispatch, no `bottles` dict on the main loop. tmux dispatch, no `bottles` dict on the main loop.
@@ -141,10 +141,9 @@ problem is everything that got bolted onto that core after.
`operator_edit_allowlist`, and their imports come out. `operator_edit_allowlist`, and their imports come out.
- **Collapse the model module.** `dashboard_model.py`'s - **Collapse the model module.** `dashboard_model.py`'s
proposal-side helpers (`QueuedProposal`, `discover_pending`, proposal-side helpers (`QueuedProposal`, `discover_pending`,
`_approval_status`, `_is_recent`, `_detail_lines`, `_approval_status`, `_detail_lines`,
`_failed_url_host`, `_proposed_payload_label`, `_failed_url_host`, `_proposed_payload_label`,
`_suffix_for_tool`, `_REFRESH_INTERVAL_MS`, `_suffix_for_tool`, `_REFRESH_INTERVAL_MS`) move back into
`_NEW_PROPOSAL_HIGHLIGHT_SEC`) move back into
`supervise.py` (CLI) or into `bot_bottle/supervise.py` `supervise.py` (CLI) or into `bot_bottle/supervise.py`
(the daemon-side module) — wherever they fit. The agents / (the daemon-side module) — wherever they fit. The agents /
picker / tmux helpers in that module (`PANE_*`, picker / tmux helpers in that module (`PANE_*`,
@@ -205,7 +204,7 @@ ripgrep
view, same as today. view, same as today.
- `q` / Esc quits. There are no dashboard-owned bottles, so no - `q` / Esc quits. There are no dashboard-owned bottles, so no
per-process teardown decision — `q` just exits. per-process teardown decision — `q` just exits.
- The new-arrival bell + green highlight + (if in tmux) the - The new-arrival bell + (if in tmux) the
`tmux select-pane` jump back to the supervise pane stay, `tmux select-pane` jump back to the supervise pane stay,
because they're real wins for the operator's "I was typing at because they're real wins for the operator's "I was typing at
claude and a proposal landed" case. They don't require any of claude and a proposal landed" case. They don't require any of
@@ -229,7 +228,7 @@ bot_bottle/cli/supervise.py
- reject(qp, *, reason) - reject(qp, *, reason)
- QueuedProposal, discover_pending - QueuedProposal, discover_pending
- _detail_lines, _approval_status, - _detail_lines, _approval_status,
_is_recent, _failed_url_host, _failed_url_host,
_proposed_payload_label, _proposed_payload_label,
_suffix_for_tool _suffix_for_tool
``` ```
@@ -262,7 +261,7 @@ a clear pointer forward. No PRD body is rewritten.
Keep: Keep:
- `tests/cli/test_dashboard*.py` cases that exercise - `tests/cli/test_dashboard*.py` cases that exercise
`discover_pending`, `approve`, `reject`, `_detail_lines`, `discover_pending`, `approve`, `reject`, `_detail_lines`,
`_is_recent`, `_approval_status`, `_failed_url_host`, `_approval_status`, `_failed_url_host`,
`_proposed_payload_label`, `_suffix_for_tool`, `_proposed_payload_label`, `_suffix_for_tool`,
`_modify` / `edit_in_editor`. `_modify` / `edit_in_editor`.
- `tests/cli/test_dashboard_once.py` (or equivalent) — the - `tests/cli/test_dashboard_once.py` (or equivalent) — the
@@ -1,39 +0,0 @@
"""Unit: supervise's new-proposal highlight window.
The curses rendering itself is exercised manually; this isolates
the pure decision `is the proposal still in its post-arrival
highlight window?`"""
import unittest
from bot_bottle.cli import supervise as supervise_cli
class TestIsRecent(unittest.TestCase):
def test_just_seen_is_recent(self):
self.assertTrue(supervise_cli._is_recent("p1", {"p1": 100.0}, now=100.5))
def test_seen_within_window(self):
# Default window is 5s.
self.assertTrue(
supervise_cli._is_recent("p1", {"p1": 100.0}, now=104.9),
)
def test_seen_past_window_is_not_recent(self):
self.assertFalse(
supervise_cli._is_recent("p1", {"p1": 100.0}, now=106.0),
)
def test_unknown_proposal_is_not_recent(self):
self.assertFalse(
supervise_cli._is_recent("p2", {"p1": 100.0}, now=100.5),
)
def test_none_args_safe_default(self):
self.assertFalse(supervise_cli._is_recent("p1", None, None))
self.assertFalse(supervise_cli._is_recent("p1", {"p1": 100.0}, None))
self.assertFalse(supervise_cli._is_recent("p1", None, 100.5))
if __name__ == "__main__":
unittest.main()