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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions evidence/focus-rule-supply-20261006/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,11 @@ AST before: 249 native production, 467 test and 288 Toad production modules, zer
Seven focused controls passed, including real mounted descendants/components across CSS read and reparse. One original matched sidebar App finished zero with empty stderr, 508 widgets/10 tabs, no provider. Right profile: same three focus updates, own declaration discovery 7.05ms -> .47ms and full method 19.03ms -> 11.22ms. Left full method 18.51ms -> 7.99ms. This is a single source headless observation. First-display medians 41.8/42.2ms versus 40.1/45.6ms, tails vary, paint was slower: no reliable overall frame or live speedup claim.

RESULT.json binds original raw profiles/logs and source evidence. No new build, install, provider, public operation or repeated benchmark. The existing empty-damage pending_for checkpoint remains on this same PR, independently scoped.

## Fresh live geometry profile followthrough

The existing sidebar-live-frame-profile-20261006 profile has two sampling errors. It shows deferred_regions under pending_for, and _damage_geometry under scrolling reflow; transition counts do not measure CPU time or damage state.

A concrete redundant decision remained in Compositor._damage_geometry: when the entire screen was already dirty, track_damage was false but the method still compared every new placement with the prior map. Those comparisons supplied no damage or resize result. The owner now wakes the original idle publication and returns immediately for that state; partial damage keeps both previous/current clipped rectangles and removed placements. Held exclusions still partition the retained full-screen damage at rendering. No root-bounds shortcut, ancestry index or cache.

Existing Package parsed 249 production modules with zero omissions: one _damage_geometry declaration, three callers in reflow/reflow_visible/full_map, no additional result consumers. New source compiles and diff-check passes. The prior CSS App result remains at 6c5c0a055, before this additional geometry change. No App/profile/control rerun or performance claim for this source-only reduction. Nonempty descendant geometry still requires the original membership/overflow relation; the samples alone do not establish redundant arrangement or its duration.
9 changes: 9 additions & 0 deletions evidence/full-damage-geometry-20261006/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Full-damage geometry comparison

Compositor owns committed geometry and pending paint damage. Reflow, visible reflow and full-map acquisition commit the new placements before _damage_geometry. When the original damage set already contains the current full-screen rectangle, the old method disabled all additions but still compared every new placement. Those comparison results supplied no resize, placement or damage output. Return after the same original owner.check_idle instead; partial damage still retains both old/current clipped rectangles and removed placements. The renderer continues to exclude held cells from full damage and retain those cells until release.

Source evidence parses 249 production modules before and after, zero omissions; one declaration and the same three consumers. No ancestry index, cached geometry, root-bounds shortcut, state or callback owner change. Arbitrary external dynamic callers are not resolved by lexical AST.

The changed existing control commits real geometry under full damage while holding a mounted subtree. It checks exact damage retention, original placements/hits, excluded cut cells, unrelated paint, quiet held callbacks and release. PASS/.39s. First assertion incorrectly used Screen._refresh_layout, which also paints before returning; that valid publication consumed the full-screen region. A correction initially landed at the wrong method call, leaving the selected test unchanged; both refusals remain. The final control calls the existing Compositor.reflow before the original partial publisher; no assertion was relaxed.

