feat(desktop): multi-window support#20521
Conversation
There was a problem hiding this comment.
Pull request overview
Adds desktop multi-window support end-to-end (Tauri + Electron backends, shared SolidJS command palette/keybind entry) so users can spawn additional independent app windows.
Changes:
- Introduces a new
Platform.spawnWindow()capability and wires it up in both Tauri and Electron desktop platforms. - Adds a new command-palette entry + keybind (
mod+shift+n) for “New Window”. - Updates an e2e keybind test to avoid the new global shortcut.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/desktop/src/index.tsx | Exposes spawnWindow() via the Tauri desktop platform implementation. |
| packages/desktop/src/bindings.ts | Adds a Tauri invoke binding for spawn_window. |
| packages/desktop/src-tauri/src/windows.rs | Generalizes MainWindow::create to accept a custom window label. |
| packages/desktop/src-tauri/src/lib.rs | Adds the spawn_window Tauri command and registers it; updates main window creation callsite. |
| packages/desktop-electron/src/renderer/index.tsx | Exposes spawnWindow() via the Electron renderer platform implementation. |
| packages/desktop-electron/src/preload/types.ts | Extends the preload API typing with spawnWindow(). |
| packages/desktop-electron/src/preload/index.ts | Exposes spawnWindow() over IPC from renderer to main. |
| packages/desktop-electron/src/main/ipc.ts | Registers a spawn-window IPC handler. |
| packages/desktop-electron/src/main/index.ts | Implements spawnWindow() by creating a new main BrowserWindow. |
| packages/app/src/pages/layout.tsx | Adds a “New Window” command palette entry and keybind. |
| packages/app/src/i18n/en.ts | Adds a new command category label (Window). |
| packages/app/src/context/platform.tsx | Extends the Platform type with optional spawnWindow(). |
| packages/app/e2e/settings/settings-keybinds.spec.ts | Adjusts the key used in a keybind-editing test to avoid the new shortcut. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| id: "window.new", | ||
| title: language.t("command.window.new"), | ||
| category: language.t("command.category.window"), | ||
| keybind: "mod+shift+n", |
There was a problem hiding this comment.
window.new is always registered (and has a global mod+shift+n keybind) even when platform.spawnWindow is undefined. On non-desktop targets this becomes a no-op command that can still steal a common browser shortcut. Consider only adding this command when platform.spawnWindow exists, or marking it disabled: true (and/or omitting keybind) when unsupported so it doesn't intercept keys or appear actionable.
| keybind: "mod+shift+n", | |
| keybind: platform.spawnWindow ? "mod+shift+n" : undefined, | |
| disabled: !platform.spawnWindow, |
| "command.category.workspace": "Workspace", | ||
| "command.category.settings": "Settings", | ||
| "command.category.window": "Window", | ||
|
|
There was a problem hiding this comment.
The UI now references language.t("command.window.new"), but this key is not present in the English dictionary. Add an English entry for command.window.new (and ideally update other locales or provide a fallback strategy) to avoid showing the raw key in the command palette.
| "command.window.new": "New window", |
| #[tauri::command] | ||
| #[specta::specta] | ||
| fn spawn_window(app: AppHandle) -> Result<(), String> { | ||
| let label = uuid::Uuid::new_v4().to_string(); | ||
| MainWindow::create(&app, &label).map(|_| ()).map_err(|e| e.to_string()) | ||
| } |
There was a problem hiding this comment.
spawn_window generates a fresh UUID label for every window. Because the app persists window state per label (via tauri_plugin_window_state), this can lead to unbounded growth of stored window-state entries over time. Consider using a bounded/reusable label scheme (e.g., main-1, main-2, reusing the lowest free index) so closed windows don't leave permanently accumulating state records.
| createMainWindow({ | ||
| updaterEnabled: UPDATER_ENABLED, | ||
| deepLinks: [], | ||
| }) |
There was a problem hiding this comment.
spawnWindow calls createMainWindow() without any per-window state/position differentiation. createMainWindow() uses electron-window-state with default settings, so multiple windows will typically restore the same x/y/width/height and open directly on top of each other. Consider passing a unique windowName/state key for additional windows or applying a small position offset when spawning to ensure the new window is visible.
| createMainWindow({ | |
| updaterEnabled: UPDATER_ENABLED, | |
| deepLinks: [], | |
| }) | |
| const existingWindow = | |
| BrowserWindow.getFocusedWindow() || BrowserWindow.getAllWindows()[0] | |
| const newWindow = (createMainWindow as any)({ | |
| updaterEnabled: UPDATER_ENABLED, | |
| deepLinks: [], | |
| }) | |
| if ( | |
| existingWindow && | |
| newWindow && | |
| typeof newWindow.setPosition === "function" | |
| ) { | |
| const [x, y] = existingWindow.getPosition() | |
| newWindow.setPosition(x + 24, y + 24) | |
| } |
Issue for this PR
Closes #20606
Type of change
What does this PR do?
Adds a multi-window feature using the Tauri/Electron APIs and SolidJS frontend, allowing users to spawn independent session windows via Cmd+Shift+N or the command palette.
How did you verify your code works?
Verified locally by spawning new windows in the desktop app, ensuring no crashes or overlapping states between multiple instances. E2E tests have also been updated and pass cleanly.
Screenshots / recordings
Checklist