Preact integration evidence — 2026-07-15
Issue: #5
Baseline commit: 0843833
Machine: macOS 26.5.1, Deno 2.9.0, Headless Chrome 150.0.0.0
Pins: Jazz 2.0.0-alpha.53, Preact 10.29.7, Astro 7.0.9
Decision rule
Correct loading, updates, local durability waits, teardown, errors, recreation, two roots, and five HMR edits are hard gates for a Preact integration candidate. The exact vanilla lane is the vendor API control established in #7, not a component-adapter candidate. A compatibility strategy may use documented aliases, but it may not add a lofi-owned implementation of React semantics. When multiple candidates pass, fewer author-facing concepts and less owned maintenance win; bundle contribution is the tie-breaker.
Results
| Strategy | Correctness | Reachable production JS | Framework-specific burden | Result |
|---|---|---|---|---|
| Exact vanilla Jazz control | Core query/write/persistence control | 213,080 B raw / 57,823 B gzip | One imperative DOM view | API baseline |
preact/ + Jazz React bindings |
Fail at build | Not meaningful | Would require a React 19 semantic polyfill | Reject |
| Thin Preact adapter over vanilla Jazz | Pass candidate gates | 228,829 B raw / 64,806 B gzip | 524 runtime LOC plus 242 test LOC | Graduate |
The thin Astro/Preact page has 15,749 B raw / 6,983 B gzip more reachable JavaScript than the single-view vanilla API control. This is an indicative floor, not a same-UI bundle claim. Both load the same 9,799,438 B Jazz WASM payload (3,078,164 B with local gzip). Browser resource timing, rather than every emitted file, defines “reachable”; the Astro/Jazz build emits additional server/client copies that the page does not request. The compat candidate has no valid production contribution because it cannot build without changing the semantic contract.
Compatibility control
Reproduce the rejected lane:
deno run -A npm:vite@8.0.1 build spikes/preact-integration/compat-control \
--config spikes/preact-integration/compat-control/vite.config.ts
Vite transforms 332 modules and then stops because jazz-tools/
statically imports React 19's use, which preact@10.29.7/ does not export. The exact Jazz
package declares optional peer react >=19; Preact compat identifies as React 18.3.1. A homemade
use implementation would own more unstable semantics than the thin adapter and would invalidate
the comparison.
Thin-adapter gates
deno test apps/ retains deterministic coverage for:
- loading to ready and reactive delivery to two consumers;
- one underlying Jazz query shared by both roots;
- idempotent unmount and final vendor unsubscribe;
- insert/update waits at the public local durability tier;
- rejected local writes and public mutation errors reaching UI state.
- concurrent recreation coalescing and idempotent database shutdown;
- HMR disposal before deferred database creation never attaching an obsolete adapter;
- rejected component-boundary mutations being handled after runtime state projection.
Production-browser results:
- a north-island write appeared in both roots and reported
localonly afterWriteHandle.wait; - reload retained the note;
- explicit database recreation retained the note and settled at one active client, two consumers, and one vendor subscription;
- the final five-edit rerun after lifecycle hardening measured 318, 131, 130, 126, and 29 ms (130 ms median, 318 ms slowest);
- after every edit, retained data remained visible and steady cardinality stayed 1 / 2 / 1;
- no browser
errororunhandledrejectionevent was observed on the live write, recreation, or HMR paths.
The rejected-local-write path is retained deterministically at the store and UI promise boundary; the exact browser database does not expose a supported fault-injection hook, so this evidence does not claim a browser-injected storage failure.
Total subscription and unsubscribe counters increase together across HMR because adapter code is replaced. The database client is deliberately preserved; preserving the adapter itself would leave edited adapter code stale.
The final rerun measured from filesystem modification time to the browser-observed replacement DOM attribute. It followed the same five-edit protocol after adding generation cancellation, serialized client recreation, and mutation-listener diagnostics; the retained row and 1 / 2 / 1 / 1 steady state were checked after every edit.
Honest limitation
The public Db.subscribeAll() callback has no asynchronous error channel. The adapter surfaces
synchronous query setup failures, rejected local/global write waits, and db.onMutationError(). It
does not claim that every deferred subscription-start failure can reach UI state. This is a Jazz API
gap, not a hidden success state.
Contract delta
DX-HMR-01 changes from “one subscription per consumer” to “one underlying vendor subscription per shared query, with consumer count reported separately.” This is stricter about resource ownership and matches the proven two-island topology.