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 `