feat: show scheduled prompts in session rows
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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))
|
||||
|
||||
|
||||
@@ -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."""
|
||||
|
||||
Reference in New Issue
Block a user