Skip to main content
Kevin Arthur
AboutCase StudiesLab
Download ResumeGet in touch

Free Resources

Design Checklist

Component specification architecture for clean design-to-dev handoffs.

Get Free Checklist

AI Readiness Audit

Is your UX ready for AI? Take the 2-minute assessment.

Start Free Audit
EmailLinkedInBehance

© 2026 Kevin Arthur. Designed & Built by Kevin Arthur.

Back to Case Studies
Shipped Productcreative-tools

Building Varve: A Local-First Design Tool That Taught Me Every UX Decision Is a Systems Decision

I designed and engineered Varve, a local-first desktop design tool that unifies vector editing, page layout, typography, motion, prototyping, and print production around a single document model. The hardest problem was not building each discipline in isolation — it was making them share one scene graph, one rendering pipeline, and one undo history without turning the architecture into six disconnected applications.

Creator, Product Designer & Engineer
Jun 2026 – Present · Public beta
TypeScript • Rust • Tauri • ...
Varve editor showing a poster design with vector shapes, layers panel, and properties inspector — one document spanning vector, layout, typography, motion, and print

Varve is a local-first design tool for vector, layout, typography, motion, prototyping, and print — one application, no subscription, no cloud account. I designed and built it because I kept hitting two walls that no existing tool was solving together.

The first wall was fragmentation. Every creative project I worked on required switching between three or four separate tools. A vector illustration lives in one app. The page layout lives in another. The animation lives in a third. The print-ready export lives in a fourth. By the time the work crosses those boundaries, someone has to manually reconstruct what the designer actually meant. Each transition doesn't just cost time — it costs design intent: the relationship between a type hierarchy in a layout and the same hierarchy in a vector illustration, the connection between a motion keyframe and the layout it animates, the constraint between a print page and the screen composition it came from.

The second wall was the platform. I work on Linux. The creative tools industry treats Linux as a second-class citizen — if it's supported at all. Figma runs in a browser. Illustrator doesn't ship native Linux builds. InDesign and After Effects don't exist on the platform. The tools that do run on Linux — Inkscape, GIMP, Krita — are excellent at what they do, but they don't cover the full creative workflow, and they weren't designed for the multi-disciplinary, multi-format work I needed to do. The gap isn't just about features. It's about priority. When a design tool company decides which platform to invest in first, Linux is almost never the answer. The result is that Linux users either dual-boot, use VMs, accept degraded web versions, or build their own tools.

The core insight: The problem isn't that any single discipline is unserved. The problem is that creative work crosses disciplines constantly, and every tool boundary is a place where design intent leaks out — and if you're on Linux, the boundaries are even wider because the tools themselves don't reach you.

Open source — read the code on GitHub or download the beta from varve.studio. Everything described below is in the public repo.

Current status (v0.2.0) — Public beta. 5,066 commits across 13 Rust crates and 20 TypeScript packages, with 161 architecture decision records. All builds are unsigned. The .varve document format can still change between releases.


1. Why I Built It — and Why the Problem Is Harder Than It Looks

The existing tools are excellent at what they do. Figma is extraordinary for interface design. Illustrator is deep for vector work. InDesign owns print layout. After Effects handles motion. But none of them treat these as parts of the same problem. Each tool has its own document model, its own coordinate system, its own undo history, and its own definition of what a "layer" means.

And most of them don't run natively on Linux.

I wasn't interested in building a better version of any single tool. I was interested in the question: what happens when you treat all six creative disciplines as parts of the same problem, and you refuse to make Linux an afterthought?

That question turned out to be much harder than I expected. Not because any one discipline is impossibly complex, but because the systems that connect them — the document model, the rendering pipeline, the undo history, the design tokens, the release engineering — all have to be designed for a level of integration that no existing tool attempts.


2. What I Believed at the Beginning

When I started the project — originally named "Strata" — I had a set of assumptions that seemed reasonable. Some turned out to be right. Several were wrong.

What I thought would be easy:

  • Building a canvas that renders shapes. (It was.)
  • Making a layer tree. (It was.)
  • Getting a basic inspector working. (It was.)

What I underestimated:

  • How deeply the rendering architecture would constrain the product. The choice between pixel-push and IR-replay wasn't a performance optimization — it determined whether the engine could run natively, whether the webview could stay lightweight, whether the product could ship on Linux without a bundled Chromium, and whether the canvas would feel responsive enough for direct manipulation.
  • How the document model would become the product opinion. I initially thought of the scene graph as an engineering detail. It turned out to be the single most consequential design decision: what nodes exist, what they can contain, how they compose — that is the product.
  • How cross-platform support would compound. "Compiles for three platforms" is not "works on three platforms." WebKitGTK on Linux behaves differently from WebKit on macOS. WebView2 on Windows has its own quirks. Each platform introduced constraints that reshaped the architecture.
  • How local-first would change everything. I chose local-first for privacy and offline access. It turned out to change assumptions about undo semantics, file recovery, future sync strategies, and even how the UI communicates state to the user.

3. The First Major Collision with Reality

The foundational architectural decision was made on day one. I needed to solve a problem that doesn't exist in browser-based design tools: the native Rust engine and the Tauri webview live in different runtime contexts. A native wgpu GPU surface cannot exist inside the webview DOM. So I had to choose how to bridge them.

I built a spike. The first approach was pixel-push: the Rust engine renders to a bitmap, pushes it across the IPC boundary, and the webview displays it. It worked. It was also impossibly slow — 8.5 fps at 960×600. The canvas felt dead. Dragging a shape was a slideshow.

The second approach was IR-replay: the Rust engine computes the scene and emits a compact render-command IR — a flat draw list measured in kilobytes. The IR crosses the Tauri IPC boundary and is replayed onto a Canvas2D or WebGPU surface in the webview. 86.4 fps at 960×600 — roughly 10× faster.

This wasn't just a performance fix. It was a product-defining decision:

  • The engine could use full Rust performance for layout, hit-testing, and effect computation, unbounded by WASM or browser memory limits.
  • The webview stayed lightweight — no bundled Chromium, no GPU surface management, just a canvas that replays commands.
  • The product could ship on Linux without depending on a specific browser runtime.
  • The renderer became deterministic and testable — it never touches the scene graph, it receives a flat draw list.

I recorded this in ADR-0001. It was the first of 161 architecture decisions, and it held through every subsequent evolution of the product.

The lesson: The hardest architecture decisions aren't about which library to use. They're about where to draw the boundary between systems — and what that boundary makes possible or impossible.


4. Decision Stories

4.1 One Scene Graph, Six Disciplines

The scene model (@varve/scene) defines every node type the application can represent:

Shape nodes — rectangles, ellipses, lines, polygons, stars, and Bézier paths with fills, strokes, effects, and transforms. These are the atoms of vector editing.

Text nodes — rich text with character and paragraph formatting, OpenType features, variable font axes, text-on-path, and adaptive contrast. Not a simplified text box — a full typographic system with HarfBuzz shaping on the native side.

Frame nodes — layout-capable containers with CSS-native flex and grid styling. Frames can hold any other node type, which means a frame can contain vector art, text, or nested layouts. This is how page layout and vector editing share the same tree.

Group nodes — isolated containers with effects and blend modes. Groups can contain any combination of shapes, text, and frames.

Table nodes — responsive tables that reflow across breakpoints. Native, not imported.

Every node shares a common base with opacity, blend mode, rotation, fills, strokes, effects, masks, constraints, and warps. The effects system includes drop shadows, inner shadows, blurs, glows, glass materials, chromatic aberration, and glitch — all composable on any node type.

What this means for a designer: You can take a vector illustration, place it inside a layout frame, add typography with OpenType features, animate it on a timeline, and export it as a print-ready PDF — all without leaving the document. The illustration doesn't get "flattened" or "imported" — it stays a live node in the same graph.

Varve workspace showing a poster design with vector shapes, text, and layout frames in the same document The same document holds vector shapes, typed headlines, layout frames, and color swatches — all as nodes in one scene graph.

4.2 Canvas2D Versus WebGPU

4.3 Local-First as a Product Decision

4.4 Monorepo with Impact-Aware Validation


5. Architecture as a UX Decision

The architecture wasn't chosen for its own sake. Every technical decision was made because it changed what the product could be.

The design system: tokens that travel

The design system had to solve a problem most design systems don't: it needs to work in two places at once. The editor UI uses the same tokens as the documents it creates. A color swatch in the inspector and the same color applied to a vector shape need to come from the same source of truth.

The token engine (@varve/tokens) implements the DTCG 2025.10 specification — a W3C community group standard for design token interchange. The CSS output (@varve/ui) includes OKLCH color space, three themes (light, dark, high contrast), fluid typography, a 4-point spacing grid, and motion tokens that respect prefers-reduced-motion. The component library includes 123 component files following APG (ARIA Authoring Practices Guide) patterns, all with Storybook stories and automated tests.

What this means for the product: The design system isn't a separate deliverable. It's the living surface of the application. Every token change propagates to both the UI and the document canvas. The system is self-consistent.

Typography as a first-class system

Typography in Varve is not a text tool — it's a typographic system. The text pipeline runs through HarfBuzz on the native side for shaping, with a full Unicode implementation. The scene model supports character formatting, paragraph formatting, text stories (threaded text across linked frames), text on path, and adaptive contrast for accessibility.

Typography specimen showing variable font axes and OpenType features edited on-canvas Typography edited directly on the canvas — weight, tracking, optical size, and OpenType features controlled in place, not in a modal.

Motion, prototyping, and print

The motion system provides keyframe animation with Oklab color interpolation, path morphing, smart animate, and auto-keyframe assist. The prototype engine supports triggers, actions, transitions, and variable-based conditional logic. The print pipeline handles font outlining, CMYK conversion through ICC profiles, and PDF generation as PDF/X-1a and PDF/X-4 — production print, not window.print().

A designer can compose a multi-page editorial spread, animate a prototype for screen, and export a print-ready PDF — all from the same document.

Two-page editorial spread with layout frames, typography, and color blocks A multi-page editorial layout: the same frame nodes, text stories, and color system used for vector illustrations and motion — no format conversion required.


6. Things That Did Not Work

Strong work requires honest failures. Here are the ones that changed the product or the process.


7. Testing the Interface, Not Just the Code

The testing strategy spans multiple layers because the product has multiple layers:

  • Unit tests — Vitest for TypeScript, cargo test with property-based testing for Rust.
  • Component tests — Testing Library for React component behavior.
  • Integration tests — Cross-package contract tests.
  • E2E tests — Playwright for web-based workflows, WebDriverIO for native desktop.
  • Visual regression — Playwright screenshot comparison at 1x and 2x DPR.
  • Benchmarks — Vitest benchmarks for canvas, table, palette, and layout performance.
  • Mutation testing — Stryker for TypeScript mutation analysis.
  • Fuzz tests — Property-based fuzzing for raster math.

The deterministic screenshot pipeline deserves special mention. Every product screenshot is generated by driving the real application into a known state — not by hand-editing images. The capture script opens real .varve documents, sets viewport, zoom, selection, and theme, then captures. If a scene can't be produced, it's recorded as skipped with a reason, and any previous output is deleted rather than silently replaced.

The distinction that matters: "The function passed" is not the same as "the editor visibly behaved correctly." The visual regression suite catches exactly the things unit tests miss — a token that renders at the wrong weight, a panel that clips at the wrong breakpoint, a theme that breaks contrast at a specific viewport.


8. Shipping It: The Release Engineering Story

Turning 33 packages into something people can install required a release pipeline that handles Linux (AppImage, deb, RPM), macOS (DMG for Apple Silicon), and Windows (NSIS installer). The first release (v0.1.0) was broken on arrival — the GitHub Actions workflow referenced a non-existent directory, and a static import of an eval-using module broke the WebView entirely. The second release (v0.1.1) fixed the pipeline but exposed a WebKit/GTK incompatibility on modern Linux hosts. The third (v0.1.2) was the first release that actually worked reliably.

The CI pipeline (10 GitHub Actions workflows) now includes change-lane detection, supply-chain security (all Actions pinned to full commit SHAs), secret scanning, installer size gates, and product truth verification. Every release is a draft-then-publish flow — tags build a draft, a human reviews and publishes.

The lesson: Release engineering is not a final-pass activity. It requires the same rigor as the product itself, and it reveals its assumptions at the worst possible time.


9. Tradeoffs and What I Would Change

Breadth versus depth. Varve covers six disciplines, which means no single discipline is as deep as a dedicated tool. The pen tool is capable but not as deep as Illustrator's. The timeline is functional but not as mature as After Effects'. This is a deliberate tradeoff: the value is in the integration, not in matching every feature of every specialist tool.

Canvas2D versus GPU. Shipping with Canvas2D as the primary renderer was the right call for cross-platform compatibility — it works on WebKitGTK, which means Linux works today. But Canvas2D has inherent limits for complex effects and large documents. The WebGPU path is growing, but it's not complete.

Local-first versus collaboration. Real-time collaboration is scaffolded in the codebase but not shipped. Local-first was the right architectural choice for privacy, offline operation, and performance — but it means collaborative workflows aren't available yet.

Scope of the public beta. The .varve format can still change between releases. Windows and macOS builds are unsigned. These are known limitations that affect trust. I chose to ship the beta and iterate rather than wait for production polish — but I don't pretend the current state is production-ready.


10. What I Would Do Differently

Establish release engineering on day one. The v0.1.0 failure was preventable. A basic "can it install and launch" smoke test before the first tag would have caught both critical bugs.

Build deterministic test fixtures earlier. The screenshot pipeline was built after many features were already in place. If it had existed from the start, visual regressions would have been caught earlier and documentation screenshots would never have gone stale.

Define supported platforms explicitly before coding. "Runs on Linux" turned out to mean different things for different Linux configurations. A explicit platform matrix — tested, not just compiled — would have prevented the WebKitGTK surprise.

Spend more time on the vertical slice. The first usable editor was built fast, but it set patterns that had to be revisited. A slightly slower start with more attention to the undo model, selection semantics, and canvas coordinate system would have saved refactoring later.

Formalize design tokens sooner. The token system was built mid-project. Earlier adoption would have prevented inconsistencies between the editor UI and the document canvas that had to be manually reconciled.


11. What I Am Still Solving

Shipped

  • Vector, layout, typography, motion, prototyping, and print in one document model
  • Cross-platform desktop builds (Linux, macOS, Windows)
  • Deterministic screenshot pipeline
  • 161 architecture decision records
  • Local-first persistence with .varve files

Working but evolving

  • WebGPU rendering (opt-in on macOS and Windows)
  • Paint tools and brush system
  • Figma import adapter
  • Image enhancement and AI-powered features

Experimental

  • Natural-language asset search
  • Depth-aware effects
  • Email template authoring
  • Browser demo at /try/

Not yet solved

  • Real-time collaboration via CRDT
  • Code signing for macOS and Windows
  • Production-stable .varve format
  • Full browser/WASM build

12. Reflection

The lesson I carry out of building Varve is that complex tools fail when architecture and interaction design are treated separately.

Every rendering decision is also an interaction decision. The IR-replay pipeline isn't just fast — it makes the canvas feel alive, which changes how a designer experiments. The pluggable compositor isn't just extensible — it means the product ships on Linux today while GPU support grows, which changes who can use it.

Every document model decision is also a product decision. A single scene graph that holds vector, layout, typography, motion, and print isn't just an engineering achievement — it's an opinion about what design is. It says that these disciplines are not separate activities but overlapping concerns in the same creative process.

Every local-first decision is also a privacy decision. Storing files locally isn't just a storage strategy — it's a statement about who owns the work and what happens when the network is unavailable. On Linux, where users tend to value sovereignty over their tools and data, this isn't a nice-to-have — it's a requirement.

Building the tool changed how I think about design systems. The tokens that style the application are the same tokens that style the documents it creates. The components that make the inspector are built from the same design language as the canvas. The boundary between "the tool" and "the work" dissolved.

And building it on Linux — where the constraints are tighter, the platform quirks are sharper, and the tooling gaps are wider — made every decision more deliberate. When you can't rely on the platform to provide a GPU surface, a browser runtime, or a code-signing certificate, you have to design systems that work without those assumptions. That constraint made the architecture stronger.


Explore the project: the full source is on GitHub (FSL-1.1-MIT licensed), and the beta is available from varve.studio.

Next case study: OpenCode Harness — engineering a deterministic AI development interface, where state correctness and debugging discipline complement the systems thinking in this project.

Like what you see?

Let's explore how we can work together on your next project.

Book a free 15-min call
Back To Case Studies