Skip to content

feat(desktop): draw the rest of the desktop app in GPUI - #879

Open
Ziinc wants to merge 113 commits into
claude/project-thread-rm0aio-sidebar-stylefrom
claude/project-thread-yruhjk
Open

Ziinc wants to merge 113 commits into
claude/project-thread-rm0aio-sidebar-stylefrom
claude/project-thread-yruhjk

Conversation

@Ziinc

@Ziinc Ziinc commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Requested by Zn · project thread
Before: only the sidebar is GPUI. Every other screen is the React webview.
After: GPUI draws the app's screens in place of the webview, reading and changing state through the Rust backend itself:

  • Onboarding: opens local repositories and checks the required tools in Rust; picks and opens remote (SSH/cloud) repositories.
  • Settings: every tab. Account and Integrations take the Supabase session from React as route fields.
  • Workspace view, local and remote: the header and tab row, the terminal pane (alacritty_terminal; remote sessions attach over the remote PTY), the file browser, header popovers, and the remote strip, status banner and cut-off overlay.
  • Merge preview, local and remote: commits ahead, the diff and the merge itself.
  • GitHub: Issues and Pull Requests tabs, the PR review page (comments and submit) and the Merge Queue tab.
  • Linear: issues and projects, kickoff and review prompts.
  • Dialogs: agent prompt, prompt history, stash, workspace dialogs, remote repository dialogs and the command palette, drawn over the whole window.
    Set TREQ_WEB_UI=1 to see React's version side by side. The React code and its integration tests stay until parity is confirmed; a stacked PR then removes the JS.
    How:
  • React only routes. A screen pushes a small route with gpui_screen_update (screen, tab, repository or workspace, plus state of flows React still owns). Dialogs use gpui_modal_update / gpui_overlay_update.
  • Each GPUI screen (gpui_shell/screen/) acts through gpui_shell::native::Backend behind a per-screen *Host trait, faked in tests. Remote repositories go through gpui_shell::remote_repo::Remote, which follows React's remote routing and refuses the same unsupported operations.
  • Changes go back to React as gpui-native-event events so its caches follow; what a screen leaves to React comes back as gpui-screen-action events.
  • React integration tests keep testing the React UI via window flags (__TREQ_REACT_TERMINAL__, __TREQ_REACT_GITHUB__, __TREQ_REACT_REMOTE_WORKSPACE__, …) set in test/setup.integration.ts; new gpui-*.test.tsx files test the bridges.
    Tests:
  • Rust: gpui_shell::screen tests cover each screen against a fake backend.
  • Integration: test/integration/gpui-*.test.tsx.
  • Native QA under Xvfb, comparing GPUI with TREQ_WEB_UI=1.
    🤖 Generated with Claude Code
    https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF

…ding

React pushes a screen snapshot with gpui_screen_update and GPUI draws it
in place of the webview, which stays loaded and hidden; clicks come back
as gpui-screen-action events run by React's own handlers. A React dialog
brings the webview back while it is open. TREQ_WEB_UI=1 keeps the webview
to compare the two.

Adds gpui-kit (the fork mobile-gpui uses) for widgets, themed with the
React app's tokens.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
…e Preview)

React keeps owning the settings state and pushes a typed snapshot; GPUI
draws the page with gpui-kit inputs, selects, slider and switches, and
sends field changes and presses back to React's handlers. Account,
Integrations and Skills stay in the webview until they are ported.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
…ebar-style' into claude/project-thread-yruhjk

# Conflicts:
#	mobile-gpui/Cargo.lock
@Ziinc Ziinc self-assigned this Oct 18, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
The workspace view's header (branch, target, PR, sync, checks, merge,
details, more) and its tab row now render in GPUI, with the webview
framed below them. React keeps both mounted but hidden, so sub-components
keep publishing their state; React's popovers still open in the webview,
and the screen steps aside while one is open. TREQ_WEB_UI=1 now tells
React to draw everything, for side-by-side comparison.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
@Ziinc Ziinc changed the title feat(desktop): draw onboarding and Settings in GPUI feat(desktop): draw onboarding, Settings and the workspace header in GPUI Oct 18, 2026
claude added 10 commits October 18, 2026 17:53
Picks up Ziinc/tauri-gpui#38, so clicks on X11 land where the pointer is
after a hidden webview had it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
A compositor decides at a popup's first map whether it is translucent,
before GPUI has drawn into it, and shadowed the first menu as opaque.
Map each popup once while it is still 1x1 so the first real show is
blended like later ones.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
GPUI's onboarding screen now runs the prerequisite check, reads the
remote-SSH preview flag and opens repositories through the backend
itself (folder picker, init, last-opened setting, window repo path).
React only follows: a `gpui-native-event` tells it the repository
opened and which toast to show. The remote dialog is still React's.

