Decide to build the toolkit, and say what would stop it
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:
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.
|
||||||
Reference in New Issue
Block a user