prototype: GPUI front-end exploration in src-gpui/#7
Draft
davidchris wants to merge 4 commits into
Draft
Conversation
Sibling Rust crate that re-implements the ThoughtTree front end natively in Zed Industries' GPUI framework, kept separate from src-tauri/. Intended as a sketch for comparing GPUI ergonomics against the shipping React + Tauri build, not as a replacement. Includes: pure-Rust port of the graph domain model from src/lib/graph/ (types, mutations, conversation-path topo sort with synthesizer test), plus toolbar / canvas / side-panel views composed via Entity<AppState>. ACP integration is deliberately stubbed with a fake streaming reply so the prototype focuses on UI-framework comparison; src-gpui/README.md captures the trade-offs and the parts most likely to need API fix-ups against current Zed main. GPUI is a git-only dep requiring Metal/Vulkan and is not built in CI; this prototype has not been compiled. The graph/ submodule does compile and its test passes in isolation.
Pins the GPUI dep at zed-industries/zed @ 9155bf4 (HEAD on 2026-05-02)
instead of rev = "main". Walks the API drift the README anticipated.
Source-side cargo check is clean against the pinned rev. cargo run is
still blocked by Apple's Metal Toolchain no longer shipping with Xcode
26 by default; BUILD-NOTES.md captures what the user needs to install
to reach a runnable binary.
Drift fixed:
- theme.rs: rgb() is no longer const fn, replaced with a local
const fn hex(u32) -> Rgba so the color constants stay const.
- main.rs: Application::new() removed; switched to
gpui_platform::application() and added gpui_platform as a dep.
Imported gpui::prelude::* so cx.new(...) resolves AppContext.
- state.rs: Context::spawn now takes AsyncFnOnce(WeakEntity, &mut
AsyncApp); rewrote the simulate_stream closure as async move |...|
instead of |...| async move.
- views/canvas.rs: on_drag requires StatefulInteractiveElement;
added .id(node.id) on the node card to make it stateful, and
loosened render_node's return type to impl IntoElement.
- views/side_panel.rs: overflow_y_scroll requires stateful; added
.id("conversation-path") on the scroll container.
The graph/ submodule is unchanged; its test still passes via cargo
test once the Metal Toolchain blocker is cleared.
Two bugs in canvas.rs caused edges to render disconnected from nodes: 1. canvas() paint runs in window coordinates, but the previous code used raw (s.x, s.y) without adding bounds.origin. Cards live inside a relative-positioned container below the toolbar, so edges drew ~44 px above where the cards rendered. Added bounds.origin.x/y to each path coordinate. 2. Cards used .min_h(NODE_MIN_HEIGHT=80) so content-heavy nodes grew past 80 px, but edges anchored their source-y at pos.y + 80 — i.e. mid-card on tall cards. Switched to fixed .h(NODE_HEIGHT=120) + .overflow_hidden() so all cards are uniform and the source-bottom anchor is always correct. Renamed NODE_MIN_HEIGHT to NODE_HEIGHT to reflect that it is no longer a minimum. Verified against the seeded demo DAG: the two diagonal S-curves from u1 to a1/a2 and the converging curves from a1/a2 into the synth node now meet card edges cleanly.
Lifts gpui/examples/input.rs as src-gpui/src/views/text_input.rs: single-line input with IME, selection, mouse, clipboard. Wired into side_panel.rs as Edit/Save/Cancel button flow seeded from the selected node's content; commits via state.set_content. Bumps the crate to edition 2024 (the lifted code uses let chains) and adds unicode-segmentation for grapheme boundaries. Verified manually on Mac: edit → type → save updates the read-only display and the side-panel content; Send-to-agent stub then sees the edited text in its mock reply. Limits inherited from the example: single line (paste collapses newlines), no undo/redo, Esc not bound. Sufficient for the GPUI viability gate; multiline + word wrap is the next step.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Sketch of what ThoughtTree's front end would look like rebuilt natively in
Zed Industries' GPUI
instead of React + Tauri. Lives in a new sibling crate
src-gpui/so theshipping
src-tauri/build is untouched.The point isn't a replacement — it's a concrete reference for comparing
GPUI ergonomics against the web stack.
src-gpui/README.mdcaptures thetrade-offs in detail.
What's in the prototype
src-gpui/src/graph/— pure-Rust port ofsrc/lib/graph/(types,mutations,
conversation_pathtopo sort with the synthesizersame-role-merge rule). Same domain language as
CONTEXT.md. Compilesclean and the synthesizer test passes.
src-gpui/src/state.rs—Entity<AppState>shared across views,filling the role of the Zustand store.
src-gpui/src/views/—ToolbarView,CanvasView(DAG cards +cubic-Bezier edges via
paint_path),SidePanelView(conversation-pathview with branch / send-to-agent actions).
src-gpui/src/state.rs::simulate_stream— fake streaming replyappended char-by-char on a background task, so the streaming render
loop is exercised without spawning ACP.
Deliberately out of scope
ACP integration, persistence, Markdown rendering, inline text editing,
auto-layout, settings, permissions dialog. README explains why each was
trimmed and where the real wiring would slot in.
Caveats
Metal/Vulkan, so it isn't built in this repo's CI and the prototype has
not been compiled. The graph submodule was verified to compile in
isolation (and its test passes).
Cargo.tomlusesrev = "main"for convenience; expect API drift on
cx.observe/cx.listenerclosure signatures,
PathBuilder, drag handlers, andWindowOptionsfields. README enumerates the likely fix-ups.
Initial impressions (full version in README)
GPUI lighter than React+Tauri at: one-language/one-process with no IPC
boundary, streaming updates without Tauri events, no bundler, state
sharing without prop drilling.
React+Tauri lighter than GPUI at: Markdown / rich-text rendering,
distribution (signed installers), API stability, devtools, talent pool.
Wash: raw rendering perf for graphs this size; DAG layout work
(both frameworks hand you a blank canvas for edge curves).
Test plan
src-gpui/Cargo.toml, runcargo run --releasefromsrc-gpui/,and chase whatever GPUI API errors fall out.
node selects it and updates the side panel, "Send to agent (stub)"
streams text into a new assistant node.
cargo teston thegraph/submodule passes (verified inisolation during prototype work).
https://claude.ai/code/session_017SrsAA3L9UitHCffzFmKAk
Generated by Claude Code