One original source sidebar App PASS/empty stderr, 508 widgets/10 tabs/no provider. Left/right first-display medians38.2/39.1ms and tails vary; no reliable overall speedup, installed, live or terminal-pixel claim. Prior CSS controls were not repeated. RESULT.json binds the actual source and original logs/profiles. New source result remains separate from the completed live cohort.
22 changes: 22 additions & 0 deletions evidence/full-damage-geometry-20261006/RESULT.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"source": "cbeaf02dbe2a8a1bd84b2a3f845d12cb4a42a025",
"contract": "1 passed in 0.39s",
"app_exit": 0,
"app_widgets": 508,
"app_tabs": 10,
"providers": 0,
"stderr_bytes": 0,
"toad_source": "d7273c666ea04ff13c1a677bb5851637da273490",
"core_source": "37563f36f6c54a2a1272e90c7c90c2108b1026e6",
"limit": "Source App completed headless display only; no installed/live/physical acceptance or reliable overall performance improvement.",
"keepers": {
"evidence/full-damage-geometry-20261006/source.json": "d9ed028d10a4d44a6973ab2a702cb893a65ae48bd6ca282bf833ba024b5c7b3f",
"evidence/full-damage-geometry-20261006/geometry-control.log": "036bb9390fb408c39eb19a61d865bc4044aa29eeea8139e62a988a5a83930a4d",
"evidence/full-damage-geometry-20261006/original-after-publication-control-refusal.log": "5e5f39f7df94ab4772d1b0dc021facb9880d1d4461292fb55d5d523fbf9670f8",
"evidence/full-damage-geometry-20261006/unchanged-target-after-wrong-edit-refusal.log": "5e5f39f7df94ab4772d1b0dc021facb9880d1d4461292fb55d5d523fbf9670f8",
"/home/ts/.cache/agent-scratch/full-damage-geometry-20261006/app.stdout.log": "5ec6d51cba69af35fb3bc449e236531adda47283ed25cdedb12ef816c0b23f20",
"/home/ts/.cache/agent-scratch/full-damage-geometry-20261006/app.stderr.log": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"/home/ts/.cache/agent-scratch/full-damage-geometry-20261006/toggle-channels.pstats": "4ee1510a69f80ca1c2027353de43805b55ac331f4123a6879e453beb3db11921",
"/home/ts/.cache/agent-scratch/full-damage-geometry-20261006/toggle-thread.pstats": "dfb8aa7a5de3c27cefcf3d193bcccf7d4d9cdabd45c37b831a01fe8f34055a6a"
}
}
2 changes: 2 additions & 0 deletions evidence/full-damage-geometry-20261006/geometry-control.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
. [100%]
1 passed in 0.39s
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
F [100%]
=================================== FAILURES ===================================
_________ test_partial_publication_keeps_geometry_and_owner_callbacks __________
tests/test_batch_paint_admission.py:215: in test_partial_publication_keeps_geometry_and_owner_callbacks
assert compositor._dirty_regions == full_damage
E assert {Region(x=0, ...20, height=3)} == {Region(x=0, ...20, height=3)}
E
E Extra items in the right set:
E Region(x=0, y=0, width=40, height=8)
E Use -v to get more diff
=========================== short test summary info ============================
FAILED tests/test_batch_paint_admission.py::test_partial_publication_keeps_geometry_and_owner_callbacks
1 failed in 0.29s
64 changes: 64 additions & 0 deletions evidence/full-damage-geometry-20261006/source.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
[
{
"revision": "7efdd92c37762cd963e02a51dfc5ef349342d045",
"parsed": 249,
"omissions": [],
"sites": [
[
"src/textual/_compositor.py",
782,
"declaration",
"def _damage_geometry(self, before: Mapping[Widget, MapGeometry], after: Mapping[Widget, MapGeometry], owner: Widget) -> None:\n \"\"\"Retain both paint rectangles from the original scene change.\n\n Compare each new placement with its previous one. Retain both clipped\n rectangles without hashing complete geometry records into temporary\n sets. Widget size publication owns resize notification separately.\n \"\"\"\n track_damage = self.size.region not in self._dirty_regions\n for widget, geometry in after.items():\n previous = before.get(widget)\n if previous == geometry:\n continue\n if previous is not None:\n if track_damage and (region := previous.visible_region):\n self._dirty_regions.add(region)\n if track_damage and (region := geometry.visible_region):\n self._dirty_regions.add(region)\n if track_damage:\n for widget, geometry in before.items():\n if widget not in after and (region := geometry.visible_region):\n self._dirty_regions.add(region)\n if self._dirty_regions:\n owner.check_idle()"
],
[
"src/textual/_compositor.py",
741,
"consumer",
"self._damage_geometry(previous_scene, map, parent)"
],
[
"src/textual/_compositor.py",
778,
"consumer",
"self._damage_geometry(previous_scene, map, parent)"
],
[
"src/textual/_compositor.py",
821,
"consumer",
"self._damage_geometry(previous, map, self.root)"
]
]
},
{
"revision": "cbeaf02dbe2a8a1bd84b2a3f845d12cb4a42a025",
"parsed": 249,
"omissions": [],
"sites": [
[
"src/textual/_compositor.py",
782,
"declaration",
"def _damage_geometry(self, before: Mapping[Widget, MapGeometry], after: Mapping[Widget, MapGeometry], owner: Widget) -> None:\n \"\"\"Retain both paint rectangles from the original scene change.\n\n Compare each new placement with its previous one. Retain both clipped\n rectangles without hashing complete geometry records into temporary\n sets. Widget size publication owns resize notification separately.\n \"\"\"\n if self.size.region in self._dirty_regions:\n owner.check_idle()\n return\n for widget, geometry in after.items():\n previous = before.get(widget)\n if previous == geometry:\n continue\n if previous is not None:\n if (region := previous.visible_region):\n self._dirty_regions.add(region)\n if (region := geometry.visible_region):\n self._dirty_regions.add(region)\n for widget, geometry in before.items():\n if widget not in after and (region := geometry.visible_region):\n self._dirty_regions.add(region)\n if self._dirty_regions:\n owner.check_idle()"
],
[
"src/textual/_compositor.py",
741,
"consumer",
"self._damage_geometry(previous_scene, map, parent)"
],
[
"src/textual/_compositor.py",
778,
"consumer",
"self._damage_geometry(previous_scene, map, parent)"
],
[
"src/textual/_compositor.py",
822,
"consumer",
"self._damage_geometry(previous, map, self.root)"
]
]
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
F [100%]
=================================== FAILURES ===================================
_________ test_partial_publication_keeps_geometry_and_owner_callbacks __________
tests/test_batch_paint_admission.py:215: in test_partial_publication_keeps_geometry_and_owner_callbacks
assert compositor._dirty_regions == full_damage
E assert {Region(x=0, ...20, height=3)} == {Region(x=0, ...20, height=3)}
E
E Extra items in the right set:
E Region(x=0, y=0, width=40, height=8)
E Use -v to get more diff
=========================== short test summary info ============================
FAILED tests/test_batch_paint_admission.py::test_partial_publication_keeps_geometry_and_owner_callbacks
1 failed in 0.29s
15 changes: 8 additions & 7 deletions src/textual/_compositor.py
Original file line number Diff line number Diff line change
Expand Up @@ -789,20 +789,21 @@ def _damage_geometry(
rectangles without hashing complete geometry records into temporary
sets. Widget size publication owns resize notification separately.
"""
track_damage = self.size.region not in self._dirty_regions
if self.size.region in self._dirty_regions:
owner.check_idle()
return
for widget, geometry in after.items():
previous = before.get(widget)
if previous == geometry:
continue
if previous is not None:
if track_damage and (region := previous.visible_region):
if region := previous.visible_region:
self._dirty_regions.add(region)
if track_damage and (region := geometry.visible_region):
if region := geometry.visible_region:
self._dirty_regions.add(region)
for widget, geometry in before.items():
if widget not in after and (region := geometry.visible_region):
self._dirty_regions.add(region)
if track_damage:
for widget, geometry in before.items():
if widget not in after and (region := geometry.visible_region):
self._dirty_regions.add(region)
if self._dirty_regions:
owner.check_idle()

Expand Down
9 changes: 9 additions & 0 deletions tests/test_batch_paint_admission.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,15 @@ async def test_partial_publication_keeps_geometry_and_owner_callbacks():
assert screen.publications and all(roots == (holder,) for roots in screen.publications)
assert compositor._dirty_regions
compositor._dirty_regions.add(compositor.size.region)
full_damage = set(compositor._dirty_regions)
# Reflow still commits geometry while full-screen damage already
# owns repaint. Held placements must survive and damage must remain
# available for the partial publisher, rather than being consumed.
compositor.reflow(screen, screen.size)
assert compositor._dirty_regions == full_damage
assert compositor.find_widget(holder) == before
assert compositor.find_widget(original) == original_geometry
assert compositor.get_widget_at(1, 1)[0] is original
update = compositor.render_update(excluded_regions=compositor.deferred_regions((holder,)))
assert isinstance(update, ChopsUpdate)
cells = list(ChopsUpdate._span_cuts(update.spans, update.cuts, 0))
Expand Down
Loading