VelquView lays out and rasterizes HTML + Tailwind CSS in pure Rust — taffy for layout, fontdue for glyphs, softbuffer for pixels. No browser, no webview, no DOM runtime. Same markup, measured pixel-for-pixel against Chromium.
git clone https://github.com/ther12k/velqu-view-starter && cd velqu-view-starter && ./dev.sh
let mut view = VelquView::new();
view.enable_tailwind();
view.load_html(r#"<main class="flex h-full">…"#)?;
let frame = view.render(Viewport::try_new(1280, 800, 1.0)?)?;
// frame.pixels() → RGBA, deterministic
<main vx-state="{ current: 'overview', }">
<aside class="w-56 h-full bg-slate-900 p-3">
<div @click="current = 'overview'"
:class="current === 'overview'
? 'bg-teal-600 text-white …'
: 'text-slate-400 …'">Overview</div>
</aside>
</main>
A real VelquView frame — winit window, taffy layout, fontdue glyphs, softbuffer presentation.
Not a webview with an API bolted on. A deterministic engine whose outputs can be pinned, diffed and gated in CI.
The spacing scale, 21 color families, flex, grid, borders, radius, typography — compiled by the engine, not a CDN. Every class outside the profile is a compile-time diagnostic, never a silent drop.
vx-state + @click + :class + vx-show — a small state machine evaluates bindings each frame and mutates only what changed. Tabs, modals, dashboards.
Same document, same viewport → same RGBA bytes, every time. Frame digests are pinned in tests; a rendering change is a build failure, not a vibe.
Unsupported utilities report themselves: tailwind diagnostic: class "shadow-md" …. Port real-world markup and watch the compiler catch every assumption.
A 3-gate external suite compares against real Chromium with real compiled Tailwind: utility contracts, section geometry vs reviewed baselines, raster digests. Regressions and unexpected improvements both fail.
The UI never claims durability. The starter's ops app observes public events, validates, persists atomically and reloads — the renderer renders, the host decides.
Four sample sets — a login card, a component stack, a chat UI, and a full admin dashboard — each rendered by VelquView and by Chromium with real Tailwind v3.4. Geometry measured, not eyeballed.
Card at exactly x 450–830, y 204–594 in both engines. Ported from Flowbite's card-with-form. Full side-by-side on GitHub →
Buttons, alerts, badges, pricing, table, testimonial — max drift 4px, from text metrics alone. Full side-by-side on GitHub →
Outgoing bubbles via flex-row-reverse land at identical coordinates in taffy and Chromium. Full side-by-side on GitHub →
A complete MIT admin template (3k★) — sidebar, stats, table, panels — cloned and measured. Full side-by-side on GitHub →
Remaining known deltas are paint-level (glyph weight, placeholder color, no emoji) — documented in FINDINGS.md, on the post-acceptance engine list.
clone and a native frame.The public surface is small on purpose. This is the whole model — plus the sharp edges we documented while building four sample apps on it.
git clone https://github.com/ther12k/velqu-view-starter
cd velqu-view-starter
./dev.sh # pinned toolchain, hot reload
<body class="bg-slate-950">
<main class="flex h-full">
…your UI, v0 utilities…
</main>
</body>
cargo build --release --locked
./target/release/velqu-view-starter \
--app-dir app --size 1440x900
The starter pins two revisions separately: the runtime crate set (Cargo.lock) and the dev tool (dev.sh → velqu-lab). They move only together, deliberately.
One type, one loop. The host owns the window (winit), calls the view, and presents the frame.
view.load_html(&html)?;
view.load_document(DocumentSource::File(path))?;
view.load_stylesheet(id, css)?;
view.reload_bundle(html)?; // state-preserving
let batch = view.take_events();
view.pump_reactive(&batch);
let frame = view.render(viewport)?;
// hit_test → pointer_press / release
view.set_focus(node_id)?;
view.insert_text("typed");
let value = view.control_value(node_id)?;
view.wheel(dx, dy); // scroll containers
view.tailwind_diagnostics(); // Vec<String>
view.reactive_diagnostics();
view.layout_facts(); // geometry for tests
view.reactive_plan(); // binding graph
Persistence is not in the API on purpose: the host observes Click/ValueChanged events, validates, writes storage atomically, then calls reload_bundle — selection and scroll survive.
<main vx-state="{
current: 'overview',
range: '7d',
modal: '',
}">
<div @click="modal = 'export'">Open</div>
<div vx-show="modal === 'export'">…dialog…</div>
</main>
:class replaces the entire class attribute — every branch must carry the full class list, or your layout classes vanish when the binding applies.{{ }} text interpolation — bake values per state and switch with vx-show, or duplicate the node.span) inside flex containers flow as text — use block leaves (div) for flex items.m-0 mt-6, never the reverse, or the shorthand silently wins.Every class outside the profile reports itself at load — port real-world markup and the compiler lists every assumption you made:
tailwind diagnostic: class "shadow-md": utility is outside the renderer profile v0
tailwind diagnostic: class "ml-auto": margin: auto is deferred in profile v0
tailwind diagnostic: class "border-b": unknown border utility "border-b"
reactive diagnostic: node 7 vx-show: expression has an unterminated string literal
In the comparison suite this is a gate: sample pages must compile with zero diagnostics before any render is measured.
:hover, cursor)Six runnable apps live in the velqu-showcase repo — one command each:
./target/release/velqu-view-starter --app-dir <sample> --data-dir /tmp/vv --size 1440x900
# <sample> = live/ 17-section component showcase (HyperUI ports)
# dash/ telemetry dashboard — tabs, KPI ranges, takeover modal
# login/ ab/ chat/ windmill/ the four A/B comparison sets
The window title comes from the document's <title>; each sample is a plain directory — index.html plus any *.css, loaded in sorted cascade order.
| Flag | What it does |
|---|---|
| --app-dir DIR | Load a directory app (index.html + sorted *.css). Title comes from the document. |
| --data-dir DIR | Where the host persists its data (ops app: records.json under flock). |
| --size WxH · --scale F | Logical window size and DPI scale for the first presentation. |
| --headless --out P | Render offscreen and write a PNG; no window. The CI path. |
| --frames N | Headless: render N frames and verify byte determinism. |
| --exit-after-ms N | Window mode: auto-close (smoke tests, automation). |
| --measure | Print launch→first-frame and uptime stats. |
Stated plainly, because the measurements say so — these are engine-level items on the post-acceptance list, frozen until the supervised operator session:
fontdue rasterizes plain coverage; Chromium's FreeType stem-darkens. Same font renders lighter in velqu — measured, zoomed, documented.
A single bundled DejaVu face. No webfonts, no italic/mono variants, no emoji coverage — missing glyphs simply don't render.
No shadows, gradients or opacity in v0 — depth comes from tonal steps and borders, which dark UIs do beautifully.