Skip to content

native: compare like sizes in Yoga's stricter-size cache rule - #1860

Merged
simonklee merged 4 commits into
mainfrom
harden/374-yoga-incremental-layout-differs-from-fresh
Oct 30, 2026
Merged

simonklee merged 4 commits into
mainfrom
harden/374-yoga-incremental-layout-differs-from-fresh

Conversation

@simonklee

Copy link
Copy Markdown
Member

Yoga's measure cache compared the new available size without the node's margin with the old size with it. A width or height that grew by less than the margin looked like a shrink, so a measured node kept the size it had for the old width. A text with margin: 1 and alignSelf: "flex-start" wrapped at 18 columns after its parent grew from 20 to 21; a fresh layout wraps at 19. It stayed wrong until the width or text changed again.

Both sides of the rule now exclude a positive margin, as in the open upstream fix (react/yoga#1965), applied as one more vendored Yoga patch. A negative margin keeps upstream's rule: a percent margin follows an owner size that no cache entry records. The rule now drops only hits whose old size the old cap may have clamped. Measure calls stay at 1 per grown or appended row in a scroll node's content, and random trees take 0.3 to 1.2 % more calls.

Tested by a Yoga test that compares incremental solves of random trees of measured and empty leaves with fresh solves (2,164 of 132,487 node layouts differed before, 0 now), by the cache predicate table, and by a text and textarea row.

When a column's height follows its content (any flexShrink 0 column,
such as a scroll node's content), Yoga passes that height to every
child as an "at most" cap. When the column grows, no cached size
matches the larger cap, so Yoga called the text measure function for
every text row: 1,002 calls for one appended row at 1,000 rows.

The native text measure uses the cap only to clamp its result, so a
size that was below the old cap is still valid. Where Yoga would call
such a function, it now takes that cached size at the same width and
stores it as a call's result. Its cache, and so every layout, stays
the same as before. Host measures keep Yoga's rule. One appended row
or one grown row now costs 1 call at any row count.

Source additions: a vendored Yoga patch with the rule and a node flag,
the flag argument through the bridge, and a test-only call counter.
The twin-tree layout test is a new kind of check: it compares every
node's layout with Yoga's own rule after each random edit.

Issue: 352
Review found two changes that broke layout without a failing test:
opting a host measure in to the larger-cap reuse, and dropping the
percent-margin guard (a reuse would keep a stale computed margin). The
host-measure tables now offer a host leaf a larger cap through both
registrations and expect a new size, and the twin-tree test compares
every margin, padding, and border edge too.

The reuse no longer searches the layout cache entry: the measure pass
at the new cap stores the measurement it needs, so counts stay the same
(1 call per grown or appended row). Its comment stays in two places.

Issue: 352
Yoga's measure cache compared the new available size without the
node's margin with the old one with it. A width or height that grew
by less than the margin looked like a shrink, so a measured node kept
the size it had for the old width. A text with margin 1 and
alignSelf "flex-start" wrapped at 18 columns after its parent grew
from 20 to 21, where a fresh layout wraps at 19.

Both sides of the rule now exclude a positive margin, as in the open
upstream fix (react/yoga#1965). A negative margin keeps upstream's
rule: a percent margin follows an owner size that no cache entry
records, so subtracting it would accept a stale size. Incremental
layouts of random flat trees of measured and empty leaves now equal
fresh layouts: 0 of 132,487 node layouts differ, against 2,164 before.

Source additions: the vendored patch, its line in the update script,
and its paragraph in the vendor notes. The incremental-against-fresh
test is a new kind of check.

Issue: 374
@simonklee
simonklee requested a review from kommander as a code owner October 29, 2026 20:15
Base automatically changed from harden/352-scrollbox-height-change-remeasures-all-text to main October 29, 2026 21:38
…mental-layout-differs-from-fresh

# Conflicts:
#	packages/native/src/tests/yoga_test.zig
#	packages/native/src/vendor/update-zig-deps.sh
#	packages/native/src/vendor/zig-deps.md
#	packages/native/src/vendor/zig-deps.sha256
#	packages/native/src/vendor/zig-deps.tar.gz
@simonklee
simonklee merged commit 6216869 into main Oct 30, 2026
26 checks passed
@simonklee
simonklee deleted the harden/374-yoga-incremental-layout-differs-from-fresh branch October 30, 2026 11:41
Dicklesworthstone added a commit to Dicklesworthstone/opentui that referenced this pull request Oct 30, 2026
Incoming from anomalyco origin/main cb3d425, eighteen commits on top of
local main e496dfb (54 local commits kept):

- cb3d425 native: prepare culled subtrees only where they show (anomalyco#1867)
- b8032e2 native: add a video player node (anomalyco#1800)
- 26f59cf native: find viewport children with an exact index (anomalyco#1837)
- bce826c core: paint TextTable into the frame (anomalyco#1834)
- ea10479 core: paint the line-number gutter into the frame (anomalyco#1833)
- bbf5719 native: add view resizes and box details to the twin harness (anomalyco#1842)
- 27c87ef core: paint Select and TabSelect into the frame (anomalyco#1831)
- 1ffb1d5 native: paint a node Yoga sizes to zero as zero cells (anomalyco#1869)
- ed309e4 native: add culled, arrow and remount kinds to the twin harness (anomalyco#1836)
- 6216869 native: compare like sizes in Yoga's stricter-size cache rule (anomalyco#1860)
- 1f1b608 native: write terminal stdout from a native thread (anomalyco#1858)
- 68f96b5 docs: rewrite the captured stdout limit for app authors
- 057e8c2 core: bound captured stdout in split-footer mode (anomalyco#1856)
- e1c27a4 core: save a crash dump when the Yoga worker test crashes (anomalyco#1868)
- dfb1d21 native: let a split key finish before the simulated stop (anomalyco#1865)
- eb51002 native: keep simulated input in arrival order (anomalyco#1864)
- 60b7ce7 core: fix ScrollBox Markdown collapse and selection listener leak (anomalyco#1863)
- ae15b99 native: ignore a nil keyboard enter on the WSLg clipboard helper (anomalyco#1862)

The merge-tree is one clean tree 1ee7ad2, with 0 path deletions.
112 files change, 11196 insertions and 1018 deletions.

The OpenAI File Downloader user-agent line stays at AGENTS.md:64 and
packages/web/AGENTS.md:159. Local commits stay on main. This merge is
pushed only to the Dicklesworthstone fork. anomalyco/opentui is not pushed.
origin/harden/macos-tty-probe is left unfolded.

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant