Skip to content
←Back to Open Source

OPEN SOURCE DEEP DIVE

AI-Native DesignVector GraphicsDesign-as-CodeAgent Teams

OpenPencil: the Rust-rewritten open-source AI-native vector design tool where design is code

OpenPencil stores designs as readable, diffable, Git-friendly .op JSON, turns one prompt into multi-screen editable designs through the op-orchestrator and Agent Teams, and exports React, Flutter and more via MCP, CLI and codegen; after the Rust rewrite the desktop app is a single 55.5 MB binary, a 10k-node canvas runs panic-free and the web build is 2.18 MB gzipped.

ZSeven-W/openpencil6.8kRustMIT10 min read

One-line positioning

OpenPencil is an open-source, AI-native vector design tool that treats Design-as-Code as its first principle: every design on the canvas is a readable, diffable, Git-friendly JSON document (.op) rather than a proprietary binary. Its stated historical coordinate is a modern successor to the old open-source prototyping tool Pencil, but the real through-line is making prompts, agent teams and MCP first-class citizens of the design loop: a one-line brief goes in, multi-screen editable designs or runnable code come out.

The repository is maintained by ZSeven under the MIT license, written mostly in Rust, created in February 2026 and still committing at a high cadence in late October 2026; the README ships in more than a dozen languages and the official site offers a web build. At collection time the project had roughly 6.7k stars and 590 forks on GitHub, a steep curve for a rewrite that finished less than a year ago.

OpenPencil main canvas: layer tree and toolbar on the left, multi-screen design in the middle, status bar on top showing live agents and MCP connections

From Pencil successor to Design-as-Code

