# Claims ledger: "Fifteen levers: the JavaScript runtime at 16 KB"

Post: `site/blog/the-javascript-runtime-at-16-kb/index.html`, published 2026-10-03. Piece: `launch-site`. Written against `main` `005790c`.
Same purpose and shape as `site/blog/why-undra-is-the-default-choice/claims.md`. Every number is a byte count gzipped at zlib level 9 as
`scripts/wasm-size.sh` measures it, unless the row says otherwise.

**Fact-checked adversarially on 2026-10-03** against `main` `a547226` (the site merged); the pass is recorded in `.10x/reviews/2026-10-03-launch-posts-fact-check.md`.
The last column is the fact-check: the source was opened and the post compared with it, digit for digit and condition for condition; the three
recorded sizes were also measured again on `a547226` with `scripts/wasm-size.sh` (15,774 / 16,285 / 39,922, exactly the record). **verified** means
the source says what the post says; **corrected** says what was wrong and how the post changed (the earlier wording is in italics); **added**
means a claim the draft made without a row. Line numbers of `adr` and `rev` are the files as of that date.

**Aliases.** `adr` = `.10x/adrs/ADR-057-js-runtime-16kb.md`. `rev` = `.10x/reviews/2026-10-02-ts-runtime-16k-review.md`. `rec` =
`bench/results/web-size.jsonl`. `stat` = `.10x/status.md`. **Checked by**: `A` the author opened the path.

