diff --git a/memory.md b/memory.md index 523448e..57b28b6 100644 --- a/memory.md +++ b/memory.md @@ -57,12 +57,10 @@ Four-agent research pass (terminal-landscape, AI-orchestration, xterm/Tauri ecos **→ Exploring first (user-selected 2026-05-28):** - [ ] **Per-session cost / token tracking.** Parse `~/.claude/projects//.jsonl` (`message.usage`: input/output/cache_read/cache_write + model per assistant line) → tokens + estimated $ per pane and per workspace. Easy parsing; the fiddly bit is mapping a tiletopia pane → its session file (capture session id / cwd at spawn). Difficulty: easy–medium. +- [ ] **Find in scrollback.** `@xterm/addon-search` — per-pane search box, `findNext`/`findPrevious`, regex + case opts, `searchOptions.decorations` for match highlight. Difficulty: easy. - [ ] **Smart link providers.** `terminal.registerLinkProvider()` to make file paths (`src/foo.ts:12:3`), `localhost:PORT`, and error locations clickable — more flexible than the regex-only web-links addon already loaded. Open file in editor / browser. Difficulty: medium. -- [x] ~~**Find in scrollback.**~~ Done (code) 2026-05-28, commit on `main` — `@xterm/addon-search` + new `src/components/SearchBar.tsx`/`.css` overlay, Ctrl+Shift+F open / Enter / Shift+Enter / Esc, regex + case toggles, decoration highlight. **Pending: `pnpm install` on Windows host + runtime verify** (addon not in WSL node_modules; tsc clean otherwise). -- [x] ~~**Unicode 11 + grapheme width.**~~ Done (code) 2026-05-28 — `@xterm/addon-unicode11` loaded after CanvasAddon, `term.unicode.activeVersion = '11'`. Same pending-install caveat. (Skipped the separate `addon-unicode-graphemes` for now.) -- [x] ~~**Pane navigation key handler.**~~ Done (code) 2026-05-28 — Ctrl+Alt+Arrow / Ctrl+Alt+HJKL (spatial via `findNeighborInDirection`) + Alt+1..9 (Nth `walkLeaves` leaf). New `NavigateIntent` union in orchestration.tsx; XtermPane emits intent via new `onNavigate` prop → LeafPane → App `navigateTo` sets active leaf (reuses isActive→focusTrigger refocus). All chords share the one `attachCustomKeyEventHandler`. **Caveats:** Alt+1..9 swallows bare Alt+digit (breaks readline digit-arg / vim buffer-jump); Ctrl+Alt+Arrow may collide with Windows virtual-desktop switching — both noted in shortcuts.ts, v2 mitigation = opt-out toggle or Ctrl+Alt+Shift+Arrow. - -**Implementation note:** the three above were built in one fan-out workflow (parallel design on haiku/sonnet → single sonnet implementer applying to shared files), since all three touch `XtermPane`'s mount + its single `attachCustomKeyEventHandler` (xterm replaces the handler on each call, so they MUST coexist in one registration — don't add a second `attachCustomKeyEventHandler` anywhere). +- [ ] **Unicode 11 + grapheme width.** `@xterm/addon-unicode11` (+ `@xterm/addon-unicode-graphemes`), set `terminal.unicode.activeVersion = '11'`. Fixes emoji/CJK/box-drawing width misalignment + cursor drift in TUIs. Difficulty: easy. +- [ ] **Pane navigation key handler.** `attachCustomKeyEventHandler()` to intercept tiling-WM chords (Ctrl+hjkl move focus, Alt+number select pane) before the PTY sees them, so shortcuts don't leak into the shell. Difficulty: easy. **Parked — circle back (saved, not yet prioritized):** diff --git a/package.json b/package.json index 102c58e..393ccb7 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,6 @@ "@tauri-apps/plugin-opener": "^2.0.0", "@xterm/addon-canvas": "^0.7.0", "@xterm/addon-fit": "^0.10.0", - "@xterm/addon-search": "^0.15.0", - "@xterm/addon-unicode11": "^0.8.0", "@xterm/addon-web-links": "^0.12.0", "@xterm/xterm": "^5.5.0", "react": "^18.3.0", diff --git a/src/App.tsx b/src/App.tsx index ffceee2..07fefe2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -98,7 +98,7 @@ import { presetTwoRows, presetTwoByTwo, } from "./lib/layout/tree"; -import { OrchestrationProvider, type Orchestration, type NavigateIntent } from "./lib/layout/orchestration"; +import { OrchestrationProvider, type Orchestration } from "./lib/layout/orchestration"; import LeafPane from "./lib/layout/LeafPane"; import Gutter from "./lib/layout/Gutter"; import Notifications, { type Toast } from "./components/Notifications"; @@ -717,36 +717,6 @@ export default function App() { setActiveLeafId(leafId); }, []); - // navigateTo — called from XtermPane's attachCustomKeyEventHandler via - // LeafPane's onNavigate prop. Resolves the target leaf from the current - // layout tree and sets it active; the LeafPane isActive→focusTrigger - // effect then refocuses the xterm textarea automatically. - const navigateTo = useCallback((intent: NavigateIntent) => { - const currentTree = treeRef.current; - const currentActiveId = activeLeafId; - - if (intent.kind === "direction") { - const layout = flattenLayout(currentTree); - if (!currentActiveId) { - const first = layout.leaves[0]?.leaf.id; - if (first) setActiveLeafId(first); - return; - } - const nextId = findNeighborInDirection( - layout.leaves, - currentActiveId, - intent.dir, - ); - if (nextId) setActiveLeafId(nextId); - } else { - // intent.kind === "index" - const leaves = Array.from(walkLeaves(currentTree)); - // Clamp: Alt+9 with 3 panes picks the 3rd pane. - const target = leaves[Math.min(intent.n, leaves.length) - 1]; - if (target) setActiveLeafId(target.id); - } - }, [activeLeafId]); // treeRef is a ref — stable, intentionally not listed - const openHostManager = useCallback(() => setHostManagerOpen(true), []); const closeHostManager = useCallback(() => setHostManagerOpen(false), []); @@ -1272,7 +1242,6 @@ export default function App() { toggleMcpAllow, openHostManager, setActive, - navigateTo, registerPaneId, broadcastFrom, notify, @@ -1297,7 +1266,6 @@ export default function App() { toggleMcpAllow, openHostManager, setActive, - navigateTo, registerPaneId, broadcastFrom, notify, diff --git a/src/components/SearchBar.css b/src/components/SearchBar.css deleted file mode 100644 index 0f389bd..0000000 --- a/src/components/SearchBar.css +++ /dev/null @@ -1,105 +0,0 @@ -/* --------------------------------------------------------------------------- - SearchBar — find-in-scrollback overlay. - - Positioned absolutely inside XtermPane's container div (which must be - position: relative). Sits at the top-right of the pane, z-index 10 so it - floats above the xterm canvas but below any app-level modals (z-index 100). - Colour palette matches Palette.css / Help.css: #181818 surface, #2a2a2a - borders, #e6e6e6 text, #1a3a5c accent. ---------------------------------------------------------------------------- */ - -.search-bar { - position: absolute; - top: 4px; - right: 4px; - z-index: 10; - display: flex; - align-items: center; - gap: 3px; - background: #181818; - border: 1px solid #2a2a2a; - border-radius: 5px; - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.55); - padding: 3px 4px; - font-family: "Cascadia Mono", "JetBrains Mono", "Consolas", monospace; - font-size: 12px; - color: #e6e6e6; -} - -.search-input { - font: inherit; - font-size: 12px; - color: #e6e6e6; - background: #1f1f1f; - border: 1px solid #2a2a2a; - border-radius: 3px; - padding: 3px 7px; - outline: none; - width: 180px; - caret-color: #e6e6e6; -} - -.search-input:focus { - border-color: #1a3a5c; - box-shadow: 0 0 0 1px #1a3a5c; -} - -.search-input::placeholder { - color: #555; -} - -/* Toggle buttons (Aa / .*) */ -.search-toggle { - font: inherit; - font-size: 11px; - background: transparent; - border: 1px solid #2a2a2a; - border-radius: 3px; - color: #888; - padding: 2px 5px; - cursor: pointer; - line-height: 1; - transition: background 0.1s, color 0.1s; -} - -.search-toggle:hover, -.search-toggle[aria-pressed="true"] { - background: #1a3a5c; - border-color: #1a5c8a; - color: #cce6ff; -} - -/* Prev / Next navigation arrows */ -.search-nav { - font: inherit; - font-size: 13px; - background: transparent; - border: 1px solid #2a2a2a; - border-radius: 3px; - color: #aaa; - padding: 1px 6px; - cursor: pointer; - line-height: 1; -} - -.search-nav:hover { - background: #2a2a2a; - color: #e6e6e6; -} - -/* Close button */ -.search-close { - background: transparent; - border: none; - color: #666; - font-size: 16px; - line-height: 1; - padding: 1px 5px; - cursor: pointer; - border-radius: 3px; -} - -.search-close:hover { - background: #2a2a2a; - color: #ddd; -} diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx deleted file mode 100644 index 1d41a99..0000000 --- a/src/components/SearchBar.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import { useRef, useEffect, useState } from "react"; -import type { SearchAddon } from "@xterm/addon-search"; -import "./SearchBar.css"; - -// --------------------------------------------------------------------------- -// SearchBar — per-pane find-in-scrollback overlay. -// -// Rendered as an absolutely-positioned sibling of the xterm canvas inside -// XtermPane's container div (position: relative). The SearchAddon instance -// is owned by XtermPane and passed down as a prop; no IPC or Context needed. -// -// Toggle state (caseSensitive, regex) uses useState so aria-pressed reflects -// the live value on every render — refs alone don't trigger re-renders. -// --------------------------------------------------------------------------- - -interface SearchBarProps { - searchAddon: SearchAddon; - onClose: () => void; -} - -export default function SearchBar({ searchAddon, onClose }: SearchBarProps) { - const inputRef = useRef(null); - const queryRef = useRef(""); - const [caseSensitive, setCaseSensitive] = useState(false); - const [useRegex, setUseRegex] = useState(false); - - // Keep stable refs to toggle values so findNext/findPrev closures always - // see the current value without needing to be recreated on each state change. - const caseSensitiveRef = useRef(caseSensitive); - const useRegexRef = useRef(useRegex); - useEffect(() => { caseSensitiveRef.current = caseSensitive; }, [caseSensitive]); - useEffect(() => { useRegexRef.current = useRegex; }, [useRegex]); - - // Autofocus the input when the bar mounts. - useEffect(() => { - queueMicrotask(() => inputRef.current?.focus()); - }, []); - - function getOptions() { - return { - caseSensitive: caseSensitiveRef.current, - regex: useRegexRef.current, - // Highlight all matches and mark the active one distinctly. - decorations: { - matchBackground: "#3a3a00", - matchBorder: "#888800", - matchOverviewRuler: "#888800", - activeMatchBackground: "#b5890080", - activeMatchBorder: "#e6c000", - activeMatchColorOverviewRuler: "#e6c000", - }, - }; - } - - function findNext() { - if (!queryRef.current) return; - searchAddon.findNext(queryRef.current, getOptions()); - } - - function findPrev() { - if (!queryRef.current) return; - searchAddon.findPrevious(queryRef.current, getOptions()); - } - - function handleInput(e: React.ChangeEvent) { - queryRef.current = e.target.value; - // Live-search: jump to next match as you type. - if (queryRef.current) { - searchAddon.findNext(queryRef.current, getOptions()); - } - } - - function handleKeyDown(e: React.KeyboardEvent) { - if (e.key === "Escape") { - e.preventDefault(); - onClose(); - } else if (e.key === "Enter") { - e.preventDefault(); - if (e.shiftKey) { - findPrev(); - } else { - findNext(); - } - } - } - - function toggleCase() { - setCaseSensitive((v) => { - const next = !v; - caseSensitiveRef.current = next; - // Re-run with the new option so decorations update immediately. - if (queryRef.current) { - searchAddon.findNext(queryRef.current, { - ...getOptions(), - caseSensitive: next, - }); - } - return next; - }); - } - - function toggleRegex() { - setUseRegex((v) => { - const next = !v; - useRegexRef.current = next; - if (queryRef.current) { - searchAddon.findNext(queryRef.current, { - ...getOptions(), - regex: next, - }); - } - return next; - }); - } - - return ( -
- - - - - - - - - - - -
- ); -} diff --git a/src/components/XtermPane.tsx b/src/components/XtermPane.tsx index a993c11..1e76493 100644 --- a/src/components/XtermPane.tsx +++ b/src/components/XtermPane.tsx @@ -1,11 +1,8 @@ -import { useRef, useEffect, useState } from "react"; +import { useRef, useEffect } from "react"; import { Terminal } from "@xterm/xterm"; import { FitAddon } from "@xterm/addon-fit"; import { WebLinksAddon } from "@xterm/addon-web-links"; import { CanvasAddon } from "@xterm/addon-canvas"; -import { SearchAddon } from "@xterm/addon-search"; -import { Unicode11Addon } from "@xterm/addon-unicode11"; -import SearchBar from "./SearchBar"; import type { UnlistenFn } from "@tauri-apps/api/event"; import { readText as clipboardReadText, @@ -24,7 +21,6 @@ import { type PaneId, type SpawnSpec, } from "../ipc"; -import type { NavigateIntent } from "../lib/layout/orchestration"; // --------------------------------------------------------------------------- // base64 helpers (private to this module) @@ -76,12 +72,6 @@ interface XtermPaneProps { focusTrigger?: number; /** Absolute font size in px. Changes are applied live (fit + PTY resize). */ fontSize?: number; - /** Called when the user presses a tiling-WM navigation chord inside the - * terminal. XtermPane only emits the intent; the parent (LeafPane/App) - * resolves the target leaf from the current layout and sets it active. - * Defined as an optional callback so single-pane windows don't require - * wiring it up. */ - onNavigate?: (intent: NavigateIntent) => void; } const DEFAULT_XTERM_FONT_SIZE = 13; @@ -100,14 +90,11 @@ export default function XtermPane({ onFocus, focusTrigger = 0, fontSize, - onNavigate, }: XtermPaneProps) { const containerRef = useRef(null); const termRef = useRef(null); const fitRef = useRef(null); const paneIdRef = useRef(null); - const searchAddonRef = useRef(null); - const [searchOpen, setSearchOpen] = useState(false); // Stash the most recent `fontSize` prop so the mount effect can pick // up the initial value without re-running when it changes (the secondary // effect below handles dynamic updates). @@ -120,18 +107,12 @@ export default function XtermPane({ const onInputRef = useRef(onInput); const onDataReceivedRef = useRef(onDataReceived); const onFocusRef = useRef(onFocus); - const onNavigateRef = useRef(onNavigate); - // Stable ref for setSearchOpen so it can be called from inside the - // attachCustomKeyEventHandler closure without the closure going stale. - const setSearchOpenRef = useRef<(v: boolean) => void>(setSearchOpen); useEffect(() => { onStatusRef.current = onStatus; }, [onStatus]); useEffect(() => { onSpawnRef.current = onSpawn; }, [onSpawn]); useEffect(() => { onInputRef.current = onInput; }, [onInput]); useEffect(() => { onDataReceivedRef.current = onDataReceived; }, [onDataReceived]); useEffect(() => { onFocusRef.current = onFocus; }, [onFocus]); - useEffect(() => { onNavigateRef.current = onNavigate; }, [onNavigate]); - useEffect(() => { setSearchOpenRef.current = setSearchOpen; }, [setSearchOpen]); // ------------------------------------------------------------------------- // Mount / unmount: create terminal, spawn PTY, wire listeners @@ -186,24 +167,6 @@ export default function XtermPane({ console.warn("CanvasAddon load failed; using DOM renderer:", e); } - // Load Unicode 11 addon for correct width handling of emoji, CJK, and - // box-drawing characters. This prevents cursor drift in TUIs that rely on - // Unicode 11 character widths. Loaded after CanvasAddon so the renderer - // surface is set before width calculations begin. - try { - term.loadAddon(new Unicode11Addon()); - term.unicode.activeVersion = "11"; - } catch (e) { - console.warn("Unicode11Addon load failed:", e); - } - - // Load the search addon so find-in-scrollback works. Must be loaded - // after open() so the terminal viewport exists for decoration rendering, - // and after CanvasAddon since it decorates the same canvas surface. - const search = new SearchAddon(); - searchAddonRef.current = search; - term.loadAddon(search); - // Initial size — fit before asking the PTY for its dimensions. fit.fit(); @@ -316,100 +279,36 @@ export default function XtermPane({ onInputRef.current?.(b64); }); - // Intercept tiling-WM chords before the PTY sees them. All families - // share ONE attachCustomKeyEventHandler call — xterm.js replaces the - // previous handler on every call, so a second call anywhere would - // silently discard all earlier interceptions. + // Ctrl+Shift+C / Ctrl+Shift+V — copy selection / paste from clipboard. + // Runs before xterm consumes the key, so the textarea never sees a raw + // Ctrl+V (which would otherwise inject ^V into the PTY). term.paste() + // routes through onData → writeToPane, so broadcasting and bracketed + // paste both keep working for free. // - // Family 1: Ctrl+Shift+C / Ctrl+Shift+V — copy selection / paste. - // Uses tauri-plugin-clipboard-manager so WebView2 never shows its - // native "Allow clipboard access?" prompt. term.paste() routes - // through onData → writeToPane so broadcasting + bracketed paste - // keep working for free. - // - // Family 2: Ctrl+Shift+F — open/focus the find-in-scrollback bar. - // Swallowed before xterm or the PTY sees the raw keypress. Uses the - // stable setSearchOpenRef so the closure never goes stale. - // - // Family 3: Ctrl+Alt+Arrow / Ctrl+Alt+H/J/K/L — spatial pane focus. - // XtermPane emits onNavigate({ kind: "direction", dir }) and returns - // false so the chord is swallowed before it reaches the PTY. The - // parent (LeafPane → App) resolves the neighbour and bumps - // focusTrigger on the new active pane. - // - // Family 4: Alt+1..9 — index-based pane focus. - // Emits onNavigate({ kind: "index", n }) and swallows. Note: bare - // Alt+digit is used by some shells (readline digit-argument, vim/nvim) - // — this interception is an accepted v1 trade-off (see shortcuts.ts). + // Uses tauri-plugin-clipboard-manager instead of navigator.clipboard so + // WebView2 doesn't surface its native "Allow clipboard access?" prompt. term?.attachCustomKeyEventHandler((e) => { if (e.type !== "keydown") return true; - - // --- Family 1 & 2: Ctrl+Shift+* (no Alt) --------------------------- - if (e.ctrlKey && e.shiftKey && !e.altKey) { - if (e.code === "KeyF") { - // Ctrl+Shift+F — open find-in-scrollback bar. - e.preventDefault(); - setSearchOpenRef.current(true); - return false; - } - if (e.code === "KeyC") { - // Ctrl+Shift+C — copy selection to clipboard. - const sel = term?.getSelection(); - if (sel) { - void clipboardWriteText(sel).catch((err) => - console.warn("clipboard write failed:", err), - ); - } - e.preventDefault(); - return false; - } - if (e.code === "KeyV") { - // Ctrl+Shift+V — paste from clipboard via term.paste() so - // broadcasting and bracketed paste work for free. - e.preventDefault(); - clipboardReadText() - .then((text) => { - if (text && term) term.paste(text); - }) - .catch((err) => console.warn("clipboard read failed:", err)); - return false; + if (!e.ctrlKey || !e.shiftKey || e.altKey) return true; + if (e.code === "KeyC") { + const sel = term?.getSelection(); + if (sel) { + void clipboardWriteText(sel).catch((err) => + console.warn("clipboard write failed:", err), + ); } + e.preventDefault(); + return false; } - - // --- Family 3: Ctrl+Alt+Arrow / Ctrl+Alt+H/J/K/L (spatial nav) ----- - if (e.ctrlKey && e.altKey && !e.shiftKey && onNavigateRef.current) { - // Arrow keys - const ARROW_DIR: Record = { - ArrowLeft: "left", - ArrowRight: "right", - ArrowUp: "up", - ArrowDown: "down", - }; - // Vim-style HJKL - const VIM_DIR: Record = { - KeyH: "left", - KeyJ: "down", - KeyK: "up", - KeyL: "right", - }; - const dir = ARROW_DIR[e.code] ?? VIM_DIR[e.code]; - if (dir) { - e.preventDefault(); - onNavigateRef.current({ kind: "direction", dir }); - return false; - } + if (e.code === "KeyV") { + e.preventDefault(); + clipboardReadText() + .then((text) => { + if (text && term) term.paste(text); + }) + .catch((err) => console.warn("clipboard read failed:", err)); + return false; } - - // --- Family 4: Alt+1..9 (index-based pane focus) ------------------- - if (e.altKey && !e.ctrlKey && !e.shiftKey && onNavigateRef.current) { - const digit = e.code.match(/^Digit([1-9])$/); - if (digit) { - e.preventDefault(); - onNavigateRef.current({ kind: "index", n: parseInt(digit[1], 10) }); - return false; - } - } - return true; }); @@ -490,7 +389,6 @@ export default function XtermPane({ term = null; termRef.current = null; fitRef.current = null; - searchAddonRef.current = null; paneIdRef.current = null; }; // spec is read once at mount; intentionally omitted from deps so we @@ -537,30 +435,5 @@ export default function XtermPane({ } }, [fontSize]); - // Close the search bar and return focus to the xterm textarea so the user - // can resume typing immediately. Queries the well-known xterm helper - // textarea selector — the same pattern used in the focusTrigger effect. - function closeSearch() { - setSearchOpen(false); - const ta = containerRef.current?.querySelector( - ".xterm-helper-textarea", - ); - ta?.focus(); - } - - // The outer wrapper is position:relative so the absolutely-positioned - // SearchBar anchors inside the pane without escaping to a positioned - // ancestor further up the tree. The FitAddon measures containerRef's div - // (the inner one), which still fills 100% of the wrapper — no sizing break. - return ( -
-
- {searchOpen && searchAddonRef.current && ( - - )} -
- ); + return
; } diff --git a/src/lib/layout/LeafPane.tsx b/src/lib/layout/LeafPane.tsx index cbd53af..75ad84c 100644 --- a/src/lib/layout/LeafPane.tsx +++ b/src/lib/layout/LeafPane.tsx @@ -194,14 +194,6 @@ export default function LeafPane({ leaf }: { leaf: LeafNode }) { [orch.setActive, leaf.id], ); - // Delegate keyboard navigation intents from XtermPane up to App via - // orch.navigateTo. XtermPane stays dumb (emits intent only); App resolves - // the target leaf from the current layout and bumps focusTrigger. - const onPaneNavigate = useCallback( - (intent: Parameters[0]) => orch.navigateTo(intent), - [orch.navigateTo], - ); - const onStatus = useCallback((msg: string, ok: boolean) => { setStatus(msg); setStatusOk(ok); @@ -583,7 +575,6 @@ export default function LeafPane({ leaf }: { leaf: LeafNode }) { onInput={onTerminalInput} onDataReceived={onDataReceived} onFocus={onXtermFocus} - onNavigate={onPaneNavigate} focusTrigger={focusTrigger} fontSize={resolveFontSize(leaf.fontSizeOffset)} /> diff --git a/src/lib/layout/orchestration.tsx b/src/lib/layout/orchestration.tsx index 46effad..cd381ff 100644 --- a/src/lib/layout/orchestration.tsx +++ b/src/lib/layout/orchestration.tsx @@ -1,5 +1,5 @@ import { createContext, useContext, type ReactNode } from "react"; -import type { Orientation, NodeId, LeafShellSpec, Direction } from "./tree"; +import type { Orientation, NodeId, LeafShellSpec } from "./tree"; import type { PaneId, SshHost } from "../../ipc"; /** @@ -62,16 +62,6 @@ export interface Orchestration { * The PTY stays alive across the move (the new window's XtermPane * adopts the existing PaneId; scrollback ring is replayed). */ moveToNewWindow: (leafId: NodeId) => void; - /** - * Navigate focus from within a pane's key-handler. XtermPane emits the - * intent; LeafPane/App resolve the target leaf and set it active. - * - * `{ kind: "direction", dir }` — move to the spatial neighbour in that - * direction using the same flattenLayout geometry as Ctrl+Shift+Arrow. - * `{ kind: "index", n }` — focus the Nth leaf in DFS (walkLeaves) order, - * 1-indexed, clamped to the leaf count (so Alt+9 with 3 panes picks pane 3). - */ - navigateTo: (intent: NavigateIntent) => void; /** Returns a PaneId only for leaves that just arrived via a window * transfer (so LeafPane can pass `existingPaneId` to XtermPane to skip * the spawn). One-shot — App clears the entry once the pane has @@ -79,13 +69,6 @@ export interface Orchestration { getInitialPaneIdFor: (leafId: NodeId) => PaneId | undefined; } -/** Discriminated intent emitted by XtermPane's key handler. App resolves - * the actual target leaf from the current tree without XtermPane needing - * to know anything about layout geometry or leaf ordering. */ -export type NavigateIntent = - | { kind: "direction"; dir: Direction } - | { kind: "index"; n: number }; - const OrchestrationContext = createContext(null); export function OrchestrationProvider({ diff --git a/src/lib/shortcuts.ts b/src/lib/shortcuts.ts index a72cddb..5b1b789 100644 --- a/src/lib/shortcuts.ts +++ b/src/lib/shortcuts.ts @@ -66,23 +66,7 @@ export const SHORTCUT_SECTIONS: ShortcutSection[] = [ { keys: "Ctrl+K", description: "Open jump-to-pane palette" }, { keys: "Ctrl+Shift+← / → / ↑ / ↓", - description: - "Focus neighbour pane in that direction (window-level — works even when no terminal is focused)", - }, - { - keys: "Ctrl+Alt+← / → / ↑ / ↓", - description: - "Focus neighbour pane in that direction (from inside the terminal — intercepted before the PTY sees it)", - }, - { - keys: "Ctrl+Alt+H / J / K / L", - description: - "Same as Ctrl+Alt+Arrow but in Vim-style HJKL order (left / down / up / right)", - }, - { - keys: "Alt+1 … Alt+9", - description: - "Focus the Nth pane in layout order (DFS: left-to-right, top-to-bottom); clamped to pane count. Note: swallows bare Alt+digit — shells using readline digit-argument or vim buffer-jump may conflict.", + description: "Focus neighbour pane in that direction", }, ], }, @@ -116,18 +100,6 @@ export const SHORTCUT_SECTIONS: ShortcutSection[] = [ keys: "Ctrl+Shift+C / Ctrl+Shift+V", description: "Copy selection / paste in terminal", }, - { - keys: "Ctrl+Shift+F", - description: "Open find-in-scrollback bar for the focused pane", - }, - { - keys: "Enter / Shift+Enter", - description: "Next / previous match (while search bar is focused)", - }, - { - keys: "Escape", - description: "Close find bar and return focus to terminal", - }, ], }, {