clone

a deno framework for local-first web apps that open instantly, work offline and sync when users choose

feat/prf-provision-custody
lofi / docs / spikes / preact-integration / evidence / 2026-07-15.md
2026-07-15.md 5.4K rendered
1# Preact integration evidence — 2026-07-1523Issue: [#5](https://github.com/FelineStateMachine/lofi/issues/5)\4Baseline commit: `0843833`\5Machine: macOS 26.5.1, Deno 2.9.0, Headless Chrome 150.0.0.0\6Pins: Jazz 2.0.0-alpha.53, Preact 10.29.7, Astro 7.0.978## Decision rule910Correct loading, updates, local durability waits, teardown, errors, recreation, two roots, and five11HMR edits are hard gates for a Preact integration candidate. The exact vanilla lane is the vendor12API control established in #7, not a component-adapter candidate. A compatibility strategy may use13documented aliases, but it may not add a lofi-owned implementation of React semantics. When multiple14candidates pass, fewer author-facing concepts and less owned maintenance win; bundle contribution is15the tie-breaker.1617## Results1819| Strategy | Correctness | Reachable production JS | Framework-specific burden | Result |20| ------------------------------------- | ------------------------------------ | ----------------------------: | ------------------------------------------ | ------------ |21| Exact vanilla Jazz control | Core query/write/persistence control | 213,080 B raw / 57,823 B gzip | One imperative DOM view | API baseline |22| `preact/compat` + Jazz React bindings | Fail at build | Not meaningful | Would require a React 19 semantic polyfill | Reject |23| 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 |2425The thin Astro/Preact page has 15,749 B raw / 6,983 B gzip more reachable JavaScript than the26single-view vanilla API control. This is an indicative floor, not a same-UI bundle claim. Both load27the same 9,799,438 B Jazz WASM payload (3,078,164 B with local gzip). Browser resource timing,28rather than every emitted file, defines “reachable”; the Astro/Jazz build emits additional29server/client copies that the page does not request. The compat candidate has no valid production30contribution because it cannot build without changing the semantic contract.3132## Compatibility control3334Reproduce the rejected lane:3536```sh37deno run -A npm:vite@8.0.1 build spikes/preact-integration/compat-control \38 --config spikes/preact-integration/compat-control/vite.config.ts39```4041Vite transforms 332 modules and then stops because `jazz-tools/dist/react-core/use-all.js`42statically imports React 19's `use`, which `preact@10.29.7/compat` does not export. The exact Jazz43package declares optional peer `react >=19`; Preact compat identifies as React 18.3.1. A homemade44`use` implementation would own more unstable semantics than the thin adapter and would invalidate45the comparison.4647## Thin-adapter gates4849`deno test apps/prototype/src/runtime` retains deterministic coverage for:5051- loading to ready and reactive delivery to two consumers;52- one underlying Jazz query shared by both roots;53- idempotent unmount and final vendor unsubscribe;54- insert/update waits at the public local durability tier;55- rejected local writes and public mutation errors reaching UI state.56- concurrent recreation coalescing and idempotent database shutdown;57- HMR disposal before deferred database creation never attaching an obsolete adapter;58- rejected component-boundary mutations being handled after runtime state projection.5960Production-browser results:6162- a north-island write appeared in both roots and reported `local` only after `WriteHandle.wait`;63- reload retained the note;64- explicit database recreation retained the note and settled at one active client, two consumers,65 and one vendor subscription;66- the final five-edit rerun after lifecycle hardening measured 318, 131, 130, 126, and 29 ms (130 ms67 median, 318 ms slowest);68- after every edit, retained data remained visible and steady cardinality stayed 1 / 2 / 1;69- no browser `error` or `unhandledrejection` event was observed on the live write, recreation, or70 HMR paths.7172The rejected-local-write path is retained deterministically at the store and UI promise boundary;73the exact browser database does not expose a supported fault-injection hook, so this evidence does74not claim a browser-injected storage failure.7576Total subscription and unsubscribe counters increase together across HMR because adapter code is77replaced. The database client is deliberately preserved; preserving the adapter itself would leave78edited adapter code stale.7980The final rerun measured from filesystem modification time to the browser-observed replacement DOM81attribute. It followed the same five-edit protocol after adding generation cancellation, serialized82client recreation, and mutation-listener diagnostics; the retained row and 1 / 2 / 1 / 1 steady83state were checked after every edit.8485## Honest limitation8687The public `Db.subscribeAll()` callback has no asynchronous error channel. The adapter surfaces88synchronous query setup failures, rejected local/global write waits, and `db.onMutationError()`. It89does not claim that every deferred subscription-start failure can reach UI state. This is a Jazz API90gap, not a hidden success state.9192## Contract delta9394DX-HMR-01 changes from “one subscription per consumer” to “one underlying vendor subscription per95shared query, with consumer count reported separately.” This is stricter about resource ownership96and matches the proven two-island topology.