mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
feat(app): redirect to next workspace after archiving
This commit is contained in:
192
docs/ELECTRON_MIGRATION.md
Normal file
192
docs/ELECTRON_MIGRATION.md
Normal file
@@ -0,0 +1,192 @@
|
||||
# Electron Migration
|
||||
|
||||
## Why switch
|
||||
|
||||
Paseo Desktop is a webview that loads an Expo web build plus a Node daemon that manages AI coding agents. Tauri is designed for apps that need a thin native wrapper — it assumes your backend logic lives in Rust. Paseo's backend is entirely Node/TypeScript. The result is that we're reimplementing Electron's built-in Node bundling inside Tauri's resource system.
|
||||
|
||||
### Pain points with Tauri today
|
||||
|
||||
**Managed runtime bundling is the #1 source of complexity.** `build-managed-runtime.mjs` (590 lines) downloads a Node distribution, packs three workspace tarballs, installs them with the bundled npm, separately installs sherpa-onnx platform packages, prunes ONNX/node-pty/ripgrep binaries per platform, and writes a runtime manifest. This exists solely because Tauri doesn't ship Node.
|
||||
|
||||
**Linux is fragile.** We wrote a custom AppImage Wayland patch (`maybePatchLinuxAddonRunpath` in sherpa-onnx-node-loader, plus AppImage patching scripts) because Tauri's Linux webview has Wayland issues. Electron handles Wayland/X11 transparently via Chromium's `--ozone-platform-hint=auto`.
|
||||
|
||||
**Native notification callbacks required Objective-C.** We wrote `paseo_notifications.m` (UNUserNotificationCenter delegate bridge) and compiled it into the Tauri build just to get notification click callbacks on macOS. Linux and Windows notification callbacks are unimplemented. Electron's `Notification` API provides click/reply/action callbacks cross-platform with zero native code.
|
||||
|
||||
**Windows is undertested.** The Rust code has `#[cfg(windows)]` blocks for named pipes, PowerShell `Expand-Archive`, `CREATE_NO_WINDOW` flags, NSIS installer hooks — all written speculatively with limited testing. Electron's cross-platform abstractions cover all of this.
|
||||
|
||||
**IPC is boilerplate-heavy.** Every desktop command requires a Rust `#[tauri::command]` function, a `spawn_blocking` wrapper, serde types, registration in `generate_handler![]`, and a TypeScript `invokeDesktopCommand()` call. There are 22 commands today. In Electron, the main process is Node — these become direct function calls or simple `ipcMain.handle()` handlers in the same language as the rest of the codebase.
|
||||
|
||||
**Tauri plugin gaps.** Features like tray menus with dynamic updates, global shortcuts, deep links, system idle detection, auto-launch on login, and rich clipboard access all require Rust plugins (often per-platform). Electron provides these as JS API calls.
|
||||
|
||||
### What we gain
|
||||
|
||||
- **Delete `build-managed-runtime.mjs`** — Node comes with Electron
|
||||
- **Delete the Wayland/AppImage patching** — Chromium handles it
|
||||
- **Delete `paseo_notifications.m`** — `Notification` API works cross-platform
|
||||
- **Delete `runtime_manager.rs`** (1080 lines) — daemon becomes a `child_process.fork()` call
|
||||
- **Delete all Rust code** — `lib.rs` (683 lines), `desktop_notifications.rs`, `build.rs`, Cargo.toml dependencies
|
||||
- **Delete sherpa rpath patching** — `electron-rebuild` handles native addon ABI matching
|
||||
- **One language** for the entire desktop app (TypeScript)
|
||||
- **Windows support** works out of the box
|
||||
- **Mature auto-update** via electron-updater (GitHub Releases, S3, etc.)
|
||||
- **Mature code signing** for macOS and Windows without custom scripts
|
||||
|
||||
### What we lose
|
||||
|
||||
- **App size increases** from ~20 MB to ~150-200 MB (ships Chromium)
|
||||
- **Memory footprint increases** (Chromium process model)
|
||||
- **Tauri's Rust escape hatch** — though we only use Rust because Tauri requires it, not because we need it
|
||||
|
||||
### ONNX / Sherpa native addons
|
||||
|
||||
`sherpa-onnx-node` and `onnxruntime-node` ship prebuilt `.node` addons per platform. In Electron, `@electron/rebuild` rebuilds or verifies native addons against Electron's Node ABI. This is standard practice — packages like `better-sqlite3`, `sharp`, and `node-pty` all work this way. The manual sherpa platform package installation and rpath patching goes away entirely.
|
||||
|
||||
## What can be collapsed
|
||||
|
||||
### Build infrastructure (delete entirely)
|
||||
|
||||
| File | Lines | Purpose |
|
||||
|---|---|---|
|
||||
| `packages/desktop/scripts/build-managed-runtime.mjs` | 590 | Bundle Node + workspaces into Tauri sidecar |
|
||||
| `packages/desktop/scripts/validate-managed-runtime.mjs` | ~100 | Validate bundled runtime integrity |
|
||||
| `packages/desktop/scripts/sign-managed-runtime-macos.mjs` | ~100 | Code-sign macOS sidecar bundles |
|
||||
| `packages/desktop/scripts/managed-daemon-smoke.mjs` | ~50 | Smoke test for managed daemon |
|
||||
|
||||
Electron replacement: None. Node is part of Electron. Workspace code loads directly via `require`/`import`.
|
||||
|
||||
### Rust backend (delete entirely)
|
||||
|
||||
| File | Lines | Purpose |
|
||||
|---|---|---|
|
||||
| `src-tauri/src/lib.rs` | 683 | App setup, IPC commands, menu, zoom |
|
||||
| `src-tauri/src/runtime_manager.rs` | 1080 | Managed runtime/daemon lifecycle |
|
||||
| `src-tauri/src/desktop_notifications.rs` | ~80 | Notification bridge |
|
||||
| `src-tauri/src/main.rs` | ~10 | Entry point |
|
||||
| `src-tauri/build.rs` | ~30 | Build script (compiles ObjC) |
|
||||
| `src-tauri/Cargo.toml` | ~30 | Rust dependencies |
|
||||
| `src-tauri/tauri.conf.json` | ~100 | Tauri configuration |
|
||||
|
||||
Electron replacement: A single `main.ts` (~200-300 lines) covering window creation, menu, IPC handlers, and daemon lifecycle.
|
||||
|
||||
### Native platform code (delete entirely)
|
||||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `src-tauri/macos/paseo_notifications.h` | ObjC notification bridge header |
|
||||
| `src-tauri/macos/paseo_notifications.m` | ObjC UNUserNotificationCenter delegate |
|
||||
| `src-tauri/Info.plist` | macOS bundle config |
|
||||
| `src-tauri/Entitlements.plist` | macOS entitlements |
|
||||
| `src-tauri/installer-hooks.nsh` | Windows NSIS post-install hooks |
|
||||
|
||||
Electron replacement: `Notification` API (2-3 lines per notification). macOS entitlements handled by `electron-builder` config.
|
||||
|
||||
### Daemon management (massive simplification)
|
||||
|
||||
**Tauri today:** Bundled Node binary → spawn as child process → pipe stdout/stderr → poll `daemon status --json` in a loop → parse JSON → return via Tauri IPC.
|
||||
|
||||
**Electron:** `child_process.fork('./packages/server/dist/scripts/daemon-runner.js')` — same Node runtime, same ABI, direct IPC via Node's built-in channel.
|
||||
|
||||
### Local daemon transport (simplification)
|
||||
|
||||
**Tauri today:** `runtime_manager.rs` implements a full WebSocket client in Rust (tokio-tungstenite) with session management, base64 binary encoding over Tauri events, and manual read/write task spawning. ~300 lines of Rust.
|
||||
|
||||
**Electron:** The renderer can connect to Unix sockets / named pipes directly via the main process using Node's `ws` package (already a dependency). Or use `ipcMain` to proxy — still TypeScript, still ~50 lines.
|
||||
|
||||
### Attachment file management (simplification)
|
||||
|
||||
**Tauri today:** 6 Rust commands (`write_attachment_base64`, `copy_attachment_file`, `read_file_base64`, `delete_attachment_file`, `garbage_collect_attachment_files`) with path validation, base64 encoding/decoding, and directory traversal protection. ~200 lines of Rust.
|
||||
|
||||
**Electron:** Same logic in TypeScript using `fs` and `app.getPath('userData')`. ~50 lines.
|
||||
|
||||
### Window management (simplification)
|
||||
|
||||
**Tauri today:** Custom `tauri-window.ts` with `plugin:window|start_dragging`, `plugin:window|toggle_maximize`, `plugin:window|is_fullscreen` invocations.
|
||||
|
||||
**Electron:** `BrowserWindow` API — `win.maximize()`, `win.isFullScreen()`, etc. Frameless window dragging via CSS `-webkit-app-region: drag`.
|
||||
|
||||
### App updates (simplification)
|
||||
|
||||
**Tauri today:** Rust `check_app_update` / `install_app_update` commands using `tauri_plugin_updater`. Custom update check UI in the renderer.
|
||||
|
||||
**Electron:** `electron-updater` with `autoUpdater.checkForUpdatesAndNotify()`. Built-in download progress, restart handling, differential updates.
|
||||
|
||||
## Migration plan
|
||||
|
||||
### Guiding principle
|
||||
|
||||
Electron is mature. We are not doing anything new. Every feature we need has official documentation and established patterns. Never use workarounds — if something doesn't work, find the official way.
|
||||
|
||||
### Phase 1: Scaffold in parallel
|
||||
|
||||
Create `packages/desktop-electron` alongside `packages/desktop` (Tauri). Both coexist.
|
||||
|
||||
- Initialize with `electron-forge` or `electron-builder`
|
||||
- Configure `BrowserWindow` to load the Expo web build (same as Tauri's webview)
|
||||
- Set up `electron-builder` for macOS, Linux, Windows packaging
|
||||
- Get a window rendering the Expo web app with no desktop features
|
||||
|
||||
### Phase 2: Introduce `isElectron` branching
|
||||
|
||||
Add platform detection infrastructure in `packages/app`:
|
||||
|
||||
- Add `isElectron()` / `isElectronMac()` alongside existing `isTauri()` / `isTauriMac()`
|
||||
- Introduce a `isDesktop()` helper that returns `isTauri() || isElectron()`
|
||||
- Create `packages/app/src/desktop/electron/invoke-desktop-command.ts` — Electron's equivalent of the Tauri invoke bridge, using `ipcRenderer.invoke()`
|
||||
- Update `invokeDesktopCommand()` to route to the correct backend based on platform
|
||||
|
||||
### Phase 3: Port features incrementally
|
||||
|
||||
Work through each Tauri feature, adding the Electron variant. Never break Tauri while doing this.
|
||||
|
||||
**Tier 1 — Core (must work for the app to be usable):**
|
||||
|
||||
1. **Daemon lifecycle** — `child_process.fork()` the daemon runner from the main process. Expose `start/stop/restart/status` via `ipcMain.handle()`.
|
||||
2. **Local daemon transport** — WebSocket client in the main process using `ws`, proxied to renderer via IPC.
|
||||
3. **Window management** — Frameless window with overlay title bar, drag regions, zoom controls, fullscreen.
|
||||
4. **Menu** — `Menu.buildFromTemplate()` with zoom controls and macOS app menu.
|
||||
|
||||
**Tier 2 — Desktop features:**
|
||||
|
||||
5. **Notifications** — `new Notification()` with click handlers.
|
||||
6. **App updates** — `electron-updater` with GitHub Releases.
|
||||
7. **File dialogs** — `dialog.showOpenDialog()` for directory picker.
|
||||
8. **Confirm dialogs** — `dialog.showMessageBox()`.
|
||||
9. **Open external URLs** — `shell.openExternal()`.
|
||||
10. **Attachment file management** — `fs` operations in main process via IPC.
|
||||
|
||||
**Tier 3 — Polish:**
|
||||
|
||||
11. **CLI symlink instructions** — Generate platform-specific instructions in Node.
|
||||
12. **Logging** — `electron-log` or simple file-based logging.
|
||||
13. **Single instance** — `app.requestSingleInstanceLock()`.
|
||||
14. **Console forwarding** — No bridge needed, main process is already Node.
|
||||
15. **NSIS installer hooks** — `electron-builder` NSIS config for CLI symlink.
|
||||
|
||||
### Phase 4: Feature parity validation
|
||||
|
||||
- Test all features on macOS, Linux (X11 + Wayland), Windows
|
||||
- Verify sherpa-onnx-node loads correctly via `@electron/rebuild`
|
||||
- Verify daemon start/stop/restart lifecycle
|
||||
- Verify auto-update flow
|
||||
- Verify notification callbacks on all platforms
|
||||
- Smoke test CLI passthrough mode
|
||||
|
||||
### Phase 5: Cut over
|
||||
|
||||
- Update CI to build Electron packages instead of Tauri
|
||||
- Remove `packages/desktop` (Tauri)
|
||||
- Remove all `isTauri()` branching, keep only `isElectron()` (rename to `isDesktop()`)
|
||||
- Delete `src-tauri/` and all Rust code
|
||||
- Delete `build-managed-runtime.mjs` and related scripts
|
||||
- Delete sherpa rpath patching code
|
||||
|
||||
## Estimated code impact
|
||||
|
||||
| Category | Lines removed | Lines added |
|
||||
|---|---|---|
|
||||
| Rust backend | ~1,900 | 0 |
|
||||
| ObjC native code | ~150 | 0 |
|
||||
| Build scripts | ~850 | 0 |
|
||||
| Tauri TypeScript bridge | ~300 | ~150 (Electron IPC) |
|
||||
| Electron main process | 0 | ~300 |
|
||||
| **Net** | **~3,200 removed** | **~450 added** |
|
||||
Reference in New Issue
Block a user