mostard-qwik-virtual
v0.1.0
Published
Qwik adapter over @tanstack/virtual-core for Mostard/Relayers feeds
Readme
mostard-qwik-virtual
Qwik adapter over @tanstack/virtual-core v3 for Mostard/Relayers feeds.
Promoted from the phase-1 spike 2026-09-21 (V1): same seam, presets and Qwik
hosting, now built to dist/ with the demo moved to examples/.
Wayfinder map: .scratch/qwik-virtual/map.md. Private package; do not publish.
Run
bun install
bun test # 28 proofs: seam + presets + headless browser-app smoke + HTML boot contract
bun run typecheck
bun run build # tsc to dist/ (qwik-virtualizer, feed-wiring, measure-guard)
bun examples/demo.ts # state-surfacing walkthroughDocs and SSR playground
just serve builds and serves the VitePress docs plus two server-rendered
Qwik scenes (/deck/, /chat/) embedded as iframes on one port (3130). See
DEVELOPMENT.md for the gate ladder (just verify).
See it in a browser (prototype, no server)
Open examples/prototype.html directly (double-click). It loads sibling
prototype.iife.js with a classic <script> — no server, no CDN. Chrome
CORS-blocks type="module" imports from file://, which is why the first
shell never ran. After editing examples/prototype-app.js, regenerate with
bun run pack. Optional: bun run demo serves at
http://127.0.0.1:8765/prototype.html.
Same core the Qwik adapter wraps, wired like the locked phase-1 slice:
- Deck column tab — 400 variable-height rows, estimate/overscan
inputs, LIVE toggle with
+N new notespending (mirrors Mostard), prepend 5/25, jump-to-index, snapshot capture/restore, reset. - Discussion tab — end-anchored
anchorTo:end+followOnAppend: stream appends while pinned vs scrolled up, Jump to latest, at-end readout. - Guided tour tab — scripted PASS/FAIL runs: windowing, real
ResizeObservermeasuring, prepend stability, chat follow. - Stats bar (offset / total / rendered window / measured / fps) +
event log (
onChange sync=…) update after every action.
App logic is examples/prototype-app.js, covered headlessly by
src/browser-smoke.test.ts (happy-dom + stub seam), so the file you
click is the code the tests prove.
What it proves
- The adapter seam is injectable:
Virtualizerruns with stubobserveElementRect/observeElementOffset/scrollToFn(src/stub-seam.ts), windows 400 rows, shifts on scroll offset, routesscrollToIndexthrough the injected scroller, restores frominitialMeasurementsCache(src/seam.test.ts). - Qwik hosting compiles:
NoSerializeisland +useVisibleTask$(document-ready) +onChangesignal bump + SSR seeds (src/qwik-virtualizer.ts, typechecked against@qwik.dev/[email protected]; not executable under plain Bun — needs the Qwik optimizer). - Feed slice as data: 400 cap / 320px estimate / overscan 4, deck
top-anchored, discussion end-anchored with follow
(
src/feed-wiring.ts). useQwikWindowVirtualizershares the element hook's host (SSR seed, count tracking, sweep) with the window as scroller;scrollMarginis measured fromlistRef(src/window-scroller.ts).
Verdict
WORKED at seam + hosting + slice level. None of the 4 abort
signals fired: no signal break (plain version bump), no
serialization break (NoSerialize island), no SSR-structure break
(null scroll element + seeds), measureElement/data-index
compatible on paper.
Still open (needs a browser harness, phase-2)
- 60fps scroll + no-blank-flash measurement with real cards —
prototype.htmlshows live fps; report yours from the tour. - Real
measureElement/ResizeObserverbehavior on variable EventCards;estimateSizetuning away from 320px. onChangearrived withsync=undefinedon stub-driven offsets — the browser log prints the real flag; confirm before relying on it.- Deck-cache extension (
saveDeckSnapshot+ snapshot/offset). - Phase-2: window scroller, lanes, RTL, smooth scroll.
- Abort fallback stays:
qwikify$+@tanstack/react-virtual.
Recipe found by the spike
Start-anchored lists do not auto-hold reading position on prepend
(only end-anchored mode does; deckListPreset(n, { holdOnPrepend: true })
uses it for exactly that). First-order compensation: after a live
prepend of N rows while scrolled, scrollToOffset(old + N × estimate).
With real variable heights that undershoots/overshoots; snap with
scrollToIndex(ids.indexOf(heldKey)) to actually keep the row. At
offset 0 do nothing (new rows appear above). Deck LIVE-at-top and the
paused +N queue both fall out of this naturally.