| ID | Claim | Source | Checked by | Fact-check (2026-10-03) |
|---|---|---|---|---|
| J01 | What a hello-world page loads up front of `@undra/runtime` went from 22,100 to 15,774 bytes gzipped | `adr` lines 17-18 (22,100); `rec` line 2 (`web/hello-runtime-js` 15,774); `stat` checkpoint 33 | A | **verified** `adr:17-18` (22,100 at `main` `a309e9f`), `rec` line 2 (15,774); re-measured 2026-10-03 on `a547226` by `scripts/wasm-size.sh`: 15,774 |
| J02 | under a 16,000-byte budget | `rec` line 2 (`budget` 16000); `adr` line 90 | A | **verified** `rec` line 2 (`budget` 16000); `adr:90` |
| J03 | What a hello page does not use loads when something first needs it, and no feature was removed; a production page reports an error as a code with a link instead of a sentence. *Was: "Nothing was removed: what left the first chunk loads with what needs it"* | `adr` "Alternatives considered" (rows 1 to 12 "none of it is removed from the package"); `stat` checkpoint 33 | A | **corrected**: `adr:381-383` says none of rows 1 to 12 "is removed from the package"; rows 13 and 14 (1,316 + 570 B) change what a production build says and calls its private members, and a development check (the list of seventeen wasm exports) is not in production (`adr:218-221`). "Nothing" was too wide; the lede now says so |
| J04 | ADR-052 asked for 16 KB up front | `adr` line 16 | A | **verified** `adr:16` |
| J05 | An earlier piece reached 21.2 KB and concluded 16 KB would mean removing behaviour; five pieces then added 0.9 KB; the gate stood at 22,100 with no room | `adr` lines 16-18 | A | **verified** `adr:16-18`; 21.2 KB is `ts-size-e4`'s 21,173 (`stat:472`) |
| J06 | A script charges each byte of the minified chunk and each bit of its gzip stream to its declaration | `adr` lines 25-29 (`scripts/web-size-attribute.mjs`) | A | **verified** `adr:25-29` |
| J07 | The nine largest of the 24 shares of the 22,100-byte chunk: wire 3,702; mirror 2,867; the host that runs the core's WebAssembly in the page 2,521; errors and sentences 2,038; call path 1,889; ports 1,834; core lifecycle 1,805; streams 945; the bundler's helper and chunk table 717. *Was: "The shares" (seven rows, introduced as "the largest")* | `adr` lines 33-41 | A | **corrected**: "the largest" was false, the draft left out ports (1,834) and core lifecycle (1,805), both larger than streams (945) and the bundler's 717. Added, with the 24 rows counted in `adr:31-57`. The other seven rows match `adr:33-41` digit for digit. The post also says a share is what code costs in the chunk, not what removing it saves (`adr:28-29`) |
| J08 | A hello page shipped code it cannot run: streams, reconnection for a core that is not remote, 20 of 24 codecs | `adr` lines 70-74 | A | **verified** `adr:70-74` (945 B of streams, 250 of reconnect, 20 codecs of 24) |
| J09 | Rolldown, the bundler of Vite 8.3.1, puts a module in the first chunk when the entry reaches it statically through any module with code of its own, even if only an on-demand chunk uses it; another bundler may place modules differently and save less. *Was: "Where the bundler emits a module is decided by re-exports, not by use"* | `adr` lines 75-79 | A | **corrected**: the rule is Rolldown's, measured (`adr:75-79`), and the ADR's risks say "the numbers are Rolldown's" (`adr:417-420`); the draft stated it as a property of bundlers |
| J10 | The sentences of errors were about a tenth of the chunk | `adr` lines 80-82 | A | **verified** `adr:80-82`; about 1,900 of 22,100 gzipped bytes are sentences (`adr:64-66`), 8.6%, "about a tenth" |
| J11 | Each lever landed with its measured number; gzip is not additive | `adr` lines 100, 276-277 | A | **verified** `adr:100`, `adr:276-277` |
| J12 | The lever table (rows 1 to 15 and 4b, with "after" and "saved"), as built and before the review's fixes | `adr` lines 279-295, the implementation note's table, copied as built | A | **verified** all 16 rows against `adr:279-295`, digit for digit; **corrected** three labels: rows 6 and 7 ("remote-only" became "only a core outside this thread needs", `adr:287`), "9, 10" became "9, 10a" with the sync-port refusal text (`adr:289`), and a note that the table predates the review, which put `snapshot` and `restore` back up front (`adr:335-338`; row 8's "on first call" holds for `stats` and `runInBackground` only) |
| J13 | Row 15 changes what is counted, not what is shipped: Vite's preload helper (691 B) is reported beside the number; with the helper left in the chunk, the final record is 16,285 against 16,600 | `adr` lines 248-251 (the helper beside the number) and 441-442 (D6: a second gate), line 360; `rec` line 3 (`web/hello-runtime-js-with-helper` 16,285, budget 16,600) | A | **verified** `rec` line 3 (16,285, budget 16,600), `bench/budgets.toml:917-920`, `adr:249-250` (691 B); re-measured 2026-10-03: 16,285; the helper is now explained in the post |
| J14 | Row 13 is the largest single lever | `adr` line 292 of the table (−1,316) | A | **verified** `adr:292` (1,316; the next largest is rows 3 and 11a together, 967) |
| J15 | A production page carries an error's class, kind and fields, and its text is a code (`T` and a number), the values and a link; the development build keeps the sentences. *Was: "its text is `T<code>` with a link"* | `adr` row 13 (lines 117, 291) and line 433 (the text is `T<code>: <values>; see https://…/errors.html#T<code>`); `site/docs/errors.html` (the `T` anchors) | A | **corrected**: the text is "`T0017: <values>; see https://…/errors.html#T0017`" (`adr:210`, `adr:433`); the draft left out the values. Class, `name`, `kind`, `code`, `detail` and the other fields are unchanged in both builds (`adr:211-213`). `site/docs/errors.html` has 166 `T` anchors |
| J16 | What left: stream support, the framed transports' codecs, reconnection, the observe waiters, `stats` and background runs, the codecs a schema does not name | `adr` lines 105-115 and the implementation note (rows 3, 4, 5, 6, 7, 8, 11) | A | **verified** `adr:105-115`, `adr:284-290` |
| J17 | Keyed-patch merging stays: read-your-writes drains are synchronous | `adr` "Levers measured and not taken" ("Keyed-patch merging loaded on the first keyed entry") | A | **verified** `adr:369` |
| J18 | The mirror's compaction stays: its backlog bound must hold offline | `adr` same table ("The mirror's compaction on first use") | A | **verified** `adr:368` |
| J19 | Object identity stays: every app's first screen waits for it | `adr` same table ("Object identity on the first create()") | A | **verified** `adr:370` |
| J20 | Connectivity and Lifecycle events, about 530 bytes; needs the core to say when it listens, an ABI decision with its own record; the next lever | `adr` same table, first row ("−530 … an ABI decision, its own ADR. This is the next lever.") | A | **verified** `adr:366` (−530, an ablation on top of row 14; "an import or a stats field": an ABI decision, its own ADR; "This is the next lever") |
| J21 | The review reproduced the sizes to the byte. *Was: "every number"* | `rev` lines 45-46 ("the implementer's numbers to the byte") | A | **corrected**: `rev:45-46` reproduced the three sizes (15,680 / 16,191 / 40,100) and the module list; the call-path numbers (`rev:56-57`) are the review's own measurement, not a reproduction |
| J22 | Four High defects | `rev` lines 12-17, 23-26 | A | **verified** `rev:23-26` (H1 to H4) |
| J23 | H1: `reclaim` was internal, the published types dropped it, generated bindings failed `tsc` against the installed package; every suite aliased the sources; a test asks the compiler about every name the generator imports | `rev` line 23 | A | **verified** `rev:23` |
| J24 | H2: `snapshot()`/`restore()` after `await import()`, which yields when cached; a call after `restore()` reached the core first; they run at the call again | `rev` line 24; `adr` "Review" first bullet | A | **verified** `rev:24`; `adr:335-338` |
| J25 | H3: the background drain at `pagehide` needed a chunk fetched, when a page is being left or is offline and may not be able to fetch one; it calls the core directly | `rev` line 25 | A | **corrected**: `rev:25` says "Offline, or on a page being left, the import fails or never completes"; the draft's "the one moment a page cannot fetch one" was absolute |
| J26 | H4: the production build turned a WebSocket close reason (reaches the server) and port errors (reach the core) into codes; data is a sentence in both builds | `rev` line 26 | A | **verified** `rev:26` (89 sites; 78 codes retired) |
| J27 | The fixes cost 131 bytes, to 15,811 | `rev` lines 12-13 | A | **verified** `rev:12-13` (15,680 to 15,811; the +131 is H2 and H3, H4 cost no gated byte, `adr:351-352`) |
| J28 | Smaller fixes and a later merge left the record at 15,774 | `rev` lines 48-50; `adr` lines 358-360 | A | **verified** `rev:48-50`, `adr:358-360`; `bench/budgets.toml:912-915` |
| J29 | On Node an awaited call takes 325 to 333 ns where it took 313 to 316, about 4 to 5% more (four alternating runs on one machine); `callSync` did not move (162 to 168 ns before, 162 to 167 after). *Was: "4% slower"* | `rev` lines 56-57; `adr` lines 353-354 | A | **corrected**: `adr:353-354` and `rev:34` say +4%; the review's four runs are 313, 314, 316, 314 and 327, 332, 333, 325 (`rev:56-57`): +3.8% best run to best run, +4.8% mean to mean. The post now gives both ranges and "4 to 5%", with the conditions (Node, one host, alternating runs) |
| J30 | When the levers landed, the development build's first chunk was 21,151 bytes; it has not been re-measured since the review's fixes and is not gated; a development page is served module by module. *Was: "is 21,151 bytes"* | `adr` lines 300-303, 400 | A | **corrected**: `adr:301-303` is the implementation note, written before the review added 131 bytes to the production chunk and moved code back up front; no later figure exists in the repository. The post says so |
| J31 | The page that uses every feature (in `wasm-main` mode: its first chunk plus every on-demand runtime chunk except the Worker script) loads 39,922 bytes, less than the 42,385 it loaded before | `rec` line 4 (`web/all-features-runtime-js` 39,922); `adr` line 299 ("42,400, which was 42,385") | A | **verified** `rec` line 4 (39,922 = 27,064 + 12,858), `adr:254-257`, `adr:299`; re-measured 2026-10-03 on `a547226`: 39,922 (27,064 + 12,858); the conditions were added |
| J32 | The gate has three rows (16,000; 16,600; 42,400 or 5% over the record, whichever is lower); a change that takes a row over its ceiling, or moves a runtime module in or out of the first chunk, fails the build; over a budget the message names the levers left (host events, about 530 B). *Was: "All three numbers are gated in CI; a change that grows the chunk fails the build and names the lever left"* | `bench/budgets.toml` lines 912-922; `rev` line 28 (L1: the message names ADR-057's levers left) | A | **corrected**: the ceiling is `min(budget, floor(record × 1.05))` (`scripts/wasm-size.sh:157`): 16,000, 16,600 and 41,918 for the third row; growth inside the 226 bytes of room passes. The dev chunk (the draft's third number) is not gated. "Names the lever" holds only over a budget (`scripts/wasm-size.sh:299-304`; `rev:28`); the module-list check is `scripts/wasm-size.sh:252-267` |
| J33 | All sizes: gzipped at zlib level 9; "up front" is the first chunk of a production build of the `undra init` web template, built by Vite 8.3.1 (Rolldown 1.2.11), counting the runtime's own code | `adr:19-21`, `adr:90-92`; `scripts/wasm-size.sh` header | A | **added**, verified |
| J34 | Vite's preload helper, 691 bytes, is a snippet Vite adds to a chunk that loads another on demand; it is reported beside the number | `adr:248-250`; `rec` line 2 (`bundler_gzipped` 691) | A | **added**, verified |
| J35 | 717 bytes of the chunk were the bundler's; each on-demand chunk costs the first chunk 15 to 30 bytes of file name | `adr:83-84`; `adr:41` | A | **added**, verified |
| J36 | A share is what code costs in this chunk, not what removing it saves, so every lever was measured by building | `adr:28-29` | A | **added**, verified |
| J37 | The review's fixes put `snapshot` and `restore` back up front; that is part of the 131 bytes | `adr:335-338`; `rev:24`, `rev:12-13` | A | **added**, verified |
| J38 | The final record: 15,774 / 16,285 / 39,922, re-measured on `a547226` (2026-10-03) with `scripts/wasm-size.sh` (rustc 1.99.0, wasm-opt 133, offline): 15,774; 16,285; 39,922 (27,064 + 12,858) | run of 2026-10-03, `.10x/reviews/2026-10-03-launch-posts-fact-check.md` | A | **added**, verified |
