Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/specs/layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.

Expand Down
7 changes: 6 additions & 1 deletion lib/src/components/wall/use-dockview-ready.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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: '<unnamed>' });
if (selectedIdRef.current === null) {
Expand Down
5 changes: 3 additions & 2 deletions lib/src/lib/shell-defaults.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
10 changes: 7 additions & 3 deletions standalone/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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<ShellEntry[]>("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(
<StrictMode>
Expand Down
Loading