From 01fa8dcc009d4f4071d25b32d69ed9091b388176 Mon Sep 17 00:00:00 2001 From: alt-glitch Date: Tue, 9 Jun 2026 06:24:14 +0000 Subject: [PATCH] opentui(keymap): adopt native @opentui/keymap for overlay close + confirm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @opentui/keymap@0.3.2 was installed but unused (the spec said we'd use it). Wire it natively: createDefaultOpenTuiKeymap(renderer) + at the render root, and a useCloseLayer(target,onClose) helper that registers a focus-within Esc/Ctrl+C → close layer. Migrated the close handling of sessionSwitcher, picker, approvalPrompt (close-only), confirmPrompt (y/n via confirm/cancel commands), and pager + agentsDashboard (close via keymap; scroll/select stay raw — not cleanly focus-gated). Overlays gain a root ref + focus-on-mount so the focus-within layer activates. q-close re-added to pager/dashboard (footer advertises it). Composer history/refocus + masked prompt + the Ctrl+C quit machine stay raw by design (need the in-flight keystroke / careful state). Test harness gains a withKeymap() wrapper so view tests mount under a provider. 91 pass; live-verified /sessions + /tools Esc-close and composer focus recovery after. --- ui-tui-opentui-v2/src/entry/main.tsx | 35 ++++++++++++------- ui-tui-opentui-v2/src/view/keymap.tsx | 27 ++++++++++++++ .../src/view/overlays/agentsDashboard.tsx | 26 +++++++++----- ui-tui-opentui-v2/src/view/overlays/pager.tsx | 30 +++++++++++----- .../src/view/overlays/picker.tsx | 10 +++--- .../src/view/overlays/sessionSwitcher.tsx | 10 +++--- .../src/view/prompts/approvalPrompt.tsx | 11 +++--- .../src/view/prompts/confirmPrompt.tsx | 30 +++++++++++++--- 8 files changed, 132 insertions(+), 47 deletions(-) create mode 100644 ui-tui-opentui-v2/src/view/keymap.tsx diff --git a/ui-tui-opentui-v2/src/entry/main.tsx b/ui-tui-opentui-v2/src/entry/main.tsx index 41c46f94cd4..985b97ecd26 100644 --- a/ui-tui-opentui-v2/src/entry/main.tsx +++ b/ui-tui-opentui-v2/src/entry/main.tsx @@ -19,6 +19,8 @@ * The body of `run` does not change when the backend swaps — that's the point of * the layer; only `makeAppLayer(...)` differs at the edge. */ +import { createDefaultOpenTuiKeymap } from '@opentui/keymap/opentui' +import { KeymapProvider } from '@opentui/keymap/solid' import { render } from '@opentui/solid' import { Deferred, Duration, Effect } from 'effect' import { writeFileSync } from 'node:fs' @@ -289,6 +291,11 @@ export const run = Effect.fn('Tui.run')(function* (input: TuiInput) { if (!renderer.isDestroyed) renderer.destroy() } + // Native keymap host (Phase 3): one keymap bound to this renderer, provided + // to the whole Solid tree via . Overlays/prompts register + // close (and confirm) layers against it through useCloseLayer/useBindings. + const keymap = createDefaultOpenTuiKeymap(renderer) + // Submit a user turn: the service value is in hand, so `gateway.request(...)` // is Effect<…, never> — fire it detached with runFork; failures are logged. const submitPrompt = (text: string) => { @@ -384,19 +391,21 @@ export const run = Effect.fn('Tui.run')(function* (input: TuiInput) { yield* Effect.promise(() => render( () => ( - store.state.theme}> - gateway.sessionId()} - history={history} - onImagePaste={onImagePaste} - pasteStore={pasteStore} - /> - + + store.state.theme}> + gateway.sessionId()} + history={history} + onImagePaste={onImagePaste} + pasteStore={pasteStore} + /> + + ), renderer ) diff --git a/ui-tui-opentui-v2/src/view/keymap.tsx b/ui-tui-opentui-v2/src/view/keymap.tsx new file mode 100644 index 00000000000..bcd71c1c90b --- /dev/null +++ b/ui-tui-opentui-v2/src/view/keymap.tsx @@ -0,0 +1,27 @@ +/** + * keymap.tsx — thin Solid helpers over the native `@opentui/keymap` (Phase 3). + * + * `useCloseLayer` is the shared CLOSE binding for overlays/prompts: a `close` + * command bound to Esc and Ctrl+C, scoped to the overlay's root box via a + * `focus-within` layer (the default when a `target` accessor is present). The + * box itself isn't focused — the native `