Skip to content

refactor(desktop): move the desktop shell from Tauri to Electron - #185

Merged
GoDiao merged 10 commits into
mainfrom
feat/electron-shell
Oct 25, 2026
Merged

GoDiao merged 10 commits into
mainfrom
feat/electron-shell

Conversation

@GoDiao

@GoDiao GoDiao commented Oct 25, 2026

Copy link
Copy Markdown
Owner

Moves the desktop app from Tauri to Electron, as decided in #174, and removes Tauri. Electron is the only desktop shell; nothing keeps compatibility with files the Tauri shell wrote.

Closes #174
Closes #173

Why

The #172 measurements showed WKWebView on macOS laying out a streaming reply about 20x slower than Chromium (about 20 ms per update, 265 ms at the slow end, against about 1 ms). The spike results are in #174 (comment) and the decisions in #174 (comment).

What changed

  • Terminal in the sidecar. src/server/services/terminalService.ts runs the PTY with Bun (openpty on macOS, ConPTY on Windows) and ports the shell resolution, login-shell environment, UTF-8 locale and Bash path check from Rust.
    • It is served over a WebSocket at /terminal, plus GET /api/terminal/default-shell and POST /api/terminal/validate-bash-path.
    • All three accept only local-trusted requests and answer 403 to H5 clients, also when H5 access is on.
    • Closing a terminal sends SIGHUP, because interactive shells ignore SIGTERM.
  • Login environment. The sidecar launcher merges the user's login-shell environment itself, which the Rust shell used to do. That is how an app opened from Finder gets the Homebrew PATH. The read is bounded by a 2 s timeout as a whole, so a slow shell rc file cannot hold up startup.
  • Electron main process (desktop/electron). It covers:
    • the window, with state stored in Electron's screen coordinates under the user data folder;
    • the macOS menu and the tray, where closing hides to the tray;
    • single instance;
    • notifications, where a click opens the target session;
    • open and save dialogs, and session export;
    • portable mode;
    • the dreamcoder:// protocol with a content security policy.
    • The page reaches the shell only through a preload bridge used by desktop/src/lib/shell.
  • Notifications on macOS. Electron cannot read the macOS notification permission, so Settings shows it as managed by system settings, with a button that opens the notification pane.
  • Packaging. electron-builder builds a macOS arm64 .dmg and a Windows x64 NSIS installer.
    • release.yml and smoke-test.yml build and check them; Rust is no longer needed.
    • Signing and notarization turn on when the signing secrets are set.
    • There is no auto-updater yet (Ship in-app auto-update with electron-updater #118); a manual check says updates are not configured.
  • Removed. desktop/src-tauri, the @tauri-apps/* packages, the Tauri scripts and Vite plugin, useWindowDrag, and the terminal-config.json migration from refactor(desktop): thin the shell and route all shell calls through one module #179.
    • Title areas drag the window through CSS app-region.
  • Docs. README (en/zh), desktop README, desktop/electron/README.md, CONTRIBUTING, TROUBLESHOOTING, ROADMAP, WEB_USAGE and AGENTS.md. A CHANGELOG [Unreleased] entry says what upgrading users will see:
    • on Windows, a new install folder;
    • window position, portable mode and webview-stored preferences start fresh.
  • Fixed along the way.
    • The memory API answered 500 for a missing memory file, and a chat link to one showed "An unexpected error occurred". It now answers 404, and the page selects the project without an error.
    • The settings close button covered page actions such as Add Provider.

How I tested

  • bun test: 1428 pass, 0 fail.
  • cd desktop && bun run lint: clean. It now also type-checks desktop/electron.
  • bun run test -- --run: 125 files, 1320 passed, 4 skipped.
  • The zero-comment grep from AGENTS.md is clean on the added lines.
  • The touched server tests pass with --randomize --seed=1.
  • New tests were checked to fail without their fix:
    • memory 404;
    • the login-environment timeout;
    • the H5-enabled terminal rejection.
  • macOS dev app (bun run electron:dev):
    • The terminal starts in the session cwd. Input, Chinese output, resize, exit code and Restart work, and closing a tab or quitting ends the shell.
    • Single instance, hide to tray and show, session export, the missing-sidecar error page, and cleanup on SIGTERM work.
    • Title bar alignment, the settings close button and the memory page were checked by hand.
  • macOS packaged .dmg, built at ddbbdcb, before the last two commits:
    • codesign --verify --deep --strict passes, and the sidecar has allow-jit;
    • /api/status, /api/sessions and the ripgrep search work;
    • the page loads under the CSP without violations;
    • the terminal works over the packaged sidecar.
  • The diff was reviewed by GPT reviewers, one per dimension, with Sonnet verifying each finding. The confirmed findings are fixed in 3dc46dd.

Not verified

  • Anything on Windows. The installer, the install folder, the ConPTY terminal, toasts, the frameless window buttons and the NSIS hooks have not run outside CI. This PR runs release.yml, which builds both installers.
  • Windows terminal tests. The terminal tests skip on Windows, and bun test CI runs on Ubuntu only. The Windows smoke test does not open a terminal yet.
  • Real macOS notification delivery and signed or notarized builds.
  • Linux.

GoDiao added 10 commits October 25, 2026 19:19
…top shells

The terminal moves from the Rust shell (portable-pty) into the sidecar
(Bun's built-in PTY), so the Tauri and Electron shells share one
implementation. Each terminal is one WebSocket at /terminal; default
shell detection and bash path validation are /api/terminal routes. Both
are limited to local-trusted requests. The sidecar reads the terminal
settings it already stores instead of receiving them on every spawn.

The sidecar launcher now merges the login shell environment into its own
environment, which the Rust shell used to do when starting it, so the
Electron shell gets the same PATH without its own copy.

Part of #174, #173 item 3.
…ri shell

- App mode and portable mode: the config directory is resolved once at
  startup from app-mode.json next to the executable or in the Tauri app
  config directory, and passed to the sidecar. Switching modes writes
  app-mode.json in the same places and format as before. With a config
  directory, Chromium's data lives in <config>/Electron.
- Window state is read from and written to the existing window-state.json
  in physical pixels, restored only when it fits on a display, including
  the maximized state.
- macOS application menu (About, Settings, Edit, View, Window), a tray
  with Show and Quit, closing hides to the tray, single instance.
- Title areas move the window through CSS app-region drag regions.
- Session export, update install preparation, dropped file paths through
  webUtils, and main-process errors without the IPC wrapper text.
- Update checks report that updates are not configured, as Tauri's
  updater did without endpoints.
- desktop lint also type-checks the Electron main process.

Part of #174.
Notifications go through Electron's Notification. Clicking one brings the
window forward and sends the notification's target back to the page,
which opens the session as before. Windows gets an AppUserModelId so
toasts show. Electron has no notification permission query on macOS, so
permission reads as granted wherever notifications are supported and
macOS asks on the first notification.

Part of #174.
…ilder

- electron-builder builds the macOS Apple Silicon dmg and the Windows x64
  NSIS installer, with the same artifact names as before. The sidecar and
  ripgrep go to Resources/bin; the macOS entitlements keep JIT for the
  sidecar under the hardened runtime. The NSIS hooks stop running
  sidecars before installing and uninstalling, as the Tauri hooks did.
- The release workflow builds with electron-builder instead of Tauri.
  Without signing secrets, macOS builds are ad-hoc signed and Windows
  builds unsigned, as today; with MACOS_CSC_LINK, APPLE_* and
  WINDOWS_CSC_LINK set, they are signed and the macOS app is notarized.
- The installer smoke test looks for the sidecar and ripgrep under
  Resources/bin and finds the Windows app by its executable name.
- The sidecar build detects the host target without rustc, and the
  release script reads the current version from desktop/package.json.

Part of #174.
- lib/shell is the Electron implementation; the Tauri implementation,
  the build-time shell switch, desktop/src-tauri, the @tauri-apps
  packages and the Tauri build scripts are gone. isTauriRuntime is now
  isDesktopRuntime and detects the preload bridge.
- Shell calls reject instead of throwing when the page has no bridge,
  as the Tauri calls did, so the H5 page keeps working.
- Window dragging uses CSS drag regions on the title areas; dropped
  files are read from the DOM drop with their paths from the preload.
- The packaged page gets the same content security policy Tauri set.
  The startup watchdog moves from an inline script to
  public/startup-guard.js so the policy allows it.
- The server trusts the dreamcoder://localhost origin instead of the
  Tauri origins and finds the H5 build next to the sidecar folder.
- Sidecar binaries build into desktop/binaries and icons live in
  desktop/electron/build. bun run electron:dev starts Vite and Electron
  together; bun run electron:install fetches the Electron binary.
  Development builds open the developer tools with F12.
- A missing sidecar binary shows the startup error instead of crashing
  the main process.
- README, CONTRIBUTING, TROUBLESHOOTING, AGENTS.md and the CHANGELOG
  describe the Electron app.

Closes #174.
…ory files

- macOS: the traffic lights line up with the sidebar toggle, and the toggle
  floating over the title bar is no longer a window drag region, so clicks
  reach it.
- Settings pages leave room for the close button at the top right.
- The memory API answers 404 for a memory file that does not exist instead
  of a 500, and a chat link to a missing memory file selects its project
  without an error.
Treat Electron as the only shell the app has had:
- app-mode.json lives in the active config directory and Electron's user
  data folder instead of the Tauri app config directory.
- Window state is stored in Electron's screen coordinates in the user data
  folder, so windows on displays with different scale factors restore where
  they were.
- The terminal-config.json migration, its launcher variable and EBWebView
  portable-data detection are gone.

Review fixes:
- macOS reports notification permission as managed by system settings,
  since Electron cannot read it, and Open Settings opens the notification
  pane on macOS as well as Windows.
- The login-shell environment timeout bounds the whole read, so a child
  process that keeps the shell's output open cannot delay sidecar startup.
- A dropped terminal connection is shown with a translated message.
- Tests cover terminal routes for authorized H5 clients with H5 enabled,
  and the portable folder name as a fixed value.
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.

RFC: Move the desktop shell from Tauri to Electron Keep config and terminal logic in the sidecar instead of duplicating it in the Rust shell

1 participant