Skip to content
Draft
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
8 changes: 7 additions & 1 deletion docs/specs/layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ All handled in a single capture-phase `keydown` listener on `window`. Every hand

Pressing `x` (or clicking the kill button) enters command mode and shows a pane-centered semi-transparent overlay (`KillConfirmOverlay` → `KillConfirmCard`) with a random uppercase letter (A-Z, excluding X). Typing that letter confirms the kill (destroys session, removes pane). Cancel with Escape key, clicking the `[ESC] to cancel` button, or clicking another panel. Any other key triggers a shake animation (400ms `shake-x` keyframe) then auto-dismisses the confirmation.

Untouched sessions skip this confirmation. A newly spawned shell starts `untouched: true`; the first user-originated PTY input flips it to false. Inputs that count include printable keys, Enter, control keys, keyboard CSI such as arrows/history, paste, and file-drop path insertion. Replay-time terminal reports, synthetic terminal reports, and stripped mouse-report-only input do not count. Killing an untouched pane runs the normal kill animation/dispose path immediately. Killing an untouched door first reattaches it only far enough to reuse the same pane removal path, then kills it without showing the confirmation overlay.

## Selection overlay

A fixed-positioned element rendered on top of dockview. Covers the active element's area inflated by 3px (half the 6px gap) for panes, or 2px for doors.
Expand Down Expand Up @@ -268,14 +270,16 @@ Pane IDs are session IDs. `TerminalPane` calls `getOrCreateTerminal(id)` on Reac
- **Create**: `getOrCreateTerminal` spawns xterm.js + FitAddon + PTY, returns existing if already created
- **Resume**: `resumeTerminal` creates xterm entry and writes replay data without spawning a new PTY. Used when the webview is recreated while the host retains Live PTYs (Link: Severed → Resuming → Live).
- **Restore**: `restoreTerminal` creates xterm entry and spawns a new PTY with saved cwd and scrollback. Used on cold start from a saved Snapshot (Link: Cold → Live).
- **Untouched**: new `getOrCreateTerminal` sessions start untouched. `isUntouched(id)` exposes the flag, and user-originated PTY input clears it via the registry input paths. Resume/restore seed the persisted flag; missing legacy snapshot data defaults to touched (`false`) so close confirmation remains conservative.
- **Shell selection replacement**: the standalone shell dropdown and VS Code shell picker send `mouseterm:new-terminal` with `replaceUntouched` when the selected shell type changes. `Wall` always creates a new session id for that request. If the currently selected pane or door is untouched, the new terminal is inserted in the same dockview position (`direction: 'within'`; doors first reattach through the normal restore path), the old untouched session is disposed, and the old panel is removed without kill confirmation. If the selected terminal is touched or no terminal is selected, the request spawns a new pane near the active panel. Announced shell-selection spawns show a transient pane-anchored notice such as `Switched to zsh` or `Opened bash`.
- During resume/restore replay, xterm.js may emit terminal-generated replies for OSC/CSI/DCS queries that were embedded in saved output. The registry drops those replay-time replies before they reach the new shell. This filter is limited to query/focus reports, and must not swallow user keyboard escape sequences such as arrows, function keys, or bracketed paste.
- **mount / unmount (DOM)**: `mountElement` reparents the persistent DOM element into a container; `unmountElement` removes it. The Registry entry survives.
- **Dispose**: `disposeSession` kills the PTY, disposes xterm, removes the registry entry. Only called on explicit kill (`x`).
- **Swap**: `swapTerminals` swaps two registry entries and reattaches DOM elements to each other's containers.

### Session persistence

Layout, scrollback, cwd, minimized items, user-pinned titles, and alert state are saved to persistent storage via a debounced save (500ms). Derived command/app labels shown on minimized doors are display-only and are not persisted as user-pinned titles. Saves are triggered by layout changes, panel add/remove, and a 30s periodic interval. Saves are flushed immediately on PTY exit, `pagehide`, and extension shutdown requests.
Layout, scrollback, cwd, minimized items, user-pinned titles, untouched state, and alert state are saved to persistent storage via a debounced save (500ms). Derived command/app labels shown on minimized doors are display-only and are not persisted as user-pinned titles. Saves are triggered by layout changes, panel add/remove, and a 30s periodic interval. Saves are flushed immediately on PTY exit, `pagehide`, and extension shutdown requests.

Saved snapshots are read through `readPersistedSession()`, which accepts the canonical object shape and defensively parses a JSON-stringified blob before validation and migration. This keeps malformed storage inert while covering hosts that hand back serialized JSON instead of the parsed object.

