Skip to content

perf(build): shrink guest JS and font atlases - #409

Draft
lfkdsk wants to merge 4 commits into
pocket-stack:mainfrom
lfkdsk:perf/guest-size-stack
Draft

perf(build): shrink guest JS and font atlases#409
lfkdsk wants to merge 4 commits into
pocket-stack:mainfrom
lfkdsk:perf/guest-size-stack

Conversation

@lfkdsk

@lfkdsk lfkdsk commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Four commits that shrink what a guest ships, reviewable one at a time. They sit
in one branch because all four touch tools/build.ts and the build-pipeline
docs; splitting them into separate PRs would just move the conflicts.

perf(build): minify guest bundle whitespace

Pass 2 switches from minify: false to
minify: { whitespace: true, identifiers: false, syntax: false }.

Six Solid apps (hero, launcher, gallery, music, settings, cards) go from
834,973 B to 584,777 B of JS (−29.96%). Hero is 136,270 → 94,655 B; the Vue
Vapor build −29.40%, Octane −27.60%. Every measured PAK is byte-identical —
this only touches pass-2 bundling.

Identifier renaming and syntax transforms stay off on purpose. Fully minified
esbuild output once nested expressions deeply enough to overflow an ESP32-P4
QuickJS task's 8 KB parse stack, and another variant parsed for four minutes
and hit its five-second watchdog; that device needed whitespace-only output plus
a 64 KB task stack. Whitespace mode also drops ASI-safe semicolons, so the token
stream changes even though Babel reports identical AST structure and nesting
depth for all eight measured bundles.

tests/guest-minify.test.ts pins the config and fails under both
minify: false and identifiers: true.

fix(font): declare runtime text charsets

Atlases previously baked ASCII 32..126 into every slot unconditionally. Baking
only the codepoints pass 1 collected saves 186,768 B of PAK across six apps
(1,968,800 → 1,782,032 B, −9.49%); hero's font payload alone drops 92,939 B.

Pass 1 cannot see text that appears at runtime, and two apps broke when it was
subset naively:

  • apps/note takes document text through the host svc channel, so loading a
    file containing ! rendered tofu instead of the glyph.
  • apps/zoomlab computes ${Math.round(v.zoom * 100)}% every frame; digit 6
    is in no source literal, so 21 of 270 frames in a zoom sweep rendered tofu —
    exactly the frames whose readout contained a 6.

So digits are always baked, and an app that receives text from outside declares
it in pocket.json:

{ "app": { "runtimeText": { "charset": "ascii" } } }

note, nsengine and ipod-nano declare it and return to their unsubset PAK
size. A universal punctuation floor was measured instead and rejected: the eight
characters note needs cost 84,384 B across six apps.

fix(font): detect runtime text access patterns + test(font): assert every OSK_LAYERS glyph is collected

The declaration is only useful if forgetting it fails the build. Pass 1 flags
svcPoll reached through member access, shorthand/renamed/nested/default-value
destructuring, alias chains, literal and dynamic computed keys, optional
chaining and TS casts; the build stops with a message naming pocket.json and
the minimal fragment to add.

The OSK needs no declaration because osk-layout.ts writes every typeable glyph
as a source literal — which is true by accident, not by construction. The added
test derives the required codepoints from the live OSK_LAYERS table, so
turning one key into String.fromCharCode(64) fails it.

Verification

Run on this branch and on a pristine main worktree: the full suite has the
same five pre-existing unit failures (pocket-package corpus, four
ipodtouch4-installation cases), contract generation is drift-free, and the
committed hero-main tape hashes diverge at the same frame-0 pair on both. Hero
build timing went 1.62 s → 1.40 s cold and 0.69 s → 0.64 s warm.

Enable Bun's whitespace-only minification while keeping identifier and syntax minification disabled. Fully minified output overflowed an ESP32-P4 QuickJS 8 KB parse stack; another variant parsed for four minutes and triggered its five-second watchdog.

Keep the behavior covered by the guest-minify regression test and documented across the build references. Same-source desktop QuickJS smoke is not PSP, Vita, or ESP32-P4 device validation.
…terals

The OSK needs no app.runtimeText declaration only because osk-layout.ts
writes every key as a source literal that the pass-1 codepoint harvest
collects. Derive the required set from the live OSK_LAYERS table and
assert each insertable glyph and key-cap label is in the collector's
textCodepoints for the table source. A key rewritten to
String.fromCharCode(64) makes the test fail with U+0040 missing.
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