diff --git a/apps/web/src/components/chat/MessagesTimeline.test.tsx b/apps/web/src/components/chat/MessagesTimeline.test.tsx
index 655156048c1d..c47402f4204b 100644
--- a/apps/web/src/components/chat/MessagesTimeline.test.tsx
+++ b/apps/web/src/components/chat/MessagesTimeline.test.tsx
@@ -1764,4 +1764,53 @@ describe("MessagesTimeline", () => {
expect(markup).toContain("lucide-circle-alert");
expect(markup).toContain("text-destructive");
});
+
+ it("only withholds an expanded tool-call label click while text is selected", async () => {
+ vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
+ vi.stubGlobal("requestAnimationFrame", () => 0);
+ vi.stubGlobal("cancelAnimationFrame", () => {});
+ let renderer: ReactTestRenderer | undefined;
+ try {
+ await act(() => {
+ renderer = create(
+ ,
+ );
+ });
+ await act(() => renderer!.root.findByProps({ "aria-expanded": false }).props.onClick());
+ const label = renderer!.root.findAll(
+ (node) => node.type === "span" && String(node.props.className).includes("select-text"),
+ )[0];
+ const stopPropagation = vi.fn();
+ // Only the click that ends a selection may be withheld from the row
+ // toggle; the plain click has to reach it so the label can collapse.
+ for (const isCollapsed of [false, true]) {
+ label!.props.onClick({
+ currentTarget: { ownerDocument: { getSelection: () => ({ isCollapsed }) } },
+ stopPropagation,
+ });
+ }
+ expect(stopPropagation).toHaveBeenCalledTimes(1);
+ } finally {
+ await act(() => renderer?.unmount());
+ }
+ });
});
diff --git a/apps/web/src/components/chat/MessagesTimeline.tsx b/apps/web/src/components/chat/MessagesTimeline.tsx
index 8e9635f89e43..52ed8bfaf803 100644
--- a/apps/web/src/components/chat/MessagesTimeline.tsx
+++ b/apps/web/src/components/chat/MessagesTimeline.tsx
@@ -3172,6 +3172,18 @@ function workEntryIconName(workEntry: TimelineWorkEntry): WorkEntryIconName {
const stopRowToggle = (e: { stopPropagation: () => void }) => e.stopPropagation();
+/**
+ * Click handler for expanded row labels, which turn text selection back on.
+ * Only a click that ends a real selection is withheld from the row toggle, so
+ * an ordinary click on the label still bubbles and collapses the row it opened.
+ */
+const stopRowToggleWhileSelectingText = (e: MouseEvent) => {
+ const selection = e.currentTarget.ownerDocument.getSelection();
+ if (selection && !selection.isCollapsed) {
+ e.stopPropagation();
+ }
+};
+
/**
* A1 spawn CTA: one anchored row per workflow run (or per-turn direct-spawn
* batch). Live status is derived from the shared agent panel model at render
@@ -3407,7 +3419,7 @@ const PlainWorkEntryRow = memo(function PlainWorkEntryRow(props: {
expanded ? "whitespace-pre-wrap break-words select-text" : "truncate",
headingClass,
)}
- onClick={expanded ? stopRowToggle : undefined}
+ onClick={expanded ? stopRowToggleWhileSelectingText : undefined}
onPointerDown={expanded ? stopRowToggle : undefined}
>
{previewText}