diff --git a/apps/fabro-web/app/routes/run-stages-chat.test.tsx b/apps/fabro-web/app/routes/run-stages-chat.test.tsx
index 9ca84db99..be86b89fd 100644
--- a/apps/fabro-web/app/routes/run-stages-chat.test.tsx
+++ b/apps/fabro-web/app/routes/run-stages-chat.test.tsx
@@ -52,6 +52,58 @@ describe("StageChatView", () => {
expect(html).toContain("1m 12s");
});
+ test("renders no node for a text-free assistant turn between tool batches", () => {
+ const html = renderToStaticMarkup(
+ ,
+ );
+
+ // The boundary keeps the two batches as separate chips, but contributes
+ // no element of its own between them.
+ expect(html.match(/1 tool call/g)).toHaveLength(2);
+ expect(html).not.toContain('class="prose prose-sm max-w-none"');
+ expect(html.match(/class="prose /g)).toHaveLength(1);
+ expect(html).toContain("Done");
+ });
+
test("connects a long prompt's expand button to its controlled content", () => {
const html = renderToStaticMarkup(
);
case "assistant": {
+ const hasText = turn.content.trim().length > 0;
const metric = turnMetric(turn);
const isFinal =
turnIndex === lastAssistantTurnIndex && !stageActive;
+ const showFooter = isFinal && Boolean(metric || duration);
+ // A text-free assistant turn is the boundary between two batches
+ // of tool calls, kept in the turn stream so those batches stay
+ // separate chips. It has nothing to show, and an empty node would
+ // still take a slot in this gap-4 column, doubling the space
+ // between the chips on either side of it.
+ if (!hasText && !showFooter) return null;
return (