This is the first screen on the Rust-native model: `gpui_shell::native`
holds the backend handle and the events, and each screen states what it
asks of the backend as a trait, faked in the GPUI tests.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
The Repository, Application and Feature Preview tabs now read and write
settings through the backend themselves. React only routes the screen
(tab, repository, branch) and follows the change through new
settingsChanged and repoSettingsChanged native events, so its stores
and caches rehydrate.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
The header and tab row load the workspace, its status, commits, checks
and PR cache through the backend, and push, sync and rebase the home
branch themselves. React sends only which workspace shows and the state
of the flows it still runs, plus a revision bumped on cache refreshes so
GPUI reads again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
GPUI lists the skill registry and installs, uninstalls and moves skills
between install levels through the backend, with the install dialog
drawn over the page.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
GPUI reads the workspace's commits ahead and diff from the backend and
merges it with the chosen strategy, then hands back to React to leave the
page and refresh its caches. Remote workspaces keep React's page.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
@Ziinc Ziinc changed the title feat(desktop): draw onboarding, Settings and the workspace header in GPUI feat(desktop): draw onboarding, Settings, the workspace header and merge preview in GPUI Oct 19, 2026
claude added 12 commits October 19, 2026 12:49
The Code tab (task input, description, stack, file list, README and
commit history) is drawn natively and reads the Rust backend directly.
React keeps the terminal pane, framed below the GPUI view, and handles
navigation the Code tab hands back (file browser, commits, sessions).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
- Task input: React's padding and 13px text, focused border on open.
- File picker: hide the framed terminal pane so the backdrop covers it.
- Terminal frame: measure the pane's controls, so the collapsed
  pane's expand button stays visible.
- Commit history: dashed separators, Chromium's collapsed day-group
  margins, and opaque highlights so shadows don't show through.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
GPUI reads and edits commits through the backend: commits by day with the
tentative working copy, diffs with line selection and agent comments,
abandon/undo/revert/stash, shift to now, conflict resolution and the
description and timestamp dialogs. Toasts can carry an Undo action that runs
in Rust. React keeps moving commits, the stash list and the terminal pane.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
Diff lines in the Commits tab are parsed with syntect (the grammar set
gpui-kit uses) and colored like prism-vs and prism-vsc-dark-plus, one
line at a time as React's HighlightedLine does, with spans cached.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
GPUI draws the Changes tab's sidebar (commit box, conflicts, selected,
changes and committed sections with staging and selection), file cards
with highlighted hunks, context expansion, conflict regions, viewed
state, discard, stash and the Start Review bar, reading and writing
through the Rust backend. React keeps the move confirmation, the review
agent terminal and the Browser view; GPUI draws the Diff/Browser switcher
in the tab row.

Also: native GPUI popup menus for screen-owned menus, Markdown heading
and inline-code highlighting, and dropdown anchors recorded at their
button instead of below it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
Line, selection and file comments, conflict notes, the pending-review
bar with Finish review / Discard, the stale-files banner and outdated
comments, kept in the local pending review like React's. A finished
review goes back to React, which starts the agent.

Also teaches tauri_test::setup to resolve modules declared from a
non-mod.rs file, as rustc does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
Cmd+F opens the find bar over the diff; matches are marked in diff and
conflict lines, Enter / Shift+Enter step through them and scroll the
current one into view, as React's useDiffSearch does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
The pull request's review threads sit on their diff line (or above the
file when the line is gone), fold when resolved and can be quoted into
a review comment. The review agent's local comments show with their
suggested change as a word diff, and Apply, Resolve and Delete run
through the backend; Send to agent goes to React.

React's Changes viewer stays rendered under GPUI, as the Commits tab's
does, so its integration tests keep covering it until parity. Also fixes
lint on the branch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
Keeps Cargo.lock unchanged so CI's Rust cache still hits.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
Checks, agent chats, app setup and system logs read from the backend,
with the level and check filters, the per-second chart, line selection,
find, and the SQL explorer. Prompts for the picked lines go to React,
which starts the agent.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
claude added 30 commits October 20, 2026 05:42
This reverts commit 1386608.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
…shell's interceptor

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
- core::remote_idempotency: remoteActionKeys in Rust. A change keeps its
  key until an outcome confirms it, scoped per repository or endpoint
  generation.
- remote_records: saved repository upsert with trust, the last-opened
  record and recents, secret-free descriptors, trust invalidation after a
  generation change, and restoreSavedRemoteRepository's sequence.
- managed: renewal_delay_ms is 0 for a lifetime already over (the port
  found it); the renewal loop takes its backoff so tests can run it
  against wiremock.
- cloud: the managed-ssh-connection cases (stable key registration,
  readiness polling and failure, reauthentication, wake keys, renewal).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
