From e3874a70e4a8ff9185835b1c9ecf49afc3b20a37 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Wed, 7 Oct 2026 04:30:34 -0400 Subject: [PATCH 1/3] Declare native line extent and scrollbar paint measurement inputs --- .../README.md | 17 ++++++++++++++++ src/textual/_measurement.py | 20 +++++++++++++++++++ src/textual/scroll_view.py | 3 +++ src/textual/scrollbar.py | 20 +++++++++++++++++++ 4 files changed, 60 insertions(+) create mode 100644 evidence/stored-extent-measurement-20261007/README.md diff --git a/evidence/stored-extent-measurement-20261007/README.md b/evidence/stored-extent-measurement-20261007/README.md new file mode 100644 index 0000000000..67675fb3dd --- /dev/null +++ b/evidence/stored-extent-measurement-20261007/README.md @@ -0,0 +1,17 @@ +# Native line-surface measurement inputs + +ScrollView measures its authored virtual_size, whose original Reactive writes +already request layout. Its two methods now declare that source through the +existing HeightDependency family. Computed or replaced virtual_size access and +undeclared method overrides remain context-sensitive. + +Original ScrollBarRender changes colors without changing cell extent. ScrollBar +and its Blank corner now supply that fact through the existing renderer hook. +The live instance renderer is checked; custom renderers/wrappers stay opaque. +This completes the virtual-child supply which made the earlier ScrollView-only +candidate ineffective. No descendant traversal, style epoch, placement update, +refresh callback, size mutation or arrangement fence is bypassed. + +Source checkpoint; affected App and custom-source checks have not run yet. +No package/public operation or latency claim. The actual recorded mount cost +remains unresolved until measured through its affected application. diff --git a/src/textual/_measurement.py b/src/textual/_measurement.py index 51eef97118..a0e7347ad7 100644 --- a/src/textual/_measurement.py +++ b/src/textual/_measurement.py @@ -38,6 +38,25 @@ def depends(self, widget: Widget) -> bool: return False +class StoredVirtualSize(HeightDependency): + """The original line surface measures its authored reactive extent. + + Extent writes already invalidate through Reactive(layout=True). Computed + or replaced descriptors retain arbitrary style/container dependencies. + """ + + def depends(self, widget: Widget) -> bool: + from textual.reactive import _STORED_REACTIVE_ACCESS + from textual.widget import Widget + + return ( + type(widget).virtual_size is not Widget.virtual_size + or widget._reactive_accessors.get("virtual_size") is not _STORED_REACTIVE_ACCESS + ) + + styles_sensitive = depends + + class NativeWidgetMeasurementHeight(HeightDependency): @abstractmethod def layout_dependency(self, widget: Widget) -> HeightDependency: @@ -189,6 +208,7 @@ def depends(self, widget: Widget) -> bool: CONTEXT_HEIGHT = ContextHeight() INDEPENDENT_HEIGHT = IndependentHeight() +STORED_VIRTUAL_SIZE = StoredVirtualSize() NATIVE_WIDGET_HEIGHT = NativeWidgetHeight() NATIVE_WIDGET_WIDTH = NativeWidgetWidth() NATIVE_LAYOUT_HEIGHT = NativeLayoutHeight() diff --git a/src/textual/scroll_view.py b/src/textual/scroll_view.py index ee70b3c5ac..39f8f61952 100644 --- a/src/textual/scroll_view.py +++ b/src/textual/scroll_view.py @@ -7,6 +7,7 @@ from rich.console import RenderableType from textual._animator import EasingFunction +from textual._measurement import STORED_VIRTUAL_SIZE, height_dependency from textual._types import AnimationLevel, CallbackType from textual.containers import ScrollableContainer from textual.geometry import Region, Size @@ -58,6 +59,7 @@ def watch_scroll_y(self, old_value: float, new_value: float) -> None: def on_mount(self): self._refresh_scrollbars() + @height_dependency(STORED_VIRTUAL_SIZE) def get_content_width(self, container: Size, viewport: Size) -> int: """Gets the width of the content area. @@ -70,6 +72,7 @@ def get_content_width(self, container: Size, viewport: Size) -> int: """ return self.virtual_size.width + @height_dependency(STORED_VIRTUAL_SIZE) def get_content_height(self, container: Size, viewport: Size, width: int) -> int: """Gets the height (number of lines) in the content area. diff --git a/src/textual/scrollbar.py b/src/textual/scrollbar.py index ac87db36cd..4dc0624d83 100644 --- a/src/textual/scrollbar.py +++ b/src/textual/scrollbar.py @@ -278,6 +278,19 @@ def validate_position(self, position: float) -> float: """Position has a granulatory of 1/8 of a cell.""" return int(position * 8) / 8 + def _render_styles_sensitive(self) -> bool: + """Native bar colors change paint, not the renderer's cell extent. + + Custom renderers and wrappers retain their arbitrary measurement + inputs. Select the live instance renderer, including runtime replacement. + """ + return ( + type(self).render is not ScrollBar.render + or type(self)._render_bar is not ScrollBar._render_bar + or type(self)._render is not Widget._render + or self.renderer is not ScrollBarRender + ) + def render(self) -> RenderableType: assert self.parent is not None styles = self.parent.styles @@ -404,6 +417,13 @@ class ScrollBarCorner(Widget): """Widget which fills the gap between horizontal and vertical scrollbars, should they both be present.""" + def _render_styles_sensitive(self) -> bool: + """Original Blank measures width and one row independently of color.""" + return ( + type(self).render is not ScrollBarCorner.render + or type(self)._render is not Widget._render + ) + def render(self) -> Blank: assert self.parent is not None styles = self.parent.styles From 8e1547f622a85f50ff1a2e4ad7cb3f6e4eaa27be Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Wed, 7 Oct 2026 04:36:09 -0400 Subject: [PATCH 2/3] Keep custom and live-replaced chrome measurements conservative --- .../checks-first.log | 14 +++ .../checks.log | 12 +++ .../custom-renderers.log | 2 + .../custom-supply.log | 2 + src/textual/scrollbar.py | 12 +-- tests/test_line_surface_style_measurement.py | 88 +++++++++++++++++++ 6 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 evidence/stored-extent-measurement-20261007/checks-first.log create mode 100644 evidence/stored-extent-measurement-20261007/checks.log create mode 100644 evidence/stored-extent-measurement-20261007/custom-renderers.log create mode 100644 evidence/stored-extent-measurement-20261007/custom-supply.log create mode 100644 tests/test_line_surface_style_measurement.py diff --git a/evidence/stored-extent-measurement-20261007/checks-first.log b/evidence/stored-extent-measurement-20261007/checks-first.log new file mode 100644 index 0000000000..d76f63c293 --- /dev/null +++ b/evidence/stored-extent-measurement-20261007/checks-first.log @@ -0,0 +1,14 @@ +F.. [100%] +=================================== FAILURES =================================== +_______ test_native_line_measurements_survive_paint_and_retire_on_extent _______ +tests/test_line_surface_style_measurement.py:30: in test_native_line_measurements_survive_paint_and_retire_on_extent + assert {widget: widget._layout_updates for widget in originals} == originals +E assert {RichLog(): 8...ickness=2): 2} == {RichLog(): 7...ickness=2): 2} +E +E Omitting 3 identical items, use -vv to show +E Differing items: +E {RichLog(): 8} != {RichLog(): 7} +E Use -v to get more diff +=========================== short test summary info ============================ +FAILED tests/test_line_surface_style_measurement.py::test_native_line_measurements_survive_paint_and_retire_on_extent +1 failed, 2 passed in 0.49s diff --git a/evidence/stored-extent-measurement-20261007/checks.log b/evidence/stored-extent-measurement-20261007/checks.log new file mode 100644 index 0000000000..c0daa38877 --- /dev/null +++ b/evidence/stored-extent-measurement-20261007/checks.log @@ -0,0 +1,12 @@ +.F [100%] +=================================== FAILURES =================================== +___________ test_custom_extent_and_method_supply_stays_conservative ____________ +tests/test_line_surface_style_measurement.py:63: in test_custom_extent_and_method_supply_stays_conservative + assert widget._content_height_dependency.styles_sensitive(widget) +E assert False +E + where False = depends(RichLog()) +E + where depends = .styles_sensitive +E + where = RichLog()._content_height_dependency +=========================== short test summary info ============================ +FAILED tests/test_line_surface_style_measurement.py::test_custom_extent_and_method_supply_stays_conservative +1 failed, 1 passed in 0.41s diff --git a/evidence/stored-extent-measurement-20261007/custom-renderers.log b/evidence/stored-extent-measurement-20261007/custom-renderers.log new file mode 100644 index 0000000000..22c9de3370 --- /dev/null +++ b/evidence/stored-extent-measurement-20261007/custom-renderers.log @@ -0,0 +1,2 @@ +. [100%] +1 passed in 0.31s diff --git a/evidence/stored-extent-measurement-20261007/custom-supply.log b/evidence/stored-extent-measurement-20261007/custom-supply.log new file mode 100644 index 0000000000..1e593b6499 --- /dev/null +++ b/evidence/stored-extent-measurement-20261007/custom-supply.log @@ -0,0 +1,2 @@ +. [100%] +1 passed in 0.30s diff --git a/src/textual/scrollbar.py b/src/textual/scrollbar.py index 4dc0624d83..3b067a73e9 100644 --- a/src/textual/scrollbar.py +++ b/src/textual/scrollbar.py @@ -285,9 +285,10 @@ def _render_styles_sensitive(self) -> bool: inputs. Select the live instance renderer, including runtime replacement. """ return ( - type(self).render is not ScrollBar.render - or type(self)._render_bar is not ScrollBar._render_bar - or type(self)._render is not Widget._render + type(self) is not ScrollBar + or getattr(self.render, "__func__", None) is not ScrollBar.render + or getattr(self._render_bar, "__func__", None) is not ScrollBar._render_bar + or getattr(self._render, "__func__", None) is not Widget._render or self.renderer is not ScrollBarRender ) @@ -420,8 +421,9 @@ class ScrollBarCorner(Widget): def _render_styles_sensitive(self) -> bool: """Original Blank measures width and one row independently of color.""" return ( - type(self).render is not ScrollBarCorner.render - or type(self)._render is not Widget._render + type(self) is not ScrollBarCorner + or getattr(self.render, "__func__", None) is not ScrollBarCorner.render + or getattr(self._render, "__func__", None) is not Widget._render ) def render(self) -> Blank: diff --git a/tests/test_line_surface_style_measurement.py b/tests/test_line_surface_style_measurement.py new file mode 100644 index 0000000000..36295cc5a0 --- /dev/null +++ b/tests/test_line_surface_style_measurement.py @@ -0,0 +1,88 @@ +"""Native source invalidation through real mounted line surfaces and chrome.""" +from textual.app import App +from textual.containers import Container +from textual.geometry import Size +from textual.scroll_view import ScrollView +from textual.scrollbar import ScrollBarRender +from textual.widgets import Log, RichLog, TextArea + + +async def test_native_line_measurements_survive_paint_and_retire_on_extent(): + lines = Log() + editor = TextArea("saved\n" * 30) + group = Container(lines, editor) + + class LinesApp(App): + CSS = "Container { height: 12; } Log, TextArea { width: 30; height: 5; }" + + def compose(self): + yield group + + async with LinesApp().run_test(size=(60, 18)) as pilot: + lines.write_line("original retained row " * 10) + await pilot.pause() + chrome = tuple(child for widget in (lines, editor) + for child in widget._get_virtual_dom()) + assert chrome + originals = {widget: widget._layout_updates for widget in (lines, editor, *chrome)} + before = (lines.virtual_size, editor.virtual_size) + group.styles.scrollbar_color = "red" + assert {widget: widget._layout_updates for widget in originals} == originals + await pilot.pause() + assert (lines.virtual_size, editor.virtual_size) == before + # Both original measurement methods follow the actual extent source. + lines.virtual_size = Size(before[0].width + 2, before[0].height + 3) + assert lines._layout_updates > originals[lines] + assert lines.get_content_width(Size(10, 10), Size(60, 18)) == lines.virtual_size.width + assert lines.get_content_height(Size(10, 10), Size(60, 18), 10) == lines.virtual_size.height + # A live replacement renderer restores the conservative child contract. + class CustomBar(ScrollBarRender): + pass + bar = editor.vertical_scrollbar + bar.renderer = CustomBar + before = editor._layout_updates, bar._layout_updates + group.styles.scrollbar_color = "blue" + assert editor._layout_updates > before[0] and bar._layout_updates > before[1] + + +async def test_custom_extent_and_method_supply_stays_conservative(): + class Computed(ScrollView): + def compute_virtual_size(self): + return Size(30, 30) + + class Replaced(ScrollView): + @property + def virtual_size(self): + return Size(30, 30) + + class CustomHeight(ScrollView): + def get_content_height(self, container, viewport, width): + return 3 + + for widget in (Computed(), Replaced(), CustomHeight(), RichLog()): + assert (widget._content_height_dependency.styles_sensitive(widget) + or widget._content_width_dependency.styles_sensitive(widget)) + before = widget._layout_updates + widget.styles.scrollbar_color = "red" + assert widget._layout_updates > before + + +def test_scrollbar_custom_declarations_and_live_method_replacement(): + from textual.scrollbar import ScrollBar, ScrollBarCorner + + class CustomBar(ScrollBar): + pass + + class CustomCorner(ScrollBarCorner): + pass + + assert CustomBar()._render_styles_sensitive() + assert CustomCorner()._render_styles_sensitive() + bar = ScrollBar() + assert not bar._render_styles_sensitive() + bar.render = lambda: "custom source" + assert bar._render_styles_sensitive() + corner = ScrollBarCorner() + assert not corner._render_styles_sensitive() + corner.render = lambda: "custom source" + assert corner._render_styles_sensitive() From 2f64f51857a7bab3edb309a9e9103fabb22cfa15 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Wed, 7 Oct 2026 04:38:57 -0400 Subject: [PATCH 3/3] Publish scoped native measurement and final loaded App qualification --- .../README.md | 34 +- .../app-stderr.log | 0 .../app-stdout.log | 315 ++++++++++++ .../app.json | 78 +++ .../source.json | 464 ++++++++++++++++++ 5 files changed, 888 insertions(+), 3 deletions(-) create mode 100644 evidence/stored-extent-measurement-20261007/app-stderr.log create mode 100644 evidence/stored-extent-measurement-20261007/app-stdout.log create mode 100644 evidence/stored-extent-measurement-20261007/app.json create mode 100644 evidence/stored-extent-measurement-20261007/source.json diff --git a/evidence/stored-extent-measurement-20261007/README.md b/evidence/stored-extent-measurement-20261007/README.md index 67675fb3dd..669b6334df 100644 --- a/evidence/stored-extent-measurement-20261007/README.md +++ b/evidence/stored-extent-measurement-20261007/README.md @@ -12,6 +12,34 @@ This completes the virtual-child supply which made the earlier ScrollView-only candidate ineffective. No descendant traversal, style epoch, placement update, refresh callback, size mutation or arrangement fence is bypassed. -Source checkpoint; affected App and custom-source checks have not run yet. -No package/public operation or latency claim. The actual recorded mount cost -remains unresolved until measured through its affected application. +Four distinct affected checks passed: mounted Log/TextArea paint preservation +and extent retirement; native container-only resize; computed/replaced extent +and undeclared width/height methods; custom scrollbar classes and live method +replacement. The two first authored failures incorrectly treated RichLog as +having the same width declaration as ScrollView and then checked only its +height policy. Its genuine width override stays conservative; raw failures +remain in checks-first.log and checks.log. No producer was broadened to pass. + +The final existing source Toad App completed at production 8e1547f62 with ten +tabs, 508 widgets, zero inputs/providers and empty stderr. Actual pointer +sidebar first-display medians were 33.5/34.8 ms (maxima 100.9/65.0 ms). +app.json records original cProfile call/duration measurements and profile +hashes; raw output is retained here. This is headless completed display, not +terminal pixels, an agent-switch comparison or an installed latency claim. +The earlier prototype App remains at +/home/ts/.cache/agent-scratch/native-line-measurement-app-20261007; final source +App profiles remain at native-line-measurement-app02-20261007 in the same +scratch parent. The final guard correction narrowed custom class/instance +supply; it did not alter native rendered dimensions or style publication. + +The original recursive traversal and every true geometry mutation remain. +Subclasses of ScrollBar/Corner stay conservative because extra descriptors can +independently alter shape. Base instances also check live bound methods and +renderer selection; no per-event result is retained. Original reactive access +supply distinguishes computed from stored extent without duplicating discovery. +AST parses 724 native production/test modules with zero omissions, covering +all dependency, source-access, renderer and invalidation consumers. + +No package/public operation or reliable overall speed claim. The actual agent +mount delay and expensive arrangements remain unresolved; this change retires +an unnecessary native measurement dependency rather than suppressing layouts. diff --git a/evidence/stored-extent-measurement-20261007/app-stderr.log b/evidence/stored-extent-measurement-20261007/app-stderr.log new file mode 100644 index 0000000000..e69de29bb2 diff --git a/evidence/stored-extent-measurement-20261007/app-stdout.log b/evidence/stored-extent-measurement-20261007/app-stdout.log new file mode 100644 index 0000000000..a3988f47f7 --- /dev/null +++ b/evidence/stored-extent-measurement-20261007/app-stdout.log @@ -0,0 +1,315 @@ +{ + "boundary": "completed headless _display; not terminal pixels", + "tabs": 10, + "pointer": true, + "active_widgets": 508, + "toggle_first_paint": { + "#channels-sidebar": { + "median_ms": 33.5, + "p95_ms": 37.9, + "max_ms": 100.9 + }, + "#thread-sidebar": { + "median_ms": 34.8, + "p95_ms": 40.9, + "max_ms": 65.0 + } + } +} +{ + "publication_trace": [ + { + "elapsed_ms": 28.2, + "renderable": "ChopsUpdate", + "batch_count": 0, + "current": true, + "right_collapsed": true, + "right_region": [ + 117, + 3, + 3, + 38 + ], + "exclusions": [ + [ + 4, + 9, + 109, + 25 + ], + [ + 4, + 10, + 109, + 1 + ] + ] + }, + { + "elapsed_ms": 100.7, + "renderable": "ChopsUpdate", + "batch_count": 0, + "current": true, + "right_collapsed": true, + "right_region": [ + 117, + 3, + 3, + 38 + ], + "exclusions": [] + }, + { + "elapsed_ms": 31.2, + "renderable": "ChopsUpdate", + "batch_count": 0, + "current": true, + "right_collapsed": true, + "right_region": [ + 117, + 3, + 3, + 38 + ], + "exclusions": [] + }, + { + "elapsed_ms": 32.8, + "renderable": "ChopsUpdate", + "batch_count": 0, + "current": true, + "right_collapsed": true, + "right_region": [ + 117, + 3, + 3, + 38 + ], + "exclusions": [] + }, + { + "elapsed_ms": 33.0, + "renderable": "ChopsUpdate", + "batch_count": 0, + "current": true, + "right_collapsed": true, + "right_region": [ + 117, + 3, + 3, + 38 + ], + "exclusions": [] + }, + { + "elapsed_ms": 35.3, + "renderable": "ChopsUpdate", + "batch_count": 0, + "current": true, + "right_collapsed": true, + "right_region": [ + 117, + 3, + 3, + 38 + ], + "exclusions": [] + }, + { + "elapsed_ms": 37.9, + "renderable": "ChopsUpdate", + "batch_count": 0, + "current": true, + "right_collapsed": true, + "right_region": [ + 117, + 3, + 3, + 38 + ], + "exclusions": [] + }, + { + "elapsed_ms": 32.3, + "renderable": "ChopsUpdate", + "batch_count": 0, + "current": true, + "right_collapsed": true, + "right_region": [ + 117, + 3, + 3, + 38 + ], + "exclusions": [] + }, + { + "elapsed_ms": 33.9, + "renderable": "ChopsUpdate", + "batch_count": 0, + "current": true, + "right_collapsed": true, + "right_region": [ + 117, + 3, + 3, + 38 + ], + "exclusions": [] + }, + { + "elapsed_ms": 37.2, + "renderable": "ChopsUpdate", + "batch_count": 0, + "current": true, + "right_collapsed": true, + "right_region": [ + 117, + 3, + 3, + 38 + ], + "exclusions": [] + }, + { + "elapsed_ms": 31.1, + "renderable": "ChopsUpdate", + "batch_count": 0, + "current": true, + "right_collapsed": false, + "right_region": [ + 80, + 3, + 40, + 38 + ], + "exclusions": [] + }, + { + "elapsed_ms": 64.8, + "renderable": "ChopsUpdate", + "batch_count": 0, + "current": true, + "right_collapsed": true, + "right_region": [ + 117, + 3, + 3, + 38 + ], + "exclusions": [] + }, + { + "elapsed_ms": 40.5, + "renderable": "ChopsUpdate", + "batch_count": 0, + "current": true, + "right_collapsed": false, + "right_region": [ + 80, + 3, + 40, + 38 + ], + "exclusions": [] + }, + { + "elapsed_ms": 24.9, + "renderable": "ChopsUpdate", + "batch_count": 0, + "current": true, + "right_collapsed": true, + "right_region": [ + 117, + 3, + 3, + 38 + ], + "exclusions": [] + }, + { + "elapsed_ms": 38.2, + "renderable": "ChopsUpdate", + "batch_count": 0, + "current": true, + "right_collapsed": false, + "right_region": [ + 80, + 3, + 40, + 38 + ], + "exclusions": [] + }, + { + "elapsed_ms": 25.2, + "renderable": "ChopsUpdate", + "batch_count": 0, + "current": true, + "right_collapsed": true, + "right_region": [ + 117, + 3, + 3, + 38 + ], + "exclusions": [] + }, + { + "elapsed_ms": 40.8, + "renderable": "ChopsUpdate", + "batch_count": 0, + "current": true, + "right_collapsed": false, + "right_region": [ + 80, + 3, + 40, + 38 + ], + "exclusions": [] + }, + { + "elapsed_ms": 32.3, + "renderable": "ChopsUpdate", + "batch_count": 0, + "current": true, + "right_collapsed": true, + "right_region": [ + 117, + 3, + 3, + 38 + ], + "exclusions": [] + }, + { + "elapsed_ms": 37.3, + "renderable": "ChopsUpdate", + "batch_count": 0, + "current": true, + "right_collapsed": false, + "right_region": [ + 80, + 3, + 40, + 38 + ], + "exclusions": [] + }, + { + "elapsed_ms": 30.2, + "renderable": "ChopsUpdate", + "batch_count": 0, + "current": true, + "right_collapsed": true, + "right_region": [ + 117, + 3, + 3, + 38 + ], + "exclusions": [] + } + ] +} diff --git a/evidence/stored-extent-measurement-20261007/app.json b/evidence/stored-extent-measurement-20261007/app.json new file mode 100644 index 0000000000..5d256ace97 --- /dev/null +++ b/evidence/stored-extent-measurement-20261007/app.json @@ -0,0 +1,78 @@ +{ + "boundary": "completed headless _display; not terminal pixels", + "tabs": 10, + "pointer": true, + "active_widgets": 508, + "toggle_first_paint": { + "#channels-sidebar": { + "median_ms": 33.5, + "p95_ms": 37.9, + "max_ms": 100.9 + }, + "#thread-sidebar": { + "median_ms": 34.8, + "p95_ms": 40.9, + "max_ms": 65.0 + } + }, + "source_head": "8e1547f62", + "terminal_exit": 0, + "stderr_bytes": 0, + "profiles": { + "toggle-channels.pstats": { + "sha256": "a4a813bff7f97d0cfe300a566ec9af5fbc9c3431a1a821b284fd1dbf9d642091", + "rows": [ + { + "function": "_update_inherited_geometry", + "primitive_calls": 6, + "total_calls": 147, + "self_seconds": 0.0006502750000000001, + "cumulative_seconds": 0.003998126 + }, + { + "function": "_measurement_updated", + "primitive_calls": 112, + "total_calls": 112, + "self_seconds": 0.000286123, + "cumulative_seconds": 0.0007214280000000001 + }, + { + "function": "_clear_arrangement_cache", + "primitive_calls": 112, + "total_calls": 112, + "self_seconds": 9.782800000000001e-05, + "cumulative_seconds": 0.000318223 + } + ] + }, + "toggle-thread.pstats": { + "sha256": "87142953a2fe34b365751bf6aa5b9c89cd017322df0ba344b16a01ecf04280c0", + "rows": [ + { + "function": "_update_inherited_geometry", + "primitive_calls": 5, + "total_calls": 115, + "self_seconds": 0.00032926, + "cumulative_seconds": 0.0020039890000000003 + }, + { + "function": "_measurement_updated", + "primitive_calls": 105, + "total_calls": 105, + "self_seconds": 0.000170051, + "cumulative_seconds": 0.000500446 + }, + { + "function": "_clear_arrangement_cache", + "primitive_calls": 105, + "total_calls": 105, + "self_seconds": 5.8089000000000006e-05, + "cumulative_seconds": 0.00023768000000000001 + } + ] + } + }, + "inputs": 0, + "providers": 0, + "limits": "Source headless first-display measurements, not terminal pixels or an agent-switch comparison. Earlier prototype App and two authored assertion failures preserved separately." +} diff --git a/evidence/stored-extent-measurement-20261007/source.json b/evidence/stored-extent-measurement-20261007/source.json new file mode 100644 index 0000000000..0c5ec52ffd --- /dev/null +++ b/evidence/stored-extent-measurement-20261007/source.json @@ -0,0 +1,464 @@ +{ + "head": "8e1547f622a85f50ff1a2e4ad7cb3f6e4eaa27be", + "modules": 724, + "omissions": [], + "sites": [ + [ + "src/textual/_measurement.py", + 22, + "declaration", + "styles_sensitive" + ], + [ + "src/textual/_measurement.py", + 80, + "declaration", + "styles_sensitive" + ], + [ + "src/textual/_measurement.py", + 97, + "declaration", + "styles_sensitive" + ], + [ + "src/textual/_measurement.py", + 119, + "declaration", + "styles_sensitive" + ], + [ + "src/textual/_measurement.py", + 126, + "declaration", + "styles_sensitive" + ], + [ + "src/textual/_measurement.py", + 90, + "Load", + "widget.layout._content_height_dependency" + ], + [ + "src/textual/_measurement.py", + 95, + "Load", + "widget.layout._content_width_dependency" + ], + [ + "src/textual/_measurement.py", + 275, + "Load", + "widget._content_width_dependency" + ], + [ + "src/textual/_measurement.py", + 278, + "Load", + "widget._content_height_dependency" + ], + [ + "src/textual/_measurement.py", + 197, + "Load", + "child._content_height_dependency" + ], + [ + "src/textual/_measurement.py", + 199, + "Load", + "child._content_width_dependency" + ], + [ + "src/textual/_measurement.py", + 54, + "Load", + "widget._reactive_accessors" + ], + [ + "src/textual/_measurement.py", + 171, + "Load", + "child._content_height_dependency" + ], + [ + "src/textual/dom.py", + 152, + "declaration", + "_style_geometry_updated" + ], + [ + "src/textual/dom.py", + 161, + "declaration", + "_update_inherited_geometry" + ], + [ + "src/textual/dom.py", + 669, + "Store", + "cls._reactive_accessors" + ], + [ + "src/textual/layout.py", + 280, + "declaration", + "get_content_width" + ], + [ + "src/textual/layout.py", + 302, + "declaration", + "get_content_height" + ], + [ + "src/textual/layout.py", + 245, + "Store", + "cls._content_width_dependency" + ], + [ + "src/textual/layout.py", + 246, + "Store", + "cls._content_height_dependency" + ], + [ + "src/textual/layout.py", + 250, + "Store", + "cls._content_height_dependency" + ], + [ + "src/textual/layout.py", + 253, + "Store", + "cls._content_width_dependency" + ], + [ + "src/textual/layouts/stream.py", + 124, + "declaration", + "get_content_width" + ], + [ + "src/textual/layouts/stream.py", + 138, + "declaration", + "get_content_height" + ], + [ + "src/textual/reactive.py", + 275, + "declaration", + "_bind_access" + ], + [ + "src/textual/reactive.py", + 309, + "Load", + "obj._reactive_accessors" + ], + [ + "src/textual/reactive.py", + 385, + "Load", + "obj._reactive_accessors" + ], + [ + "src/textual/reactive.py", + 399, + "Load", + "obj._reactive_accessors" + ], + [ + "src/textual/reactive.py", + 490, + "Load", + "obj._reactive_accessors" + ], + [ + "src/textual/scroll_view.py", + 63, + "declaration", + "get_content_width" + ], + [ + "src/textual/scroll_view.py", + 76, + "declaration", + "get_content_height" + ], + [ + "src/textual/scrollbar.py", + 281, + "declaration", + "_render_styles_sensitive" + ], + [ + "src/textual/scrollbar.py", + 421, + "declaration", + "_render_styles_sensitive" + ], + [ + "src/textual/widget.py", + 319, + "declaration", + "_render_styles_sensitive" + ], + [ + "src/textual/widget.py", + 323, + "declaration", + "_style_geometry_updated" + ], + [ + "src/textual/widget.py", + 2104, + "declaration", + "get_content_width" + ], + [ + "src/textual/widget.py", + 2136, + "declaration", + "get_content_height" + ], + [ + "src/textual/widget.py", + 4162, + "Store", + "cls._content_height_dependency" + ], + [ + "src/textual/widget.py", + 4163, + "Store", + "cls._content_width_dependency" + ], + [ + "src/textual/widget.py", + 325, + "Load", + "self._content_height_dependency" + ], + [ + "src/textual/widget.py", + 326, + "Load", + "self._content_width_dependency" + ], + [ + "src/textual/widgets/_button.py", + 376, + "declaration", + "get_content_width" + ], + [ + "src/textual/widgets/_digits.py", + 89, + "declaration", + "get_content_width" + ], + [ + "src/textual/widgets/_digits.py", + 101, + "declaration", + "get_content_height" + ], + [ + "src/textual/widgets/_input.py", + 701, + "declaration", + "get_content_width" + ], + [ + "src/textual/widgets/_input.py", + 705, + "declaration", + "get_content_height" + ], + [ + "src/textual/widgets/_option_list.py", + 840, + "declaration", + "get_content_width" + ], + [ + "src/textual/widgets/_option_list.py", + 859, + "declaration", + "get_content_height" + ], + [ + "src/textual/widgets/_rich_log.py", + 141, + "declaration", + "get_content_width" + ], + [ + "src/textual/widgets/_rule.py", + 200, + "declaration", + "get_content_width" + ], + [ + "src/textual/widgets/_rule.py", + 205, + "declaration", + "get_content_height" + ], + [ + "src/textual/widgets/_static.py", + 87, + "declaration", + "_render_styles_sensitive" + ], + [ + "src/textual/widgets/_switch.py", + 196, + "declaration", + "get_content_width" + ], + [ + "src/textual/widgets/_switch.py", + 199, + "declaration", + "get_content_height" + ], + [ + "src/textual/widgets/_toggle_button.py", + 231, + "declaration", + "get_content_width" + ], + [ + "src/textual/widgets/_toggle_button.py", + 238, + "declaration", + "get_content_height" + ], + [ + "tests/test_box_model.py", + 14, + "declaration", + "get_content_width" + ], + [ + "tests/test_box_model.py", + 17, + "declaration", + "get_content_height" + ], + [ + "tests/test_box_model.py", + 53, + "declaration", + "get_content_width" + ], + [ + "tests/test_box_model.py", + 56, + "declaration", + "get_content_height" + ], + [ + "tests/test_box_model.py", + 102, + "declaration", + "get_content_width" + ], + [ + "tests/test_box_model.py", + 105, + "declaration", + "get_content_height" + ], + [ + "tests/test_box_model.py", + 151, + "declaration", + "get_content_width" + ], + [ + "tests/test_box_model.py", + 154, + "declaration", + "get_content_height" + ], + [ + "tests/test_box_model.py", + 175, + "declaration", + "get_content_width" + ], + [ + "tests/test_box_model.py", + 178, + "declaration", + "get_content_height" + ], + [ + "tests/test_compositor.py", + 171, + "declaration", + "get_content_height" + ], + [ + "tests/test_height_independent_arrangement.py", + 234, + "declaration", + "get_content_height" + ], + [ + "tests/test_height_independent_measurement.py", + 99, + "declaration", + "get_content_height" + ], + [ + "tests/test_height_independent_measurement.py", + 160, + "declaration", + "get_content_width" + ], + [ + "tests/test_line_surface_style_measurement.py", + 50, + "declaration", + "compute_virtual_size" + ], + [ + "tests/test_line_surface_style_measurement.py", + 59, + "declaration", + "get_content_height" + ], + [ + "tests/test_line_surface_style_measurement.py", + 63, + "Load", + "widget._content_height_dependency" + ], + [ + "tests/test_line_surface_style_measurement.py", + 64, + "Load", + "widget._content_width_dependency" + ], + [ + "tests/test_static.py", + 70, + "declaration", + "get_content_height" + ], + [ + "tests/test_static.py", + 77, + "declaration", + "_render_styles_sensitive" + ] + ], + "dynamic_limits": "Custom line measurement methods retain CONTEXT_HEIGHT. Custom scrollbar classes, live renderer/method replacement, computed and replaced extent descriptors remain conservative. Mutation of private class declaration supply outside its owner is not certified." +}