A code-review pass: four real bugs, one per-frame layout read, and a round of
deduplication across the shared helpers. No API changes — every fix below either
restores documented behaviour or is invisible to consumers.
Fixed
typed: trueno longer destroys every--const-meta-*value. A source's
undeclared value was always registered with the<number>default — but the
metaplugin writes strings (#3367d6,/cover.jpg,dark light) under
property names it only learns from the page at runtime, so it can't declare
them. A<number>-registered property rejects a string at computed-value time,
so every meta value computed to0. Undeclared strings are now left
untyped; numbers, and any value whose source declares apropssyntax, are
registered exactly as before.img-colorno longer re-decodes on every scroll-back. The swatch cache
lived insidestart, which the viewport gate re-runs on each re-entry — so the
cache it was written for could never survive one, and every pass over a gallery
re-rancreateImageBitmap+getImageDataper image. It's now a module-scoped
WeakMap, keyed by the rendered source.video-colorno longer pins a core on a tainted canvas. The sample
throttle only advanced on a successful read, so a cross-origin video with no
CORS grant re-drew and re-threw on every presented frame, forever. Taint is now
latched: it gives up after the first failure, and still writes nothing.select's--live-value-numno longer goes stale. It was written only for
a numeric value and never cleared, so picking"3"and then a non-numeric
option left3behind — and the documentedvar(--live-value-num, …)fallback
could never apply again. It's now removed when the value isn't numeric.batteryno longer leaves an unhandled promise rejection whengetBattery
exists but rejects (insecure context, or disabled by policy).
Changed
scroll-velocityreads the scroll position in its scroll handler, not
inside the frame. Readingwindow.scrollYfrom a per-frame sampler forced a
style+layout recalc every frame, since the previous frame's writes left style
dirty — the one place the library broke its own read/write split. Behaviour is
unchanged in a browser (scroll events fire before rAF in the same frame).pointer-local,motion, andorientationnow seed zeros, like every
other source, so their properties resolve on frame one instead of needing a
var(…, 0)fallback. One thing to know: avar(--live-local-pointer-x-ratio)
with no fallback used to invalidate its whole declaration until the first
event, and now resolves to0— so gate on--live-local-pointer-inside
rather than letting a highlight sit in the top-left corner on load. Global
pointerstill doesn't seed: the position genuinely isn't knowable before the
first event.ctx.write(name, '')removes a property, for a value that stops being
meaningful. Verified across Chromium, Firefox, and WebKit.visual-viewportandkeyboardshare one set of visual-viewport
listeners via the new ref-countedonVisualViewport, instead of attaching
their own.batteryandnetworklisteners are now passive, like every other
source's.form-state's post-reset recompute rides the shared frame loop, replacing
the library's only strayrequestAnimationFrame.
Internal
core/window-events.ts+core/document-events.tscollapse into
core/events.ts— one ref-counted hub factory per target (onWindow,
onDocument,onVisualViewport), plus anonAllhelper for the per-element
case.observeResize/observeIntersectionlikewise share one hub factory.
Sharednoopandslugreplace ~15 and 2 copies respectively.- The per-target queue maps in the writer are pooled and reused rather than
reallocated every frame.