From 3ee5ca72f8eae9ed443e29007a72c3213d6f2152 Mon Sep 17 00:00:00 2001 From: Guilhem Lemouel Date: Thu, 1 Oct 2026 09:10:00 -0700 Subject: [PATCH] fix(sessions): tell a mounting composer apart from a busy one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The retry that waits for a chat flow's composer could not tell "not there yet" from "the reader is writing": both read as a declined offer, so the proposal stayed pending and would have landed in the box whenever the composer next changed — words appearing long after they were proposed. `composerReady` answers the first question on its own; past it the proposal is settled either way, and a reader's draft is left alone. Co-Authored-By: Claude Opus 5 (1M context) --- .../lib/components/details/FlowDetail.svelte | 23 +++++++++++-------- .../flows/conversations/FlowChat.svelte | 8 +++++++ .../conversations/flowChatViewHost.svelte.ts | 7 ++++++ 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/frontend/src/lib/components/details/FlowDetail.svelte b/frontend/src/lib/components/details/FlowDetail.svelte index 4d3e834086..35f168c580 100644 --- a/frontend/src/lib/components/details/FlowDetail.svelte +++ b/frontend/src/lib/components/details/FlowDetail.svelte @@ -381,16 +381,21 @@ seededSeq = seededRun.seq return } - // The rest of the proposal goes with it: a chat flow's schema can declare more than - // `user_message`, and sending the message alone would run on saved values or schema - // defaults — a different run from the one being offered. + // The composer mounts a flush or two after the panel does, so until it is there this + // effect waits — it reads the host's registration through `composerReady`, and runs + // again when that lands. Not latching is what keeps the proposal alive across those + // flushes. + if (!flowChat?.composerReady()) return + // Past that point the answer is final either way. The rest of the proposal goes with + // the message: a chat flow's schema can declare more than `user_message`, and sending + // the message alone would run on saved values or schema defaults — a different run + // from the one being offered. A composer already holding a draft declines, and the + // proposal is dropped rather than held: injecting it whenever the reader happens to + // clear their draft would put words in the box long after they were proposed. The + // card still shows what was asked for. const { user_message: _m, ...inputs } = seededRun.args ?? {} - // Only on success. The composer mounts a flush or two after the panel, and a - // declined offer here means it is not there yet — this effect tracks the host's - // input through `offerMessage` and runs again when it registers. A composer that - // already holds a draft also declines, and re-offering on its next change is right: - // the reader may clear it, and then the message lands. - if (flowChat?.offerMessage(message, inputs)) seededSeq = seededRun.seq + seededSeq = seededRun.seq + flowChat.offerMessage(message, inputs) } else if (runForm) { seededSeq = seededRun.seq runForm.setArgs(seededRun.args) diff --git a/frontend/src/lib/components/flows/conversations/FlowChat.svelte b/frontend/src/lib/components/flows/conversations/FlowChat.svelte index e73c9fa94d..5820e4bef4 100644 --- a/frontend/src/lib/components/flows/conversations/FlowChat.svelte +++ b/frontend/src/lib/components/flows/conversations/FlowChat.svelte @@ -193,6 +193,14 @@ return true } + /** Whether the conversation on screen has a composer to offer a message to. A caller that + * gets `false` from `offerMessage` reads this to tell "mounting, try again" from "the + * reader is writing, leave them alone". */ + export function composerReady(): boolean { + const panel = shownKey ? pool?.get(shownKey) : undefined + return panel?.host.composerReady ?? false + } + // Derive additional inputs schema (excluding user_message) for chat mode const additionalInputsSchema = $derived.by(() => { const props = inputSchema?.properties ?? {} diff --git a/frontend/src/lib/components/flows/conversations/flowChatViewHost.svelte.ts b/frontend/src/lib/components/flows/conversations/flowChatViewHost.svelte.ts index ac62039769..4efa9c8c40 100644 --- a/frontend/src/lib/components/flows/conversations/flowChatViewHost.svelte.ts +++ b/frontend/src/lib/components/flows/conversations/flowChatViewHost.svelte.ts @@ -582,6 +582,13 @@ export class FlowChatViewHost implements ChatViewHost, DraftSender