diff --git a/evidence/dm-initial-publication-20261008/README.md b/evidence/dm-initial-publication-20261008/README.md index 735df72d9..14cbdef0d 100644 --- a/evidence/dm-initial-publication-20261008/README.md +++ b/evidence/dm-initial-publication-20261008/README.md @@ -647,3 +647,64 @@ incoming messages; its route ended at Window. That source observation also included the merged prepared-style rule and preceded the final single-dispatch correction, so its aggregate timings are not a causal measurement of this patch. Neither this viewport change nor the native mount candidate is published live. + + +## Published document wheel / native mount integration + +Textual120 and Toad571 merged. Matched published frontend: Toad fbb8e395d, +Textual c330608250, unchanged Core client09a0131ab. Full954assets /69packages / +2857RECORD entries and retained native Pi matched. + +Actual installed private-driver observation preserved160 ordered wheel packets, +three reversals and20 incoming messages /16recipients, provider0. Wheel median +3.88ms/p95 78.80/max219.98; backlog after final post3.86ms. Frame p95 109.87ms/ +max337.05; loop max105.21. Overall pacing still FAIL, not a statistical speedup +claim. Timer update reached61.32ms UI CPU; layout48.81ms UI CPU. + +Installed real-click disclosure passed: no closed body, opened body current, live +error, closed update deferred, reopen current, original pre-mount explicit read. +Reviewed frontend publisher changed NEW launches only; backend defaults, active +route and native package preserved. Existing windows keep imported modules. +Receipt: `.artifacts/document-wheel-routing-delivery-20261008/live-publication.json`. +Raw build/App/disclosure/publication files remain in +`/home/ts/.cache/agent-scratch/prepared-style-measurement-20261008/`. + +Removed55,187,908bytes of superseded never-published native-mount candidate +runtime after fresh privileged census226processes/no gaps/no refs and separate +default/publication checks. Wheels/source/proofs/raw and actual published/rollback +runtimes retained. The first evidence append met absent sparse-checkout path; +no evidence was changed then. Materialized original HEAD bytes and appended here. + +Next work is original synchronous geometry/layout and paint publication; native +capture/FIFO is preserved rather than bypassed to mask its costs. + + +## Wire row resources owned by composition + +WireMarkdownMessage and IRCMessage now retain the actual body and notification +widgets created by their original compose methods. Recomposition acquires fresh +resources; unmounted resources cannot supply read/extent evidence. Compact and +full Markdown remain distinct rendering contracts. CommsChatView uses the row's +notification resource, revalidating attachment after the asynchronous read. +Three body queries and the per-row notification descendant query are deleted. +No message, read ledger, status or preparation result is copied. + +AST:288 production/417 authored modules parsed, zero omissions. All changed row +family body/feedback selector calls are gone. The separate incoming/outgoing +transcript WireMessageHandling queries belong to another declared resource +family; this change does not claim to replace them. + +Existing real private wire partial-paint control passed: only painted bodies +acknowledged, scrolling admits the remaining original page and compact/Markdown +style replacement preserves identities/read results. Actual private Toad App +resource control passed: both styles own their mounted children, recomposition +replaces retired instances and removed bodies cannot authorize reads. First +resource fixture used a bare Screen without the real history viewport and failed +its Markdown admission; preserved, then corrected to the real wire/window. +No production fallback or timeout changed. + +Raw/source control files are under +`/home/ts/.cache/agent-scratch/dm-owned-resources-20261008/`. Speed improvement is +unmeasured. The next required installed observation also retains Layout.widget +identity and original node/layout source epochs in the existing recorder so +native work can distinguish real changed extents from repeated late requests. diff --git a/src/toad/widgets/comms_chat.py b/src/toad/widgets/comms_chat.py index 2b55d8545..2882e1f6a 100644 --- a/src/toad/widgets/comms_chat.py +++ b/src/toad/widgets/comms_chat.py @@ -38,7 +38,6 @@ ) from toad.widgets.flash import Flash from toad.widgets.prompt import Prompt -from toad.widgets.message_notifications import MessageNotifications from toad.owner_preparation import read_thread_presentation from toad.screens.session_view import SessionView from toad.command_catalog import CommandCatalog @@ -318,8 +317,8 @@ async def _read_notifications(self, rows: tuple[tuple[WireMessage, Widget], ...] visible = {widget for _, widget in self._visible_notification_rows()} for message, widget in rows: if widget in visible: - feedback = next(iter(widget.query(MessageNotifications)), None) - if feedback is None: + feedback = widget.notifications + if feedback is None or not feedback.is_attached: continue # Style replacement has unmounted this row's children. if error is not None: feedback.show_error(error) diff --git a/src/toad/widgets/irc_message.py b/src/toad/widgets/irc_message.py index e7d76b775..fd25632c8 100644 --- a/src/toad/widgets/irc_message.py +++ b/src/toad/widgets/irc_message.py @@ -96,6 +96,10 @@ class WireMarkdownMessage(CoreEventReceiver, ConversationBlock, VerticalGroup): def __init__(self, message: Message, *, direction: str = "Inbound"): super().__init__() self.message = message + # Composition acquires these native resources. Recomposition replaces + # them; the row never borrows a retired body from a selector cache. + self.body = None + self.notifications = None self.direction = ( f"History · {message.source.original_root} · @{message.sender} incarnation {message.sender_created_at}" if isinstance(message, HistoricalMessage) else direction @@ -104,7 +108,8 @@ def __init__(self, message: Message, *, direction: str = "Inbound"): def compose(self) -> ComposeResult: yield MessageDivider(self.direction, clock=MessageClock.recorded(self.message.timestamp)) yield from self.compose_body() - yield MessageNotifications() + self.notifications = MessageNotifications() + yield self.notifications def compose_body(self) -> ComposeResult: from toad.widgets.agent_response import AgentResponse @@ -114,7 +119,8 @@ def compose_body(self) -> ComposeResult: yield ThreadLink(self.message.sender, source) yield Static(" → ", markup=False, expand=False) yield ThreadLink(self.message.target, source) - yield AgentResponse(self.message.body, show_divider=False) + self.body = AgentResponse(self.message.body, show_divider=False) + yield self.body if self.message.mentions: with HorizontalGroup(): yield Static("Mentioned: ", expand=False) @@ -123,14 +129,12 @@ def compose_body(self) -> ComposeResult: def read_ack_widget(self) -> Widget | None: """Only the rendered message body can authorize a read.""" - from toad.widgets.agent_response import AgentResponse - - return self.query_one(AgentResponse) + return self.body if self.body is not None and self.body.is_attached else None @property def native_extent_ready(self) -> bool: """Paging borrows the body's real extent, not its loading placeholder.""" - return self.read_ack_widget().body_ready + return self.body is not None and self.body.is_attached and self.body.body_ready def action_open_target(self, target: str): if isinstance(self.message, HistoricalMessage) and not target.startswith("#"): @@ -160,16 +164,17 @@ class IRCMessage(WireMarkdownMessage, can_focus=True): """ def compose_body(self) -> ComposeResult: - yield IRCMessageText(IRCMessageSource(self.message)) + self.body = IRCMessageText(IRCMessageSource(self.message)) + yield self.body def read_ack_widget(self) -> Widget | None: """Only the text block can authorize a read, never its divider.""" - body = self.query_one(IRCMessageText) - return body if body.paint_ready else None + body = self.body + return body if body is not None and body.is_attached and body.paint_ready else None @property def native_extent_ready(self) -> bool: - return self.query_one(IRCMessageText).preparation_complete + return self.body is not None and self.body.is_attached and self.body.preparation_complete def get_clipboard_text(self) -> str: return f"{self.message.sender} → {self.message.target}: {self.message.body}"