Blitsen

Documentation M0 — feasibility

M0 — feasibility decision#

Decision date: 2026-08-10 Decision: GO, RE-SCOPED, on Linux x64.

The architecture is credible enough to build M1–M3 on Linux: Bun can host the window loop, Blitz accepts bridge-driven mutation, HTML fragments can be parsed, and an application-owned wgpu texture can join Blitz's render pass. Two original product claims did not survive measurement and are withdrawn:

  1. The Phase 2 25–50 MB installed estimate is not a valid target. The measured JSC + Blitz floor is already 52.48 MB before the host, bridge, loader, native APIs, GPU renderer, or application are added.
  2. An arbitrary unmodified Vite application is not presently a drop-in input. Its JavaScript must run through the Blitsen runtime, and applications must fit a published HTML/CSS/web-API compatibility profile.

The fallback position is: a native web-output runtime, with a renderer the app ships and controls, intended to remain materially smaller than Electron. A new numeric size budget will be set only after a production-shaped Linux host can be measured.

macOS and Windows measurements are deliberately deferred. This decision does not claim feasibility or performance on either platform.

Spike record#

SpikeMeasured resultConsequence
S0The Linux x64 stripped+LTO JSC + Blitz executable is 52,480,904 B (52.48 MB / 50.05 MiB) installed and 24,076,701 B (24.08 MB) with gzip -9. JSC-only was 37,980,984 B; Blitz-only was 18,620,152 B.The 25–50 MB installed headline is dead. Retain the “far below Electron” direction, but publish no replacement number before measuring the full host. Keep the JSC context process-lived until its standalone teardown assertion is resolved.
S1Across 600 Linux samples, a Bun-owned loop pumping winit at about 62.2 Hz produced 16.073 ms mean paint intervals and input-to-paint p50/p95/p99 of 8.190/15.328/16.034 ms. Adding 4 ms of JS work did not materially change pacing. Calling uv_run from the addon aborts.Bun owns the outer loop in Phase 1. A repeating JS task calls winit's non-blocking pump; callbacks finish synchronously and return to Bun. Do not depend on Bun internals or create a second JS thread.
S2Class, attribute, inline-style, insertion, and removal mutations incrementally produced the same layout as full recomputation. SlotMap handles reject stale nodes and transaction drop requests redraw. ID mutation leaves Blitz's internal ID index stale (upstream #677).The mutation path is workable with a small bridge-side ID index or an upstream fix. Pin Blitz; no fork is justified. Expose opaque document + NodeId handles, never raw pointers.
S3A Bun 1.3.14 Node-API addon owned an X11 winit/wgpu window and presented 120/120 frames both directly and from bun build --compile, including from an empty working directory. The addon was 11,233,584 B and the compiled prototype 105,814,144 B.The Phase 1 addon host is feasible on Linux. The 105.8 MB artifact is a prototype observation, not a Phase 2 budget.
S4Blitz's native set_inner_html works when an explicit HtmlProvider is configured. IDs, handles, adoption, table/select context, and incremental layout were correct. Repeated replacement leaked one fragment-root node per call (upstream #678).Use the native fragment parser; do not build a fallback parser. Pin/fix the leak before long-running dynamic applications.
S5A double-buffered 480×320 application texture rendered through the same Vello device and one WindowRenderer surface/present path, at the expected z-order (below 2, app 4, above 5). Blitz's normal custom-widget wrapper currently fails to compile (upstream #679).The application viewport seam is viable on Linux. Land or pin the wrapper fix before integrating it through the normal shell API.
S6Raw React, Vue, and Svelte Vite outputs were blank because Blitz's plain frontend does not execute JS. With their resulting DOM serialized, React/Shadcn scored RMSE 0.125972 / NCC 0.898129, Vue/Conduit 0.108616 / 0.359666, and Svelte/Wordle+ 0.173018 / 0.191305. Block/flex/grid, responsive layout, borders, spacing, and variables were broadly useful; numeric glyphs, visibility/opacity layers, transforms/positioning/stacking, controls/resets, and SVG fidelity were not.Replace “arbitrary drop-in” with ordinary built output within a documented compatibility profile. The runtime must execute the module graph; blitsen doctor must reject unsupported APIs/features. Numeric text and layer composition are blockers before dashboard/game compatibility claims.
S7A two-phase prefetch handled Vite static, dynamic, root-relative, redirected, and asset modules and connected to vite-hmr. Bun cannot directly import HTTP modules or asynchronously resolve them, gives fetched modules synthetic file:///http://… identity, lacks EventSource, and the browser HMR client was not executed.Keep directory watch + full JS-context reload for v0. Defer HTTP proxy/HMR mode to v1, when URL/source-map identity and the required web APIs exist.

Commitments after M0#

Stop/review gates#

Revisit the decision if M1 cannot perform a JS-originated DOM mutation visibly in one window, if M3 cannot sustain a usable 60 Hz Pong loop, or if the first complete runtime is no longer materially smaller than an equivalent Electron export. The broader adoption claim remains gated on fixing the S6 blocker list and repeating the real-application corpus.