From eb45ada74e1199c08d9e7f800bfb6ee18ab55992 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 7 Aug 2026 10:39:39 -0400 Subject: [PATCH] fix(tui): preserve burst form input --- packages/tui/src/mini/footer.form.tsx | 9 ++++++--- packages/tui/src/routes/session/form.tsx | 10 ++++++++-- packages/tui/test/cli/tui/form.test.tsx | 6 +++--- packages/tui/test/mini/footer.view.test.tsx | 6 +++--- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/packages/tui/src/mini/footer.form.tsx b/packages/tui/src/mini/footer.form.tsx index 21b8e7c0a2..b0b428504e 100644 --- a/packages/tui/src/mini/footer.form.tsx +++ b/packages/tui/src/mini/footer.form.tsx @@ -71,6 +71,7 @@ export function RunFormBody(props: { return typeof value === "string" ? value : undefined }) let area: TextareaRenderable | undefined + let editingReady = false createEffect(() => { setState((previous) => formSync(previous, props.request)) @@ -93,6 +94,7 @@ export function RunFormBody(props: { if (!area || area.isDestroyed || !state().editing) return area.focus() area.cursorOffset = area.plainText.length + editingReady = true }) }) @@ -209,7 +211,6 @@ export function RunFormBody(props: { return } if (unsupported()) return - if (state().editing) return const character = !event.ctrl && !event.meta && @@ -219,12 +220,14 @@ export function RunFormBody(props: { /^[^\p{C}\p{Zl}\p{Zp}]$/u.test(event.sequence) ? event.sequence : undefined - if (custom() && state().selected === rows().length && character) { - const next = formPick(state(), props.request) + if (custom() && state().selected === rows().length && character && !editingReady) { + const next = state().editing ? state() : formPick(state(), props.request) + if (!state().editing) editingReady = false setState(formSetDraft(next, current(), formInput(next, current()) + character)) event.preventDefault() return } + if (state().editing) return if ( event.name === "tab" || event.name === "left" || diff --git a/packages/tui/src/routes/session/form.tsx b/packages/tui/src/routes/session/form.tsx index 28200852b6..9d45b884ab 100644 --- a/packages/tui/src/routes/session/form.tsx +++ b/packages/tui/src/routes/session/form.tsx @@ -67,6 +67,7 @@ export function FormPrompt(props: { form: FormWithLocation }) { }) let textarea: TextareaRenderable | undefined + let editingReady = false let review: ScrollBoxRenderable | undefined const message = createMemo(() => { @@ -178,13 +179,16 @@ export function FormPrompt(props: { form: FormWithLocation }) { onCleanup( keymap.intercept("key", ({ event, consume }) => { if (keymap.mode.current() !== FORM_MODE) return - if (store.editing || textual() || !other()) return + if (textual() || !other() || (store.editing && editingReady)) return if (event.ctrl || event.meta || event.option || event.super || event.hyper) return if (!/^[^\p{C}\p{Zl}\p{Zp}]$/u.test(event.sequence)) return const current = answerField() if (!current) return setStore("custom", { ...store.custom, [current.key]: input() + event.sequence }) - setStore("editing", true) + if (!store.editing) { + editingReady = false + setStore("editing", true) + } consume() }), ) @@ -884,8 +888,10 @@ export function FormPrompt(props: { form: FormWithLocation }) { textarea = val val.traits = { status: "ANSWER" } queueMicrotask(() => { + val.setText(input()) val.focus() val.gotoLineEnd() + editingReady = true }) }} initialValue={input()} diff --git a/packages/tui/test/cli/tui/form.test.tsx b/packages/tui/test/cli/tui/form.test.tsx index 87795a0a28..9123548f34 100644 --- a/packages/tui/test/cli/tui/form.test.tsx +++ b/packages/tui/test/cli/tui/form.test.tsx @@ -148,9 +148,9 @@ test("typing starts a highlighted custom answer without losing the first charact expect(prompt.app.renderer.currentFocusedEditor).toBeNull() prompt.app.mockInput.pressKey("j") - prompt.app.mockInput.pressKey("1") - await prompt.app.waitFor(() => prompt.app.renderer.currentFocusedEditor?.plainText === "1") - expect(prompt.app.renderer.currentFocusedEditor?.plainText).toBe("1") + await prompt.app.mockInput.typeText("123") + await prompt.app.waitFor(() => prompt.app.renderer.currentFocusedEditor?.plainText === "123") + expect(prompt.app.renderer.currentFocusedEditor?.plainText).toBe("123") } finally { prompt.app.renderer.destroy() } diff --git a/packages/tui/test/mini/footer.view.test.tsx b/packages/tui/test/mini/footer.view.test.tsx index 713d945892..641a64a861 100644 --- a/packages/tui/test/mini/footer.view.test.tsx +++ b/packages/tui/test/mini/footer.view.test.tsx @@ -300,9 +300,9 @@ test("direct footer typing starts a highlighted custom answer without losing the expect(app.renderer.currentFocusedEditor).toBeNull() app.mockInput.pressKey("j") - app.mockInput.pressKey("h") - await app.waitFor(() => app.renderer.currentFocusedEditor?.plainText === "h") - expect(app.renderer.currentFocusedEditor?.plainText).toBe("h") + await app.mockInput.typeText("hello") + await app.waitFor(() => app.renderer.currentFocusedEditor?.plainText === "hello") + expect(app.renderer.currentFocusedEditor?.plainText).toBe("hello") } finally { app.cleanup() }