From 8182a169e658d7873b0a8929bf92467777080543 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Sun, 4 Oct 2026 02:12:35 -0400 Subject: [PATCH 1/4] Claim native style source and layout publication closure --- .../SCOPE.md | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 evidence/native-style-layout-publication-20261004/SCOPE.md diff --git a/evidence/native-style-layout-publication-20261004/SCOPE.md b/evidence/native-style-layout-publication-20261004/SCOPE.md new file mode 100644 index 0000000000..bf306855ec --- /dev/null +++ b/evidence/native-style-layout-publication-20261004/SCOPE.md @@ -0,0 +1,30 @@ +# Native style mutation and layout request have distinct lifetimes + +Source: Styles._update_rules commits authored inputs, then DOMNode._style_rules_updated +retires descendant measurements and ancestor geometry through the original hooks. +Styles._refresh then calls Widget.refresh(layout=True), which treats the request +as another source mutation and runs _invalidate_layout/_measurement_updated again. +Raw style writes must remain synchronous source invalidation without unsolicited +refresh; imperative content refresh must still invalidate before an await. + +Use the existing style-notification / Widget layout publication family to carry +already-published style requests. Keep source mutation owned by Styles and ordinary +Widget input mutation owned by its current public refresh contract. Delete repeated +retirement from style scheduling, not legitimate layout or actual content writes in +leaf notify hooks. All native and Toad overrides/callers must migrate coherently; +no new mirror, cache, flag, typed wrapper, scene map, timer or alternate renderer. +Public/custom override contracts require explicit source review before selecting a +hook signature; a new keyword cannot silently break existing subclass hooks. + +Native ownership: Widget/DOM style-to-layout scheduling, Styles notification callers, +original native widget overrides. Heisenberg owns Toad source/admission/preparation/ +paint/retirement and its eight style-notification overrides; coordinate the exact +shared boundary directly. No edits to his worktree. Native52/53 source and installed +qualification stay frozen on main and are not a performance finish line. + +Current main c1c1ebe5; existing NRA Package complete native249 and Toad288 parsed with +zero omissions. Source activation from original422 is not CPU dominance or a latency +measurement. Read all declarations, writes and hooks before implementation. Coherent +family implementation first; affected controls and one changed installed user path +last with the workflow owner. No old film, new worktree/environment/native copy or +parallel measurement project. Full structural UI responsiveness remains active. From 70c7d5f5eba789d2fcbdf5747efffd05412fb177 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Sun, 4 Oct 2026 02:20:44 -0400 Subject: [PATCH 2/4] Keep style source retirement separate from native layout requests --- .../SCOPE.md | 30 +++++++++++++++++-- src/textual/css/styles.py | 9 +++--- src/textual/dom.py | 10 +++---- src/textual/widget.py | 12 ++++---- 4 files changed, 42 insertions(+), 19 deletions(-) diff --git a/evidence/native-style-layout-publication-20261004/SCOPE.md b/evidence/native-style-layout-publication-20261004/SCOPE.md index bf306855ec..5564ddf137 100644 --- a/evidence/native-style-layout-publication-20261004/SCOPE.md +++ b/evidence/native-style-layout-publication-20261004/SCOPE.md @@ -11,10 +11,10 @@ Use the existing style-notification / Widget layout publication family to carry already-published style requests. Keep source mutation owned by Styles and ordinary Widget input mutation owned by its current public refresh contract. Delete repeated retirement from style scheduling, not legitimate layout or actual content writes in -leaf notify hooks. All native and Toad overrides/callers must migrate coherently; +leaf notify hooks. All original native/Toad overrides retain their public contract; scheduling callers migrate coherently; no new mirror, cache, flag, typed wrapper, scene map, timer or alternate renderer. -Public/custom override contracts require explicit source review before selecting a -hook signature; a new keyword cannot silently break existing subclass hooks. +The existing public style callback keeps its signature and behavior; source review +rejected introducing a new keyword to existing subclass callbacks. Native ownership: Widget/DOM style-to-layout scheduling, Styles notification callers, original native widget overrides. Heisenberg owns Toad source/admission/preparation/ @@ -28,3 +28,27 @@ measurement. Read all declarations, writes and hooks before implementation. Cohe family implementation first; affected controls and one changed installed user path last with the workflow owner. No old film, new worktree/environment/native copy or parallel measurement project. Full structural UI responsiveness remains active. + +## Working owner implementation + +Existing Widget now owns `_request_layout(required: bool = True)`: it joins the +actual native `_layout_required` work flag without retiring source inputs or +triggering idle on its own. DOMNode supplies the non-rendered no-op hook. Public +Widget.refresh(layout=True) still calls synchronous _invalidate_layout FIRST, then +requests the original Layout publication and preserves its ordinary idle/paint +lifetime. Styles._refresh requests that same publication after existing authored +source invalidation, then retains original paint/idle/notify behavior. Child +inherited refresh requests and DOMNode.reset_styles use the same owner. The third +foreign flag write was reset_styles, not a _refresh_styles method. + +No notify_style_update signature changes: all nine native/eight Toad leaf hooks +retain their independent content/style/worker preparation behavior. New content +mutations in those hooks may legitimately invalidate again; only treating the +same style-frame request as another input mutation is deleted. BodyMeasurement's +synchronous invalidation/participation change remains untouched, as do its genuine +refresh(layout=True) tree publication consumers. No copied frame flags or source +frontier, no cache/map/type, no source/receipt/follow semantic bypass. + +This is a working source checkpoint, not App/live qualified. Final coherent +controls and changed installed workflow follow with the Toad integration owner; +no old film or environment/package launch was made for this source pass. diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index 3d9119a2d3..211432aadb 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -1061,17 +1061,16 @@ def _refresh(self, requests: list[tuple[bool, bool, bool, bool]]) -> None: parent_requests = [repaint for _, _, parent, repaint in requests if parent] if parent_requests and node._parent is not None: node._parent.refresh(repaint=any(parent_requests)) - node.refresh( - layout=any(layout for layout, _, _, _ in requests), - repaint=any(repaint for _, _, _, repaint in requests), - ) + node._request_layout(any(layout for layout, _, _, _ in requests)) + node.refresh(repaint=any(repaint for _, _, _, repaint in requests)) child_requests = [(layout, repaint) for layout, children, _, repaint in requests if children] if child_requests: child_layout = any(layout for layout, _ in child_requests) child_repaint = any(repaint for _, repaint in child_requests) for child in node.walk_children(with_self=False, reverse=True): child.notify_style_update() - child.refresh(layout=child_layout, repaint=child_repaint) + child._request_layout(child_layout) + child.refresh(repaint=child_repaint) def reset(self) -> None: """Reset the rules to initial state.""" diff --git a/src/textual/dom.py b/src/textual/dom.py index 05fc706836..c169552889 100644 --- a/src/textual/dom.py +++ b/src/textual/dom.py @@ -702,11 +702,11 @@ def _post_mount(self): _rich_traceback_omit = True Reactive._initialize_object(self) - def notify_style_update(self) -> None: - """Called after styles are updated. + def _request_layout(self, required: bool = True) -> None: + """Non-rendered DOM nodes have no layout publication.""" - Implement this in a subclass if you want to clear any cached data when the CSS is reloaded. - """ + def notify_style_update(self) -> None: + """Clear subclass resources after original styles change.""" @property def _node_bases(self) -> Sequence[Type[DOMNode]]: @@ -1410,7 +1410,7 @@ def reset_styles(self) -> None: node._css_styles.reset() if isinstance(node, Widget): node._set_dirty() - node._layout_required = True + node._request_layout() def _add_child(self, node: Widget) -> None: """Add a new child node. diff --git a/src/textual/widget.py b/src/textual/widget.py index 78ec964737..9c5c3c0439 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -349,11 +349,9 @@ def _measurement_updated(self) -> None: self._clear_arrangement_cache() def _invalidate_layout(self) -> None: - """Publish changed layout inputs before the idle Layout notification. + """Retire authored layout inputs before requesting a frame. - Scene placement still invalidates every enclosing capture. Measurement - propagation retains the original fixed-size ancestor boundary; scrolling - changes only the scene and never enters this source lifetime. + Capture invalidation spans ancestors; measurement stops at fixed size. """ self._invalidate_subtree_geometry() self._measurement_updated() @@ -4594,8 +4592,7 @@ def refresh( if layout: self._invalidate_layout() - if layout and not self._layout_required: - self._layout_required = True + self._request_layout() if recompose: self._recompose_required = True @@ -4962,6 +4959,9 @@ async def prepare_input(self, event: events.InputEvent) -> None: that geometry; widget mouse handlers run after target selection. """ + def _request_layout(self, required: bool = True) -> None: + self._layout_required |= required + def notify_style_update(self) -> None: self._rich_style_cache.clear() self._visual_style_cache.clear() From a3fe919a4fed230de0380f2587208f909849379f Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Sun, 4 Oct 2026 02:29:02 -0400 Subject: [PATCH 3/4] Preserve App style layout publication and verify source lifetimes --- .../SCOPE.md | 25 ++++++++++ src/textual/app.py | 16 +++---- tests/css/test_programmatic_style_changes.py | 46 +++++++++++++++++++ 3 files changed, 79 insertions(+), 8 deletions(-) diff --git a/evidence/native-style-layout-publication-20261004/SCOPE.md b/evidence/native-style-layout-publication-20261004/SCOPE.md index 5564ddf137..2e7a3f512e 100644 --- a/evidence/native-style-layout-publication-20261004/SCOPE.md +++ b/evidence/native-style-layout-publication-20261004/SCOPE.md @@ -52,3 +52,28 @@ frontier, no cache/map/type, no source/receipt/follow semantic bypass. This is a working source checkpoint, not App/live qualified. Final coherent controls and changed installed workflow follow with the Toad integration owner; no old film or environment/package launch was made for this source pass. + +## Complete caller closure and final native App batch + +App's existing refresh forwards to the current Screen. App therefore overrides +the same _request_layout hook to forward already-invalidated style publication; +its public refresh(layout=True) still retires actual Screen content inputs. +The mounted-App control verifies source retirement once and actual Screen +publication, preventing a silent App-level lost-layout regression. + +Screen's remaining flag writes belong to its native frame lifetime: _on_layout +joins child Layout messages, _on_timer_update consumes the admitted reflow, and +inactive presentation retirement requests reconstruction of released resources. +Screen prevents Widget's default idle handler and consumes its own pending work. +Those decisions remain with Screen. No unrelated flag/map/lifetime is unified. + +The native family closes IMPL-13/BOUND-2: scheduling uses the existing layout +publication owner instead of repeating its source-retirement algorithm. Four +production files change 23 added/24 deleted lines; no source counters removed. +The existing programmatic-style App batch plus two concrete lifetime controls +passed 14 cases in 1.60s. They cover native grid/align children, cohort atomicity, +offset retained paint/hits, raw-style synchronous geometry invalidation without +unsolicited layout, separate frame publication, actual new-content height/paint, +and App-to-Screen layout delivery. This is source App qualification, not an +installed Toad recording or demonstrated CPU gain. The changed installed workflow +remains with Heisenberg's existing holder after its current F4 lease is returned. diff --git a/src/textual/app.py b/src/textual/app.py index f7bc8cdaae..2679dd5416 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -3799,6 +3799,10 @@ async def _on_exit_app(self) -> None: self._begin_batch() # Prevent repaint / layout while shutting down self._message_queue.put_nowait(None) + def _request_layout(self, required: bool = True) -> None: + if self._screen_stack: + self.screen._request_layout(required) + def refresh( self, *, @@ -3806,15 +3810,11 @@ def refresh( layout: bool = False, recompose: bool = False, ) -> Self: - """Refresh the entire screen. + """Refresh the current screen and return this App. - Args: - repaint: Repaint the widget (will call render() again). - layout: Also layout widgets in the view. - recompose: Re-compose the widget (will remove and re-mount children). - - Returns: - The `App` instance. + ``layout=True`` retires actual screen layout inputs; style scheduling + uses _request_layout after its source has already changed. ``repaint`` + invalidates rendered content; ``recompose`` removes and mounts children. """ if recompose: self._recompose_required = recompose diff --git a/tests/css/test_programmatic_style_changes.py b/tests/css/test_programmatic_style_changes.py index 7a9794adc2..0bd44a477b 100644 --- a/tests/css/test_programmatic_style_changes.py +++ b/tests/css/test_programmatic_style_changes.py @@ -5,6 +5,52 @@ from textual.widgets import Label +async def test_style_publication_preserves_source_and_content_lifetimes(): + """A frame request must not retire measurements twice or hide new content.""" + from textual.css.scalar import Scalar + + app = App() + async with app.run_test(size=(40, 12)) as pilot: + widget = Label("one") + await app.mount(widget) + await pilot.pause() + updates = widget._layout_updates + geometry = app.screen._geometry_revision + widget._inline_styles.set_rule("width", Scalar.parse("12")) + assert widget._layout_updates == updates + 1 + assert app.screen._geometry_revision > geometry + assert not widget._layout_required + widget._inline_styles.refresh(layout=True, repaint=False) + assert widget._layout_updates == updates + 1 + await pilot.pause() + assert widget.size.width == 12 + + updates = widget._layout_updates + geometry = app.screen._geometry_revision + widget.update("one\ntwo\nthree") + assert widget._layout_updates == updates + 1 + assert app.screen._geometry_revision > geometry + await pilot.pause() + assert widget.size.height == 3 + assert "three" in widget.render_line(2).text + + +async def test_app_style_publication_keeps_current_screen_layout(): + """App style damage must reach its current Screen without retiring it twice.""" + app = App() + async with app.run_test(size=(40, 12)) as pilot: + widget = Label("current screen") + await app.mount(widget) + await pilot.pause() + updates = app.screen._layout_updates + app.styles.padding = (1, 2) + assert app.screen._layout_updates == updates + 1 + assert app.screen._layout_required + await pilot.pause() + assert not app.screen._layout_required + assert widget in app.screen._compositor.visible_widgets + + @pytest.mark.parametrize( "style, value", [ From bb936deb08cff0b676385007df52759c84356b78 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Sun, 4 Oct 2026 02:30:30 -0400 Subject: [PATCH 4/4] Record native55 caller closure and affected App qualification --- .../NATIVE-APP-QUALIFIED.json | 35 + .../SCOPE.md | 11 +- .../owner-consumers.json | 922 ++++++++++++++++++ 3 files changed, 965 insertions(+), 3 deletions(-) create mode 100644 evidence/native-style-layout-publication-20261004/NATIVE-APP-QUALIFIED.json create mode 100644 evidence/native-style-layout-publication-20261004/owner-consumers.json diff --git a/evidence/native-style-layout-publication-20261004/NATIVE-APP-QUALIFIED.json b/evidence/native-style-layout-publication-20261004/NATIVE-APP-QUALIFIED.json new file mode 100644 index 0000000000..f9e62a4d22 --- /dev/null +++ b/evidence/native-style-layout-publication-20261004/NATIVE-APP-QUALIFIED.json @@ -0,0 +1,35 @@ +{ + "scope": "Native55 source-retirement/frame-request family", + "production_head": "a3fe919a4fed230de0380f2587208f909849379f", + "base": "c1c1ebe5c40499f75dc370c26a7818588114e71d", + "state": "NATIVE_APP_QUALIFIED_INSTALLED_PENDING", + "app_controls": { + "command": "PYTHONPATH=src PYTHONDONTWRITEBYTECODE=1 timeout 60 python -m pytest -q -p no:cacheprovider tests/css/test_programmatic_style_changes.py", + "exit": 0, + "passed": 14, + "elapsed_seconds": 1.6 + }, + "packaged_ratchet": { + "exit": 0, + "positive": {}, + "nonzero": { + "GodClassExcess:src/textual/css/styles.py::Styles": -1 + } + }, + "production_diff": { + "files": 4, + "added": 23, + "deleted": 24 + }, + "evidence": { + "native55-final-app-batch.log": { + "path": "/home/ts/wt/textual-native-subtree-strips-20261002/.artifacts/native55-final-app-batch.log", + "sha256": "1d4524fd568da8973a7da69bd2746b8e9cca768cef9c7d41729fbb9482edc325" + }, + "native55-ratchet.json": { + "path": "/home/ts/wt/textual-native-subtree-strips-20261002/.artifacts/native55-ratchet.json", + "sha256": "67007b3273329121349d605412489386596a2a44420e0ba9ce2b7a839d5822da" + } + }, + "installed_qualification": "Pending one changed workflow with Heisenberg; existing F4 holder is exclusively borrowed. No installed writes/App/capture by this branch. No CPU, latency or smoothness claim." +} diff --git a/evidence/native-style-layout-publication-20261004/SCOPE.md b/evidence/native-style-layout-publication-20261004/SCOPE.md index 2e7a3f512e..ef497edd23 100644 --- a/evidence/native-style-layout-publication-20261004/SCOPE.md +++ b/evidence/native-style-layout-publication-20261004/SCOPE.md @@ -49,9 +49,9 @@ synchronous invalidation/participation change remains untouched, as do its genui refresh(layout=True) tree publication consumers. No copied frame flags or source frontier, no cache/map/type, no source/receipt/follow semantic bypass. -This is a working source checkpoint, not App/live qualified. Final coherent -controls and changed installed workflow follow with the Toad integration owner; -no old film or environment/package launch was made for this source pass. +The first published checkpoint was source-only. The completed native App batch +is recorded below; installed Toad qualification remains pending. No old film or +environment/package launch was made for this source pass. ## Complete caller closure and final native App batch @@ -77,3 +77,8 @@ unsolicited layout, separate frame publication, actual new-content height/paint, and App-to-Screen layout delivery. This is source App qualification, not an installed Toad recording or demonstrated CPU gain. The changed installed workflow remains with Heisenberg's existing holder after its current F4 lease is returned. + +The original packaged F0 ratchet exits 0 against main c1c1: no admitted +measure grows; Styles GodClassExcess decreases by one, all other deltas zero. +App/DOM/Widget source-to-frame hooks replace existing family work; no waiver, +threshold or scanner change. Hosted Debt must bind the final published head. diff --git a/evidence/native-style-layout-publication-20261004/owner-consumers.json b/evidence/native-style-layout-publication-20261004/owner-consumers.json new file mode 100644 index 0000000000..e3e157f87b --- /dev/null +++ b/evidence/native-style-layout-publication-20261004/owner-consumers.json @@ -0,0 +1,922 @@ +{ + "before": { + "revision": "c1c1ebe5c40499f75dc370c26a7818588114e71d", + "modules": 249, + "omitted": [], + "sites": [ + { + "path": "src/textual/app.py", + "line": 3825, + "call": "self.screen.refresh(repaint=repaint, layout=layout)" + }, + { + "path": "src/textual/app.py", + "line": 4808, + "call": "self.refresh(layout=True)" + }, + { + "path": "src/textual/app.py", + "line": 3703, + "call": "widget.notify_style_update()" + }, + { + "path": "src/textual/app.py", + "line": 4447, + "call": "parent.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 154, + "call": "obj.refresh(layout=self.layout, children=self.refresh_children)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 260, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 307, + "call": "obj.refresh(layout=True, children=self.refresh_children)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 657, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 688, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 741, + "call": "obj.refresh(layout=True, children=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 890, + "call": "obj.refresh(layout=self._layout, children=self._refresh_children, parent=self._refresh_parent)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 150, + "call": "obj.refresh(layout=self.layout, children=self.refresh_children)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 224, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 288, + "call": "obj.refresh(layout=True, children=self.refresh_children)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 363, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 496, + "call": "obj.refresh(layout=layout)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 569, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 572, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 610, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 626, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 724, + "call": "obj.refresh(layout=True, children=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 787, + "call": "obj.refresh(layout=True, repaint=False)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 942, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 947, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 963, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 387, + "call": "obj.refresh(layout=self.affects_geometry(current_value, new_value))" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 790, + "call": "obj.refresh(layout=True, repaint=False)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 813, + "call": "obj.refresh(layout=True, repaint=False)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 968, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 971, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/query.py", + "line": 453, + "call": "node.refresh(repaint=repaint, layout=layout, recompose=recompose)" + }, + { + "path": "src/textual/css/query.py", + "line": 436, + "call": "node.refresh(layout=True)" + }, + { + "path": "src/textual/css/styles.py", + "line": 1060, + "call": "node.notify_style_update()" + }, + { + "path": "src/textual/css/styles.py", + "line": 1064, + "call": "node.refresh(layout=any((layout for layout, _, _, _ in requests)), repaint=any((repaint for _, _, _, repaint in requests)))" + }, + { + "path": "src/textual/css/styles.py", + "line": 1574, + "call": "self._inline_styles.refresh(layout=layout, children=children, parent=parent, repaint=repaint)" + }, + { + "path": "src/textual/css/styles.py", + "line": 990, + "call": "node._style_rules_updated(Styles._revision, geometry)" + }, + { + "path": "src/textual/css/styles.py", + "line": 1073, + "call": "child.notify_style_update()" + }, + { + "path": "src/textual/css/styles.py", + "line": 1074, + "call": "child.refresh(layout=child_layout, repaint=child_repaint)" + }, + { + "path": "src/textual/dom.py", + "line": 172, + "definition": "_style_rules_updated" + }, + { + "path": "src/textual/dom.py", + "line": 705, + "definition": "notify_style_update" + }, + { + "path": "src/textual/dom.py", + "line": 531, + "call": "self.refresh(layout=True)" + }, + { + "path": "src/textual/dom.py", + "line": 1045, + "call": "self.refresh(layout=True)" + }, + { + "path": "src/textual/dom.py", + "line": 1413, + "write": "node._layout_required = True" + }, + { + "path": "src/textual/dom.py", + "line": 1830, + "call": "self._inline_styles.refresh(layout=True)" + }, + { + "path": "src/textual/reactive.py", + "line": 432, + "call": "obj.refresh(repaint=self._repaint, layout=request_layout, recompose=self._recompose)" + }, + { + "path": "src/textual/screen.py", + "line": 1604, + "write": "self._layout_required = True" + }, + { + "path": "src/textual/screen.py", + "line": 1479, + "write": "self._layout_required = True" + }, + { + "path": "src/textual/screen.py", + "line": 1274, + "write": "self._layout_required = False" + }, + { + "path": "src/textual/widget.py", + "line": 344, + "definition": "_measurement_updated" + }, + { + "path": "src/textual/widget.py", + "line": 351, + "definition": "_invalidate_layout" + }, + { + "path": "src/textual/widget.py", + "line": 4965, + "definition": "notify_style_update" + }, + { + "path": "src/textual/widget.py", + "line": 514, + "write": "self._layout_required = False" + }, + { + "path": "src/textual/widget.py", + "line": 859, + "call": "self.refresh(layout=True)" + }, + { + "path": "src/textual/widget.py", + "line": 1456, + "call": "self._invalidate_layout()" + }, + { + "path": "src/textual/widget.py", + "line": 1829, + "call": "previous.refresh(layout=True)" + }, + { + "path": "src/textual/widget.py", + "line": 1830, + "call": "parent.refresh(layout=True)" + }, + { + "path": "src/textual/widget.py", + "line": 4598, + "write": "self._layout_required = True" + }, + { + "path": "src/textual/widget.py", + "line": 4969, + "call": "super().notify_style_update()" + }, + { + "path": "src/textual/widget.py", + "line": 883, + "call": "self.refresh(layout=True)" + }, + { + "path": "src/textual/widget.py", + "line": 1763, + "call": "self.refresh(layout=True)" + }, + { + "path": "src/textual/widget.py", + "line": 4596, + "call": "self._invalidate_layout()" + }, + { + "path": "src/textual/widget.py", + "line": 4850, + "write": "self._layout_required = False" + }, + { + "path": "src/textual/widget.py", + "line": 179, + "call": "self._parent.refresh(layout=True)" + }, + { + "path": "src/textual/widgets/_data_table.py", + "line": 1123, + "definition": "notify_style_update" + }, + { + "path": "src/textual/widgets/_data_table.py", + "line": 1124, + "call": "super().notify_style_update()" + }, + { + "path": "src/textual/widgets/_data_table.py", + "line": 1830, + "call": "self.refresh(layout=True)" + }, + { + "path": "src/textual/widgets/_data_table.py", + "line": 1868, + "call": "self.refresh(layout=True)" + }, + { + "path": "src/textual/widgets/_digits.py", + "line": 75, + "call": "self.refresh(layout=layout_required)" + }, + { + "path": "src/textual/widgets/_footer.py", + "line": 424, + "call": "key.refresh(layout=changed_size)" + }, + { + "path": "src/textual/widgets/_input.py", + "line": 550, + "call": "self.refresh(layout=True)" + }, + { + "path": "src/textual/widgets/_log.py", + "line": 95, + "definition": "notify_style_update" + }, + { + "path": "src/textual/widgets/_log.py", + "line": 97, + "call": "super().notify_style_update()" + }, + { + "path": "src/textual/widgets/_markdown.py", + "line": 922, + "definition": "notify_style_update" + }, + { + "path": "src/textual/widgets/_markdown.py", + "line": 926, + "call": "super().notify_style_update()" + }, + { + "path": "src/textual/widgets/_option_list.py", + "line": 708, + "definition": "notify_style_update" + }, + { + "path": "src/textual/widgets/_option_list.py", + "line": 710, + "call": "super().notify_style_update()" + }, + { + "path": "src/textual/widgets/_option_list.py", + "line": 410, + "call": "self.refresh(layout=self.styles.auto_dimensions)" + }, + { + "path": "src/textual/widgets/_pretty.py", + "line": 56, + "call": "self.refresh(layout=True)" + }, + { + "path": "src/textual/widgets/_rich_log.py", + "line": 128, + "definition": "notify_style_update" + }, + { + "path": "src/textual/widgets/_rich_log.py", + "line": 129, + "call": "super().notify_style_update()" + }, + { + "path": "src/textual/widgets/_static.py", + "line": 77, + "call": "self.refresh(layout=True)" + }, + { + "path": "src/textual/widgets/_static.py", + "line": 110, + "call": "self.refresh(layout=layout)" + }, + { + "path": "src/textual/widgets/_text_area.py", + "line": 816, + "definition": "notify_style_update" + }, + { + "path": "src/textual/widgets/_text_area.py", + "line": 818, + "call": "super().notify_style_update()" + }, + { + "path": "src/textual/widgets/_toggle_button.py", + "line": 192, + "call": "self.refresh(layout=True)" + }, + { + "path": "src/textual/widgets/_tree.py", + "line": 1467, + "definition": "notify_style_update" + }, + { + "path": "src/textual/widgets/_tree.py", + "line": 1079, + "call": "self.refresh(layout=True)" + }, + { + "path": "src/textual/widgets/_tree.py", + "line": 1468, + "call": "super().notify_style_update()" + } + ] + }, + "after": { + "revision": "a3fe919a4fed230de0380f2587208f909849379f", + "modules": 249, + "omitted": [], + "sites": [ + { + "path": "src/textual/app.py", + "line": 3802, + "definition": "_request_layout" + }, + { + "path": "src/textual/app.py", + "line": 3804, + "call": "self.screen._request_layout(required)" + }, + { + "path": "src/textual/app.py", + "line": 3825, + "call": "self.screen.refresh(repaint=repaint, layout=layout)" + }, + { + "path": "src/textual/app.py", + "line": 4808, + "call": "self.refresh(layout=True)" + }, + { + "path": "src/textual/app.py", + "line": 3703, + "call": "widget.notify_style_update()" + }, + { + "path": "src/textual/app.py", + "line": 4447, + "call": "parent.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 154, + "call": "obj.refresh(layout=self.layout, children=self.refresh_children)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 260, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 307, + "call": "obj.refresh(layout=True, children=self.refresh_children)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 657, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 688, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 741, + "call": "obj.refresh(layout=True, children=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 890, + "call": "obj.refresh(layout=self._layout, children=self._refresh_children, parent=self._refresh_parent)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 150, + "call": "obj.refresh(layout=self.layout, children=self.refresh_children)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 224, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 288, + "call": "obj.refresh(layout=True, children=self.refresh_children)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 363, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 496, + "call": "obj.refresh(layout=layout)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 569, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 572, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 610, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 626, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 724, + "call": "obj.refresh(layout=True, children=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 787, + "call": "obj.refresh(layout=True, repaint=False)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 942, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 947, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 963, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 387, + "call": "obj.refresh(layout=self.affects_geometry(current_value, new_value))" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 790, + "call": "obj.refresh(layout=True, repaint=False)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 813, + "call": "obj.refresh(layout=True, repaint=False)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 968, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/_style_properties.py", + "line": 971, + "call": "obj.refresh(layout=True)" + }, + { + "path": "src/textual/css/query.py", + "line": 453, + "call": "node.refresh(repaint=repaint, layout=layout, recompose=recompose)" + }, + { + "path": "src/textual/css/query.py", + "line": 436, + "call": "node.refresh(layout=True)" + }, + { + "path": "src/textual/css/styles.py", + "line": 1060, + "call": "node.notify_style_update()" + }, + { + "path": "src/textual/css/styles.py", + "line": 1064, + "call": "node._request_layout(any((layout for layout, _, _, _ in requests)))" + }, + { + "path": "src/textual/css/styles.py", + "line": 1573, + "call": "self._inline_styles.refresh(layout=layout, children=children, parent=parent, repaint=repaint)" + }, + { + "path": "src/textual/css/styles.py", + "line": 990, + "call": "node._style_rules_updated(Styles._revision, geometry)" + }, + { + "path": "src/textual/css/styles.py", + "line": 1071, + "call": "child.notify_style_update()" + }, + { + "path": "src/textual/css/styles.py", + "line": 1072, + "call": "child._request_layout(child_layout)" + }, + { + "path": "src/textual/dom.py", + "line": 172, + "definition": "_style_rules_updated" + }, + { + "path": "src/textual/dom.py", + "line": 705, + "definition": "_request_layout" + }, + { + "path": "src/textual/dom.py", + "line": 708, + "definition": "notify_style_update" + }, + { + "path": "src/textual/dom.py", + "line": 531, + "call": "self.refresh(layout=True)" + }, + { + "path": "src/textual/dom.py", + "line": 1045, + "call": "self.refresh(layout=True)" + }, + { + "path": "src/textual/dom.py", + "line": 1830, + "call": "self._inline_styles.refresh(layout=True)" + }, + { + "path": "src/textual/dom.py", + "line": 1413, + "call": "node._request_layout()" + }, + { + "path": "src/textual/reactive.py", + "line": 432, + "call": "obj.refresh(repaint=self._repaint, layout=request_layout, recompose=self._recompose)" + }, + { + "path": "src/textual/screen.py", + "line": 1604, + "write": "self._layout_required = True" + }, + { + "path": "src/textual/screen.py", + "line": 1479, + "write": "self._layout_required = True" + }, + { + "path": "src/textual/screen.py", + "line": 1274, + "write": "self._layout_required = False" + }, + { + "path": "src/textual/widget.py", + "line": 344, + "definition": "_measurement_updated" + }, + { + "path": "src/textual/widget.py", + "line": 351, + "definition": "_invalidate_layout" + }, + { + "path": "src/textual/widget.py", + "line": 4962, + "definition": "_request_layout" + }, + { + "path": "src/textual/widget.py", + "line": 4965, + "definition": "notify_style_update" + }, + { + "path": "src/textual/widget.py", + "line": 512, + "write": "self._layout_required = False" + }, + { + "path": "src/textual/widget.py", + "line": 4963, + "write": "self._layout_required |= required" + }, + { + "path": "src/textual/widget.py", + "line": 857, + "call": "self.refresh(layout=True)" + }, + { + "path": "src/textual/widget.py", + "line": 1454, + "call": "self._invalidate_layout()" + }, + { + "path": "src/textual/widget.py", + "line": 1827, + "call": "previous.refresh(layout=True)" + }, + { + "path": "src/textual/widget.py", + "line": 1828, + "call": "parent.refresh(layout=True)" + }, + { + "path": "src/textual/widget.py", + "line": 4969, + "call": "super().notify_style_update()" + }, + { + "path": "src/textual/widget.py", + "line": 881, + "call": "self.refresh(layout=True)" + }, + { + "path": "src/textual/widget.py", + "line": 1761, + "call": "self.refresh(layout=True)" + }, + { + "path": "src/textual/widget.py", + "line": 4594, + "call": "self._invalidate_layout()" + }, + { + "path": "src/textual/widget.py", + "line": 4595, + "call": "self._request_layout()" + }, + { + "path": "src/textual/widget.py", + "line": 4847, + "write": "self._layout_required = False" + }, + { + "path": "src/textual/widget.py", + "line": 179, + "call": "self._parent.refresh(layout=True)" + }, + { + "path": "src/textual/widgets/_data_table.py", + "line": 1123, + "definition": "notify_style_update" + }, + { + "path": "src/textual/widgets/_data_table.py", + "line": 1124, + "call": "super().notify_style_update()" + }, + { + "path": "src/textual/widgets/_data_table.py", + "line": 1830, + "call": "self.refresh(layout=True)" + }, + { + "path": "src/textual/widgets/_data_table.py", + "line": 1868, + "call": "self.refresh(layout=True)" + }, + { + "path": "src/textual/widgets/_digits.py", + "line": 75, + "call": "self.refresh(layout=layout_required)" + }, + { + "path": "src/textual/widgets/_footer.py", + "line": 424, + "call": "key.refresh(layout=changed_size)" + }, + { + "path": "src/textual/widgets/_input.py", + "line": 550, + "call": "self.refresh(layout=True)" + }, + { + "path": "src/textual/widgets/_log.py", + "line": 95, + "definition": "notify_style_update" + }, + { + "path": "src/textual/widgets/_log.py", + "line": 97, + "call": "super().notify_style_update()" + }, + { + "path": "src/textual/widgets/_markdown.py", + "line": 922, + "definition": "notify_style_update" + }, + { + "path": "src/textual/widgets/_markdown.py", + "line": 926, + "call": "super().notify_style_update()" + }, + { + "path": "src/textual/widgets/_option_list.py", + "line": 708, + "definition": "notify_style_update" + }, + { + "path": "src/textual/widgets/_option_list.py", + "line": 710, + "call": "super().notify_style_update()" + }, + { + "path": "src/textual/widgets/_option_list.py", + "line": 410, + "call": "self.refresh(layout=self.styles.auto_dimensions)" + }, + { + "path": "src/textual/widgets/_pretty.py", + "line": 56, + "call": "self.refresh(layout=True)" + }, + { + "path": "src/textual/widgets/_rich_log.py", + "line": 128, + "definition": "notify_style_update" + }, + { + "path": "src/textual/widgets/_rich_log.py", + "line": 129, + "call": "super().notify_style_update()" + }, + { + "path": "src/textual/widgets/_static.py", + "line": 77, + "call": "self.refresh(layout=True)" + }, + { + "path": "src/textual/widgets/_static.py", + "line": 110, + "call": "self.refresh(layout=layout)" + }, + { + "path": "src/textual/widgets/_text_area.py", + "line": 816, + "definition": "notify_style_update" + }, + { + "path": "src/textual/widgets/_text_area.py", + "line": 818, + "call": "super().notify_style_update()" + }, + { + "path": "src/textual/widgets/_toggle_button.py", + "line": 192, + "call": "self.refresh(layout=True)" + }, + { + "path": "src/textual/widgets/_tree.py", + "line": 1467, + "definition": "notify_style_update" + }, + { + "path": "src/textual/widgets/_tree.py", + "line": 1079, + "call": "self.refresh(layout=True)" + }, + { + "path": "src/textual/widgets/_tree.py", + "line": 1468, + "call": "super().notify_style_update()" + } + ] + }, + "limits": "AST shows syntax and declared inheritance; it does not resolve arbitrary dynamic method replacement. All native9/Toad8 style hooks retain their existing ABI and source-specific behavior. Screen consumes native reflow; Widget posts child Layout; App delegates current-screen style work." +}