Covers what test/integration exercised through the NAPI bridge so the
JS suite can go: typed remote commands (undo, revert, pull, stash,
discard, archive, branch switch, remote info, probe/init/clone),
sparse and symlink metadata, home-commit sync with a dirty workspace,
devcontainer status, remote-scope review drafts, review draft
lifecycle, and deep-link workspace lookup. local_db resolves the app
data dir through core::app_data_dir so tests can scope it per thread.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
Home-task new-workspace toggle (off, remembered, hidden in a
workspace), submodule row pin and Sync, and the file picker search,
pick and dismiss, which only the React integration tests covered.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
…l key handlers

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
The window controller now runs Dashboard's remote flows itself:

- window::remote holds the remote dialogs, the SSH endpoint and its
  generation, the repository form and the remote repository on screen;
  remote_actions handles remote.*, remoteRepo.*, terminal.newRemoteShell
  and remote agents (default agent, Copilot refused), and the window's
  remote mutation feedback.
- The controller probes, opens, clones, inits and restores over SSH off
  the main thread, restores the last remote repository at launch, adopts
  managed endpoints (connect and renewal) and follows cutoffs and the
  cloud workspace's state.
- The backend host reads a remote repository's workspaces, statuses,
  detail, branches and git remote over SSH; clone and init are keyed per
  endpoint generation (keyed_endpoint_mutation); cutoffs go to an
  app-wide registry fed by the transport and remote://cutoff.
- The snapshots carry the repository, the remote dialogs, the onboarding
  repository form, the remote chrome and remote shells.
- The "React owns the window" handover is gone: a driven window ignores
  React's pushes and sends it no action. Sidebar menus, Bulk Archive and
  drags work on a remote repository (off the main thread), with delete,
  archive and retarget routed over SSH.
- React skips restoring the last remote repository in a GPUI window
  unless __TREQ_REACT_REMOTE__ is set, which the integration tests set.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
… repos

Pins the window controller to Dashboard: a backend-started session shows
no toast and keeps the page unless asked to select; archiving lands on
the stack parent or home, a removal seen by a poll goes home, and Bulk
Archive keeps the rows that failed; the sidebar draws a home row and a
labelled group per repository, main first, missing ones unread.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
Repo settings save through `save_repo_settings` (adding the optional
gitignore entries on opt-in), onboarding opens a repository through
`SettingsHost`, and deep links start through `WindowHost`, so each is
tested without an app: the non-git toast that remembers nothing, the
opt-in gitignore entries, and the "Workspace not found" toast.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
`create_review_draft` (create, then set the line hash) moves to
`core::review_drafts`; the command and the PR review screen call it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
A shell window's controller reads the database and binary caches as soon
as it is made. Opened before AppState was managed, its first reads raced
setup and panicked (state() called before manage()), leaving the window
without its data.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
As `review/conflict.test.tsx`: no Conflicts section when clean; a card
with Side #1, Base and Side #2 in order and each line on its side; no
line comments on a conflicted file; no card without regions; the empty
placeholder; a committed conflict with committed changes hidden; and the
deleted side of a delete/modify conflict. Adds the debug selectors the
tests find them by.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
As `review/comments-inline.test.tsx`: the edit box offers Save, Cancel
and Discard; Cancel and Escape restore the comment; an emptied comment
cannot be saved; Discard deletes it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
As `workspace/logs-tab.test.tsx`: the level filter reads the chosen
levels; the chart shows once there are buckets; multi-select toggles a
line; the Errors by job template runs its GROUP BY; a rejected query
shows its error; the System source searches and has no explorer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
As `workspace/commits.test.tsx`: a deferred file diff loads on "Load
diff"; a commit too large to render shows only its reason; a workspace
branch gets the "Recent on" divider (with the no-commits note when it
has none), the default branch does not.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
As `workspace/stack-panel.test.tsx`: no panel at home or for a lone
workspace; a stacked workspace lists its chain, from either end, and a
row opens its workspace; siblings are left out.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
As `workspace/tab-indicators.test.tsx`: the LOC marker sits in the tab
row and the file count on the Changes tab; neither shows without
changes; the LOC sums the workspace's commits and working copy, never
the target's.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
A read that panicked left the controller's read in flight for good, so
the window never read its repositories again and stayed empty.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
ui_zoom only resized rem-based lengths; the pixel ones (text sizes,
heights, radii, gaps) stayed put. Pixel lengths are now written in rems
at the 16px base (ui::zpx), so they follow the window's rem size, and
every GPUI window (the shell, its dialog window and its popups) sizes
its rems from one app-wide zoom. Popups measure at 100% and size their
windows at the zoom.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF
# Conflicts:
#	src-tauri/src/gpui_shell/mod.rs
#	src-tauri/src/gpui_shell/window/tests.rs
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SB3w6PGDfHYDQK5wQqPjaF

This branch has not been deployed

No deployments
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