OpenPencil started as a TypeScript plus Electron app; at v0.7.5 the author retired that line (issue #129) and moved every capability into a Rust workspace: two dozen crates plus three vendored submodules (jian as the GPU-Skia UI foundation, casement as a winit fork, agent as the agent-rs runtime), together with an in-tree fork of the Anthropic Agent SDK. The rewrite was not a line-by-line port: the .op document format is defined as plain JSON, so the node tree, scene variables (scene_vars), pages and components all live in clear text, which means designs can be code-reviewed, three-way merged and linted in CI by op-design-lint.

Design-as-Code is more than a format slogan. op-git puts a clone wizard, branch picker and cascading pull/push inside the editor; folder mode performs three-way merges with an on-disk MERGE_HEAD and per-node, per-field conflict cards instead of a whole-file either/or. op-opmerge owns document-level merge semantics while op-collab keeps collaboration a transport-free pure protocol: canonical hashing plus exact apply, regression-tested by the two-process recovery matrix of op-collab-smoke. Design documents therefore get the same version-control treatment as source code for the first time.

One prompt to a full screen: the orchestrator and Agent Teams

The centre of the generation pipeline is op-orchestrator, internally codenamed the S3a design orchestrator (single-screen sequential skeleton): it rebuilds the four phases validated in the TypeScript era as pure Rust functions, with every side effect pushed behind the DocSink and LlmClient traits so the orchestration logic can be unit-tested offline. The module split is fine-grained: intent parses the brief, plan emits the screen skeleton, parse plus plan_normalize and plan_repair coerce model output back into legal DSL, plan_coverage back-fills missed regions, program_gen and design_system inject design-system variables into generation, and deck_echo serves the deck scenario.

The model adaptation layer sorts the market into three tiers with matching prompt strategies (see table 2): the Full tier gives Claude-family models the complete prompt and a thinking budget; the Standard tier gives GPT-4o, Gemini and DeepSeek the complete prompt with thinking off; the Basic tier gives MiniMax, Qwen, Llama and Mistral a simplified nested-JSON prompt so small models still emit parseable structures.

When one agent is not enough, Agent Teams take over: the orchestrator splits a page into spatial sub-tasks (hero screen, list region, detail region), dispatches them concurrently to several members, and members hand context to each other through a delegate tool; each member gets its own indicator on the canvas so a reader can see which block is being rewritten by which agent, and any member failure falls back to a recovery strategy instead of rerunning the whole page. In the official screenshot the top bar shows four agents and three MCP connections at once, the left rail holds the full layer tree, the middle carries a three-screen travel app, and the agent panel at the bottom right is decomposing a three-screen travel brief into a seven-item structure checklist (figure 2).

Agent Teams in action: the agent panel decomposes the brief into a structure checklist while three mobile screens sit side by side on the canvas and the top bar reports 4 agents and 3 MCP connections
flowchart TB
    B["one-line brief / .op doc / Figma .fig"] --> O["op-orchestrator (S3a)
intent - plan - parse/repair - coverage"] O --> T{"model_profile
Full / Standard / Basic"} T --> A1["agent member 1
hero-screen spatial sub-task"] T --> A2["agent member 2
list-region sub-task"] T --> A3["agent member N
delegate hand-off"] A1 --> S["DocSink
writes PenDocument and scene_vars"] A2 --> S A3 --> S S --> C["canvas
jian + GPU Skia, member indicators"] C --> L["op-design-lint
anti-slop diversity checks"] L --> D{"exit"} D --> E1[".op JSON into Git
op-git three-way merge"] D --> E2["MCP and CLI
design_* tools, op design @file"] D --> E3["codegen
React / Flutter / SwiftUI and more"]

Canvas and runtime: the performance ledger of the Rust rewrite

The refreshed performance ledger is the hardest selling point of the rewrite: a 10,000-node canvas runs without panics, idle CPU sits near zero, and a full layout snapshot takes about 0.68 seconds; wheel zoom, the hottest path, once burned roughly 69% CPU until a fix that is almost embarrassingly simple - stop re-serializing the whole document every frame - brought it back near zero. The desktop build is a single 55.5 MB native binary with winit windows and GPU Skia rendering, free of Electron's two-process tax; the web build is 8.2 MB of raw wasm and 2.18 MB gzipped, with the icon catalogue split out contributing about 48% of the wire-size reduction.

Crate-level documentation pins these wins as invariants: the wasm bundle is capped at 6 MiB gzipped with zero env.* imports, measured in CI by the check-wasm-bundle script (4.99 MiB measured in late September 2026, 5.20 MiB on the web SDK basis); an 800-line-per-file cap with zero violations repo-wide; error handling through eighty-plus typed error enums with bare Result<_, String> allowed at exactly two external boundaries; op-util as a dependency-free leaf crate where hex_color, json_escape and xml_escape are single-sourced; and op-host-native requiring the gl-host feature for its full test suite (676 versus 55 tests). These rules look pedantic, yet they are why a large workspace maintained by a tiny team does not collapse.

DimensionTypeScript + Electron era (through v0.7.5)Rust workspace (v0.8.x)
Desktop formElectron shell plus web viewsingle 55.5 MB native binary, winit plus GPU Skia
Canvas scalestruggles from a few thousand nodes10,000 nodes without panics, full layout snapshot around 0.68 s
CPU usagewheel zoom around 69%idle and post-fix zoom both near 0%
Web artifactJS bundle redundant with the shell8.2 MB raw wasm, 2.18 MB gzipped, CI cap 6 MiB
Platform reachdesktop-firstffi, jni and napi carry the core to iOS, Android and OpenHarmony
Outcomeretired by issue #129the only mainline today

MCP, CLI and code export: the design-in-version-control workflow

There are three outward interfaces. The first is MCP (op-mcp): the desktop app spawns it over stdio with --mcp <path>, while a live session exposes an HTTP endpoint on a localhost port; the tool surface covers the three-stage design flow design_skeleton, design_content and design_refine (batched layered refinement), single-node insert_node, style-guide retrieval get_style_guide and get_style_guide_tags (fifty-plus guides with fuzzy tag matching), and the code-export pipeline codegen_plan, codegen_submit_chunk, codegen_assemble and codegen_clean; prompts themselves are fetched in segments on demand so context is never flooded at once.

The second is the CLI (op): op start launches the desktop app, op start --headless --file runs a headless session, op design @file executes a DSL script (scripts run in sandboxed JS), op insert drops a node straight in, op import:figma eats Figma .fig files, and a dash takes over stdin; op install can place openpencil-skill into the skill directory of Codex and friends. The third is agent integration: nine-plus built-in providers, Claude Code through the Claude Agent SDK with local OAuth, Codex CLI, OpenCode and GitHub Copilot through Agent Settings, and Kimi, Zhipu GLM, Doubao, Ark, Bailian DashScope and ModelScope via Anthropic-format passthrough.

The exits are equally wide: code export targets React with Tailwind, HTML with CSS, Vue, Svelte, Flutter, SwiftUI, Jetpack Compose and React Native; decks ship six 16:9 templates exportable to PDF, HTML, pptx and hyperframes; op-brand can reverse-engineer shadcn variables and a design.md from an HTML/CSS bundle or even raw pixels, turning a brand kit into versionable text as well. Figure 3 shows a four-screen music app generated from one prompt: home, search, library and paywall share one component set and palette, exactly the input shape codegen and the design linter like.

A four-screen music app generated from one prompt: home, search, library and paywall sharing components and palette
MCP tool groupPurposeImplementation site
design_skeleton / design_content / design_refinethree-stage generation with batched layered refinementop-mcp batched-layered module
insert_nodeinsert a single node under a chosen parentop-mcp
get_style_guide / get_style_guide_tagsfifty-plus style guides with fuzzy tag retrievalop-mcp
codegen_plan / codegen_submit_chunk / codegen_assemble / codegen_cleancode-export pipeline: plan, chunked submit, assemble, cleanop-mcp codegen tools module
Model tierRepresentative modelsPrompt strategyThinking budget
FullClaude familycomplete prompt and full tool surfaceon
StandardGPT-4o, Gemini, DeepSeekcomplete promptoff
BasicMiniMax, Qwen, Llama, Mistralsimplified nested-JSON promptnot applicable

Collaboration, multi-platform reach and ecosystem

Collaboration runs over authenticated P2P: a Noise handshake plus direct TCP, falling back to public relays and regional hubs when hole-punching fails; pairing codes are ten characters and region-tagged, and live cursors plus the conflict panel expose per-edit detail with replay. Multi-platform reach comes from op-engine-ffi, op-engine-jni and op-engine-napi carrying the editor core to iOS, Android and OpenHarmony; the serve-web mode of op-host-desktop self-hosts a multi-tenant web edition, an alternative deployment shape to op-hub; packages also include a VS Code extension and a Chrome web-capture extension that lifts live pages straight into canvas nodes. i18n covers fifteen languages, accessibility rides AccessKit plus a web DOM mirror, supply-chain safety is enforced by cargo-deny in CI, and the Rust toolchain is pinned at 1.94.

The ecosystem ring: jian supplies the GPU-Skia UI foundation, agent-rs supplies the agent runtime, and noema, zode and dsh-openpencil are derivative projects around it; web credentials live in a dedicated localStorage key, and self-hosting controls credential persistence, allowed origins and the AI endpoint allowlist (the SSRF guard) through three environment variables. Install surfaces include a brew cask, scoop, GitHub Releases, a nix flake (x86_64-linux only) and Docker images.

Limits and rough edges

The plugin system is still an open roadmap item; today extensibility leans on MCP, skills and crate-level integration. The web host runs only built-in API-key agents, while local agents such as Claude Code and Codex are available on desktop, CLI and ACP channels only. The nix flake covers x86_64-linux alone. A several-dozen-megabyte native binary looks like a stranger to some antivirus products, and false-positive reports appear from time to time. The 6 MiB wasm ceiling keeps tension with feature growth, and every new UI block has to pay for itself through crafts like the icon-catalogue split. For a project maintained mostly by one person these boundaries are honest and clearly documented; neither the README nor the crate docs hide them.

Getting started

Desktop users install from Releases, brew or scoop and run op start to enter the canvas; to let agents work, run op install first and then point the MCP endpoint at the session from agent settings; web users open the official site or a self-hosted Docker image; Figma legacy assets migrate through op import:figma or the in-editor import wizard. The template library holds fifty-eight scene templates across six scene families plus fifty-plus style guides, so a first screen never has to start from blank. Once the design lands on disk as .op JSON, the rest belongs to Git: branch, review, merge, exactly like code.

As an Amazon Associate, we earn from qualifying purchases.