← Back to all sparks
V

vellum

ANALYTICS
Velocity5.0

Low-Level Graphics with Device-Independent Layout and Queryable Scenes

vellum's bugs are now found by using it, not testing it — the downstream grammar is doing the QA.

r-graphicsrendering-enginelintingaccessibilitysvgintegration-testing
Current state
The rendering engine shipped nine releases in the two weeks around the end of July, six of them on a single day. Almost every entry is a correctness fix in a capability that worked when drawn and failed when measured, or worked in isolation and failed in composition. The release notes are unusually forensic: each one states the mechanism, the observable symptom, and why the fix mirrors the draw path rather than reimplementing it.
Where it's heading
The pivotal detail is stated outright in 0.6.3 — the first bug in the series found by using the engine from vellumplot rather than testing it in isolation. Every release since names the downstream as the source: the contrast rule's false positives, the lint rules that fired on all five sample plots, the keyed roundrect batch. A rendering engine with a real grammar built on top of it is now getting the integration coverage that unit tests structurally cannot provide, and the fixes are converging on one theme: the measurement path and the draw path must not drift.
Prediction
Expect the release rate to fall as the vellumplot integration surface is exhausted, with remaining work concentrated in the lint rule set now that it is meant to gate builds rather than just inform.

Recent moves

  1. 11d ago

    Linter grows to 20 rules and stops firing on every plot

    Running the new rules against vellumplot's output exposed invisible_fill and bleed as pure false-positive generators — five ordinary plots produced one of each with no true positives. Both now carry exemptions: a mark spanning its whole viewport is a substrate, and text overhanging its strip by a few pixels is normal. The linter reached 20 rules and is positioned as something you can gate a build on, which only works if the noise floor is near zero.

    View source ↗
  2. 13d ago

    Animated SVGs no longer blink once and vanish or play in reverse

    Three rendering fixes with a shared root: per-frame independence was never accounted for. Each frame restarted its defs id counters, so every clip-path reference resolved to frame zero's hidden clip and the panel was clipped away for 47 of 48 frames; the negative animation-delay was offset by i rather than n minus i, running the cycle backwards. Gradient strokes on circles also collapsed to a single stop on the batched fast path.

    View source ↗
  3. 13d ago

    Pick table now reports device pixels instead of two coordinate systems

    element_geometry() returned viewport-local coordinates for most row types and device pixels for text and roundrect — one table in two coordinate systems — which made vl_nearest() actively wrong in any offset panel, reporting a mark at distance zero with the cursor nowhere near it. Both now report device pixels matching scene_model() and the rendered SVG, as documented.

    View source ↗
  4. 14d ago

    Contrast rule stops flagging every plot; gridlines become PDF artifacts

    The contrast rule sampled four points around a label and took the worst, so any label grazing a tick or gridline read as roughly 1:1 and dark text on white panels was flagged nearly everywhere. Taking the second-worst side ignores incidental adjacent ink while still catching genuinely low-contrast placement. Separately, nodes with role grid now register as PDF artifacts rather than bogus Figure entries in the structure tree.

    View source ↗
  5. 14d ago

    grobwidth and grobheight now measure wrapped text, not the unwrapped line

    The same shape as the previous release: a capability that worked when drawn and not when measured. Layout tracks sized by grobheight() reserved one line for labels that drew as several, so titles and captions overran whatever sat above them. Extent now wraps and stacks lines through the draw path rather than a parallel implementation, which is what stops the two from diverging again.

    View source ↗
  6. 14d ago

    Keyed roundrect becomes a real batch after downstream integration exposes it

    roundrect_grob() draws one box per element but was treated as a single shape on both sides of the seam, silently truncating keys to length one and aborting the element-count check. It is now a keyed batch like text_grob(). The note attached to this release is the significant part: the first bug in the series found by using the engine from vellumplot rather than testing it in isolation.

    View source ↗