Skip to content

Replace the Electron app with a native GPUI one - #38

Merged
killerwolf merged 4 commits into
mainfrom
migrate-to-gpui
Sep 10, 2026
Merged

Replace the Electron app with a native GPUI one#38
killerwolf merged 4 commits into
mainfrom
migrate-to-gpui

Conversation

@killerwolf

Copy link
Copy Markdown
Owner

Rewrites QuickToss on GPUI — Zed's GPU-accelerated Rust UI framework — with GPUI Kit for components, and drops Electron, React, Vite, Tailwind and the npm tree.

The UX is deliberately unchanged: same three screens, same keys, same Trash-not-delete safety. settings.json is read from where it already lives, so anyone upgrading keeps their preferences.

Numbers

Before After
DMG 110 MB (×2 architectures) 6.0 MB (one universal)
Installed 341 MB 13 MB
node_modules 822 MB

Worth being explicit, and ADR 0002 now says so: that size win is a "stop shipping Chromium" win, not a GPUI win. Tauri would have landed in the same order of magnitude. What GPUI adds on top is no process boundary, no npm, no JIT entitlements, and rendering that doesn't drift with the OS's WebKit.

Previews are the substantive improvement

One call to QLThumbnailGenerator — the renderer behind Finder's Quick Look — replaces pdfjs, mammoth, SheetJS, JSZip and heic2any. Word, PowerPoint and Excel files get a real preview for the first time; a deck looks like the deck rather than a list of its text runs. Measured on real files:

Format Time
HEIC (9–26 MB) 233–523 ms
PDF 29–108 ms
Video poster frame (170 MB .mov) 650 ms
PNG / text 0 ms (native decode)

This also isn't GPUI-specific — Tauri's backend is Rust too — but it's the change that most improves the app.

The regression

Video no longer plays. GPUI has no video element, and the only third-party player needs GStreamer installed system-wide, which a 13 MB app shouldn't demand. Video shows its poster frame, and a new O binding opens any file in the app that owns it. Tracked in #37, with a scrubbable-filmstrip approach sketched out.

videoAutoplay is dropped with it. The other two settings carry over.

Also in here

  • Hardened-runtime entitlements gone. Signing needed allow-jit and allow-unsigned-executable-memory for V8 — precisely the exceptions that weaken the hardened runtime.
  • Release pipeline off Node. electron-builderscripts/bundle.sh (~120 readable lines). Icon generation no longer pulls electron-icon-builder and its deprecated phantomjs-prebuilt; qlmanage/sips/iconutil do it. Changelog extraction is now shell.
  • One universal download instead of one per architecture, so the "which Mac do I have?" table is gone from the README and the landing page.
  • react-pdf pinned at 6.2.2 because of Promise.withResolvers — that whole class of constraint stops arriving.

Verification

38 tests, clippy -D warnings and fmt clean, the bundled .app launches, the DMG builds. The Quick Look bridge is tested against real files rather than mocked, including that a missing file fails rather than hanging.

Not verified: the UI itself. Screen access wasn't available during the work, so the layout has never been looked at and no toss/keep/undo cycle has been driven through the actual interface. It compiles, launches and stays running — that's all that can be claimed. Please exercise it before merging.

Review notes

The diff is large but lopsided: ~12k insertions are mostly Cargo.lock, and ~14k deletions are mostly package-lock.json. The code worth reading is ~2,700 lines across app/src/, of which roughly 700 are tests.

session.rs, files.rs, settings.rs and update.rs are plain functions with no GPUI types in them — that's where the ported logic lives and where the tests point. ui.rs is the one file with no test coverage at all.

🤖 Generated with Claude Code

killerwolf and others added 4 commits September 10, 2026 23:15
QuickToss shows you one file and waits for one keystroke. Doing that
through Electron cost a 110MB DMG, a 341MB installed bundle, a 9-channel
IPC contract between two processes, and five JavaScript libraries each
approximating a file format macOS already renders.

This rewrites the app on GPUI (Zed's GPU-accelerated Rust framework) with
GPUI Kit for components. The download is now ~6MB and the installed app
13MB, from one universal binary rather than one build per architecture.

The UX is deliberately unchanged: same three screens, same keys, same
Trash-not-delete safety, and settings.json is read from where it already
lives so existing preferences carry over. The chrome is new — the blue
gradient is gone, the window follows the system appearance, and the
folder you are working through is now visible while you work through it.

Previews are the substantive improvement. One call to QLThumbnailGenerator
replaces pdfjs, mammoth, SheetJS, JSZip and heic2any, so Word, PowerPoint
and Excel files get a real preview for the first time and PDFs and HEIC
render the way they do in Finder.

Video playback is the one regression: GPUI has no video element and the
only third-party player needs GStreamer installed system-wide. Video files
now show their poster frame, and a new `O` binding opens any file in the
app that owns it.

Also drops the hardened-runtime JIT entitlements V8 required, ports the
release pipeline and icon tooling off Node, and records the reasoning in
ADR 0002.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Tauri paragraph claimed it would have kept the JavaScript preview
stack. That is wrong: Tauri's backend is Rust, so the same
QLThumbnailGenerator bridge would have worked there. The size win was
likewise never GPUI-specific, since Tauri uses the system WebView.

Both were presented as reasons GPUI won, which overstated the case and
would mislead anyone revisiting this decision later. The benefits are now
split into what leaving Electron buys — the bulk of it, available from
either framework — and the four things GPUI adds on top, with Tauri's
three genuine advantages named, video playback first among them.

Also corrects a stale 21 MB figure to the measured 13 MB.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four documents cited issue #12 as the tracker for the video-playback
regression. #12 is closed, and it was about DOCX/PPTX/XLSX/HEIC previews
— the work the Quick Look bridge actually completed. Pointing readers at
a closed, unrelated issue is worse than not linking at all, so the
limitation is now described in place. The filmstrip idea is written down
in CONTRIBUTING rather than attributed to an issue that doesn't exist.

Also:
- .gitignore was still entirely Node-shaped; trimmed to what this repo
  produces. Its `/build/` rule had been hiding a stray copy of the V8 JIT
  entitlements, never tracked and dead since electron-builder left.
- docs/PRD.md specified SwiftUI in section 5. It's a pre-release document
  and the flows still hold, so it gets a note about which parts didn't
  survive rather than a rewrite. The unmet sandboxing requirement is now
  marked as unmet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Opened #37 for the poster-frame limitation and linked it from the four
places that previously cited the closed #12.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@killerwolf
killerwolf merged commit 7356e6f into main Sep 10, 2026
2 checks passed
@killerwolf
killerwolf deleted the migrate-to-gpui branch September 10, 2026 21:59
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.

1 participant