diff --git a/docs/decisions/0024-lumbridge-builds-its-own-toolkit.md b/docs/decisions/0024-lumbridge-builds-its-own-toolkit.md new file mode 100644 index 0000000..4d116fc --- /dev/null +++ b/docs/decisions/0024-lumbridge-builds-its-own-toolkit.md @@ -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.