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
45 changes: 45 additions & 0 deletions evidence/mutation-box-admission-20261007/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Mutation-box admission

Ordinary Widget box measurements previously called is_attached before asking
Compositor whether the widget owns a held mutation box. The original retained
channel cProfile contains about28,000 box queries, nearly all taking that check.
Its wall/cumulative profile is not a reliable attribution of total UI CPU.

The compositor already owns transient layout_geometry, filled only for the
acquired mutation roots during original arrangement and restored in finally.
mutation_box now checks membership first and attachment only for a candidate.
Widget still uses its actual screen descriptor. NoScreen permits ordinary
measurement only when is_attached is false; an attached source retains the
original NoScreen refusal. Other errors propagate. Custom attachment and screen
behavior stays dynamically dispatched; no snapshot or cached lifetime is added.

Native arrangement, style/child epochs, full and visible maps, hit geometry,
clipping, mutation exclusions and held-box margins remain unchanged. This removes
unnecessary attachment traversal from ordinary measurement; it does not reduce
required scroll placement frames or certify all source invalidations redundant.

Existing Package parsed250 production modules with zero omissions; mutation_box
has exactly one consumer in Widget._get_box_model. New ordinary/detached sizing
plus affected held-root geometry/callback publication controls:2PASS/.49s.
Final attached/custom NoScreen refusal control:1PASS/.36s.

One original unprofiled channel-history driver run retained installed Toad ef421
and Core d4b9, replacing only native source at e7b105fd4. Native includes ffcb82
and PreparedTextArea97. Original readonly runtime:
/home/ts/wt/toad-sidebar-context-pointer-20261006/.artifacts/channel-history-delivery-20261007/runtime
Driver history_check.py from the same artifact; HISTORY_PROFILE=0 and new owned
output /home/ts/.cache/agent-scratch/native98-channel-history-20261007.

160 authored initial private messages,20 incoming private messages,30 PageUp;
no provider, public input, package or installed pin change. Terminal0, empty
stderr,120 mounted rows/oldest sequence33, no App exception. p95 loop28.59ms,
maximum134.26ms,38 intervals over50ms. Matching unprofiled baseline p95 28.84ms,
maximum159.89ms,46 intervals over50ms. One pair establishes no reliable overall
improvement; meaningful pauses remain. Pilot global-idle waits remain included.
Original runtime_fixture owns teardown; controller joined and no driver/output
process reference remained. Exact child birth identities were not recorded.

The subsequent final source change only restores the original attached NoScreen
refusal, exercised separately above. The normal mounted App path is unchanged;
no repeat App run was needed. Source-only/author-App evidence, not installed
physical acceptance. Parent owns durable delivery and affected live observation.
11 changes: 11 additions & 0 deletions evidence/mutation-box-admission-20261007/app-result.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"main_loop_samples": 2504,
"median_ms": 5.9183514676988125,
"p95_ms": 28.594268020242453,
"max_ms": 134.25777899101377,
"gaps_above_50ms": 38,
"mounted_rows": 120,
"oldest_sequence": 33,
"app_error": null,
"profiling": false
}
2 changes: 1 addition & 1 deletion src/textual/_compositor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1664,7 +1664,7 @@ def _publication_cuts(self, exclusions: Iterable[Region]) -> list[list[int]]:

def mutation_box(self, widget: Widget) -> BoxModel | None:
"""Borrow the committed root box; an unplaced mutation has no size yet."""
if widget not in self._layout_geometry:
if widget not in self._layout_geometry or not widget.is_attached:
return None
geometry = self._layout_geometry[widget]
size = Size() if geometry is None else geometry.region.size
Expand Down
10 changes: 9 additions & 1 deletion src/textual/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -1945,7 +1945,15 @@ def _get_box_model(
Returns:
The size and margin for this widget.
"""
if self.is_attached and (box := self.screen._compositor.mutation_box(self)) is not None:
try:
box = self.screen._compositor.mutation_box(self)
except NoScreen:
# Unmounted widgets still have ordinary native measurements.
# An attached source with no screen retains the original refusal.
if self.is_attached:
raise
box = None
if box is not None:
# Parent layouts measure the same committed box as Compositor;
# never descend through an actively changing child to size siblings.
return box
Expand Down
64 changes: 64 additions & 0 deletions tests/test_mutation_box_admission.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from fractions import Fraction

import pytest

from textual.app import App
from textual.widget import NoScreen
from textual.widgets import Static


class ObservedAttachment(Static):
attachment_reads = 0

@property
def is_attached(self) -> bool:
self.attachment_reads += 1
return super().is_attached


async def test_ordinary_box_measurement_does_not_acquire_mutation_attachment():
app = App()
async with app.run_test(size=(60, 20)) as pilot:
widget = ObservedAttachment("native content")
widget.styles.width = 20
widget.styles.height = 3
await app.mount(widget)
await pilot.pause()
widget.attachment_reads = 0
boxes = [widget._get_box_model(app.size, app.size, Fraction(60), Fraction(20))
for _ in range(20)]
assert widget.attachment_reads == 0
assert all(box == boxes[0] for box in boxes)
assert (boxes[0].width, boxes[0].height) == (20, 3)

# NoScreen is the original unmounted boundary, not a refusal to measure
# an ordinary authored box in this active application.
detached = ObservedAttachment("not mounted")
detached.styles.width = 7
detached.styles.height = 2
box = detached._get_box_model(app.size, app.size, Fraction(60), Fraction(20))
assert (box.width, box.height) == (7, 2)


class RefusedScreen(ObservedAttachment):
refuse_screen = False

@property
def screen(self):
if self.refuse_screen:
raise NoScreen("original custom source refused its screen")
return super().screen


async def test_attached_custom_screen_refusal_is_not_unmounted_measurement():
app = App()
async with app.run_test() as pilot:
widget = RefusedScreen("attached source")
await app.mount(widget)
await pilot.pause()
widget.refuse_screen = True
try:
with pytest.raises(NoScreen, match="original custom source"):
widget._get_box_model(app.size, app.size, Fraction(80), Fraction(24))
finally:
widget.refuse_screen = False
Loading