research/rune.md
Rune, against GPUI Kit
Use this file to answer "should we look at Rune instead of GPUI?" (raised by Kamal and a colleague, 2026-11-11), with the pros and cons for qnative, and the test the colleague set: Rune matters only if GPUI is proving visually restrictive. Facts come from the repo (anomalyco/rune, dev branch), its PLAN.md, AGENTS.md, crates/rune/README.md, crates/rune-kit/README.md, WEB.md, CHANGELOG.md and research/01-gpui-zed-warp.md, plus the GitHub API, all read 2026-11-11. Our side comes from gpui-kit.md, comparison.md and qnative c2's review-3.md. Companion: comparison.md, which predates Rune's visibility and does not include it. Shared as the support page Rune or GPUI?.
What it is
- "A native GPU-rendered Rust UI framework for macOS, Windows, Linux and the web, with its own renderer, text, layout and springs." Every app can run headless, so an agent can render it to PNG, dump the element tree and drive it with scripted input.
- By Anomaly, the company behind OpenCode (the same org as strobe, which we already use for the budget). MIT.
- Created 2026-10-22, 20 days before this read. 6,598 stars, 237 forks. Pre-release: no tags, not on crates.io. Apps pin a git rev of
dev, which is fast-forward-only. - 2,073 commits on
dev, about 100 a day. One author wrote 1,705 of them; three others wrote most of the rest. - No outside pull requests: "Have your agent open an issue… our agents usually turn it around fast." GitHub search shows 300 open issues and 14 closed. 284 PRs merged, all from the team.
- The stack is wgpu (Metal on macOS), winit, Taffy layout, Parley text and AccessKit, on Rust 2024.
- Its flagship test is recreating OpenCode Desktop.
rune-kitcarries "the names, variants, sizes and states of@opencode/ui, so screens port from the Solid app by name." - Its own research looked at forking GPUI and ruled against it: "Do NOT fork GPUI… build a fresh, modular architecture." That research is a competitor's, so weigh it as one.
Pros
| Pro | Why it matters for qnative |
|---|---|
Agent tooling is built in. Every app takes --snap (PNG), --tree (the element and accessibility tree as JSON), --script (timed pointer, key, drag, resize and menu events) and --video, all on a fixed virtual clock. | c132's native capture mode and the stories crate build this on top of GPUI by hand. In Rune it comes with the framework. |
A storybook ships with it. App::playground gives stories, variants, triggers and knobs, with headless unit tests (open_playground, playground_trigger, rt.frame(dt)). The docs map an app's own story table onto it row by row. | Our stories crate (c132 D1) would map onto it with little loss. |
It diffs web against native. scripts/kit-diff.py renders a Storybook story in Chromium and the matching Rune scene, then writes web, native and diff PNGs and a score per story. | This is the c132 D7 wipe, automated. rune-kit is itself a web design system ported to native component by component: the same move as q-nuxt-layer to our drawn layer. |
| Motion is first-class. Analytic springs retarget with their velocity. Layout animates per edge, elements animate in and out, colours move in OKLab, and reduced motion is built in. Views never re-run for animation. | GPUI animates by rebuilding the wrapped subtree on each tick. Upstream, a spinner cost 85 % of a core (comparison.md, Idle CPU). |
| More visual primitives. Radial and angled gradients, inset shadows, dashed borders, vector shapes, backdrop blur, subtree blur, system materials, isolated opacity and a compositor hook. | Headroom for glass and effects that GPUI lacks (the comparison table notes no backdrop filter on GPUI). |
Text closes one of our gaps. El::wrap_anywhere() is CSS overflow-wrap: anywhere. Rich text takes a background per range, and editing has inline pills. | DataRankedBars sits at fidelity 2 because GPUI wraps only at word breaks. Rune would fix that one. |
| Small and quiet. The settings example is 9.3 MB stripped. Over a 180-frame bench, frame CPU averages 0.13 ms (p95 0.29 ms) in one draw call. An idle window draws nothing. | Their at-rest rules (no idle redraws, poll returns true only on change, no file I/O in the frame loop) are the ones we hold the budget to. |
| The same views run on the web. An experimental WebGPU target uses the same views, layout, atlas and shaders. | A native mock could, in principle, run on the design site. It is experimental. |
Docs written for agents. AGENTS.md, CLAUDE.md and a 2,900-line crate README, with a starter prompt. | This partly offsets having no training data. |
Cons
| Con | Why it matters for qnative |
|---|---|
| 20 days old, pre-release, not on crates.io. Apps pin a git rev. | GPUI Kit ships versioned crates weekly in step with gpui-pre. We pin gpui-kit =0.9.1. |
Heavy churn. About 100 commits a day, and a 3,200-line changelog in three weeks. Its AGENTS.md lists twelve public types whose new fields break apps. | Each bump would be a migration. The API learned this week moves next week. |
| Bus factor. One author has 82 % of the commits, and there are four core people. | GPUI Kit has 233 contributors, with Zed upstream beneath it. |
| Closed to contributions. Issues only, and 300 open against 14 closed. | We could not land our own fixes. We would carry patches or wait. |
Built for one product. The kit mirrors @opencode/ui, and the plan follows OpenCode Desktop. | Our needs (data tables, charts, a tray-first app) would ride on someone else's priorities. |
| A thinner kit for our pages. About 30 controls, plus Tree, Table, Markdown, Diff and Mermaid. No charts and no docking. | GPUI Kit has 80 component pages, including DataTable, charts and Dock. We use the kit for the shell only, though (next section). |
| Desktop gaps are still open. You can't claim a URL scheme at run time (#636). There is no credential-store API (#635). Text input and IME are its own top risk, and the window shell is winit for now. | Tray windows, notifications (rune-notify) and on_open_url exist, which is about parity with GPUI Kit (it had none of those either; platform.md). |
| Memory is not clearly better. After their own fix, a settings window settled at 70 MB (target: 50 MiB RSS or less). | qnative's whole window idles at 62 MB private and the tray at 21 MB. The metrics differ, so this is not like-for-like. |
The switching cost is ours. It covers the drawn layer (about 5,400 lines, 30 components on GPUI primitives), the stories crate on GPUI's test-support, devmode, c132's native capture and the token bridge. | All of it would be re-expressed in a different state model: a plain view(&State) function with queued &mut handlers instead of GPUI entities. |
| No training data. | This is the same problem c130 noted for Native Shell. |
The colleague's test: is GPUI visually restrictive?
Not so far. qnative c2's scorecard (review-3.md, 2026-11-10) drew 30 of 36 components on GPUI primitives, 28 of them at fidelity 3. The two at fidelity 2 are both text:
- DataRankedBars: stacked labels end in an ellipsis where the web breaks words, because GPUI wraps only at word breaks. Rune's
wrap_anywherefixes this. - Skill Markdown: text runs carry no border, so outlined spans are tinted instead. Rune has per-range backgrounds and inline pills, but no outline per range was found.
GPUI costs workarounds, not walls. The same review lists what the web had and what stood in on GPUI. Rune's column is from its docs, untried in our code:
| The web has | What qnative did on GPUI | In Rune |
|---|---|---|
CSS grid (subgrid, fit-content, auto-fill minmax) | Flex tracks with fixed row heights; a zero-height probe learns a width one frame late | Taffy grid is in its plan; no grid API in its docs |
position: sticky | The page reads its scroll offset and draws a pinned copy in an overlay | Built in: .sticky_top(), .stuck_shadow() |
| SVG | Canvas paths, recoloured images cached per logo and colour | Vector shapes with Bézier paths; recolouring not checked |
break-words | An ellipsis (DataRankedBars, fidelity 2) | Built in: wrap_anywhere() |
| Outlines on inline text | Tints (Skill Markdown, fidelity 2) | Per-range backgrounds and pills; no per-range outline |
| Eased height | An animated max height | Per-edge layout springs |
| Capture, stories, the web-against-native wipe | Built by hand (c132, c138) | Built in: --snap, --tree, --script, playground, kit-diff.py |
One wrinkle in "GPUI has a solid component library": the same scorecard counts 2 kit components used as is and none composed from the kit. The kit carries the shell (title bar, root layers, theme), and our own vocabulary is drawn. The kit's breadth matters less to us than it sounds. What a switch would really cost is our drawn layer, not the kit.
Recommendation
Agreed: stay on GPUI Kit for V1. GPUI isn't restricting the look, and Rune is too young and moves too fast to bet the app on. It is worth watching, though for a different reason than looks: its headless agent tooling and its motion model. Those are where qnative spends hand-built effort today (c132, c138).
- Borrow the ideas now. Shape our dev-mode and stories CLI after
--snap/--tree/--script, and our web-against-native check after kit-diff's per-story scoring. - Revisit in 6 to 8 weeks, once tagged releases exist and the churn settles. Run a one-day spike: DataRankedBars and the Status page in Rune, a headless snap wiped against the lab capture, and a strobe reading with the metric stated.
- Revisit sooner if motion becomes a design priority (springs, choreographed transitions), or a third visual gap turns up that GPUI can't draw.