Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions evidence/dm-initial-publication-20261008/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -843,3 +843,67 @@ route and existing loaded windows:
`.artifacts/dm-body-publication-delivery-20261008/live-publication.json`.
Raw source/installed/build/burst/publication:
`/home/ts/.cache/agent-scratch/dm-body-publication-20261008/`.

## Supplied native titles and settled receipts — delivered

Reviewed merged native123 preserves supplied Collapsible titles instead of
publishing its default Toggle first. Same-text Content span/metadata changes
reach the original child. Native constructor/subclass watchers, arbitrary
children and collapsed events retain their original contracts. The installed
original supplied-title control and actual notification disclosure control
passed. The native-supplied-title runtime was published for new launches.

Toad576 now starts painted-read work only while the original history has tail,
channel or historical receipts. No flag/copy/queue/cache was added. Actual
pending acknowledgments retain their source/currentness/paint witnesses.
The existing private App partial-paint control passed against source and the
installed wheel: unseen bodies remain unread; scrolling acknowledges the rest;
50 settled visibility calls start no acknowledgment work; both style changes
retain original row/read identity. Production changes only mark_visible;705
modules parse with0 omissions. Read acquisition/publication remains independent.

New-launch frontend is published through its existing owner:
`.artifacts/settled-wire-receipts-delivery-20261008/live-publication.json`.
Raw checks/build/publication:
`/home/ts/.cache/agent-scratch/settled-wire-receipts-20261008/`.
Backend defaults/route/running owners remain unchanged. Existing windows retain
their loaded modules. Broad frame-time acceptance is still incomplete.

The custom thread-time cProfile experiment returned impossible negative totals,
including after UI-thread restriction. Those timing results are rejected and
remain raw in native-supplied-title scratch; no speedup is inferred from them.
The recorder retains UI-thread gating and uses its original monotonic profiler
clock again; enclosing CPU spans remain separate measurements.

Three superseded unused runtime directories were removed after a fresh225-process
census with no gaps/references and checking all default symlinks:173008144bytes.
Wheels/source/proofs/raw and current/immediate rollback/backend runtimes remain.
Original cleanup evidence is under disk-cleanup-owner-20261002:
`Parent-superseded-dm-runtimes-retired-20261008.json`.

## Message-owned timestamp caption

Compact and Markdown wire messages now paint their timestamp/direction on the
original row border, deleting one MessageDivider child per message. MessageClock
still owns dates; body paint alone owns read acknowledgment. Notification
disclosure, links, clipboard and historical identity remain distinct contracts.
Timestamp tooltip also appears over the row through native ancestor lookup.

Actual private Apps passed compact/Markdown caption/direction, caption-only
unread protection, wrapping/routing, and reader preservation under body growth
and tail replacement. Final installed caption/spacing and reader checks passed.
705 Python modules parsed without omissions. Review caught application .block
margin precedence; the owning stylesheet preserves top/right/bottom spacing.
Initial CSS color refusal and stale ToolCall fixture input remain in raw logs.

An older reader fixture acquired startup predecessor rows before source page
acceptance and waited on a retired body. The corrected acquisition waits the
existing source_checkpoint_available; no production exemption was added.

One installed private native-driver burst delivered160 wheel packets/3 reversals
and20 incoming messages over48 rows in order, provider/public inputs zero.
Frame p95 remained92.8ms and maximum225ms: pacing remains unacceptable.
This removes48 widget lifetimes, not a broad performance pass. Raw checks and
negatives: /home/ts/.cache/agent-scratch/dm-native-caption-20261008/.
New-launch delivery uses the original reviewed frontend owner; existing user
windows are not restarted.
4 changes: 4 additions & 0 deletions src/toad/toad.tcss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
margin: 0 1 1 0;
}

WireMarkdownMessage.block {
margin: 1 1 1 0;
}


Markdown {
layout: stream;
Expand Down
23 changes: 19 additions & 4 deletions src/toad/widgets/irc_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
from toad.widgets.inline_message import IRCMessageSource
from toad.widgets.worker_static import WorkerStatic
from contextlib import asynccontextmanager
from toad.widgets.message_divider import MessageDivider, MessageClock
from toad.widgets.message_divider import MessageClock
from textual.content import Content
from toad.widgets.message_notifications import MessageNotifications


Expand Down Expand Up @@ -93,6 +94,19 @@ class WireMarkdownMessage(CoreEventReceiver, ConversationBlock, VerticalGroup):

CACHE_SUBTREE_GEOMETRY = True
DEFAULT_CLASSES = "block"
DEFAULT_CSS = """
WireMarkdownMessage {
margin-top: 1;
border-top: solid $foreground 60%;
border-title-align: center;
border-title-color: $text-muted;
border-title-style: bold;
}
WireMarkdownMessage:ansi {
border-top: solid ansi_bright_black;
border-title-color: ansi_bright_black;
}
"""
BINDINGS = [
("enter", "open_sender", "Open sender"),
("shift+enter", "open_destination", "Open destination"),
Expand All @@ -108,9 +122,10 @@ def __init__(self, message: Message, *, direction: str = "Inbound"):
f"History · {message.source.original_root} · @{message.sender} incarnation {message.sender_created_at}"
if isinstance(message, HistoricalMessage) else direction
)
clock, self.tooltip = MessageClock.recorded(message.timestamp).display()
self.border_title = Content(f"{self.direction} · {clock}")

def compose(self) -> ComposeResult:
yield MessageDivider(self.direction, clock=MessageClock.recorded(self.message.timestamp))
yield from self.compose_body()
self.notifications = MessageNotifications()
yield self.notifications
Expand Down Expand Up @@ -162,7 +177,7 @@ class IRCMessage(WireMarkdownMessage, can_focus=True):
DEFAULT_CLASSES = ""
DEFAULT_CSS = """
IRCMessage {
width: 1fr; height: auto; margin: 0; padding: 0;
width: 1fr; height: auto; margin: 1 0 0 0; padding: 0;
IRCMessageText { width: 1fr; height: auto; text-wrap: wrap; }
}
"""
Expand All @@ -172,7 +187,7 @@ def compose_body(self) -> ComposeResult:
yield self.body

def read_ack_widget(self) -> Widget | None:
"""Only the text block can authorize a read, never its divider."""
"""Only the text block can authorize a read, never its caption."""
body = self.body
return body if body is not None and body.is_attached and body.paint_ready else None

Expand Down
10 changes: 5 additions & 5 deletions tests/divider_unread_paint_pilot.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from runtime_fixture import ToadApp
from toad.widgets.comms_chat import CommsChatView
from toad.widgets.irc_message import IRCMessageText
from toad.widgets.message_divider import MessageDivider
from textual.geometry import Region


async def main() -> None:
Expand Down Expand Up @@ -45,22 +45,22 @@ async def main() -> None:
while not (chat.message_history.reader is not None and not chat.message_history.reader.source.loading):
await pilot.pause(.02)
block = next(widget for message, widget in chat.message_history.rows if message.seq == last.seq)
divider = block.query_one(MessageDivider)
body = block.query_one(IRCMessageText)
await pilot.pause()
caption = Region(block.region.x, block.region.y, block.region.width, 1)
viewport = chat.window.content_region
chat.window.scroll_to(
y=chat.window.scroll_y + divider.region.bottom - viewport.bottom,
y=chat.window.scroll_y + caption.bottom - viewport.bottom,
animate=False, immediate=True,
)
await pilot.pause()
assert divider.region.overlaps(chat.window.content_region)
assert Region(block.region.x, block.region.y, block.region.width, 1).overlaps(chat.window.content_region)
assert not body.region.overlaps(chat.window.content_region)
assert last.seq not in {seq for source, seq in chat.message_history.painted_keys() if not source}

page = await chat.message_history.reader.page(after=last.seq - 1, limit=chat.message_history.reader.source.page_limit)
chat.message_history.channel_receipts[last.seq] = page
mark_visible(chat)
mark_visible(chat.message_history)
await pilot.pause(.2)
assert comms.views.viewer_snapshot(str(root)).channel_unread["#team"] == 1
assert app._exception is None
Expand Down
29 changes: 16 additions & 13 deletions tests/message_dividers_pilot.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
from toad.widgets.irc_message import IRCMessage, WireMarkdownMessage
from toad.widgets.message_divider import MessageDivider
from toad.widgets.tool_call import ToolCall
from toad.acp.status import ToolCallStatus
from acp import schema as protocol
from toad.widgets.user_input import UserInput


Expand Down Expand Up @@ -307,7 +309,7 @@ async def main() -> None:
assert reply is not None
incoming = await native.post(IncomingMessage(IncomingTranscript(sent.body, route=MessageRoute(sent.sender, (sent.target,)), source=sent.reference, timestamp=sent.timestamp)))
thought = await native.post(AgentThought("not a displayed message"))
tool = await native.post(ToolCall({"toolCallId": "tool-one", "title": "Read source"}))
tool = await native.post(ToolCall(ToolCallStatus.from_acp(protocol.ToolCall.model_validate({"toolCallId": "tool-one", "title": "Read source"}, strict=True))))
await pilot.pause()
assert len(user.query(MessageDivider)) == 1
assert "User" in user.query_one(MessageDivider).render().plain
Expand Down Expand Up @@ -350,31 +352,32 @@ async def main() -> None:
wire_block = next(widget for message, widget in chat.message_history.rows
if message.seq == sent.seq)
assert isinstance(wire_block, IRCMessage)
assert len(wire_block.query(MessageDivider)) == 1
expected_time = time.strftime("%H:%M:%S", time.localtime(sent.timestamp))
inbound_divider = wire_block.query_one(MessageDivider)
assert not wire_block.query(MessageDivider)
expected_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(sent.timestamp))
wire_block.scroll_visible(animate=False, immediate=True)
await pilot.pause()
assert expected_time in inbound_divider.render().plain, (
expected_time, inbound_divider.clock, inbound_divider.size.width,
inbound_divider.render().plain,
)
assert "Inbound" in wire_block.query_one(MessageDivider).render().plain
assert expected_time in wire_block.border_title
assert "Inbound" in wire_block.border_title
rows = app.screen._compositor.render_strips()
assert expected_time in rows[wire_block.region.y].text
outgoing_block = next(widget for message, widget in chat.message_history.rows
if message.seq == outbound.seq)
assert "Outbound" in outgoing_block.query_one(MessageDivider).render().plain
assert "Outbound" in outgoing_block.border_title
await chat.message_history.toggle_style()
await pilot.pause()
wire_block = next(widget for message, widget in chat.message_history.rows
if message.seq == sent.seq)
assert isinstance(wire_block, WireMarkdownMessage)
assert len(wire_block.query(MessageDivider)) == 1
assert not wire_block.query(MessageDivider)
assert wire_block.styles.margin.top == 1
assert wire_block.styles.margin.right == 1
assert wire_block.styles.margin.bottom == 1
assert len(wire_block.query(AgentResponse)) == 1
assert len(wire_block.query(AgentResponse).first().query(MessageDivider)) == 0
assert expected_time in wire_block.query_one(MessageDivider).render().plain
assert expected_time in wire_block.border_title
assert app._exception is None
await asyncio.get_running_loop().shutdown_default_executor()
print("timestamped full-width user, inbound, agent and IRC/Markdown dividers; no thought divider")
print("timestamped native dividers and wire row borders; no nested wire or thought divider")


if __name__ == "__main__":
Expand Down
Loading