From e68244d9478560c12bcf2ac48c624c4cdf54df23 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Sun, 25 Oct 2026 02:29:32 -0400 Subject: [PATCH 1/2] Sidebar tooltip shows the thread model's text, not its repr Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013ejYrMLnLWah8wCDDcb57V From 0073717b49c714c1beded52b0bf7234fe2b54b09 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Sun, 25 Oct 2026 02:31:01 -0400 Subject: [PATCH 2/2] Sidebar tooltip shows the thread model's text, not its repr ThreadStatusRow.show joined str(value) over the tooltip parts, and row.model is a ThreadModel member, so the tooltip printed DeclaredModel(text='...'). The model family already answers its shown text: TextChoice.texts() is (text,) when given and () when absent, so the tooltip spreads that instead of stringifying the object (BOUND-8: the family's own text flattened by repr at the call site). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013ejYrMLnLWah8wCDDcb57V --- src/toad/widgets/comms_sidebar.py | 4 ++-- tests/textual/test_welcome_view.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/toad/widgets/comms_sidebar.py b/src/toad/widgets/comms_sidebar.py index 3ad88cfb6..32fcba0ad 100644 --- a/src/toad/widgets/comms_sidebar.py +++ b/src/toad/widgets/comms_sidebar.py @@ -257,8 +257,8 @@ def show(self, row: ThreadRowModel, *, pinned: bool = False) -> None: previous = self.thread_row self.thread_row, self.pinned, self.action_status = row, pinned, action_status summary = action_status if action_status is not None else row.summary - self.tooltip = Content("\n".join(str(value) for value in ( - row.name, summary, row.unread.detail, "Pinned in this channel" if pinned else None, row.model, + self.tooltip = Content("\n".join(value for value in ( + row.name, summary, row.unread.detail, "Pinned in this channel" if pinned else None, *row.model.texts(), ) if value)) badge = f"{row.unread.label} " if row.unread.label else "" title = self.activity_content(row.title, Content(row.label), self.busy, diff --git a/tests/textual/test_welcome_view.py b/tests/textual/test_welcome_view.py index c90f2f1e6..8c0d886d0 100644 --- a/tests/textual/test_welcome_view.py +++ b/tests/textual/test_welcome_view.py @@ -114,3 +114,18 @@ async def test_a_launch_that_names_a_session_opens_it(workspace): await ui.until(lambda: len(ui.tabs()) == 1, why="the named session's tab") assert ui.selected_tab == ui.tabs()[0].identity assert not ui.shows(HINT) + + +async def test_a_thread_row_tooltip_names_its_model_by_text(workspace): + from dataclasses import replace + + from agent_comms.thread_model import DeclaredModel + from toad.widgets.comms_sidebar import ThreadStatusRow + + thread = Thread("alpha", frozenset({"team"}), str(workspace.project)) + workspace.comms.registry.declare(replace(thread, model=DeclaredModel("openai-codex/gpt-6.1-sol"))) + async with workspace.app(session=False) as ui: + await ui.until_shows(THREADS) + app, _ = ui.textual + row = next(row for row in app.screen.query(ThreadStatusRow) if row.thread_row.name == "alpha") + assert "openai-codex/gpt-6.1-sol" in str(row.tooltip).splitlines()