From dde642bc1b2118b194302ff29e1fb5bc4ed7de06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pascal=20Andr=C3=A9?= Date: Tue, 25 Aug 2026 22:56:16 +0200 Subject: [PATCH] fix(v2): keep queued prompts and forms stable Render pending inbox prompts in their existing message bubbles with queue, steer, edit, and remove actions instead of maintaining a separate queue panel. Keep duplicate form sync results from replacing the active form object, preventing local field state from resetting and avoiding the resulting virtual timeline height correction. Validated with UI and server typechecks, focused form, projection, shortcut, bottom-pin, and virtual-follow tests, plus the production UI build. --- packages/ui/src/components/message-block.tsx | 21 +++++ packages/ui/src/components/message-item.tsx | 79 ++++++++++++++++- .../ui/src/components/message-section.tsx | 17 ++-- packages/ui/src/components/prompt-queue.tsx | 76 ---------------- .../src/components/session/session-view.tsx | 25 ++---- packages/ui/src/components/tool-call.tsx | 8 +- packages/ui/src/stores/forms.test.ts | 14 ++- packages/ui/src/stores/forms.ts | 4 +- packages/ui/src/styles/messaging.css | 1 - .../ui/src/styles/messaging/prompt-queue.css | 88 ------------------- 10 files changed, 136 insertions(+), 197 deletions(-) delete mode 100644 packages/ui/src/components/prompt-queue.tsx delete mode 100644 packages/ui/src/styles/messaging/prompt-queue.css diff --git a/packages/ui/src/components/message-block.tsx b/packages/ui/src/components/message-block.tsx index 3800eff7..829fcf4c 100644 --- a/packages/ui/src/components/message-block.tsx +++ b/packages/ui/src/components/message-block.tsx @@ -1,6 +1,7 @@ import { For, Index, Match, Show, Suspense, Switch, createEffect, createMemo, createSignal, lazy, onCleanup, untrack, type Accessor } from "solid-js" import { Copy, ExternalLink, FoldVertical, Loader2, Volume2, XCircle } from "lucide-solid" import MessageItem from "./message-item" +import type { SessionInboxUser } from "@opencode-ai/client" import type { InstanceMessageStore } from "../stores/message-v2/instance-store" import type { ClientPart, Message, MessageInfo, TextPart } from "../types/message" import { isHiddenSyntheticTextPart, partHasRenderableText } from "../types/message" @@ -279,6 +280,11 @@ interface MessageContentItemProps { messageIndex: number onRevert?: (messageId: string) => void onFork?: (messageId?: string) => void + pendingPrompt?: SessionInboxUser + pendingPromptBusy?: boolean + onPendingPromptDeliveryChange?: (item: SessionInboxUser) => void + onPendingPromptEdit?: (item: SessionInboxUser) => void + onPendingPromptRemove?: (item: SessionInboxUser) => void onContentRendered?: () => void } @@ -367,6 +373,11 @@ function MessageContentItem(props: MessageContentItemProps) { showAgentMeta={showAgentMeta()} onRevert={props.onRevert} onFork={props.onFork} + pendingPrompt={props.pendingPrompt} + pendingPromptBusy={props.pendingPromptBusy} + onPendingPromptDeliveryChange={props.onPendingPromptDeliveryChange} + onPendingPromptEdit={props.onPendingPromptEdit} + onPendingPromptRemove={props.onPendingPromptRemove} onContentRendered={props.onContentRendered} /> @@ -506,6 +517,11 @@ interface MessageBlockProps { toolVisibility: (toolName: string) => VisibilityPreference onRevert?: (messageId: string) => void onFork?: (messageId?: string) => void + pendingPrompt?: SessionInboxUser + pendingPromptBusy?: boolean + onPendingPromptDeliveryChange?: (item: SessionInboxUser) => void + onPendingPromptEdit?: (item: SessionInboxUser) => void + onPendingPromptRemove?: (item: SessionInboxUser) => void onContentRendered?: () => void searchQuery?: Accessor searchResultMessageIds?: Accessor> @@ -768,6 +784,11 @@ export default function MessageBlock(props: MessageBlockProps) { messageIndex={props.messageIndex} onRevert={props.onRevert} onFork={props.onFork} + pendingPrompt={props.pendingPrompt} + pendingPromptBusy={props.pendingPromptBusy} + onPendingPromptDeliveryChange={props.onPendingPromptDeliveryChange} + onPendingPromptEdit={props.onPendingPromptEdit} + onPendingPromptRemove={props.onPendingPromptRemove} onContentRendered={props.onContentRendered} /> diff --git a/packages/ui/src/components/message-item.tsx b/packages/ui/src/components/message-item.tsx index cd42d939..00e09b95 100644 --- a/packages/ui/src/components/message-item.tsx +++ b/packages/ui/src/components/message-item.tsx @@ -1,6 +1,7 @@ import { For, Show, createEffect, createSignal, onCleanup } from "solid-js" import { Portal } from "solid-js/web" -import { Copy, Split, Undo, Volume2 } from "lucide-solid" +import { Copy, Pencil, Play, Split, Trash2, Undo, Volume2 } from "lucide-solid" +import type { SessionInboxUser } from "@opencode-ai/client" import type { MessageInfo, ClientPart } from "../types/message" import { isHiddenSyntheticTextPart, partHasRenderableText } from "../types/message" import type { MessageRecord } from "../stores/message-v2/types" @@ -22,6 +23,11 @@ interface MessageItemProps { parts: ClientPart[] onRevert?: (messageId: string) => void onFork?: (messageId?: string) => void + pendingPrompt?: SessionInboxUser + pendingPromptBusy?: boolean + onPendingPromptDeliveryChange?: (item: SessionInboxUser) => void + onPendingPromptEdit?: (item: SessionInboxUser) => void + onPendingPromptRemove?: (item: SessionInboxUser) => void showAgentMeta?: boolean contentStartPartId?: string onContentRendered?: () => void @@ -388,9 +394,35 @@ export default function MessageItem(props: MessageItemProps) { onSelect: () => void speech.toggle(), }) } + + const pending = props.pendingPrompt + if (pending) { + items.push( + { + key: "pending-delivery", + label: t(`promptQueue.actions.${pending.delivery === "queue" ? "steer" : "queue"}`), + icon: pending.delivery === "queue" + ?