Repository navigation
typeset.us 4.3.1
4.3.1 is a patch release of 4.3.0: no API, option, outcome code or default
changed. It fixes three timing defects in keeping composed text correct
while a page changes, found by the checks that GitHub's hosted runners and
the 4.3.0 release cut ran.
Rendering changes
None. scripts/v4/verify-golden.mjs run with the published 4.3.0 build as
its baseline, 3,932 cells per engine (every corpus and adversarial
paragraph at four widths in two fonts, plain, rich, legacy, with finishes,
comment separators, as titles and justified): 0 cells differ in Chromium,
WebKit or Firefox. The fixes below change when a block composes while the
page changes and, in the WebKit iframe case, which width its lines end up
composed for (4.3.0 kept lines composed for the old width); what settled
text composes is unchanged.
Fixed
- React hosts no longer recompose in every frame of a font-size transition
on a slow device.TypesetTextandTypesetRichTextshow native lines
while a block's text metrics change again within 100 ms of a check
composing it, and compose it once they hold for 100 ms. 4.3.0 timed both
windows in wall-clock time from the block's own composition, and a frame
on a slow device may spend up to 120 ms composing on-screen blocks. Once a
frame's compositions ran past 100 ms, or another frame's compositions fell
between a block's composition and its next check, the window had lapsed
and the same blocks recomposed in every frame. Both windows now leave out
the adapters' own work, during which no text metric can change. On
screen, where a held block paints native lines, that applies only to a
change within two rendering updates of the block's composition (a
slider's next input comes one update on, a transition's next step two),
once the composition before it had followed such a change too: a
continuous change holds from its second step, and a one-off second
change, such as a text-size control used twice on a slow device,
composes as in 4.3.0. A 1.2 s font-size transition over 16 blocks in
Chromium at 4x CPU (scripts/v4/verify-recompose-storms.mjs): 4.3.0
failed the check in 4 of 6 staged release-cut runs on the maintainer's M2
Pro (66 to 76 compositions, 9 to 14 long tasks of up to 135 ms, half of
them over 100 ms) and in 4 of 11 runs on GitHub's macos-15 runners (up to
88 compositions and 17 long tasks); 4.3.1 made 33 to 44 compositions and
0 to 3 long tasks in 24 transitions on the M2 Pro. With the CPU
throttled further, where 4.3.0 storms in most runs (6x: 30 to 68
compositions and 6 to 11 long tasks; 8x: 45 to 55 and 11 to 13), 4.3.1
composes each block two or three times (6x: 38 compositions and 3 or 4
long tasks; 8x: 38 to 41 and 5 to 7; 12x: 32 to 34). What a block
composes is unchanged; a held block paints native lines until the change
settles, as in 4.3.0. mount()recomposes a width change that WebKit lays out before it
delivers the change's mutation record. Since 4.3.0,mount()keeps a
composed block whose lines still fit a new width when only its own
compositions happened since its ResizeObserver last delivered (two
autogrid tracks traded room without end otherwise). It learned of
outside changes from their mutation records, and WebKit delivers the
records of a change that an about:blank page makes in its same-origin
iframe (a preview frame) after that frame's ResizeObserver callbacks. A
width change made in the frame after a composition was then taken for the
controller's own: a block widened from 230 to 290 px kept its 230 px
lines, and one narrowed to a width its composed lines still fit kept lines
composed for the old width. The ResizeObserver now takes the records
still queued, its own and the stylesheet records of the lifecycle
observer (inmount(body)nothing else watches<head>), and counts them
as outside changes; a pass handles queued records before it stops
observing instead of discarding them. This wasverify-iframe-mount's
WebKit failure of "a width change is recomposed" on GitHub's macos-15
runners: 20 of 60 runs of 4.3.0 in a repeat loop (Nightly 36506714283
and 36508950284), none of 60 with the fix. Its new checks, which widen a
block by its style attribute and by a stylesheet from the page's
requestAnimationFrame, failed 4.3.0 in every run there (60 of 60 and
30 of 30).- A React block just below the fold of a scroll pane no longer paints
native lines for a frame when the page scrolls right after mounting.
TypesetTextandTypesetRichTextcompose the blocks within a viewport
height of the screen, or of the scroll container they scroll in, before
they come on screen. They learned which blocks those were from an
IntersectionObserver, whose first report comes in a task after the next
rendering update. A page that scrolled from its own
requestAnimationFramein the first frame or two after mounting (an app
shell'soverflow:autopane, restored or flicked as the screen appears)
brought the first block below the fold on screen before that report, and
the block painted native lines and was rewrapped a frame later. Until the
observer first reports on a block, the adapters now judge its nearness
from its box, which they read each frame anyway, as the observer would:
against the scroll container's box, or the window's, grown by its height.
verify-scheduler's "TypesetText in an overflow:auto scroller, scrolled
in 0 ms after mounting" failed with this flash in every WebKit run on
GitHub's macos-15 runners (19 of 19, always one frame of block 1), in 2 of
6 staged 4.3.0 release-cut runs on the maintainer's M2 Pro in Chromium,
and in 4 of 20 Chromium runs of the same page there before this fix.
Scrolled from the first frame after mounting, 4.3.0 flashed the block in
every run in all three engines (15 of 15). With the fix, none flashed in
56 probe runs of that page (scrolled in the first frame or after a 0 ms
timer, 8 of them at 4x CPU) or in 11 fullverify-schedulerruns.
Blocks that 4.3.0 composed in the second frame after mounting, once the
observer reported them near, may now compose in the first, within the
same 12 ms frame budget; what they compose is unchanged. A block that the
budget leaves for the second frame, because the first frame's on-screen
work used it up, still flashes when the page scrolls it in from the frame
after mounting: with five blocks on screen in the same pane, in every
run in the three engines (12 of 12, as with 4.3.0, which also flashed a
second block in 4 of them), and in none scrolled after a 0 ms timer
(4.3.0: 2 of 4 in Chromium). SUPPORT.md's known limitation, which 4.3.0
gave as "text scrolled into view during its first frames, in Chromium",
now describes that case.
Known limitations
- A block just below a scroll pane's fold that the first frame's budget
leaves for the second frame still paints native lines for one frame when
the page scrolls it in from the frame after mounting (see the fix above,
and SUPPORT.md).priority="sync"composes a block in its commit
instead. Planned for 4.4. - 4.3.0's other known limitations stand; SUPPORT.md lists them.
Development
verify-mount-ownershipjudges "settled" as 500 ms with no pass and no
change to the markup, reached within 3 s, instead of one sample 250 ms
after the change. Firefox on a hosted runner once made the resize's single
pass later than 250 ms (CI 36521588100: passes 1 then 2), which the sample
read as a rewrite. Controllers that keep rewriting still fail.verify-package-contentsallows 1.91 MB unpacked instead of 1.9 MB. The
fixes above take the package from 1.88 MB (4.3.0) to 1.90 MB (1,902,907
bytes, 87 files): the iframe record handling in every copy ofmount(),
the React registry's hold-off rules in both React builds, and the notes
on them in SUPPORT.md and MIGRATION.md.- The storm check's 60-frame text-size slider is enforced on GitHub's
hosted runners again. Its speed-calibrated entry, added after 65
compositions against a limit of 64 in macos-15 run 36284847012, blamed
the wall-clock hold-off window fixed above. On the M2 Pro the slider made
28 to 39 compositions and 2 long tasks in 8 runs, and 25 or 26
compositions at 6x to 12x CPU. verify-iframe-mountreports what a failed recomposition check saw: the
block's outcome, stale mark, lines against breaks and widths, the
controller's passes and compositions, and a timeline of layout, record
delivery and writes. Each pass starts from a 420 px iframe; the
mount(body)pass used to inherit the first pass's 260 px, so its "iframe
resize" step resized nothing. Two new checks widen a block, by its style
attribute and by a stylesheet, from the page'srequestAnimationFramein
the frame after a composition, where WebKit delivers the record after
layout; Chromium and Firefox deliver it first and pass either way.verify-scheduler's scroll-pane check also scrolls from the frame after
mounting, where 4.3.0 painted the block below the fold native in every
run in the three engines, and reports which frames flashed.- The WebKit speed-calibrated entry for
verify-scheduler's "TypesetText
in an overflow:auto scroller, scrolled in 0 ms after mounting" is removed,
so GitHub's hosted runners enforce it again. It failed there in every
run with the same one-frame flash of the same block, which was the race
fixed above, not speed. verify-scheduler's scroll-pane checks read whether every block ended
composed, and the pane's observer released, once the check's frames stop,
and report the blocks still waiting when its 2 s watch ended. The watch's
own animation frame leaves every idle period too short for the adapters'
idle work, so the two blocks more than a pane height below the fold
compose on 1 s idle timeouts in Chromium and Firefox, and the second
composed 37 to 48 ms before the watch ended on the M2 Pro; a hosted
Firefox run (CI 36478408446, attempt 1) ended it with a block still
waiting and no flash. Each frame is now compared with the lines as the
blocks end, so a block painted native through the watch and composed
after it still fails.
Install
npm i -E typeset.us@4.3.1<script src="https://typeset.us/go@4.3.1.js" integrity="sha384-PQvP42IgERdCbNk9MWI9BkD7n+k1WbL6ImFN3cAMbXQq25PhdJynmVXQM0UB3ksB" crossorigin="anonymous" defer></script>Pinned files never change. Archive, docs and every artifact:
https://typeset.us/releases/4.3.1/
Integrity
- npm tarball
typeset.us-4.3.1.tgz: sha1067d61b7d540e3579bf3ee9e2edd6c7f536b6902,sha512-xhcY1oHtYycAtTShIjqi9f94Agygr4g9Vy1N81Af4Tv1mJPC4oqaacjWEePaX/Yami8/W2Bpjo9ppq7TMNI+UA== - The same tarball is attached below. It is recorded in
published.json, the
append-only ledger of every published file. - Published from GitHub Actions with npm provenance:
npm view typeset.us@4.3.1 dist.attestations. - How this Release was created: release workflow run 36601559574 verified the tag (the tarball rebuilt byte for byte, every suite against the committed artifacts, the npm dry run) and, after the maintainer's approval in the
npmenvironment, published 4.3.1 with a signed provenance statement. Its next step checked the registry 2 seconds later, while npm was still processing the version ("may take a few minutes to become available"), and failed, so the workflow did not create this Release. Once npm served 4.3.1 (about 2 minutes later),verify-ledger.mjs --network --version 4.3.1passed 11 of 11 checks,npm audit signaturesreported a verified registry signature and a verified attestation, and this Release was created from that run's evidence with the same notes script.npm-attestations.jsonis attached.
Evidence
Attached to this release (retained with the release, unlike CI artifacts):
acceptance.json: 78 checks, 0 failed.alignment.json: 78 checks, 0 failed.audit.json: 78 checks, 0 failed.break-semantics.json: 71 checks, 0 failed.budget-verification.json: 8 checks, 0 failed.cli-verification.json: 31 checks, 0 failed.controller-regression.json: 66 checks, 0 failed.copy-privacy.json: 75 checks, 0 failed.docs-verification.json: 43 checks, 0 failed.fixture-invariants.json: 72 checks, 0 failed.framework-text.json: 339 checks, 2 failed.golden.json: 194 checks, 0 failed.iframe-mount.json: 54 checks, 0 failed.immutable-4.2.json: 17 checks, 0 failed.inline-code-regression.json: 267 checks, 0 failed.inline-nowrap-regression.json: 150 checks, 0 failed.keep.json: 254 checks, 0 failed.ledger-guards.json: 6 checks, 0 failed.ledger-verification.json: 73 checks, 0 failed.live-regions.json: 57 checks, 0 failed.loaders.json: 240 checks, 0 failed.mount-ownership-regression.json: 69 checks, 0 failed.name-groups-regression.json: 162 checks, 0 failed.native-ax.json: 76 checks, 0 failed.npm-attestations.jsonoptions.json: 86 checks, 0 failed.package-contents.json: 29 checks, 0 failed.print-resize.json: 53 checks, 0 failed.promise.json: 3086 checks, 0 failed.react-node.json: 61 checks, 0 failed.react.json: 231 checks, 0 failed.recipe-reproduces.json: 5 checks, 0 failed.recompose-storms.json: 53 checks, 0 failed.reflow-triggers.json: 46 checks, 0 failed.release-check.json: 12 checks, 0 failed.release-craft.json: 81 checks, 0 failed.release-trust.json: 64 checks, 0 failed.scheduler.json: 97 checks, 1 failed.settle.json: 420 checks, 0 failed.site-index.json: 43 checks, 0 failed.smart-quotes.json: 85 checks, 0 failed.spacing.json: 5384 checks, 0 failed.strict-csp.json: 30 checks, 0 failed.test-v4-summary.json: 45 suites, 43 passed, 0 known failing, 2 slow on the hosted runner (speed-calibrated checks reported, not enforced; see scripts/v4/speed-calibrated.json), 0 failed; 13639 checks (release).tracking-clipping.json: 810 checks, 0 failed.translation.json: 36 checks, 0 failed.visibility.json: 291 checks, 0 failed.wrap-ownership-regression.json: 90 checks, 0 failed.
Support range and testing limits:
https://typeset.us/releases/4.3.1/SUPPORT.md