From 6c4ecbcfd9e1cc3157118b622dca7941187f203d Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 6 Oct 2026 22:12:50 -0400 Subject: [PATCH 1/4] Infer Static layout from native Content measurement --- .../before.json | 709 ++++++++++++++++++ src/textual/widgets/_static.py | 19 +- 2 files changed, 726 insertions(+), 2 deletions(-) create mode 100644 evidence/static-content-layout-20261006/before.json diff --git a/evidence/static-content-layout-20261006/before.json b/evidence/static-content-layout-20261006/before.json new file mode 100644 index 0000000000..654c350670 --- /dev/null +++ b/evidence/static-content-layout-20261006/before.json @@ -0,0 +1,709 @@ +{ + "native": { + "modules": 249, + "omissions": [], + "declarations": [ + [ + "src/textual/_styles_cache.py", + 164, + "render" + ], + [ + "src/textual/app.py", + 2420, + "render" + ], + [ + "src/textual/app.py", + 3273, + "render" + ], + [ + "src/textual/canvas.py", + 44, + "render" + ], + [ + "src/textual/canvas.py", + 62, + "render" + ], + [ + "src/textual/canvas.py", + 99, + "render" + ], + [ + "src/textual/canvas.py", + 136, + "render" + ], + [ + "src/textual/canvas.py", + 210, + "render" + ], + [ + "src/textual/command.py", + 509, + "render" + ], + [ + "src/textual/content.py", + 134, + "Content" + ], + [ + "src/textual/content.py", + 1332, + "render" + ], + [ + "src/textual/css/stylesheet.py", + 928, + "update" + ], + [ + "src/textual/demo/home.py", + 222, + "Content" + ], + [ + "src/textual/layout.py", + 280, + "get_content_width" + ], + [ + "src/textual/layout.py", + 302, + "get_content_height" + ], + [ + "src/textual/layouts/stream.py", + 124, + "get_content_width" + ], + [ + "src/textual/layouts/stream.py", + 138, + "get_content_height" + ], + [ + "src/textual/renderables/digits.py", + 200, + "render" + ], + [ + "src/textual/renderables/gradient.py", + 163, + "render" + ], + [ + "src/textual/screen.py", + 616, + "render" + ], + [ + "src/textual/scroll_view.py", + 61, + "get_content_width" + ], + [ + "src/textual/scroll_view.py", + 73, + "get_content_height" + ], + [ + "src/textual/scroll_view.py", + 110, + "render" + ], + [ + "src/textual/scrollbar.py", + 281, + "render" + ], + [ + "src/textual/scrollbar.py", + 407, + "render" + ], + [ + "src/textual/strip.py", + 732, + "render" + ], + [ + "src/textual/visual.py", + 123, + "Visual" + ], + [ + "src/textual/widget.py", + 319, + "_render_styles_sensitive" + ], + [ + "src/textual/widget.py", + 2091, + "get_content_width" + ], + [ + "src/textual/widget.py", + 2123, + "get_content_height" + ], + [ + "src/textual/widget.py", + 4671, + "render" + ], + [ + "src/textual/widget.py", + 4699, + "_render" + ], + [ + "src/textual/widgets/_button.py", + 376, + "get_content_width" + ], + [ + "src/textual/widgets/_button.py", + 407, + "render" + ], + [ + "src/textual/widgets/_digits.py", + 60, + "update" + ], + [ + "src/textual/widgets/_digits.py", + 77, + "render" + ], + [ + "src/textual/widgets/_digits.py", + 89, + "get_content_width" + ], + [ + "src/textual/widgets/_digits.py", + 101, + "get_content_height" + ], + [ + "src/textual/widgets/_footer.py", + 127, + "render" + ], + [ + "src/textual/widgets/_header.py", + 48, + "render" + ], + [ + "src/textual/widgets/_header.py", + 68, + "render" + ], + [ + "src/textual/widgets/_header.py", + 94, + "render" + ], + [ + "src/textual/widgets/_input.py", + 701, + "get_content_width" + ], + [ + "src/textual/widgets/_input.py", + 705, + "get_content_height" + ], + [ + "src/textual/widgets/_key_panel.py", + 113, + "render" + ], + [ + "src/textual/widgets/_loading_indicator.py", + 67, + "render" + ], + [ + "src/textual/widgets/_markdown.py", + 847, + "render" + ], + [ + "src/textual/widgets/_markdown.py", + 1416, + "update" + ], + [ + "src/textual/widgets/_option_list.py", + 840, + "get_content_width" + ], + [ + "src/textual/widgets/_option_list.py", + 859, + "get_content_height" + ], + [ + "src/textual/widgets/_placeholder.py", + 146, + "render" + ], + [ + "src/textual/widgets/_pretty.py", + 45, + "render" + ], + [ + "src/textual/widgets/_pretty.py", + 48, + "update" + ], + [ + "src/textual/widgets/_progress_bar.py", + 99, + "render" + ], + [ + "src/textual/widgets/_progress_bar.py", + 165, + "render" + ], + [ + "src/textual/widgets/_progress_bar.py", + 181, + "render" + ], + [ + "src/textual/widgets/_progress_bar.py", + 342, + "update" + ], + [ + "src/textual/widgets/_rich_log.py", + 141, + "get_content_width" + ], + [ + "src/textual/widgets/_rule.py", + 162, + "render" + ], + [ + "src/textual/widgets/_rule.py", + 200, + "get_content_width" + ], + [ + "src/textual/widgets/_rule.py", + 205, + "get_content_height" + ], + [ + "src/textual/widgets/_select.py", + 248, + "update" + ], + [ + "src/textual/widgets/_sparkline.py", + 88, + "render" + ], + [ + "src/textual/widgets/_static.py", + 14, + "Static" + ], + [ + "src/textual/widgets/_static.py", + 55, + "visual" + ], + [ + "src/textual/widgets/_static.py", + 68, + "content" + ], + [ + "src/textual/widgets/_static.py", + 73, + "content" + ], + [ + "src/textual/widgets/_static.py", + 79, + "render" + ], + [ + "src/textual/widgets/_static.py", + 87, + "_render_styles_sensitive" + ], + [ + "src/textual/widgets/_static.py", + 100, + "update" + ], + [ + "src/textual/widgets/_switch.py", + 186, + "render" + ], + [ + "src/textual/widgets/_switch.py", + 196, + "get_content_width" + ], + [ + "src/textual/widgets/_switch.py", + 199, + "get_content_height" + ], + [ + "src/textual/widgets/_tabs.py", + 80, + "render" + ], + [ + "src/textual/widgets/_tabs.py", + 195, + "update" + ], + [ + "src/textual/widgets/_toast.py", + 106, + "render" + ], + [ + "src/textual/widgets/_toggle_button.py", + 215, + "render" + ], + [ + "src/textual/widgets/_toggle_button.py", + 231, + "get_content_width" + ], + [ + "src/textual/widgets/_toggle_button.py", + 238, + "get_content_height" + ], + [ + "src/textual/worker.py", + 267, + "update" + ] + ] + }, + "toad": { + "modules": 288, + "omissions": [], + "declarations": [ + [ + "src/toad/about.py", + 79, + "render" + ], + [ + "src/toad/acp/log_records.py", + 215, + "render" + ], + [ + "src/toad/acp/log_records.py", + 223, + "render" + ], + [ + "src/toad/acp/log_records.py", + 232, + "render" + ], + [ + "src/toad/acp/log_records.py", + 237, + "render" + ], + [ + "src/toad/acp/tool_calls.py", + 31, + "update" + ], + [ + "src/toad/ansi/_ansi.py", + 727, + "update" + ], + [ + "src/toad/ansi/_ansi.py", + 761, + "update" + ], + [ + "src/toad/ansi/_ansi.py", + 767, + "update" + ], + [ + "src/toad/ansi/_ansi.py", + 773, + "update" + ], + [ + "src/toad/ansi/_ansi.py", + 779, + "update" + ], + [ + "src/toad/ansi/_ansi.py", + 1264, + "update" + ], + [ + "src/toad/conversation_markdown.py", + 89, + "content" + ], + [ + "src/toad/conversation_markdown.py", + 119, + "content" + ], + [ + "src/toad/conversation_markdown.py", + 132, + "content" + ], + [ + "src/toad/conversation_markdown.py", + 148, + "render" + ], + [ + "src/toad/coordination_context_format.py", + 32, + "_render" + ], + [ + "src/toad/db.py", + 157, + "update" + ], + [ + "src/toad/db.py", + 170, + "update" + ], + [ + "src/toad/mcp_declarations.py", + 125, + "render" + ], + [ + "src/toad/mcp_declarations.py", + 153, + "render" + ], + [ + "src/toad/mcp_declarations.py", + 206, + "render" + ], + [ + "src/toad/sidebar_preparation.py", + 61, + "content" + ], + [ + "src/toad/sidebar_preparation.py", + 126, + "content" + ], + [ + "src/toad/slash_command.py", + 37, + "content" + ], + [ + "src/toad/terminal_attention.py", + 132, + "update" + ], + [ + "src/toad/tool_output.py", + 282, + "content" + ], + [ + "src/toad/visuals/columns.py", + 153, + "render" + ], + [ + "src/toad/visuals/columns.py", + 169, + "_render" + ], + [ + "src/toad/widgets/comms_sidebar.py", + 166, + "update" + ], + [ + "src/toad/widgets/condensed_path.py", + 109, + "render" + ], + [ + "src/toad/widgets/condensed_path.py", + 113, + "get_content_width" + ], + [ + "src/toad/widgets/conversation.py", + 198, + "render" + ], + [ + "src/toad/widgets/conversation.py", + 245, + "render" + ], + [ + "src/toad/widgets/message_divider.py", + 74, + "render" + ], + [ + "src/toad/widgets/message_divider.py", + 88, + "get_content_height" + ], + [ + "src/toad/widgets/prepared_markdown.py", + 71, + "update" + ], + [ + "src/toad/widgets/prepared_markdown.py", + 192, + "get_content_height" + ], + [ + "src/toad/widgets/question.py", + 194, + "update" + ], + [ + "src/toad/widgets/session_tabs.py", + 114, + "render" + ], + [ + "src/toad/widgets/session_thread_panels.py", + 78, + "update" + ], + [ + "src/toad/widgets/side_bar.py", + 407, + "render" + ], + [ + "src/toad/widgets/side_bar.py", + 535, + "render" + ], + [ + "src/toad/widgets/side_bar.py", + 915, + "render" + ], + [ + "src/toad/widgets/streaming_markdown.py", + 79, + "update" + ], + [ + "src/toad/widgets/strike_text.py", + 43, + "render" + ], + [ + "src/toad/widgets/thread_comms.py", + 179, + "update" + ], + [ + "src/toad/widgets/throbber.py", + 135, + "render" + ], + [ + "src/toad/widgets/throbber.py", + 138, + "_render_styles_sensitive" + ], + [ + "src/toad/widgets/viewport_body.py", + 114, + "render" + ], + [ + "src/toad/widgets/viewport_body.py", + 234, + "render" + ], + [ + "src/toad/widgets/viewport_body.py", + 271, + "render" + ], + [ + "src/toad/widgets/viewport_body.py", + 339, + "render" + ], + [ + "src/toad/widgets/viewport_body.py", + 465, + "render" + ], + [ + "src/toad/widgets/viewport_body.py", + 690, + "get_content_height" + ], + [ + "src/toad/widgets/viewport_body.py", + 700, + "get_content_width" + ], + [ + "src/toad/widgets/worker_static.py", + 91, + "update" + ], + [ + "src/toad/widgets/worker_static.py", + 181, + "get_content_width" + ], + [ + "src/toad/widgets/worker_static.py", + 185, + "get_content_height" + ] + ] + } +} diff --git a/src/textual/widgets/_static.py b/src/textual/widgets/_static.py index 0c792d492c..967fb74719 100644 --- a/src/textual/widgets/_static.py +++ b/src/textual/widgets/_static.py @@ -94,17 +94,32 @@ def _render_styles_sensitive(self) -> bool: return ( type(self).render is not Static.render or type(self).visual is not Static.visual + or type(self)._render is not Widget._render + or type(self).get_content_width is not Widget.get_content_width + or type(self).get_content_height is not Widget.get_content_height or type(self.__dict__.get("_Static__visual")) is not Content ) - def update(self, content: VisualType = "", *, layout: bool = True) -> None: + def update(self, content: VisualType = "", *, layout: bool | None = None) -> None: """Update the widget's content area with a string, a Visual (such as [Content][textual.content.Content]), or a [Rich renderable](https://rich.readthedocs.io/en/latest/protocol.html). Args: content: New content. - layout: Also perform a layout operation (set to `False` if you are certain the size won't change). + layout: Force a layout operation with `True`, or skip it with `False`. + By default, unchanged native Content text only repaints; custom + rendering and measurement retain layout. """ + previous_visual = self.__visual self.__content = content self.__visual = visualize(self, content, markup=self._render_markup) + if layout is None: + layout = not ( + not self._render_styles_sensitive() + and type(previous_visual) is Content + and type(self.__visual) is Content + and self._native_box_measurement + and self._native_measurement_layout_hooks + and previous_visual == self.__visual + ) self.refresh(layout=layout) From 258a6cef1fd5c0e75602d4269d6969f648b46c4a Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 6 Oct 2026 22:16:01 -0400 Subject: [PATCH 2/4] Verify Static paint updates in native and loaded Toad Apps --- src/textual/widgets/_static.py | 1 + tests/participant_content_update_pilot.py | 80 +++++++++++++++++ tests/test_static.py | 102 ++++++++++++++++++++++ 3 files changed, 183 insertions(+) create mode 100644 tests/participant_content_update_pilot.py diff --git a/src/textual/widgets/_static.py b/src/textual/widgets/_static.py index 967fb74719..245197dd66 100644 --- a/src/textual/widgets/_static.py +++ b/src/textual/widgets/_static.py @@ -120,6 +120,7 @@ def update(self, content: VisualType = "", *, layout: bool | None = None) -> Non and type(self.__visual) is Content and self._native_box_measurement and self._native_measurement_layout_hooks + and not self.is_container and previous_visual == self.__visual ) self.refresh(layout=layout) diff --git a/tests/participant_content_update_pilot.py b/tests/participant_content_update_pilot.py new file mode 100644 index 0000000000..cfb423501d --- /dev/null +++ b/tests/participant_content_update_pilot.py @@ -0,0 +1,80 @@ +"""Exercise participant paint metadata in the existing loaded source Toad App. + +No participant/backend input is submitted. This measures native layout admission, +not installed terminal latency or provider delivery. +""" +import asyncio +import cProfile +import json +import os +from pathlib import Path +import pstats +import tempfile +from time import perf_counter + +from agent_comms.comms import wire +from agent_comms.threads import Thread +from sidebar_collapse_latency_pilot import FrameApp +from textual.content import Content +from textual.style import Style +from toad.widgets.channel_participants import ChannelParticipants +from toad.widgets.prompt import Prompt + + +async def main(output: Path): + output.mkdir(parents=True, exist_ok=True) + with tempfile.TemporaryDirectory(prefix="participant-content-", dir=output) as folder: + root = Path(folder) + os.environ.update(XDG_CONFIG_HOME=str(root / "config"), + XDG_STATE_HOME=str(root / "state"), + XDG_DATA_HOME=str(root / "data"), AGENT_COMMS_ROOT=str(root / "wire")) + comms = wire(root / "wire") + comms.registry.declare(Thread(root.name, frozenset(), str(root))) + for index in range(24): + comms.registry.declare(Thread(f"worker-{index:02}", frozenset(), str(root))) + app = FrameApp(project_dir=str(root)) + async with app.run_test(size=(120, 42)) as pilot: + for _ in range(9): + await app.session_navigation.new(app.session_navigation.default_source) + participants = ChannelParticipants() + await app.selected_session.query_one(Prompt).mount( + participants, before="#prompt-container" + ) + names = participants.names + names.update(Content("Active: worker-00")) + await pilot.pause() + revision = names._geometry_revision + region = names.region + replacement = Content.styled("Active: worker-00", "$warning").stylize( + Style.from_meta({"@click": ("open_thread", ("worker-00",))}) + ) + profile = cProfile.Profile() + start = perf_counter() + profile.enable() + names.update(replacement) + profile.disable() + elapsed = perf_counter() - start + assert names._geometry_revision == revision + assert not names._layout_required + await pilot.pause() + assert names.region == region and names.region.area + assert names.visual.is_same(replacement) + style = app.screen._compositor.get_style_at(region.x, region.y) + assert style.meta["@click"] == ("open_thread", ("worker-00",)) + assert app._exception is None + profile.dump_stats(str(output / "update.pstats")) + calls = {name: sum(values[1] for (_, _, function), values in pstats.Stats(profile).stats.items() + if function == name) + for name in ("_invalidate_layout", "_request_layout", "_arrange_root")} + result = dict(boundary="source Toad ParticipantNames Static.update paint-only admission", + update_seconds=elapsed, calls=calls, tabs=10, + active_widgets=len(app.screen.query("*")), + geometry_preserved=True, painted_action_metadata=True, + inputs_submitted=0, provider_calls=0) + print(json.dumps(result, indent=2), flush=True) + (output / "result.json").write_text(json.dumps(result, indent=2) + "\n") + + +if __name__ == "__main__": + import sys + asyncio.run(main(Path(sys.argv[1]))) diff --git a/tests/test_static.py b/tests/test_static.py index fb69a7ea43..f5248bccaf 100644 --- a/tests/test_static.py +++ b/tests/test_static.py @@ -1,5 +1,7 @@ from textual.content import Content from textual.widgets import Static +from textual.app import App, ComposeResult +from textual.style import Style async def test_content_property(): @@ -16,3 +18,103 @@ async def test_content_property(): assert isinstance(static.content, str) assert static.visual == "Hello" assert isinstance(static.visual, Content) + + +async def test_content_update_preserves_geometry_and_repaints_action_metadata(): + class ContentApp(App): + accepted = False + + def compose(self) -> ComposeResult: + yield Static(Content.styled("same names", "red"), id="names") + + def action_accept(self): + self.accepted = True + + app = ContentApp() + async with app.run_test(size=(40, 10)) as pilot: + await pilot.pause() + names = app.query_one("#names", Static) + names.auto_links = False + revision = names._geometry_revision + region = names.region + replacement = Content.styled("same names", "green").stylize( + Style.from_meta({"@click": "app.accept"}) + ) + names.update(replacement) + assert not names._layout_required + assert names._geometry_revision == revision + await pilot.pause() + assert names.region == region + assert names.visual.is_same(replacement) + style = app.screen._compositor.get_style_at(region.x, region.y) + assert style.meta["@click"] == "app.accept" + assert style.color.get_truecolor() == (0, 128, 0) + await pilot.click(names, offset=(1, 0)) + assert app.accepted + + names.update(replacement, layout=True) + assert names._geometry_revision > revision + await pilot.pause() + revision = names._geometry_revision + names.update("new\nlines") + assert names._geometry_revision > revision + await pilot.pause() + assert names.region.height == 2 + revision = names._geometry_revision + names.update("explicit skip", layout=False) + assert names._geometry_revision == revision + + +async def test_custom_measurement_and_setter_keep_layout_contract(): + class MeasuredStatic(Static): + def get_content_height(self, container, viewport, width): + return len(self.visual.spans) + 1 + + class RenderedStatic(Static): + def render(self): + return super().render() + + class VisualStatic(Static): + @property + def visual(self): + return super().visual + + class CustomContent(Content): + pass + + class SetterStatic(Static): + def update(self, content="", *, layout=None): + raise AssertionError("content setter must not invoke update override") + + for widget in ( + MeasuredStatic("same"), RenderedStatic("same"), VisualStatic("same"), + Static(CustomContent("same")), + ): + assert widget.visual == "same" + revision = widget._geometry_revision + widget.update(Content.styled("same", "red")) + assert widget._geometry_revision > revision + widget = SetterStatic("same") + assert widget.visual == "same" + revision = widget._geometry_revision + widget.content = "same" + assert widget._geometry_revision > revision + + +async def test_container_update_keeps_layout(): + class ContainerStatic(Static): + def compose(self): + yield Static("child") + + class ContainerApp(App): + def compose(self): + yield ContainerStatic("same", id="parent") + + app = ContainerApp() + async with app.run_test(size=(40, 10)) as pilot: + await pilot.pause() + parent = app.query_one("#parent", ContainerStatic) + assert parent.is_container + revision = parent._geometry_revision + parent.update(Content.styled("same", "red")) + assert parent._geometry_revision > revision From ec01115552e9f5e7bd3a225256ef491a2566586b Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 6 Oct 2026 22:16:47 -0400 Subject: [PATCH 3/4] Share native content measurement without conflating style override contracts --- src/textual/widgets/_static.py | 25 ++++++++++++++----------- tests/test_static.py | 4 ++++ 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/textual/widgets/_static.py b/src/textual/widgets/_static.py index 245197dd66..f2b0a2b644 100644 --- a/src/textual/widgets/_static.py +++ b/src/textual/widgets/_static.py @@ -91,13 +91,20 @@ def _render_styles_sensitive(self) -> bool: content from paint rules. Keep their geometry lifetime conservative. Do not create or re-render a visual merely to classify a rule mutation. """ + return not self._has_native_content_measurement() + + def _has_native_content_measurement(self) -> bool: + """Whether Content's text alone supplies this leaf's intrinsic size.""" return ( - type(self).render is not Static.render - or type(self).visual is not Static.visual - or type(self)._render is not Widget._render - or type(self).get_content_width is not Widget.get_content_width - or type(self).get_content_height is not Widget.get_content_height - or type(self.__dict__.get("_Static__visual")) is not Content + type(self).render is Static.render + and type(self).visual is Static.visual + and type(self)._render is Widget._render + and type(self).get_content_width is Widget.get_content_width + and type(self).get_content_height is Widget.get_content_height + and type(self.__visual) is Content + and self._native_box_measurement + and self._native_measurement_layout_hooks + and not self.is_container ) def update(self, content: VisualType = "", *, layout: bool | None = None) -> None: @@ -115,12 +122,8 @@ def update(self, content: VisualType = "", *, layout: bool | None = None) -> Non self.__visual = visualize(self, content, markup=self._render_markup) if layout is None: layout = not ( - not self._render_styles_sensitive() + self._has_native_content_measurement() and type(previous_visual) is Content - and type(self.__visual) is Content - and self._native_box_measurement - and self._native_measurement_layout_hooks - and not self.is_container and previous_visual == self.__visual ) self.refresh(layout=layout) diff --git a/tests/test_static.py b/tests/test_static.py index f5248bccaf..a9fa366942 100644 --- a/tests/test_static.py +++ b/tests/test_static.py @@ -74,6 +74,10 @@ class RenderedStatic(Static): def render(self): return super().render() + def _render_styles_sensitive(self): + # Style independence does not declare content measurement semantics. + return False + class VisualStatic(Static): @property def visual(self): From 93ae314127b9179c1e218357385affbf5db958ba Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 6 Oct 2026 22:18:01 -0400 Subject: [PATCH 4/4] Record Static owner and scoped App acceptance --- .../static-content-layout-20261006/README.md | 42 + .../static-content-layout-20261006/after.json | 1890 +++++++++++++++++ .../app-result.json | 15 + .../controls.log | 2 + 4 files changed, 1949 insertions(+) create mode 100644 evidence/static-content-layout-20261006/README.md create mode 100644 evidence/static-content-layout-20261006/after.json create mode 100644 evidence/static-content-layout-20261006/app-result.json create mode 100644 evidence/static-content-layout-20261006/controls.log diff --git a/evidence/static-content-layout-20261006/README.md b/evidence/static-content-layout-20261006/README.md new file mode 100644 index 0000000000..9721bddbb2 --- /dev/null +++ b/evidence/static-content-layout-20261006/README.md @@ -0,0 +1,42 @@ +# Static Content layout admission + +Static.update previously requested layout even for equal plain text with changed +colors or action metadata. Native Content width and wrapping use text and current +rules, not spans. Static now owns the native measurement contract once, shared +by its style-sensitivity and update consumers. Style independence does not itself +certify native Content measurement. + +Default update infers layout. Exact native Content on an original measured leaf +with equal text only repaints. Different text, unknown visual/render/measurement, +custom box/layout hooks, containers, and first unresolved visual retain layout. +Explicit True/False retain caller authority. The content setter remains original +force-layout without dispatching through update overrides. Native Tab Relabelled +notification and Toad WorkerStatic preparation overrides are unchanged. + +Four focused native App controls pass (controls.log): painted spans and actual +click delivery without geometry invalidation; changed text and explicit layout; +custom render/measurement/visual and setter dispatch; container layout. +An initial color assertion failed because original @click link styling replaces +foreground (2 passed, 1 failed: expected green, observed #e0e0e0). The corrected +control disables that independent link-color policy while testing span color +and real click delivery. No production color fix. + +The loaded source Toad App passed with 546 widgets and ten tabs. Original +ParticipantNames retained geometry and painted action metadata. The profiled +synchronous update issued zero layout requests/invalidation or arrangement. +See app-result.json. This App exercised the same measurement predicate before +its factoring into the shared Static method; final native controls cover that +factor and the distinct style-override contract. No whole-frame gain or +installed/live acceptance claimed. Zero backend inputs or provider calls. + +Raw App profile/stdout/stderr and the first setup refusal are retained at +/home/ts/.cache/agent-scratch/static-content-layout-20261006/. app01 refused before +measurement because Conversation.contents admits nominal transcript blocks. +app02 used the original Prompt mount owner; its stderr is empty. Both runs are +terminal and temporary private registries were removed by their context managers. +No package or installed pin changed. + +before.json and after.json use the existing refactor-audit Package parser: +249 native and 288 Toad modules, no omissions. Lexical update calls include +unrelated receivers; dynamic override resolution is not claimed. Unknown native +render/measurement declarations retain layout. diff --git a/evidence/static-content-layout-20261006/after.json b/evidence/static-content-layout-20261006/after.json new file mode 100644 index 0000000000..2f9c1a95b6 --- /dev/null +++ b/evidence/static-content-layout-20261006/after.json @@ -0,0 +1,1890 @@ +{ + "native": { + "head": "ec01115552e9f5e7bd3a225256ef491a2566586b", + "modules": 249, + "omissions": [], + "declarations": [ + [ + "src/textual/_styles_cache.py", + 164, + "render" + ], + [ + "src/textual/app.py", + 2420, + "render" + ], + [ + "src/textual/app.py", + 3273, + "render" + ], + [ + "src/textual/canvas.py", + 44, + "render" + ], + [ + "src/textual/canvas.py", + 62, + "render" + ], + [ + "src/textual/canvas.py", + 99, + "render" + ], + [ + "src/textual/canvas.py", + 136, + "render" + ], + [ + "src/textual/canvas.py", + 210, + "render" + ], + [ + "src/textual/command.py", + 509, + "render" + ], + [ + "src/textual/content.py", + 1332, + "render" + ], + [ + "src/textual/css/stylesheet.py", + 928, + "update" + ], + [ + "src/textual/layout.py", + 280, + "get_content_width" + ], + [ + "src/textual/layout.py", + 302, + "get_content_height" + ], + [ + "src/textual/layouts/stream.py", + 124, + "get_content_width" + ], + [ + "src/textual/layouts/stream.py", + 138, + "get_content_height" + ], + [ + "src/textual/renderables/digits.py", + 200, + "render" + ], + [ + "src/textual/renderables/gradient.py", + 163, + "render" + ], + [ + "src/textual/screen.py", + 616, + "render" + ], + [ + "src/textual/scroll_view.py", + 61, + "get_content_width" + ], + [ + "src/textual/scroll_view.py", + 73, + "get_content_height" + ], + [ + "src/textual/scroll_view.py", + 110, + "render" + ], + [ + "src/textual/scrollbar.py", + 281, + "render" + ], + [ + "src/textual/scrollbar.py", + 407, + "render" + ], + [ + "src/textual/strip.py", + 732, + "render" + ], + [ + "src/textual/widget.py", + 319, + "_render_styles_sensitive" + ], + [ + "src/textual/widget.py", + 2091, + "get_content_width" + ], + [ + "src/textual/widget.py", + 2123, + "get_content_height" + ], + [ + "src/textual/widget.py", + 4671, + "render" + ], + [ + "src/textual/widget.py", + 4699, + "_render" + ], + [ + "src/textual/widgets/_button.py", + 376, + "get_content_width" + ], + [ + "src/textual/widgets/_button.py", + 407, + "render" + ], + [ + "src/textual/widgets/_digits.py", + 60, + "update" + ], + [ + "src/textual/widgets/_digits.py", + 77, + "render" + ], + [ + "src/textual/widgets/_digits.py", + 89, + "get_content_width" + ], + [ + "src/textual/widgets/_digits.py", + 101, + "get_content_height" + ], + [ + "src/textual/widgets/_footer.py", + 127, + "render" + ], + [ + "src/textual/widgets/_header.py", + 48, + "render" + ], + [ + "src/textual/widgets/_header.py", + 68, + "render" + ], + [ + "src/textual/widgets/_header.py", + 94, + "render" + ], + [ + "src/textual/widgets/_input.py", + 701, + "get_content_width" + ], + [ + "src/textual/widgets/_input.py", + 705, + "get_content_height" + ], + [ + "src/textual/widgets/_key_panel.py", + 113, + "render" + ], + [ + "src/textual/widgets/_loading_indicator.py", + 67, + "render" + ], + [ + "src/textual/widgets/_markdown.py", + 847, + "render" + ], + [ + "src/textual/widgets/_markdown.py", + 1416, + "update" + ], + [ + "src/textual/widgets/_option_list.py", + 840, + "get_content_width" + ], + [ + "src/textual/widgets/_option_list.py", + 859, + "get_content_height" + ], + [ + "src/textual/widgets/_placeholder.py", + 146, + "render" + ], + [ + "src/textual/widgets/_pretty.py", + 45, + "render" + ], + [ + "src/textual/widgets/_pretty.py", + 48, + "update" + ], + [ + "src/textual/widgets/_progress_bar.py", + 99, + "render" + ], + [ + "src/textual/widgets/_progress_bar.py", + 165, + "render" + ], + [ + "src/textual/widgets/_progress_bar.py", + 181, + "render" + ], + [ + "src/textual/widgets/_progress_bar.py", + 342, + "update" + ], + [ + "src/textual/widgets/_rich_log.py", + 141, + "get_content_width" + ], + [ + "src/textual/widgets/_rule.py", + 162, + "render" + ], + [ + "src/textual/widgets/_rule.py", + 200, + "get_content_width" + ], + [ + "src/textual/widgets/_rule.py", + 205, + "get_content_height" + ], + [ + "src/textual/widgets/_select.py", + 248, + "update" + ], + [ + "src/textual/widgets/_sparkline.py", + 88, + "render" + ], + [ + "src/textual/widgets/_static.py", + 55, + "visual" + ], + [ + "src/textual/widgets/_static.py", + 68, + "content" + ], + [ + "src/textual/widgets/_static.py", + 73, + "content" + ], + [ + "src/textual/widgets/_static.py", + 79, + "render" + ], + [ + "src/textual/widgets/_static.py", + 87, + "_render_styles_sensitive" + ], + [ + "src/textual/widgets/_static.py", + 96, + "_has_native_content_measurement" + ], + [ + "src/textual/widgets/_static.py", + 110, + "update" + ], + [ + "src/textual/widgets/_switch.py", + 186, + "render" + ], + [ + "src/textual/widgets/_switch.py", + 196, + "get_content_width" + ], + [ + "src/textual/widgets/_switch.py", + 199, + "get_content_height" + ], + [ + "src/textual/widgets/_tabs.py", + 80, + "render" + ], + [ + "src/textual/widgets/_tabs.py", + 195, + "update" + ], + [ + "src/textual/widgets/_toast.py", + 106, + "render" + ], + [ + "src/textual/widgets/_toggle_button.py", + 215, + "render" + ], + [ + "src/textual/widgets/_toggle_button.py", + 231, + "get_content_width" + ], + [ + "src/textual/widgets/_toggle_button.py", + 238, + "get_content_height" + ], + [ + "src/textual/worker.py", + 267, + "update" + ] + ], + "update_calls": [ + [ + "src/textual/_compositor.py", + 241, + "widgets.update", + [] + ], + [ + "src/textual/_compositor.py", + 242, + "invisible_widgets.update", + [] + ], + [ + "src/textual/_compositor.py", + 1953, + "self._dirty_regions.update", + [] + ], + [ + "src/textual/_compositor.py", + 672, + "active.update", + [] + ], + [ + "src/textual/_compositor.py", + 878, + "mutation_paths.update", + [] + ], + [ + "src/textual/_compositor.py", + 1222, + "map.update", + [] + ], + [ + "src/textual/_compositor.py", + 1223, + "widgets.update", + [] + ], + [ + "src/textual/_compositor.py", + 1147, + "widgets.update", + [] + ], + [ + "src/textual/_compositor.py", + 1609, + "placed.update", + [] + ], + [ + "src/textual/_compositor.py", + 927, + "retained_paths.update", + [] + ], + [ + "src/textual/_compositor.py", + 991, + "widgets.update", + [] + ], + [ + "src/textual/_compositor.py", + 1021, + "screen_coordinates.update", + [] + ], + [ + "src/textual/_doc.py", + 110, + "hash.update", + [] + ], + [ + "src/textual/_doc.py", + 109, + "hash.update", + [] + ], + [ + "src/textual/_markup_playground.py", + 109, + "results.update", + [] + ], + [ + "src/textual/_markup_playground.py", + 110, + "spans.update", + [] + ], + [ + "src/textual/_markup_playground.py", + 114, + "results.update", + [] + ], + [ + "src/textual/_markup_playground.py", + 115, + "spans.update", + [] + ], + [ + "src/textual/_on.py", + 63, + "selectors.update", + [] + ], + [ + "src/textual/_styles_cache.py", + 78, + "self._dirty_lines.update", + [] + ], + [ + "src/textual/app.py", + 760, + "self._installed_screens.update", + [ + null + ] + ], + [ + "src/textual/app.py", + 3847, + "stylesheet.update", + [ + "animate" + ] + ], + [ + "src/textual/app.py", + 2860, + "self.stylesheet.update", + [] + ], + [ + "src/textual/app.py", + 2416, + "self.stylesheet.update", + [] + ], + [ + "src/textual/app.py", + 3859, + "stylesheet.update", + [ + "animate" + ] + ], + [ + "src/textual/app.py", + 2418, + "self.stylesheet.update", + [] + ], + [ + "src/textual/await_remove.py", + 51, + "pruning_nodes.update", + [] + ], + [ + "src/textual/binding.py", + 332, + "self.key_to_bindings.update", + [] + ], + [ + "src/textual/css/_styles_builder.py", + 283, + "self.styles.important.update", + [] + ], + [ + "src/textual/css/parse.py", + 474, + "reference_tokens.update", + [] + ], + [ + "src/textual/css/stylesheet.py", + 492, + "self._invalid_css.update", + [] + ], + [ + "src/textual/css/stylesheet.py", + 992, + "scope_names.setdefault(root, set()).update", + [] + ], + [ + "src/textual/demo/game.py", + 395, + "self.query_one(Digits).update", + [] + ], + [ + "src/textual/design.py", + 245, + "colors.update", + [] + ], + [ + "src/textual/dom.py", + 253, + "self._classes.update", + [] + ], + [ + "src/textual/dom.py", + 1938, + "self._classes.update", + [] + ], + [ + "src/textual/dom.py", + 647, + "reactives.update", + [] + ], + [ + "src/textual/dom.py", + 841, + "component_classes.update", + [] + ], + [ + "src/textual/file_monitor.py", + 56, + "self._paths.update", + [] + ], + [ + "src/textual/message_pump.py", + 374, + "self._disabled_messages.update", + [] + ], + [ + "src/textual/message_pump.py", + 561, + "callback_message._prevent.update", + [] + ], + [ + "src/textual/message_pump.py", + 990, + "message._prevent.update", + [] + ], + [ + "src/textual/pad.py", + 50, + "options.update", + [ + "width", + "height" + ] + ], + [ + "src/textual/screen.py", + 1846, + "tooltip.update", + [] + ], + [ + "src/textual/visual.py", + 340, + "app.console_options.update", + [ + "highlight", + "width", + "height" + ] + ], + [ + "src/textual/visual.py", + 317, + "console_options.update_width(width).update", + [ + "highlight" + ] + ], + [ + "src/textual/widget.py", + 1794, + "nodes.update", + [] + ], + [ + "src/textual/widget.py", + 2906, + "self._dirty_regions.update", + [] + ], + [ + "src/textual/widget.py", + 2907, + "self._repaint_regions.update", + [] + ], + [ + "src/textual/widgets/_collapsible.py", + 88, + "self.update", + [] + ], + [ + "src/textual/widgets/_collapsible.py", + 90, + "self.update", + [] + ], + [ + "src/textual/widgets/_data_table.py", + 2174, + "options.update", + [ + "no_wrap" + ] + ], + [ + "src/textual/widgets/_header.py", + 221, + "self.query_one(HeaderTitle).update", + [] + ], + [ + "src/textual/widgets/_help_panel.py", + 107, + "self.query_one(Markdown).update", + [] + ], + [ + "src/textual/widgets/_link.py", + 64, + "self.update", + [] + ], + [ + "src/textual/widgets/_markdown.py", + 280, + "self.update", + [] + ], + [ + "src/textual/widgets/_markdown.py", + 130, + "document.update", + [] + ], + [ + "src/textual/widgets/_markdown.py", + 710, + "child.update", + [ + "layout" + ] + ], + [ + "src/textual/widgets/_markdown.py", + 1173, + "self.update", + [] + ], + [ + "src/textual/widgets/_markdown.py", + 1717, + "self.document.update", + [] + ], + [ + "src/textual/widgets/_markdown.py", + 981, + "label.update", + [ + "layout" + ] + ], + [ + "src/textual/widgets/_progress_bar.py", + 288, + "self.update", + [] + ], + [ + "src/textual/widgets/_progress_bar.py", + 323, + "self.update", + [ + "progress" + ] + ], + [ + "src/textual/widgets/_progress_bar.py", + 327, + "self.update", + [ + "total" + ] + ], + [ + "src/textual/widgets/_progress_bar.py", + 340, + "self.update", + [ + "advance" + ] + ], + [ + "src/textual/widgets/_rich_log.py", + 222, + "render_options.update", + [ + "overflow", + "no_wrap" + ] + ], + [ + "src/textual/widgets/_select.py", + 256, + "self.query_one('#label', Static).update", + [] + ], + [ + "src/textual/widgets/_select.py", + 717, + "select_current.update", + [] + ], + [ + "src/textual/widgets/_select.py", + 609, + "select_current.update", + [] + ], + [ + "src/textual/widgets/_select.py", + 615, + "select_current.update", + [] + ], + [ + "src/textual/widgets/_selection_list.py", + 675, + "self._values.update", + [] + ], + [ + "src/textual/widgets/_tabs.py", + 193, + "self.update", + [] + ], + [ + "src/textual/widgets/_tabs.py", + 197, + "super().update", + [] + ], + [ + "src/textual/worker_manager.py", + 44, + "counter.update", + [] + ] + ] + }, + "toad": { + "head": "343eec0aaa077c4db4b8b69a7c6ac0af45d27afc", + "modules": 288, + "omissions": [], + "declarations": [ + [ + "src/toad/about.py", + 79, + "render" + ], + [ + "src/toad/acp/log_records.py", + 215, + "render" + ], + [ + "src/toad/acp/log_records.py", + 223, + "render" + ], + [ + "src/toad/acp/log_records.py", + 232, + "render" + ], + [ + "src/toad/acp/log_records.py", + 237, + "render" + ], + [ + "src/toad/acp/tool_calls.py", + 31, + "update" + ], + [ + "src/toad/ansi/_ansi.py", + 727, + "update" + ], + [ + "src/toad/ansi/_ansi.py", + 761, + "update" + ], + [ + "src/toad/ansi/_ansi.py", + 767, + "update" + ], + [ + "src/toad/ansi/_ansi.py", + 773, + "update" + ], + [ + "src/toad/ansi/_ansi.py", + 779, + "update" + ], + [ + "src/toad/ansi/_ansi.py", + 1264, + "update" + ], + [ + "src/toad/conversation_markdown.py", + 89, + "content" + ], + [ + "src/toad/conversation_markdown.py", + 119, + "content" + ], + [ + "src/toad/conversation_markdown.py", + 132, + "content" + ], + [ + "src/toad/conversation_markdown.py", + 148, + "render" + ], + [ + "src/toad/coordination_context_format.py", + 32, + "_render" + ], + [ + "src/toad/db.py", + 157, + "update" + ], + [ + "src/toad/db.py", + 170, + "update" + ], + [ + "src/toad/mcp_declarations.py", + 125, + "render" + ], + [ + "src/toad/mcp_declarations.py", + 153, + "render" + ], + [ + "src/toad/mcp_declarations.py", + 206, + "render" + ], + [ + "src/toad/sidebar_preparation.py", + 66, + "content" + ], + [ + "src/toad/sidebar_preparation.py", + 131, + "content" + ], + [ + "src/toad/slash_command.py", + 37, + "content" + ], + [ + "src/toad/terminal_attention.py", + 132, + "update" + ], + [ + "src/toad/tool_output.py", + 282, + "content" + ], + [ + "src/toad/visuals/columns.py", + 153, + "render" + ], + [ + "src/toad/visuals/columns.py", + 169, + "_render" + ], + [ + "src/toad/widgets/comms_sidebar.py", + 165, + "update" + ], + [ + "src/toad/widgets/condensed_path.py", + 109, + "render" + ], + [ + "src/toad/widgets/condensed_path.py", + 113, + "get_content_width" + ], + [ + "src/toad/widgets/conversation.py", + 198, + "render" + ], + [ + "src/toad/widgets/conversation.py", + 245, + "render" + ], + [ + "src/toad/widgets/message_divider.py", + 74, + "render" + ], + [ + "src/toad/widgets/message_divider.py", + 88, + "get_content_height" + ], + [ + "src/toad/widgets/prepared_markdown.py", + 71, + "update" + ], + [ + "src/toad/widgets/prepared_markdown.py", + 192, + "get_content_height" + ], + [ + "src/toad/widgets/question.py", + 194, + "update" + ], + [ + "src/toad/widgets/session_tabs.py", + 114, + "render" + ], + [ + "src/toad/widgets/session_thread_panels.py", + 78, + "update" + ], + [ + "src/toad/widgets/side_bar.py", + 407, + "render" + ], + [ + "src/toad/widgets/side_bar.py", + 535, + "render" + ], + [ + "src/toad/widgets/side_bar.py", + 915, + "render" + ], + [ + "src/toad/widgets/sidebar_tree.py", + 19, + "render" + ], + [ + "src/toad/widgets/streaming_markdown.py", + 79, + "update" + ], + [ + "src/toad/widgets/strike_text.py", + 43, + "render" + ], + [ + "src/toad/widgets/thread_comms.py", + 185, + "update" + ], + [ + "src/toad/widgets/throbber.py", + 135, + "render" + ], + [ + "src/toad/widgets/throbber.py", + 138, + "_render_styles_sensitive" + ], + [ + "src/toad/widgets/viewport_body.py", + 114, + "render" + ], + [ + "src/toad/widgets/viewport_body.py", + 234, + "render" + ], + [ + "src/toad/widgets/viewport_body.py", + 271, + "render" + ], + [ + "src/toad/widgets/viewport_body.py", + 339, + "render" + ], + [ + "src/toad/widgets/viewport_body.py", + 465, + "render" + ], + [ + "src/toad/widgets/viewport_body.py", + 690, + "get_content_height" + ], + [ + "src/toad/widgets/viewport_body.py", + 700, + "get_content_width" + ], + [ + "src/toad/widgets/worker_static.py", + 91, + "update" + ], + [ + "src/toad/widgets/worker_static.py", + 181, + "get_content_width" + ], + [ + "src/toad/widgets/worker_static.py", + 185, + "get_content_height" + ] + ], + "update_calls": [ + [ + "src/toad/acp/session_updates.py", + 50, + "self.agent.tools.update", + [] + ], + [ + "src/toad/ansi/_ansi.py", + 607, + "state.dec_state.update", + [] + ], + [ + "src/toad/ansi/_ansi.py", + 723, + "cls.update", + [] + ], + [ + "src/toad/ansi/_ansi.py", + 1204, + "self._updated_lines.update", + [] + ], + [ + "src/toad/complete.py", + 18, + "self._words.update", + [] + ], + [ + "src/toad/conversation_kind.py", + 98, + "history.historical_receipts.update", + [] + ], + [ + "src/toad/conversation_kind.py", + 194, + "history.channel_receipts.update", + [] + ], + [ + "src/toad/db.py", + 158, + "Session.update", + [ + "where", + "parameters", + null + ] + ], + [ + "src/toad/db.py", + 175, + "Session.update", + [ + "where", + "parameters", + "meta_json" + ] + ], + [ + "src/toad/file_kind.py", + 60, + "content.update", + [] + ], + [ + "src/toad/render_identity.py", + 37, + "digest.update", + [] + ], + [ + "src/toad/render_identity.py", + 58, + "digest.update", + [] + ], + [ + "src/toad/render_identity.py", + 60, + "digest.update", + [] + ], + [ + "src/toad/render_identity.py", + 44, + "digest.update", + [] + ], + [ + "src/toad/render_identity.py", + 45, + "digest.update", + [] + ], + [ + "src/toad/render_identity.py", + 62, + "digest.update", + [] + ], + [ + "src/toad/rich_preparation.py", + 117, + "options.update", + [ + "width", + "height", + "highlight" + ] + ], + [ + "src/toad/screens/goal_details.py", + 85, + "widget.update", + [] + ], + [ + "src/toad/screens/goal_edit.py", + 81, + "self.query_one('#goal-edit-error', Static).update", + [] + ], + [ + "src/toad/screens/historical_sessions.py", + 101, + "self.query_one('#source-label', Static).update", + [] + ], + [ + "src/toad/screens/main.py", + 213, + "self.app.terminal_attention.update", + [] + ], + [ + "src/toad/screens/mcp_decision.py", + 73, + "self.query_one('#mcp-decision-status', Static).update", + [] + ], + [ + "src/toad/screens/mcp_decision.py", + 107, + "self.query_one('#mcp-decision-status', Static).update", + [] + ], + [ + "src/toad/screens/mcp_inventory.py", + 86, + "self.query_one('#mcp-inventory-status', Static).update", + [] + ], + [ + "src/toad/screens/mcp_inventory.py", + 103, + "self.query_one('#mcp-inventory-status', Static).update", + [] + ], + [ + "src/toad/screens/workspace.py", + 341, + "targets.update", + [] + ], + [ + "src/toad/setting_effects.py", + 71, + "app.terminal_attention.update", + [] + ], + [ + "src/toad/terminal_attention.py", + 101, + "self.attention.update", + [] + ], + [ + "src/toad/terminal_attention.py", + 126, + "self.update", + [] + ], + [ + "src/toad/terminal_attention.py", + 130, + "self.update", + [] + ], + [ + "src/toad/terminal_attention.py", + 139, + "self.update", + [] + ], + [ + "src/toad/tool_output.py", + 122, + "widget.update", + [] + ], + [ + "src/toad/web_server.py", + 57, + "response.headers.update", + [] + ], + [ + "src/toad/widgets/acp_log.py", + 74, + "self.query_one('.log-status', Static).update", + [] + ], + [ + "src/toad/widgets/acp_log.py", + 97, + "self.query_one('.log-status', Static).update", + [] + ], + [ + "src/toad/widgets/acp_log.py", + 87, + "self.query_one('.log-status', Static).update", + [] + ], + [ + "src/toad/widgets/channel_participants.py", + 47, + "self.names.update", + [] + ], + [ + "src/toad/widgets/comms_command_dialog.py", + 82, + "self.query_one('#command-confirmation', Static).update", + [] + ], + [ + "src/toad/widgets/comms_sidebar.py", + 416, + "self.update", + [] + ], + [ + "src/toad/widgets/comms_sidebar.py", + 105, + "self.unread_badge.update", + [ + "layout" + ] + ], + [ + "src/toad/widgets/comms_sidebar.py", + 263, + "self.update", + [] + ], + [ + "src/toad/widgets/comms_sidebar.py", + 411, + "self.update", + [] + ], + [ + "src/toad/widgets/comms_transfer.py", + 147, + "self.query_one('#transfer-error', Static).update", + [] + ], + [ + "src/toad/widgets/context_explorer.py", + 364, + "self.query_one('.context-status', Static).update", + [] + ], + [ + "src/toad/widgets/context_explorer.py", + 434, + "status.update", + [] + ], + [ + "src/toad/widgets/context_explorer.py", + 445, + "status.update", + [] + ], + [ + "src/toad/widgets/context_explorer.py", + 321, + "status.update", + [] + ], + [ + "src/toad/widgets/context_explorer.py", + 338, + "self.query_one('.context-status', Static).update", + [] + ], + [ + "src/toad/widgets/context_explorer.py", + 328, + "status.update", + [] + ], + [ + "src/toad/widgets/context_explorer.py", + 439, + "status.update", + [] + ], + [ + "src/toad/widgets/conversation.py", + 2057, + "self._loading.update", + [] + ], + [ + "src/toad/widgets/flash.py", + 78, + "self.update", + [] + ], + [ + "src/toad/widgets/future_text.py", + 98, + "self.update", + [ + "layout" + ] + ], + [ + "src/toad/widgets/goal_bar.py", + 45, + "self.update", + [] + ], + [ + "src/toad/widgets/goal_bar.py", + 184, + "header.update", + [] + ], + [ + "src/toad/widgets/goal_bar.py", + 189, + "control.update", + [] + ], + [ + "src/toad/widgets/goal_text.py", + 37, + "self.update", + [] + ], + [ + "src/toad/widgets/history_anchor.py", + 263, + "protected.update", + [] + ], + [ + "src/toad/widgets/input_delivery.py", + 225, + "self.query_one('#delivery-records', Static).update", + [] + ], + [ + "src/toad/widgets/input_delivery.py", + 230, + "self.query_one('#delivery-historical-summary', Static).update", + [] + ], + [ + "src/toad/widgets/input_delivery.py", + 242, + "self.query_one('#delivery-historical-records', Static).update", + [] + ], + [ + "src/toad/widgets/input_delivery.py", + 99, + "summary.update", + [] + ], + [ + "src/toad/widgets/input_delivery.py", + 108, + "historical.update", + [] + ], + [ + "src/toad/widgets/input_delivery.py", + 111, + "error.update", + [] + ], + [ + "src/toad/widgets/input_delivery.py", + 214, + "overview.update", + [] + ], + [ + "src/toad/widgets/input_delivery.py", + 220, + "error.update", + [] + ], + [ + "src/toad/widgets/message_notifications.py", + 40, + "self.details.update", + [] + ], + [ + "src/toad/widgets/message_notifications.py", + 54, + "self.details.update", + [] + ], + [ + "src/toad/widgets/model_switcher.py", + 204, + "self.query_one('.model-count', Static).update", + [] + ], + [ + "src/toad/widgets/native_history.py", + 22, + "self.update", + [] + ], + [ + "src/toad/widgets/observed_thread_activity.py", + 110, + "self.update", + [] + ], + [ + "src/toad/widgets/patch_diff.py", + 48, + "before.update", + [] + ], + [ + "src/toad/widgets/patch_diff.py", + 49, + "after.update", + [] + ], + [ + "src/toad/widgets/prepared_markdown.py", + 78, + "super().update", + [] + ], + [ + "src/toad/widgets/prepared_markdown.py", + 189, + "self.update", + [ + "layout" + ] + ], + [ + "src/toad/widgets/project_panel.py", + 207, + "self.query_one('#file-preview-loading', Static).update", + [] + ], + [ + "src/toad/widgets/project_panel.py", + 195, + "self.query_one('#file-preview-loading', Static).update", + [] + ], + [ + "src/toad/widgets/prompt.py", + 88, + "self.update", + [] + ], + [ + "src/toad/widgets/prompt.py", + 529, + "info.update", + [] + ], + [ + "src/toad/widgets/prompt.py", + 637, + "summary.update", + [] + ], + [ + "src/toad/widgets/prompt.py", + 658, + "self.query_one(AgentInfo).update", + [] + ], + [ + "src/toad/widgets/prompt.py", + 660, + "self.query_one(AgentInfo).update", + [] + ], + [ + "src/toad/widgets/prompt.py", + 699, + "self.question.update", + [] + ], + [ + "src/toad/widgets/prompt.py", + 705, + "self.prompt_label.update", + [ + "layout" + ] + ], + [ + "src/toad/widgets/prompt.py", + 713, + "self.prompt_label.update", + [ + "layout" + ] + ], + [ + "src/toad/widgets/prompt.py", + 720, + "self.prompt_label.update", + [ + "layout" + ] + ], + [ + "src/toad/widgets/recovery_view.py", + 94, + "self.update", + [ + "layout" + ] + ], + [ + "src/toad/widgets/recovery_view.py", + 143, + "self.update", + [ + "layout" + ] + ], + [ + "src/toad/widgets/recovery_view.py", + 82, + "self.update", + [ + "layout" + ] + ], + [ + "src/toad/widgets/recovery_view.py", + 115, + "self.update", + [ + "layout" + ] + ], + [ + "src/toad/widgets/recovery_view.py", + 137, + "self.update", + [ + "layout" + ] + ], + [ + "src/toad/widgets/session_sidebar.py", + 108, + "self.update", + [ + "layout" + ] + ], + [ + "src/toad/widgets/session_sort.py", + 48, + "self.update", + [] + ], + [ + "src/toad/widgets/session_tabs.py", + 179, + "label.update", + [ + "layout" + ] + ], + [ + "src/toad/widgets/session_tabs.py", + 318, + "label.update", + [ + "layout" + ] + ], + [ + "src/toad/widgets/session_thread_sidebar.py", + 30, + "self.plan.update", + [] + ], + [ + "src/toad/widgets/session_thread_sidebar.py", + 39, + "self.plan.update", + [] + ], + [ + "src/toad/widgets/side_bar.py", + 185, + "self.update", + [ + "layout" + ] + ], + [ + "src/toad/widgets/side_bar.py", + 818, + "toggle_mode.update", + [ + "layout" + ] + ], + [ + "src/toad/widgets/sidebar_tree.py", + 134, + "inputs.update", + [] + ], + [ + "src/toad/widgets/sidebar_tree.py", + 168, + "pending.update", + [] + ], + [ + "src/toad/widgets/thread_comms.py", + 36, + "widget.update", + [ + "layout" + ] + ], + [ + "src/toad/widgets/tool_call.py", + 190, + "header.update", + [] + ], + [ + "src/toad/widgets/tool_call.py", + 376, + "self.query_one(ToolCallHeader).update", + [] + ], + [ + "src/toad/widgets/transcript_history.py", + 927, + "protected.update", + [] + ], + [ + "src/toad/widgets/transcript_history.py", + 252, + "leaf.update", + [] + ], + [ + "src/toad/widgets/transcript_history.py", + 939, + "protected.update", + [] + ], + [ + "src/toad/widgets/worker_static.py", + 88, + "super().update", + [] + ], + [ + "src/toad/widgets/worker_static.py", + 113, + "app.console_options.update", + [ + "width", + "height", + "highlight" + ] + ], + [ + "src/toad/widgets/worker_static.py", + 142, + "super().update", + [] + ] + ] + }, + "resolution_limit": "Lexical calls include unrelated receivers. Dynamic subclass and instance mutation is not resolved. Unknown render/measurement declarations retain layout." +} diff --git a/evidence/static-content-layout-20261006/app-result.json b/evidence/static-content-layout-20261006/app-result.json new file mode 100644 index 0000000000..9685a59cba --- /dev/null +++ b/evidence/static-content-layout-20261006/app-result.json @@ -0,0 +1,15 @@ +{ + "boundary": "source Toad ParticipantNames Static.update paint-only admission", + "update_seconds": 0.00031942606437951326, + "calls": { + "_invalidate_layout": 0, + "_request_layout": 0, + "_arrange_root": 0 + }, + "tabs": 10, + "active_widgets": 546, + "geometry_preserved": true, + "painted_action_metadata": true, + "inputs_submitted": 0, + "provider_calls": 0 +} diff --git a/evidence/static-content-layout-20261006/controls.log b/evidence/static-content-layout-20261006/controls.log new file mode 100644 index 0000000000..bed0862cda --- /dev/null +++ b/evidence/static-content-layout-20261006/controls.log @@ -0,0 +1,2 @@ +.... [100%] +4 passed in 0.42s