Decide to build the toolkit, and say what would stop it
CI / rust-headless (push) Successful in 7m31s
CI / rust-ui (push) Successful in 13m45s

Lumbridge will build and open-source lumbridge-toolkit and ship on it, the same
arrangement Zed has with GPUI and for the same reason: nothing existing is built
for a terminal-grid-and-agent-panels product. GPUI stays as the backend until
parity, then goes.

What changed is not appetite. Writing a toolkit used to mean writing a GPU
renderer, a shaping and layout engine, and an accessibility layer from scratch;
vello, parley, accesskit, kurbo, peniko, winit and taffy now do that under a
permissive licence. Measured at ce48461e the GPUI family is 129,110 lines across
nine crates, and the large majority of it is those primitives. What is left --
the element model, layout composition, the paint pipeline, input routing, and
the terminal grid -- is what this project actually has to write.

Two claims justify building rather than adopting, and no other Rust toolkit
makes them. Accessibility is a crate the element model is built on rather than
an adapter added later, which matters because both candidates Lumbridge
evaluated failed that hard gate and 0017 shipped knowing it. And the terminal
grid is a first-class widget rather than an embedding problem, which matters
because this repository has already got cell measurement wrong twice.

M0 is a gate with its thresholds written before the run, so the result cannot be
rationalised afterwards: five panels of 200x60 styled cells no worse than the
GPUI baseline on frame p95 and key-to-present, an IME round trip that does not
split UTF-8, and an AccessKit tree driven by Orca and VoiceOver -- a passing
TreeUpdate unit test explicitly does not count. Failing it means staying on
pinned GPUI and superseding this record.

