Skip to content

prototype: GPUI front-end exploration in src-gpui/#7

Draft
davidchris wants to merge 4 commits into
mainfrom
claude/gpui-thought-tree-prototype-roCj7
Draft

prototype: GPUI front-end exploration in src-gpui/#7
davidchris wants to merge 4 commits into
mainfrom
claude/gpui-thought-tree-prototype-roCj7

Conversation

@davidchris

Copy link
Copy Markdown
Owner

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 the
shipping 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.md captures the
trade-offs in detail.

What's in the prototype

  • src-gpui/src/graph/ — pure-Rust port of src/lib/graph/ (types,
    mutations, conversation_path topo sort with the synthesizer
    same-role-merge rule). Same domain language as CONTEXT.md. Compiles
    clean and the synthesizer test passes.
  • src-gpui/src/state.rsEntity<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-path
    view with branch / send-to-agent actions).
  • src-gpui/src/state.rs::simulate_stream — fake streaming reply
    appended 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

  • Unverified. GPUI is a git-only dep (no crates.io release) requiring
    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).
  • Pin a Zed commit before building. Cargo.toml uses rev = "main"
    for convenience; expect API drift on cx.observe / cx.listener
    closure signatures, PathBuilder, drag handlers, and WindowOptions
    fields. 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

  • On a Mac with Xcode tools, pin a current Zed commit in
    src-gpui/Cargo.toml, run cargo run --release from src-gpui/,
    and chase whatever GPUI API errors fall out.
  • Verify the demo DAG renders (3 nodes, one synthesizer), clicking a
    node selects it and updates the side panel, "Send to agent (stub)"
    streams text into a new assistant node.
  • cargo test on the graph/ submodule passes (verified in
    isolation during prototype work).

https://claude.ai/code/session_017SrsAA3L9UitHCffzFmKAk


Generated by Claude Code

claude and others added 4 commits May 1, 2026 09:24
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants