From 11243152679095f8d3cc6bab4ae0a79b72f85673 Mon Sep 17 00:00:00 2001 From: Simon Klee Date: Mon, 18 May 2026 14:30:20 +0200 Subject: [PATCH] run: refresh prompt layout after paste (#28164) Pasting into the prompt textarea left its layout stale until the next edit, so the visible content did not reflect the pasted text. Mark the layout dirty on paste and notify the content-change handler once the renderer is idle so the prompt updates immediately. --- .../src/cli/cmd/run/footer.prompt.tsx | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/cli/cmd/run/footer.prompt.tsx b/packages/opencode/src/cli/cmd/run/footer.prompt.tsx index 54f20dbc07..5addff5168 100644 --- a/packages/opencode/src/cli/cmd/run/footer.prompt.tsx +++ b/packages/opencode/src/cli/cmd/run/footer.prompt.tsx @@ -6,8 +6,15 @@ // while the footer view renders the current menu state below it. /** @jsxImportSource @opentui/solid */ import { pathToFileURL } from "bun" -import { StyledText, bg, fg, type KeyBinding, type KeyEvent, type TextareaRenderable } from "@opentui/core" -import { useKeyboard } from "@opentui/solid" +import { + StyledText, + bg, + fg, + type KeyBinding, + type KeyEvent, + type TextareaRenderable, +} from "@opentui/core" +import { useKeyboard, useRenderer } from "@opentui/solid" import fuzzysort from "fuzzysort" import path from "path" import { createEffect, createMemo, createResource, createSignal, onCleanup, onMount, type Accessor } from "solid-js" @@ -197,13 +204,45 @@ export function RunPromptBody(props: { onContentChange: () => void bind: (area?: TextareaRenderable) => void }) { + const renderer = useRenderer() let area: TextareaRenderable | undefined + let pasteTick: ReturnType | undefined + + const refreshPasteLayout = () => { + if (pasteTick) { + clearTimeout(pasteTick) + } + + pasteTick = setTimeout(() => { + pasteTick = undefined + if (!area || area.isDestroyed) { + return + } + + // Paste can leave the textarea layout stale until the next edit. + area.getLayoutNode().markDirty() + renderer.requestRender() + void renderer + .idle() + .then(() => { + if (!area || area.isDestroyed) { + return + } + + props.onContentChange() + }) + .catch(() => {}) + }, 0) + } onMount(() => { props.bind(area) }) onCleanup(() => { + if (pasteTick) { + clearTimeout(pasteTick) + } props.bind(undefined) }) @@ -226,6 +265,9 @@ export function RunPromptBody(props: { keyBindings={props.bindings()} onSubmit={props.onSubmit} onKeyDown={props.onKeyDown} + onPaste={() => { + refreshPasteLayout() + }} onContentChange={props.onContentChange} ref={(next) => { area = next