From b3181745d9648aa029d65fee96dc821911034381 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Mon, 18 May 2026 12:36:37 -0700 Subject: [PATCH 1/2] Enable kitty keyboard protocol so Shift+Enter is distinguishable In a stock xterm.js terminal, Shift+Enter and Enter both send `\r`, so TUIs like Claude Code can't bind them differently and both submit. Upgrading @xterm/xterm to a 6.1.0 beta and opting into `vtExtensions.kittyKeyboard` lets the terminal advertise the kitty keyboard protocol; when the inner program negotiates it, modified keys round-trip as distinct CSI u sequences. Co-Authored-By: Claude Opus 4.7 --- lib/package.json | 4 ++-- lib/src/lib/terminal-lifecycle.ts | 1 + pnpm-lock.yaml | 32 +++++++++++++++++-------------- standalone/package.json | 4 ++-- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/lib/package.json b/lib/package.json index 83886588..09c05c1d 100644 --- a/lib/package.json +++ b/lib/package.json @@ -15,8 +15,8 @@ }, "dependencies": { "@phosphor-icons/react": "^2.1.10", - "@xterm/addon-fit": "^0.11.0", - "@xterm/xterm": "^6.0.0", + "@xterm/addon-fit": "0.12.0-beta.216", + "@xterm/xterm": "6.1.0-beta.216", "clsx": "^2.1.1", "dockview-react": "^5.1.0", "react": "^19.2.0", diff --git a/lib/src/lib/terminal-lifecycle.ts b/lib/src/lib/terminal-lifecycle.ts index 909c8bb9..6db3c174 100644 --- a/lib/src/lib/terminal-lifecycle.ts +++ b/lib/src/lib/terminal-lifecycle.ts @@ -54,6 +54,7 @@ function createXtermHost(): { terminal: Terminal; fit: FitAddon; element: HTMLDi fontFamily: editorFontFamily, cursorBlink: true, theme, + vtExtensions: { kittyKeyboard: true }, }); const fit = new FitAddon(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e3ede9f5..02ab270c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,11 +14,11 @@ importers: specifier: ^2.1.10 version: 2.1.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@xterm/addon-fit': - specifier: ^0.11.0 - version: 0.11.0 + specifier: 0.12.0-beta.216 + version: 0.12.0-beta.216(@xterm/xterm@6.1.0-beta.216) '@xterm/xterm': - specifier: ^6.0.0 - version: 6.0.0 + specifier: 6.1.0-beta.216 + version: 6.1.0-beta.216 clsx: specifier: ^2.1.1 version: 2.1.1 @@ -96,11 +96,11 @@ importers: specifier: ^2.10.1 version: 2.10.1 '@xterm/addon-fit': - specifier: ^0.11.0 - version: 0.11.0 + specifier: 0.12.0-beta.216 + version: 0.12.0-beta.216(@xterm/xterm@6.1.0-beta.216) '@xterm/xterm': - specifier: ^6.0.0 - version: 6.0.0 + specifier: 6.1.0-beta.216 + version: 6.1.0-beta.216 dockview-react: specifier: ^5.1.0 version: 5.1.0(react@19.2.4) @@ -1640,11 +1640,13 @@ packages: engines: {node: '>= 20'} hasBin: true - '@xterm/addon-fit@0.11.0': - resolution: {integrity: sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==} + '@xterm/addon-fit@0.12.0-beta.216': + resolution: {integrity: sha512-IgKE3ngNodSnmj1O+EEYpKQZkSbAUbghPlCWd8G32RL0piIMqb3FX3BuYLnWZeLNoD9iMtublLMG1T9XjGeVvA==} + peerDependencies: + '@xterm/xterm': ^6.1.0-beta.216 - '@xterm/xterm@6.0.0': - resolution: {integrity: sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg==} + '@xterm/xterm@6.1.0-beta.216': + resolution: {integrity: sha512-87rfymzVje5eYUlGG94hz1WkOYvFRcFDGdiOAbg4d8xt8OGSGR2nMNU4I1n5MDE1RBPBqRd+WVJ5w7q3pwMoZA==} acorn@8.16.0: resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} @@ -4845,9 +4847,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@xterm/addon-fit@0.11.0': {} + '@xterm/addon-fit@0.12.0-beta.216(@xterm/xterm@6.1.0-beta.216)': + dependencies: + '@xterm/xterm': 6.1.0-beta.216 - '@xterm/xterm@6.0.0': {} + '@xterm/xterm@6.1.0-beta.216': {} acorn@8.16.0: {} diff --git a/standalone/package.json b/standalone/package.json index 3c0b8a56..c586e818 100644 --- a/standalone/package.json +++ b/standalone/package.json @@ -15,8 +15,8 @@ "@tauri-apps/api": "^2.0.0", "@tauri-apps/plugin-shell": "^2.0.0", "@tauri-apps/plugin-updater": "^2.10.1", - "@xterm/addon-fit": "^0.11.0", - "@xterm/xterm": "^6.0.0", + "@xterm/addon-fit": "0.12.0-beta.216", + "@xterm/xterm": "6.1.0-beta.216", "dockview-react": "^5.1.0", "mouseterm-lib": "workspace:*", "react": "^19.0.0", From 559a29d592f013dad10510498ec7de3ce1f1715b Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Mon, 18 May 2026 14:44:03 -0700 Subject: [PATCH 2/2] Rename OSC.md to terminal-escapes.md and document CSI handling Adds a Supported CSI section covering CSI > q (iTerm2 device attributes), DECSET/DECRST observation for mouse/bracketed-paste mode, kitty keyboard protocol, and replay-time CSI reply filtering. The escape-sequence registry now covers both OSC and CSI so the fail-inertly rule has a single home. Co-Authored-By: Claude Opus 4.7 (1M context) --- AGENTS.md | 2 +- docs/specs/alert.md | 2 +- docs/specs/{OSC.md => terminal-escapes.md} | 47 ++++++++++++++++++++-- docs/specs/terminal-state.md | 4 +- docs/specs/transport.md | 2 +- 5 files changed, 48 insertions(+), 9 deletions(-) rename docs/specs/{OSC.md => terminal-escapes.md} (60%) diff --git a/AGENTS.md b/AGENTS.md index 1962f1a1..71d66956 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,7 +34,7 @@ The primary job of a spec is to be an accurate reference for the current state o - **`docs/specs/layout.md`** — Tiling layout, pane/door containers, dockview configuration, modes (passthrough/command), keyboard shortcuts, selection overlay, spatial navigation, minimize/reattach, inline rename, session lifecycle, session persistence, and theming. Read this when touching: `Wall.tsx`, `Baseboard.tsx`, `Door.tsx`, `TerminalPane.tsx`, `spatial-nav.ts`, `layout-snapshot.ts`, `terminal-registry.ts`, `session-save.ts`, `session-restore.ts`, `reconnect.ts`, `index.css`, `theme.css`, or any keyboard/navigation/mode behavior. - **`docs/specs/alert.md`** — Activity monitoring state machine, alert trigger/clearing rules, attention model, TODO lifecycle, bell button visual states and interaction, door alert indicators, hardening (a11y, motion, i18n, overflow), notification protocols (`OSC 9` / `OSC 9;4` / `OSC 99` / `OSC 777` / `BEL`), the `ActivityNotification` model, notification text handling and security, and the notification preview/detail UI. Read this when touching: `activity-monitor.ts`, `alert-manager.ts`, `AlertManager` notification/progress paths, the alert bell or TODO pill in `Wall.tsx` (TerminalPaneHeader), alert indicators in `Door.tsx`, the `a`/`t` keyboard shortcuts, or TODO notification preview UI. Layout.md defers to this spec for all alert/TODO behavior. - **`docs/specs/terminal-state.md`** — Terminal semantic state for CWD, shell prompt/editing/running/finished lifecycle, command runs, terminal title fallback, normalized semantic OSC events (`OSC 7`, `OSC 9;9`, `OSC 133`, `OSC 633`, `OSC 1337`, `OSC 0/2`), title-candidate diagnostics, header derivation, and grouping keys. Read this when touching `terminal-state.ts`, `terminal-state-store.ts`, semantic event parsing in `terminal-protocol.ts`, adapter semantic event forwarding, or derived pane/door labels. -- **`docs/specs/OSC.md`** — Registry of every supported OSC sequence with pointers to the spec defining its behavior (alert.md or terminal-state.md), the canonical parsing-location and `pty:data` strip semantics, iTerm2 self-identification (env vars, `CSI > q` response, fail-inertly rule), and known-unimplemented iTerm2 and clipboard-capable sequences. Read this when touching: OSC parsing at the PTY data boundary, the iTerm2 identity env vars (`TERM_PROGRAM`, `LC_TERMINAL`), or adding support for a new OSC sequence. +- **`docs/specs/terminal-escapes.md`** — Registry of every terminal escape sequence MouseTerm parses or responds to: supported OSCs with pointers to the spec defining their behavior (alert.md or terminal-state.md), the canonical parsing-location and `pty:data` strip semantics, supported CSI sequences (`CSI > q`, DECSET/DECRST observation, kitty keyboard protocol) and replay-time CSI filtering, iTerm2 self-identification (env vars, fail-inertly rule), and known-unimplemented iTerm2 and clipboard-capable sequences. Read this when touching: OSC parsing at the PTY data boundary, CSI handlers in `terminal-protocol.ts` / `mouse-mode-observer.ts` / `terminal-report-filter.ts`, the iTerm2 identity env vars (`TERM_PROGRAM`, `LC_TERMINAL`), or adding support for a new escape sequence. - **`docs/specs/transport.md`** — Adapter-agnostic protocol shared across VS Code, standalone, and fake adapters: PTY lifecycle (decoupled from webview), `replayChunks`/`scrollbackChunks` buffering, reconnection sequence (`mouseterm:init` → `pty:list` + `pty:replay`), the full webview ↔ host message protocol, persisted-session types, and universal invariants (shell login args, scrollback trailing newline, replay drop-replies-only). Read this when touching: `pty-manager.ts`, `pty-host.js`, `pty-core.js`, `message-router.ts`, `message-types.ts`, `vscode-adapter.ts`, `fake-adapter.ts`, `reconnect.ts`, `session-save.ts`, `session-restore.ts`, `session-types.ts`, or any code crossing the webview/host boundary. - **`docs/specs/vscode.md`** — VS Code-specific layer: hosting modes (WebviewView + WebviewPanel), extension manifest, VS Code persistence flow (`workspaceState`, `vscode.setState`, `WebviewPanelSerializer`, deactivate ordering, `mergeAlertStates` rule, `retainContextWhenHidden`), theme integration (`--vscode-*` → `--color-*` with the runtime resolver), CSP, build pipeline, and dream-architecture commands. The transport protocol it speaks (PTY lifecycle, message protocol, persisted-session types) lives in `transport.md`. Read this when touching: `extension.ts`, `webview-view-provider.ts`, `session-state.ts`, `webview-html.ts`, the theme resolver/observer in `terminal-theme.ts`, or VS Code commands and context keys. - **`docs/specs/tutorial.md`** — Playground tutorial on the website: 3-pane layout, interactive `tut` TUI runner with three sections (keyboard navigation, alerts/TODOs, copy/paste), per-item detection wired to `WallEvent` / activity store / mouse-selection store, single-key `mouseterm-tut-v3` localStorage scheme, theme picker, and FakePtyAdapter extensions (`sendOutput`, `pumpActivity`, `setInputHandler`). Read this when touching: `website/src/pages/Playground.tsx`, `website/src/lib/tut-runner.ts`, `website/src/lib/tut-detector.ts`, `website/src/lib/tutorial-state.ts`, `website/src/lib/tut-items.ts`, `website/src/lib/tutorial-shell.ts`, `lib/src/components/ThemePicker.tsx`, `lib/src/lib/themes/`, `lib/src/lib/platform/fake-scenarios.ts` (tutorial scenarios), the `WallEvent` union, or the `onApiReady`/`onEvent`/`initialPaneIds` props on Wall. diff --git a/docs/specs/alert.md b/docs/specs/alert.md index e7016243..5189086e 100644 --- a/docs/specs/alert.md +++ b/docs/specs/alert.md @@ -68,7 +68,7 @@ Public `status` is a projection: 3. `COMMAND_EXIT_ARMED` if command-exit alerting is armed. 4. Otherwise `watchingStatus`. -Persist `status`, `watchingEnabled`, `todo`, and sanitized `notification`. Restore `todo` and `notification`, then restart WATCHING only if `watchingEnabled` is true. Restore must not recreate protocol progress, command-exit arms, or a fresh ring; replay filtering in `docs/specs/OSC.md` prevents old terminal output from firing notification side effects again. +Persist `status`, `watchingEnabled`, `todo`, and sanitized `notification`. Restore `todo` and `notification`, then restart WATCHING only if `watchingEnabled` is true. Restore must not recreate protocol progress, command-exit arms, or a fresh ring; replay filtering in `docs/specs/terminal-escapes.md` prevents old terminal output from firing notification side effects again. Legacy TODO values migrate to boolean: `2`, numeric soft buckets `[0, 1]`, `'soft'`, and `'hard'` become `true`; `false`, `-1`, and unknown values become `false`. diff --git a/docs/specs/OSC.md b/docs/specs/terminal-escapes.md similarity index 60% rename from docs/specs/OSC.md rename to docs/specs/terminal-escapes.md index e30281a7..70e7043c 100644 --- a/docs/specs/OSC.md +++ b/docs/specs/terminal-escapes.md @@ -1,10 +1,20 @@ -# OSC Sequence Registry +# Terminal Escape Sequence Registry -> Single registry of OSC sequences MouseTerm parses. Behavioral details live in `docs/specs/alert.md` (notifications) and `docs/specs/terminal-state.md` (CWD, prompt/command, title fallback). This file also documents iTerm2 self-identification because the same identity is what causes most of these sequences to be emitted at us. +> Single registry of the terminal escape sequences MouseTerm parses or responds to — OSC (Operating System Command) for out-of-band metadata, and the CSI/DCS sequences MouseTerm handles directly rather than delegating to xterm.js. Behavioral details for OSCs live in `docs/specs/alert.md` (notifications) and `docs/specs/terminal-state.md` (CWD, prompt/command, title fallback). This file also documents iTerm2 self-identification because the same identity is what causes most of these sequences to be emitted at us. ## Goal -MouseTerm parses a small set of OSC (Operating System Command) escape sequences from PTY output to drive alerts, terminal state, and titles. This document is the index — every supported OSC has one row in the table below pointing to the spec that defines its full behavior. +MouseTerm parses a small set of escape sequences from PTY output to drive alerts, terminal state, titles, and identity responses. Most terminal control is delegated to xterm.js; MouseTerm intervenes only where it must — OSCs that drive product state, CSI/DCS that MouseTerm answers itself or that need observation, and the security-sensitive sequences the iTerm2 identity provokes. This document is the index — every supported sequence has one row in the tables below pointing to the spec that defines its full behavior. + +## CSI vs OSC vs DCS + +These are the three escape-sequence families that show up in this spec: + +- **CSI** (`ESC [`, "Control Sequence Introducer") — terminal *control*: cursor movement, colors/SGR, scrolling, mode switches, key-input encoding. Numeric/short parameters, terminated by a final letter. Most CSIs are handled by xterm.js; MouseTerm only intervenes where noted in [Supported CSI](#supported-csi). +- **OSC** (`ESC ]`, "Operating System Command") — *out-of-band metadata* to the terminal emulator itself: titles, CWD, notifications, hyperlinks, clipboard, prompt markers. String-shaped payloads, terminated by `BEL` or `ST`. MouseTerm parses these at the PTY data boundary; see [Supported OSCs](#supported-oscs). +- **DCS** (`ESC P`, "Device Control String") — longer payloads, currently relevant only as the response shape for `CSI > q` (iTerm2 extended device attributes). + +Rule of thumb: CSI talks to the screen, OSC talks to the application hosting the screen, DCS is the response channel for richer queries. ## Parsing location @@ -51,6 +61,34 @@ Unknown non-iTerm2 OSC families pass through to xterm.js unchanged so xterm.js c Some sequences are dual-purpose. The notification rows for `OSC 9 ; ST`, `OSC 99` (`p=title`/`p=body`), and `OSC 777 ; notify` also feed the title-candidate channel in `terminal-state.md` — see its [Title candidate diagnostics](terminal-state.md#supported-osc-inputs) table. Only the OSC 9 *message* form can become a header/door label; OSC 99 and OSC 777 candidates are stored for the diagnostic popup only. The OSC 9 *progress* form (`OSC 9 ; 4`) carries no text and never contributes a title candidate. +## Supported CSI + +The vast majority of CSI handling is delegated to xterm.js. MouseTerm only intervenes in the cases below — either to answer a query itself (so the response shape is under our control), to observe a state change xterm.js processes, to enable an xterm.js feature, or to filter replay output. + +| Sequence | Role | Disposition | Where | +|---|---|---|---| +| `CSI > q` | iTerm2 extended device-attributes query | MouseTerm answers with `DCS > \| iTerm2 [version] ST` at the PTY boundary; not forwarded to xterm.js. | [iTerm2 identity](#iterm2-identity) | +| `CSI ? ... h` (DECSET) | Private-mode set, including mouse tracking and bracketed paste | Observed via an xterm.js parser hook that returns false (xterm still handles the sequence); the mouse-selection store reads `terminal.modes` in a microtask. | `docs/specs/mouse-and-clipboard.md` | +| `CSI ? ... l` (DECRST) | Private-mode reset, including mouse tracking and bracketed paste | Same observation pattern as DECSET. | `docs/specs/mouse-and-clipboard.md` | +| Kitty keyboard protocol | Disambiguated key-event reporting (CSI u with modifiers, e.g. Shift+Enter distinguishable from Enter) | Enabled by passing `vtExtensions: { kittyKeyboard: true }` to the xterm.js `Terminal` constructor; xterm.js handles the push/pop (`CSI > u` / `CSI < u`) and the modified key reports. | `lib/src/lib/terminal-lifecycle.ts` | + +### Replay-time CSI filtering + +During `pty:replay`, MouseTerm reconstructs scrollback by replaying saved bytes through xterm.js. Apps in that scrollback often left behind cursor-position reports, device-attribute responses, focus reports, and similar terminal-generated replies. xterm.js may re-emit those during replay; routing them into the new shell would corrupt its input buffer. MouseTerm's reply filter (`lib/src/lib/terminal-report-filter.ts`) drops replay-time CSI replies of the following shapes: + +- Cursor-position / device-status (`CSI [?]\d+;\d+ R/n`) +- Primary/secondary/tertiary device attributes (`CSI [?>=]\d* c`) +- Window manipulation reports (`CSI \d+;\d+ t/x`) +- DECRQSS-style reports (`CSI [?]\d+ $y`) +- Focus reports (`CSI I` / `CSI O`) +- OSC and DCS replies of any shape + +This filter is limited to *terminal-generated reports*. User keyboard escape sequences — arrows, function keys, bracketed paste, and modified key reports from the kitty keyboard protocol — must not be swallowed. See `docs/specs/transport.md` and `docs/specs/layout.md` for the contexts that invoke the filter. + +### Pass-through and fail-inertly + +Unknown CSI sequences pass through to xterm.js so it can handle standard terminal behavior MouseTerm does not model. The same fail-inertly rule that applies to OSCs (see [iTerm2 identity](#iterm2-identity)) applies to CSIs: any sequence that xterm.js does not recognize must be consumed silently — no visible terminal garbage, no clipboard or file access, no focus changes, no other side effects. + ## iTerm2 identity MouseTerm reports an iTerm2-compatible identity so that tools (shells, build systems, agent clients) emit the iTerm2-style escape codes that this spec set supports. @@ -70,7 +108,7 @@ Device/version query: - Use a single compatibility version across env and device responses. - Do not advertise feature-specific support until the relevant behavior exists. -Because this identity can cause tools to emit more iTerm2 escape codes than MouseTerm implements, **unsupported escape codes must fail inertly**: consume or ignore them without visible terminal garbage, privilege escalation, clipboard access, file access, or focus stealing. +Because this identity can cause tools to emit more iTerm2 escape codes than MouseTerm implements, **unsupported escape codes must fail inertly**: consume or ignore them without visible terminal garbage, privilege escalation, clipboard access, file access, or focus stealing. This rule applies to both OSC and CSI sequences (see [Known-unimplemented iTerm2 and clipboard-capable sequences](#known-unimplemented-iterm2-and-clipboard-capable-sequences) for OSCs and the [Pass-through and fail-inertly](#pass-through-and-fail-inertly) note under CSI). ## Known-unimplemented iTerm2 and clipboard-capable sequences @@ -98,4 +136,5 @@ This list is non-exhaustive. Any iTerm2-compatibility OSC family that MouseTerm - VS Code shell integration sequences (OSC 633): https://code.visualstudio.com/docs/terminal/shell-integration - Windows Terminal CWD OSC 9;9: https://learn.microsoft.com/en-us/windows/terminal/tutorials/new-tab-same-directory - kitty desktop notifications (OSC 99): https://sw.kovidgoyal.net/kitty/desktop-notifications/ +- kitty keyboard protocol: https://sw.kovidgoyal.net/kitty/keyboard-protocol/ - WezTerm escape sequences (OSC 777): https://wezterm.org/escape-sequences.html diff --git a/docs/specs/terminal-state.md b/docs/specs/terminal-state.md index 60ab1616..549cfd53 100644 --- a/docs/specs/terminal-state.md +++ b/docs/specs/terminal-state.md @@ -1,6 +1,6 @@ # Terminal CWD and Command State -> See `docs/specs/glossary.md` for Session vocabulary. This spec defines the per-Session terminal semantic state that layout and grouping consume. Alert/TODO behavior and notification OSCs (OSC 9 / 9;4 / 99 / 777 / BEL) live in `docs/specs/alert.md`. The OSC sequence registry and parsing-location rules live in `docs/specs/OSC.md`. +> See `docs/specs/glossary.md` for Session vocabulary. This spec defines the per-Session terminal semantic state that layout and grouping consume. Alert/TODO behavior and notification OSCs (OSC 9 / 9;4 / 99 / 777 / BEL) live in `docs/specs/alert.md`. The escape-sequence registry and parsing-location rules live in `docs/specs/terminal-escapes.md`. ## Goal @@ -169,7 +169,7 @@ Non-OSC title source: The `user_input` command fallback is best effort. It is sufficient for headers and grouping, but command-exit alerting may treat it as lower confidence or ignore it until deeper shell integration exists. -The parser accepts both BEL and ST terminators and handles split chunks. Supported-but-malformed semantic OSCs are consumed without changing state. Unsupported OSC pass-through vs. consume/ignore behavior is defined centrally in `docs/specs/OSC.md`. +The parser accepts both BEL and ST terminators and handles split chunks. Supported-but-malformed semantic OSCs are consumed without changing state. Unsupported OSC pass-through vs. consume/ignore behavior is defined centrally in `docs/specs/terminal-escapes.md`. ## Reducer diff --git a/docs/specs/transport.md b/docs/specs/transport.md index dfe2879c..3c142afc 100644 --- a/docs/specs/transport.md +++ b/docs/specs/transport.md @@ -110,7 +110,7 @@ Message types live in `vscode-ext/src/message-types.ts` (the canonical schema; o | `mouseterm:openThemeDebugger` | Command-triggered request to open the shared theme debugger dialog | | `alert:state` | Alert state change (projected status, watchingEnabled, todo, notification, attentionDismissedRing) | -The OSC parsing/stripping rules that produce `pty:data` and `terminal:semanticEvents` are specified in `docs/specs/OSC.md`. +The OSC parsing/stripping rules that produce `pty:data` and `terminal:semanticEvents` are specified in `docs/specs/terminal-escapes.md`. ## Persisted session types