fix(app): keep grouped reasoning closed when Expand thinking is off (#8042)

Thoughts are grouped with tool calls only while Expand thinking is off,
yet opening a group rendered each thought with `defaultExpanded`, so the
reasoning text popped open against the setting. Grouped thoughts now open
like any other call: a closed Thinking row the reader clicks.

The folded history also treated the latest retained turn as live whenever
the agent was active without a known turn id, which unfolded the previous
turn until the new turn's rows arrived. Without head rows, the latest turn
stays live only while the active run is that turn.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Mohamed Boudra
2026-10-27 22:34:13 +07:00
committed by GitHub
co-authored by Claude
parent 78f580f9e8
commit 25721c7847
4 changed files with 49 additions and 10 deletions
+14 -1
View File
@@ -475,7 +475,20 @@ async function expectThinkingPresentation(page: Page, autoExpandReasoning: boole
await expect(group).toContainText(/read 1 file/i);
await expect(group.getByRole("button").first()).not.toContainText(/thought/i);
await group.getByRole("button").first().click();
await expect(reasoning).toBeVisible();
// Expand thinking is off: the group opens its rows, but the thought row stays closed
// until the reader opens it.
const thought = group
.getByTestId("tool-call-badge")
.filter({ hasText: /^Thinking/ })
.first();
await expect(thought).toBeVisible();
await expect(reasoning).toHaveCount(0);
// A press right after the group opens occasionally does not register; retry until the
// thought row opens.
await expect(async () => {
await thought.getByRole("button").first().click();
await expect(reasoning).toBeVisible({ timeout: 2_000 });
}).toPass();
}
}
@@ -702,6 +702,28 @@ it("does not reopen the previous turn while a new run waits for its first row",
expect(result.tail.map((item) => item.id)).toEqual(["prompt", "reply"]);
});
// A sent prompt counts as activity before the daemon opens its turn, so the only known turn is
// the completed one; it must stay folded until the new turn's rows arrive.
it("keeps the previous turn folded while a sent prompt waits for its turn to open", () => {
const timestamp = new Date();
const tail: StreamItem[] = [
{ kind: "user_message", id: "prompt", text: "Work", timestamp },
toolCall("one", "Bash"),
{ kind: "assistant_message", id: "reply", text: "Done", timestamp },
];
for (const item of tail) item.turnId = "completed";
const result = createStreamPresentation()({
...presentationOptions,
transform: undefined,
tail,
head: [],
display: "collapseTurns",
isTurnActive: true,
activeTurnId: null,
});
expect(result.tail.map((item) => item.id)).toEqual(["prompt", "reply"]);
});
it("groups reasoning with tools unless Expand thinking is enabled, including setting changes", () => {
const present = createStreamPresentation();
const timestamp = new Date();
+10 -8
View File
@@ -113,14 +113,16 @@ export function createStreamPresentation() {
}
// Live text updates reach the head only, so the folded history is reused until the
// retained rows, the latest turn's liveness, or the reader's expanded turns change.
const isLatestTurnOpen =
(input.isTurnActive || projection.head.length > 0) &&
!isTurnBoundary(projection.tail.at(-1) ?? null, projection.head[0] ?? null) &&
!(
input.activeTurnId &&
projection.tail.at(-1)?.turnId &&
input.activeTurnId !== projection.tail.at(-1)?.turnId
);
const last = projection.tail.at(-1) ?? null;
const first = projection.head[0] ?? null;
const activeTurnIsAnother = Boolean(
input.activeTurnId && last?.turnId && input.activeTurnId !== last.turnId,
);
// Without head rows, the latest retained turn is live only while the active run is that
// turn. A run that has not opened yet carries no id and belongs to the next turn.
const isLatestTurnOpen = first
? !isTurnBoundary(last, first) && !activeTurnIsAnother
: input.isTurnActive && (last?.turnId === undefined || input.activeTurnId === last.turnId);
if (
!collapsed ||
collapsedSource !== projection.tail ||
+3 -1
View File
@@ -898,12 +898,14 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
? group.run.calls.map((call, index) => (
<React.Fragment key={call.id}>
{call.kind === "thought" ? (
// Reasoning is grouped only while Expand thinking is off, so an opened
// group shows each thought as a closed row like any other call.
<ThoughtSlot
itemId={call.id}
text={call.text}
status={call.status}
isLastInSequence={index === group.run.calls.length - 1}
defaultExpanded
defaultExpanded={false}
onInlineDetailsExpandedChangeByItemId={setInlineDetailsExpanded}
/>
) : (