From 3944b22506609493c3e103f9243fe6b8d6340efe Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Sat, 25 Apr 2026 19:54:06 -0500 Subject: [PATCH] fix(tui): suspend Ink properly when opening $EDITOR via Ctrl+G The Ctrl+G handler was toggling the alt-screen by hand (`\x1b[?1049l` ... `\x1b[?1049h`) without releasing stdin or kitty keyboard mode, so the launched editor would lose keystrokes (Ink kept swallowing them) and editors that don't speak CSI-u (e.g. nano) would print "Unknown sequence" for every Ctrl-key. Switch to `withInkSuspended` from @hermes/ink, the same helper `/setup` already uses. It pauses Ink, removes stdin listeners, drops raw mode, disables kitty/modifyOtherKeys + mouse + focus reporting, runs the editor, then restores everything with a full repaint. --- ui-tui/src/app/interfaces.ts | 2 +- ui-tui/src/app/useComposerState.ts | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/ui-tui/src/app/interfaces.ts b/ui-tui/src/app/interfaces.ts index 0105b44376..9049c17f9a 100644 --- a/ui-tui/src/app/interfaces.ts +++ b/ui-tui/src/app/interfaces.ts @@ -121,7 +121,7 @@ export interface ComposerActions { dequeue: () => string | undefined enqueue: (text: string) => void handleTextPaste: (event: PasteEvent) => MaybePromise - openEditor: () => void + openEditor: () => Promise pushHistory: (text: string) => void replaceQueue: (index: number, text: string) => void setCompIdx: StateSetter diff --git a/ui-tui/src/app/useComposerState.ts b/ui-tui/src/app/useComposerState.ts index f229067edc..0821dd2c5d 100644 --- a/ui-tui/src/app/useComposerState.ts +++ b/ui-tui/src/app/useComposerState.ts @@ -3,7 +3,7 @@ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' -import { useStdin } from '@hermes/ink' +import { useStdin, withInkSuspended } from '@hermes/ink' import { useStore } from '@nanostores/react' import { useCallback, useMemo, useState } from 'react' @@ -253,14 +253,16 @@ export function useComposerState({ [handleResolvedPaste, onClipboardPaste, querier] ) - const openEditor = useCallback(() => { + const openEditor = useCallback(async () => { const editor = process.env.EDITOR || process.env.VISUAL || 'vi' const file = join(mkdtempSync(join(tmpdir(), 'hermes-')), 'prompt.md') + let code: null | number = null writeFileSync(file, [...inputBuf, input].join('\n')) - process.stdout.write('\x1b[?1049l') - const { status: code } = spawnSync(editor, [file], { stdio: 'inherit' }) - process.stdout.write('\x1b[?1049h\x1b[2J\x1b[H') + + await withInkSuspended(async () => { + code = spawnSync(editor, [file], { stdio: 'inherit' }).status + }) if (code === 0) { const text = readFileSync(file, 'utf8').trimEnd()