From 9a514f65b73d347191530249b75b8d6a62d438bb Mon Sep 17 00:00:00 2001 From: Edgar Twigg Date: Thu, 30 Apr 2026 09:39:11 -0700 Subject: [PATCH 1/2] Respect selected shell for auto-spawned terminals --- docs/specs/layout.md | 6 +++--- lib/src/components/wall/use-dockview-ready.ts | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/specs/layout.md b/docs/specs/layout.md index 8f6bb224..d81abd40 100644 --- a/docs/specs/layout.md +++ b/docs/specs/layout.md @@ -272,8 +272,8 @@ Layout, scrollback, cwd, minimized items, and alert state are saved to persisten On startup, recovery is priority-based: 1. **Resume** (webview hidden/shown, live PTYs): request PTY list + replay data from platform, `resumeTerminal()` for each (500ms timeout). If the saved session covers every live PTY, restore the saved dockview layout when its visible panel set matches and reattach saved minimized items as doors. This still counts as a live resume when every live session is minimized, so recovery must not fall through to cold restore just because the visible `paneIds` list is empty. 2. **Restore** (app restart, cold start): restore layout from serialized dockview state, `restoreTerminal()` for each pane with saved cwd + scrollback, and spawn each PTY with the current default shell selection -3. **Fallback/manual pane creation**: when no saved layout can be safely applied, add multiple panes as splits from the previous pane rather than tabs -4. **Empty state**: create a single new pane +3. **Fallback/manual pane creation**: when no saved layout can be safely applied, add multiple panes as splits from the previous pane rather than tabs, and spawn each PTY with the current default shell selection +4. **Empty state**: create a single new pane with the current default shell selection ### Activity state @@ -319,7 +319,7 @@ Case handling is purely rect-based (measure before and after removal), so 2-pane ### Auto-spawn delay -When `onDidRemovePanel` triggers the "always keep one pane visible" auto-spawn (see corner case #10), the `api.addPanel` call is deferred by 440ms. This lets the outgoing animation (kill ghost crush, or minimize's selection-overlay slide to the door) complete before the replacement's reveal starts — they play sequentially in the same screen region instead of fighting each other. The deferred spawn re-checks `totalPanels` at fire time and becomes a no-op if anything repopulated the pane area during the delay (e.g. a door reattach). +When `onDidRemovePanel` triggers the "always keep one pane visible" auto-spawn (see corner case #10), the `api.addPanel` call is deferred by 440ms. This lets the outgoing animation (kill ghost crush, or minimize's selection-overlay slide to the door) complete before the replacement's reveal starts — they play sequentially in the same screen region instead of fighting each other. The deferred spawn re-checks `totalPanels` at fire time and becomes a no-op if anything repopulated the pane area during the delay (e.g. a door reattach). If it does create a replacement pane, that pane spawns with the current default shell selection, matching manual splits and the standalone `[+]` action. The deferred spawn also only calls `selectPane` if selection is null. The kill handler clears selection to null, so the new pane takes focus. The minimize flow sets selection to the just-created door; preserving that door focus across the delay is the point. diff --git a/lib/src/components/wall/use-dockview-ready.ts b/lib/src/components/wall/use-dockview-ready.ts index 0b4991b8..c439f3ef 100644 --- a/lib/src/components/wall/use-dockview-ready.ts +++ b/lib/src/components/wall/use-dockview-ready.ts @@ -61,11 +61,15 @@ export function useDockviewReady({ doorsRef.current = restoredDoors; setDoors(restoredDoors); - const addTerminalPanel = (id: string) => { + const primeDefaultShell = (id: string) => { const defaults = getDefaultShellOpts(); if (defaults?.shell) { setPendingShellOpts(id, { shell: defaults.shell, args: defaults.args }); } + }; + + const addTerminalPanel = (id: string) => { + primeDefaultShell(id); const referencePanel = e.api.panels[e.api.panels.length - 1] ?? null; const direction = pickSplitDirection(referencePanel); e.api.addPanel({ @@ -146,6 +150,7 @@ export function useDockviewReady({ const spawn = () => { if (e.api.totalPanels > 0) return; const id = generatePaneId(); + primeDefaultShell(id); freshlySpawnedRef.current.set(id, 'top-left'); e.api.addPanel({ id, component: 'terminal', tabComponent: 'terminal', title: '' }); if (selectedIdRef.current === null) { From c5ab9533a61b79b9cf3ffca65f8443b53c1c766a Mon Sep 17 00:00:00 2001 From: Edgar Twigg Date: Thu, 30 Apr 2026 10:01:22 -0700 Subject: [PATCH 2/2] Seed standalone shell before restore --- lib/src/lib/shell-defaults.ts | 5 +++-- standalone/src/main.tsx | 10 +++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/src/lib/shell-defaults.ts b/lib/src/lib/shell-defaults.ts index 3b299980..b8cabab6 100644 --- a/lib/src/lib/shell-defaults.ts +++ b/lib/src/lib/shell-defaults.ts @@ -1,6 +1,7 @@ // Shared "currently selected" shell, used when spawning without an explicit -// choice (e.g. a keyboard-driven split). Updated by AppBar's ShellDropdown in -// standalone and by the VSCode extension pushing mouseterm:selectedShell. +// choice (e.g. a keyboard-driven split). Seeded before standalone Wall mount, +// updated by AppBar's ShellDropdown, and updated by the VSCode extension +// pushing mouseterm:selectedShell. // // Extracted into its own module to avoid circular dependencies between // terminal-registry and platform/vscode-adapter. diff --git a/standalone/src/main.tsx b/standalone/src/main.tsx index 7c5067b3..b59e14bf 100644 --- a/standalone/src/main.tsx +++ b/standalone/src/main.tsx @@ -3,6 +3,7 @@ import { createRoot } from "react-dom/client"; import { invoke } from "@tauri-apps/api/core"; import { setPlatform } from "mouseterm-lib/lib/platform"; import { resumeOrRestore } from "mouseterm-lib/lib/reconnect"; +import { setDefaultShellOpts } from "mouseterm-lib/lib/shell-defaults"; import { restoreActiveTheme } from "mouseterm-lib/lib/themes"; import App from "mouseterm-lib/App"; import "mouseterm-lib/index.css"; @@ -26,13 +27,16 @@ async function bootstrap() { const { initAlertStateReceiver } = await import("mouseterm-lib/lib/terminal-registry"); initAlertStateReceiver(); restoreActiveTheme(); - const result = await resumeOrRestore(platform); - - startUpdateCheck(); // Fetch app bar data from Rust backend const detectedShells = await invoke("get_available_shells"); const shells: ShellEntry[] = detectedShells.length > 0 ? detectedShells : [{ name: 'shell', path: '' }]; + const initialShell = shells[0]; + setDefaultShellOpts(initialShell ? { shell: initialShell.path, args: initialShell.args } : null); + + const result = await resumeOrRestore(platform); + + startUpdateCheck(); createRoot(document.getElementById("root")!).render(