From 86775c18f396a3052e431e038a035fc2e4c4f0a2 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Mon, 29 Jun 2026 22:14:34 +0200 Subject: [PATCH] fix(app): hide turn actions during active tool calls Tool rows can arrive before a running turn resumes text. Treat those rows as part of the active turn so copy/fork actions wait until the turn closes. --- packages/app/src/agent-stream/layout.test.ts | 18 ++++++++++++++++++ packages/app/src/agent-stream/layout.ts | 15 +++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/packages/app/src/agent-stream/layout.test.ts b/packages/app/src/agent-stream/layout.test.ts index 719fdb2b4..b93ef7acd 100644 --- a/packages/app/src/agent-stream/layout.test.ts +++ b/packages/app/src/agent-stream/layout.test.ts @@ -352,4 +352,22 @@ describe("layoutStream", () => { expect(footerOwners(layout)).toEqual([assistant.id]); }, ); + + it.each(["web", "android"] as const)( + "does not render a completed footer before tool rows while the turn is running on %s", + (platform) => { + const assistant = assistantMessage("a1", 2); + const tool = toolCall("tool-1", 3); + const layout = layoutFor({ + platform, + agentStatus: "running", + tail: [userMessage("u1", 1), assistant, tool], + timingIds: [assistant.id], + }); + + expect(layout.auxiliaryTurnFooter).toBeNull(); + expect(findLayoutItem(layout, assistant.id).completedFooter).toBeNull(); + expect(footerOwners(layout)).toEqual([]); + }, + ); }); diff --git a/packages/app/src/agent-stream/layout.ts b/packages/app/src/agent-stream/layout.ts index 0f5c4c97e..26ac70830 100644 --- a/packages/app/src/agent-stream/layout.ts +++ b/packages/app/src/agent-stream/layout.ts @@ -150,6 +150,8 @@ function shouldRenderCompletedFooter(input: { belowItem: StreamItem | null; agentStatus: string; auxiliaryTurnFooter: TurnFooterHost | null; + boundaryIndex: number | null; + boundaryBelowItem: StreamItem | null; }): boolean { if ( input.item.kind !== "assistant_message" || @@ -179,6 +181,17 @@ function shouldRenderCompletedFooter(input: { items: input.items, startIndex: input.index, }); + const belowTurnItem = getSegmentNeighbor({ + strategy: input.strategy, + items: input.items, + index: turnEndIndex, + relation: "below", + boundaryIndex: input.boundaryIndex, + boundaryItem: input.boundaryBelowItem, + }); + if (input.agentStatus === "running" && belowTurnItem?.kind !== "user_message") { + return false; + } const assistantIndex = findLatestAssistantIndexInTurn({ strategy: input.strategy, items: input.items, @@ -265,6 +278,8 @@ function layoutSegment(input: LayoutSegmentInput): StreamLayoutItem[] { belowItem, agentStatus: input.agentStatus, auxiliaryTurnFooter: input.auxiliaryTurnFooter, + boundaryIndex: input.boundaryIndex, + boundaryBelowItem: input.boundaryBelowItem, }) ? createTurnFooterHost({ item,