diff --git a/src/toad/conversation_kind.py b/src/toad/conversation_kind.py index 99d072b80..0b1e0ac54 100644 --- a/src/toad/conversation_kind.py +++ b/src/toad/conversation_kind.py @@ -8,7 +8,6 @@ from agent_comms.declared_family import DeclaredFamily from agent_comms.comms import Comms from agent_comms.message_page import MessagePage -from toad.widgets.session_details import SessionDetails class ConversationKind(DeclaredFamily, affix="Conversation"): @@ -94,8 +93,8 @@ async def status_text(cls, comms, target, *, read_only: bool) -> str: ... @classmethod @abstractmethod - def activity_widgets(cls) -> tuple[type[SessionDetails], ...]: - """Declare widget classes; the composing view owns their instances.""" + def compose_activity(cls, view): + """Select the view's explicit composition for this conversation kind.""" ... @classmethod @@ -153,7 +152,7 @@ def current_identity(cls, page: MessagePage) -> tuple | None: ) @classmethod - def activity_widgets(cls) -> tuple[type[SessionDetails], ...]: + def compose_activity(cls, view): return () @classmethod @@ -271,8 +270,8 @@ def label(cls, target: str) -> str: return f"@{target}" @classmethod - def activity_widgets(cls) -> tuple[type[SessionDetails], ...]: - return (SessionDetails,) + def compose_activity(cls, view): + return view.compose_dm_activity() @classmethod async def update_mentions(cls, view, comms) -> None: diff --git a/src/toad/widgets/comms_chat.py b/src/toad/widgets/comms_chat.py index 81cb6f53c..2e7d189a3 100644 --- a/src/toad/widgets/comms_chat.py +++ b/src/toad/widgets/comms_chat.py @@ -34,6 +34,7 @@ ) from toad.widgets.flash import Flash from toad.widgets.native_history import NativeHistory +from toad.widgets.session_details import SessionDetails from toad.widgets.prompt import Prompt from toad.screens.session_view import SessionView from toad.command_catalog import CommandCatalog @@ -127,12 +128,7 @@ def compose(self) -> ComposeResult: yield ChannelActivityTray(id="comms-activity") yield Flash() with containers.Vertical(id="prompt-stack"): - for activity_type in self.conversation_kind.activity_widgets(): - self.thread_activity = activity_type( - history=NativeHistory(), - status_source=lambda: self.thread_activity.row, - ) - yield self.thread_activity + yield from self.conversation_kind.compose_activity(self) yield self.throbber yield self.conversation_kind.prompt(self.target).data_bind( project_path=Conversation.project_path, @@ -142,6 +138,14 @@ def compose(self) -> ComposeResult: status=Conversation.status, ) + def compose_dm_activity(self) -> ComposeResult: + """The direct-message view owns its peer's status widget.""" + self.thread_activity = SessionDetails( + history=NativeHistory(), + status_source=lambda: self.thread_activity.row, + ) + yield self.thread_activity + async def initialize_view(self) -> None: # Reuse the canonical core service already shared by tab sidebars and # transcript readers. Its revision-aware caches remain model-owned.