Expand Down Expand Up @@ -317,6 +321,8 @@ When a pane is added, its dockview group element gets a directional `.pane-spawn

The direction is carried via `FreshlySpawnedContext` — a `Map<paneId, SpawnDirection>` written by the spawn call site and consumed once by `TerminalPanel`'s `useLayoutEffect` on first mount.

Shell-selection replacement uses the same pane add/remove primitives but also shows a short fixed-position notice over the resulting pane. The notice fades in/out over 1500ms via `.shell-spawn-notice` and is suppressed to a static render for reduced-motion users.

### Kill (in-place fade + FLIP reclaim)

`orchestrateKill(api, killedId)` in `lib/src/lib/kill-animation.ts` runs on kill confirmation. `Wall.tsx` owns the command dispatch and calls it after the user confirms. It fades the real pane element in place (its content dissolves against the same-colored background), then removes the panel and FLIP-reveals the survivors:
Expand Down
4 changes: 4 additions & 0 deletions docs/specs/transport.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ Message types live in `vscode-ext/src/message-types.ts` (the canonical schema; o
| `pty:cwd` | CWD query response (matched by requestId) |
| `pty:scrollback` | Scrollback query response (matched by requestId) |
| `pty:shells` | Available shells list response (matched by requestId) |
| `mouseterm:newTerminal` | Host/UI request to spawn a terminal. Payload may include `shell`, `args`, display `name`, `replaceUntouched`, and `announce`; the webview replaces the selected untouched terminal in-place only when `replaceUntouched` is true, otherwise it spawns a new pane. |
| `mouseterm:selectedShell` | Update the webview's default shell options for later split/spawn/restore paths. |
| `mouseterm:flushSessionSave` | Request webview to save state now (host shutdown trigger, matched by requestId) |
| `mouseterm:openThemeDebugger` | Command-triggered request to open the shared theme debugger dialog |
| `alert:state` | Alert state change (projected status, todo, notification, attentionDismissedRing) |
Expand All @@ -126,6 +128,7 @@ interface PersistedPane {
title: string;
scrollback: string | null;
resumeCommand: string | null;
untouched: boolean;
alert?: PersistedAlertState | null;
}

Expand Down Expand Up @@ -155,5 +158,6 @@ These rules apply to every adapter. Adapter-specific layering (deactivate orderi
- **Shell login args are shell-specific.** The shared `pty-core.js` launches POSIX shells with `-l` only for shells that accept it. `csh`/`tcsh` must be spawned without `-l` so users whose login shell is C-shell-derived can open a usable terminal in any adapter.
- **Scrollback trailing newline.** Restored scrollback must end with `\n` to avoid zsh printing a `%` artifact at the top of the terminal.
- **Replay drops terminal replies only.** While saved output is being replayed into xterm.js, terminal-generated OSC/CSI/DCS query and focus reports are dropped so they do not enter the resumed/restored shell's input buffer. The replay filter must preserve user keyboard escape sequences, including arrows, function keys, and bracketed paste.
- **Untouched defaults conservatively.** New saved panes include `untouched`. Older saved panes without the field are read as `untouched: false`, so legacy sessions still require kill confirmation.
- **PTY ownership.** Each message router tracks the PTY ids it owns. A PTY routed to one webview must not be stolen by another router; new routers attaching to a host must respect existing ownership.
- **Replay filtering does not re-fire alerts.** `pty:replay` re-injects buffered output into xterm.js but must not re-trigger `AlertManager`, activity-monitor events, or protocol notifications.
20 changes: 19 additions & 1 deletion docs/specs/vscode.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,20 @@ Universal PTY/transport invariants live in `docs/specs/transport.md`. The rules
{ "command": "mouseterm.focus", "title": "MouseTerm: Focus",
"icon": { "light": "icon-tiny-light.png", "dark": "icon-tiny-dark.png" } },
{ "command": "mouseterm.open", "title": "MouseTerm: Open in Editor" },
{ "command": "mouseterm.debugTheme", "title": "MouseTerm: Debug Theme" }
{ "command": "mouseterm.debugTheme", "title": "MouseTerm: Debug Theme" },
{ "command": "mouseterm.newTerminal", "title": "MouseTerm: New Terminal",
"icon": "$(add)" },
{ "command": "mouseterm.selectShell", "title": "MouseTerm: Select Shell",
"icon": "$(gear)" }
],
"menus": {
"view/title": [
{ "command": "mouseterm.selectShell", "group": "navigation@1",
"when": "view == mouseterm.view" },
{ "command": "mouseterm.newTerminal", "group": "navigation@2",
"when": "view == mouseterm.view" }
]
},
"viewsContainers": {
"panel": [
{ "id": "mouseterm-panel", "title": "MouseTerm", "icon": "$(terminal)" }
Expand Down Expand Up @@ -127,6 +139,12 @@ VS Code-specific consequences:

PTY lifecycle, buffering, the reconnection sequence, and the full message protocol live in `docs/specs/transport.md`.

### Shell selection

The VS Code view title contributes `MouseTerm: Select Shell` and `MouseTerm: New Terminal`. The selected shell name is mirrored into the `WebviewView.description`, and `mouseterm:selectedShell` keeps the webview's default-shell slot current for split/spawn/restore paths.

`mouseterm.newTerminal` focuses the MouseTerm view and posts `mouseterm:newTerminal` with the currently selected shell. `mouseterm.selectShell` opens a QuickPick, saves the shell path globally or per workspace, applies the description/default-shell update, and, when the picked shell differs from the previous selection, focuses the view and posts `mouseterm:newTerminal` with `replaceUntouched: true` and `announce: true`. The shared `Wall` logic then replaces only a selected untouched terminal in-place; touched terminals cause an additional pane to be spawned instead.

### Serialization and restore

`WebviewPanelSerializer` is registered so VS Code can restore editor panels after restart:
Expand Down
Loading
Loading