From dfea5a56300e245ca2cd66191a9844dc969368b3 Mon Sep 17 00:00:00 2001 From: Felix Martin Date: Thu, 11 Jun 2026 09:04:26 -0400 Subject: [PATCH] feat: show scheduled prompts in session rows Co-Authored-By: Claude Opus 4.8 (1M context) --- src/hqt/tui/widgets/session_list.py | 20 ++++++++++++- tests/test_tui.py | 45 +++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/hqt/tui/widgets/session_list.py b/src/hqt/tui/widgets/session_list.py index efe5742..3b8da7d 100644 --- a/src/hqt/tui/widgets/session_list.py +++ b/src/hqt/tui/widgets/session_list.py @@ -1,3 +1,6 @@ +from datetime import datetime +from math import ceil + from rich.markup import escape from textual.app import ComposeResult from textual.message import Message @@ -28,12 +31,23 @@ _STATUS_COLORS: dict[str, str] = { } +def format_scheduled_prompt(due_at: datetime, now: datetime | None = None) -> str: + if now is None: + now = datetime.now() + seconds = max(0, (due_at - now).total_seconds()) + if seconds < 3600: + return f"@ {max(1, ceil(seconds / 60))}m" + return f"@ {due_at.strftime('%H:%M')}" + + def format_session_text( nickname: str, harness_name: str, status_text: str, symbol: str, worktree_branch: str | None = None, + scheduled_prompt=None, + now: datetime | None = None, ) -> str: color = _STATUS_COLORS.get(status_text, "#c6d0f5") # default: Text name = nickname @@ -47,7 +61,10 @@ def format_session_text( else: name = f"{nickname} ⎇ {worktree_branch}" label = escape(f"{name} [{harness_name}]") - return f"[{color}]{symbol}[/] {label} {status_text}" + schedule = "" + if scheduled_prompt is not None: + schedule = f" {format_scheduled_prompt(scheduled_prompt.due_at, now=now)}" + return f"[{color}]{symbol}[/] {label} {status_text}{schedule}" class SessionList(Widget, can_focus=False): @@ -71,6 +88,7 @@ class SessionList(Widget, can_focus=False): status_text, symbol, getattr(s, "worktree_branch", None), + scheduled_prompt=getattr(info, "scheduled_prompt", None), ) new_items.append((f"sess-{s.id}", text, s.id)) diff --git a/tests/test_tui.py b/tests/test_tui.py index b5900d3..964afc0 100644 --- a/tests/test_tui.py +++ b/tests/test_tui.py @@ -1032,6 +1032,51 @@ def test_format_session_text_status_colors(): ) # Overlay0 +def _make_scheduled_prompt(due_at): + prompt = MagicMock() + prompt.due_at = due_at + prompt.status = "pending" + return prompt + + +def test_format_session_text_shows_scheduled_prompt_countdown(): + from datetime import datetime, timedelta + from hqt.tui.widgets.session_list import format_session_text + + now = datetime(2026, 6, 11, 14, 0, 0) + scheduled = _make_scheduled_prompt(now + timedelta(minutes=12, seconds=1)) + + text = format_session_text( + "mywork", + "claude", + "idle", + "●", + scheduled_prompt=scheduled, + now=now, + ) + + assert "@ 13m" in text + + +def test_format_session_text_shows_scheduled_prompt_clock_time(): + from datetime import datetime, timedelta + from hqt.tui.widgets.session_list import format_session_text + + now = datetime(2026, 6, 11, 14, 0, 0) + scheduled = _make_scheduled_prompt(now + timedelta(hours=2, minutes=30)) + + text = format_session_text( + "mywork", + "claude", + "idle", + "●", + scheduled_prompt=scheduled, + now=now, + ) + + assert "@ 16:30" in text + + @pytest.mark.asyncio async def test_session_list_renders_harness_name_brackets(): """Regression: '[claude]' must be visible, not swallowed as a markup tag."""