# 0023: The GPUI dependency moves to a pinned Zed revision, and the accessibility adapter is written before it moves Status: accepted. Supersedes the dependency choice in decision 0017; that record's disposition of Floem, its account of the licence closure, and its reasoning about accent colour all stand. Decision 0017 chose published `gpui 0.2.2` from crates.io over a Zed git revision, accepted a known-unmet accessibility gate as the price, and described the way back: "Reversing this decision means changing one dependency line and one adapter implementation." Both halves of that sentence have since been falsified. The published crate is not a release line that can be migrated from later, and the adapter that was supposed to make the migration cheap was never written. ## The published crate is not a channel Measured against the crates.io API and Zed's `main` on 2026-09-01: - `gpui`'s newest version is `0.2.2`, published **2025-10-22**. Every non-yanked release was published in a seventeen-day window in October 2025, and nothing has been published in the ten months since. - Zed's `main` still declares `version = "0.2.2"` in `crates/gpui/Cargo.toml`. Ten months of development, no version bump: nobody upstream is preparing a publish. - The platform layer has since been split into `gpui_platform`, `gpui_linux`, `gpui_macos`, `gpui_windows`, `gpui_wgpu`, `gpui_web` and `gpui_apple`. All of them inherit `publish = false` from Zed's workspace; only `crates/gpui` sets `publish = true`. On `main` the `x11` and `wayland` features of `gpui` itself are now empty markers, because the backends they used to select live in `gpui_linux`. - Published 0.2.2 has no `accesskit` dependency at all, and `docs.rs/gpui/0.2.2/gpui/enum.Role.html` is a 404. At `ce48461e`, `crates/gpui/Cargo.toml:50` carries `accesskit.workspace = true`. - Published 0.2.2 renders through `blade-graphics`. Upstream rendering has moved to `gpui_wgpu`. So the choice was never "0.2.2 now, a newer published version later". Even if Zed published tomorrow, the publishable crate no longer contains a Linux platform backend. Staying means freezing permanently on an October-2025 artifact, and permanently failing the hard gate in `UX_VERTICAL_SLICE.md` that says the accessibility tree must name each pane, its selected state, its execution target, and its waiting state. ## A pinned revision is not an unpinned upstream Decision 0017's objection was "a git dependency on an unpinned upstream". That is not what is adopted here. With a full-SHA `rev`, a committed `Cargo.lock`, and `deny.toml` naming the permitted Git sources, a git dependency is *more* pinned than `gpui = "0.2.2"`, which is a caret requirement that would silently accept a `0.2.3` nobody reviewed. The real cost is that bumping the revision becomes a deliberate scheduled act. That cost is accepted and named here rather than avoided. The target revision is `ce48461eaadd16c65c31f835511ab96bd3b6e746` (2026-08-31), which is the revision `spikes/gpui-accessibility-probe` already pins and already proves compiles with stable accessibility IDs, workspace/pane roles, selected state, and real `EntityInputHandler`/`ElementInputHandler` implementations. Its toolchain is Rust 1.97.1, moving the workspace from 1.94.1. ## The adapter comes first, and it does not exist yet Decision 0017 said the mitigation was "a narrow `a11y` adapter trait, introduced now while there are few call sites", so that the migration would be "a swap rather than a rewrite". No such trait, module, or file exists anywhere in the workspace. The only trace is `apps/lumbridge/src/sidebar/model.rs`, where `describe()` produces the screen-reader sentence, is tested, is called by nothing, and carries an `#[allow(dead_code, reason = "wired up with the accessibility adapter, decision 0017")]`. Meanwhile the call sites stopped being few: `apps/lumbridge/src` now builds 147 elements and gives only 20 of them a stable identity. Writing the adapter is therefore the first step and happens **before** the dependency changes, on 0.2.2, where its bodies are no-ops. That keeps the dependency swap a swap, as 0017 intended, instead of paying for the swap and the wiring in one unreviewable change. The `#[allow(dead_code)]` is deleted by that step rather than carried. ## Order, and why each step is alone Each stage lands separately because each fails differently, and a combined failure is unattributable: 1. **The `a11y` adapter, still on 0.2.2.** No-op bodies; wire the elements that carry identity; give `describe()` a caller. 2. **The toolchain, still on 0.2.2.** Move `rust-toolchain.toml`, the workspace `rust-version`, and the toolchain named in `.gitea/workflows/ci.yml` — which hardcodes it twice rather than reading the file — to 1.97.1. Three minor versions of new `clippy::pedantic` lints arrive as hard errors under `-D warnings`. Isolating this stage is what stops a lint avalanche being mistaken for a GPUI API break. 3. **`deny.toml`.** Add `zed-industries/zed` and `zed-industries/font-kit` to `allow-git`. The second is not optional: `gpui_wgpu` reaches `font-kit` through a Git source, so the move brings two Git sources, not one. 4. **The dependency swap.** `gpui` and `gpui_platform` at the pinned rev; `wayland`/`x11` requested on `gpui_platform`, where they still mean something; and `Application::new()` — which does not exist at that revision — becomes `gpui_platform::application()`. 5. **Real semantics in the adapter**, and the probe's AccessKit tree test ported into `apps/lumbridge`. 6. **The probe changes job**: it stops proving AccessKit exists and starts proving the next revision still compiles. The renderer swap from blade to wgpu in stage 4 is the least characterised risk. `scripts/native-libs.sh` addresses one missing `libxkbcommon-x11` symlink and says nothing about a Vulkan or GL loader, and the Gitea job installs no wgpu dependency. Expect to learn something here. ## What is knowingly still unmet A passing unit test over an AccessKit `TreeUpdate` is not an assistive-technology claim. The gate is not met until AT-SPI/Orca on Linux and VoiceOver on macOS have been driven by hand. Stage 5 makes the semantics real; it does not close the gate, and nothing in this record should be read as closing it. ## Enforcement This record is not self-enforcing, and the previous one was not obeyed. Three mechanical checks carry it instead: - `scripts/workspace-guard.sh` asserts the gpui source and version in `Cargo.lock` — the lockfile, because a manifest states an intent while the lockfile states what would actually compile. It runs in the fast headless job, which is not `continue-on-error`, so it gates every push. - `deny.toml`'s `unknown-git = "deny"` plus an explicit `allow-git` list makes a fourth Git source a CI failure. `scripts/ci.sh` now actually runs `cargo deny check sources`; it previously ran only `check licenses`, and the source policy had never once been executed. - `AGENTS.md` names the gpui dependency, `rust-toolchain.toml`, the workspace `rust-version`, and `deny.toml` as things a contributor or agent may not change without a decision record.