The failure mode being guarded against is a year with neither a finished toolkit
nor a finished product, so Phase 1's exit criterion is not deferred for toolkit
work, and no GPUI capability is removed before its replacement passes the same
gate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SPYebLiN2w4TqnHUYGdECq
This commit is contained in:
Metal Agent
2026-09-01 14:03:56 -07:00
co-authored by Claude Opus 5
parent 2aab0c4aab
commit d7560d4b8b
@@ -0,0 +1,150 @@
# 0024: Lumbridge builds its own UI toolkit, and GPUI becomes the interim backend
Status: accepted. Supersedes the framework selection in decisions 0017 and 0023.
Those records' accounts of *why* the published GPUI crate is unusable, and the
pinned revision Lumbridge runs on today, both stand — this record changes where
the project is going, not where it currently is.
## The decision
Lumbridge will build and open-source `lumbridge-toolkit`, a native Rust UI
toolkit, and Lumbridge will ship on it. GPUI remains the backend until the
toolkit reaches parity, and is then removed.
This is the same shape as Zed's own arrangement — an application and the toolkit
it needed, developed together — and it is chosen for the same reason: no
existing toolkit is built for what this product is. It is not a general-purpose
GUI framework, and competing with GPUI on breadth is not the goal.
## Why now, and what changed
The case against building a toolkit was that it means writing a GPU renderer, a
text shaping and layout engine, and an accessibility layer from scratch, and
that owning menus, IME, clipboard, drag-and-drop, multi-window and packaging on
three platforms is where such projects die. Most of that is still true. What has
changed is that the hardest primitives are now solved by permissively licensed
crates that did not exist, or were not ready, when GPUI was started:
- `vello` for GPU 2D rendering, `wgpu` underneath;
- `parley` with `swash`/`skrifa` for shaping, layout and font fallback;
- `kurbo` and `peniko` for geometry, paths and brushes;
- `accesskit` for the platform accessibility tree;
- `winit` for windowing, input and IME;
- `taffy` for flex layout, which GPUI itself uses.
Measured at revision `ce48461e`, the GPUI family is 129,110 lines across nine
crates — `gpui` alone is 75,995, with 15,391 for the Linux backend, 12,282 for
Windows and 10,503 for macOS — carrying 108 non-Zed dependencies. The large
majority of that is the primitives above. What remains, and what this project
actually has to write, is the element model, layout composition, the paint
pipeline, input routing, and the terminal grid.
## What the toolkit is for, and what it is not
Two claims are worth making because nothing else in the Rust ecosystem makes
them, and they are the reasons this is worth building rather than adopting:
**Accessibility is structural.** `lt-a11y` is a crate the element model is built
on, not an adapter added later. Both candidates Lumbridge evaluated failed its
accessibility hard gate — Floem has no AccessKit integration at its pinned
revision, and published GPUI has no AccessKit dependency at all — and decision
0017 knowingly shipped with that gate unmet. A toolkit that cannot be retrofitted
into that state is the point.
**The terminal grid is a first-class widget**, not an embedding problem. Every
Rust application that puts a terminal on screen rebuilds cell measurement, styled
run coalescing and viewport arithmetic, and `docs/ARCHITECTURE.md` records two
ways this project already got it wrong: a cell advance hardcoded 13% wider than
the face actually painted, and `ws_xpixel`/`ws_ypixel` reported as zero, so every
program doing pixel arithmetic was told the window had no size.
It is explicitly **not** a general-purpose application framework, not a web
target, and not a competitor for editor authors. Its first client is Lumbridge,
and features arrive because Lumbridge needs them.
## Repository and licence
A separate repository, consumed by Lumbridge as a revision-pinned Git dependency
through the same guard that pins GPUI today.
Separate, rather than a directory in this tree, because this repository has twice
shipped code that no build ever looked at: the GPUI shell in `spikes/`, which
`Cargo.toml` excluded so that every test written into it silently never ran
(decision 0017), and `crates/lumbridge-devices`, which was in neither `members`
nor `exclude` and did not compile at all. A toolkit developed in an unlinted
corner of an application's tree is the third instance waiting to happen. It also
needs its own CI, its own issue tracker, and a release cadence that is not
Lumbridge's.
Licensed **MIT OR Apache-2.0**, the Rust ecosystem's dual-licence norm. This is
not only about adoption: an Apache-2.0 crate that unconditionally pulls
GPL-3.0-or-later is precisely the trap that ended Lumbridge's use of upstream
GPUI as a long-term dependency, and a permissive dual licence with a `cargo deny`
source and licence gate from the first commit is how this project avoids setting
the same trap for somebody else.
## Shape
```text
lt-core ids, geometry, colour, units (kurbo, peniko)
lt-text shaping, font fallback, cell metrics (parley, swash)
lt-layout flex composition and measurement (taffy)
lt-render scene building, paint runs, damage (vello)
lt-input key encoding, IME, mouse, focus (winit)
lt-a11y the AccessKit tree
lt-platform window, menus, clipboard, drag-and-drop
lt-element the builder API applications write against
lt-terminal the styled grid
```
## M0 is a gate, not a formality
Before any port begins, one spike answers the three questions that would end
this. Its thresholds are written here, in advance, so the result cannot be
rationalised afterwards. The baseline is the current GPUI shell on the same
machine and the same fixture.
1. **Density.** Five panels of 200x60 styled cells consuming live PTY output.
Frame p95 no worse than the GPUI baseline, and key-to-present no worse.
2. **Input.** Composed accents and multi-codepoint graphemes survive an IME
round trip without splitting UTF-8 — the same assertions
`spikes/gpui-accessibility-probe` already makes.
3. **Accessibility.** An AccessKit tree naming each pane, its selected state,
its execution target and its waiting state, **driven by Orca on Linux and
VoiceOver on macOS**. A passing unit test over a `TreeUpdate` is not an
assistive-technology claim and does not satisfy this.
Failing M0 means Lumbridge stays on pinned GPUI and this record is superseded.
Three weeks is the budget.
## Migration, and what is not allowed to happen
Lumbridge ships on GPUI throughout. The port is a strangler, and the seam already
exists: no crate outside `apps/lumbridge` imports a GPUI type, `lumbridge-terminal`
already produces framework-neutral immutable snapshots and coalesced paint runs,
`lumbridge-theme` derives colours with no framework types, and the `a11y` trait
already abstracts the semantics away from the framework that renders them.
- **M1** — the `lt-*` crates, with `lt-terminal` rendering the grid.
- **M2** — `lt-element`, and the framework-neutral `lumbridge-ui` boundary that
`ARCHITECTURE.md` has named since the beginning. One panel ports. GPUI renders
the rest.
- **M3** — the shell builds on either backend behind a Cargo feature, both in CI,
with the deterministic fixture replaying identically through each. That
identical replay is the parity evidence; a screenshot is not.
- **M4** — GPUI is removed, and `lumbridge-toolkit` 0.1 is published.
The failure mode to guard against is a year spent with neither a finished toolkit
nor a finished product. Two rules follow. Phase 1 of `ROADMAP.md` — "Lumbridge is
worth using as a terminal multiplexer without AI features" — is not deferred for
toolkit work. And no GPUI capability is removed before its `lt-` replacement
passes the same gate, so `main` is never the worse option.
## Known risks
`vello` is pre-1.0, and this trades one pre-1.0 dependency for several. macOS
notarization and VoiceOver are where toolkit projects die, and there is currently
no Mac in the development loop at all — every macOS row of
`UI_SPIKE_SCORECARD.md` is still `pending`. Text is a tarpit; the mitigation is
that a terminal grid needs a fraction of what a word processor does, and that
scoping must be defended rather than allowed to erode.