diff --git a/apps/web/src/components/chat/MessagesTimeline.logic.ts b/apps/web/src/components/chat/MessagesTimeline.logic.ts
index a87531673506..24970f8a4245 100644
--- a/apps/web/src/components/chat/MessagesTimeline.logic.ts
+++ b/apps/web/src/components/chat/MessagesTimeline.logic.ts
@@ -37,11 +37,19 @@ const TIMELINE_MINIMAP_MAX_HEIGHT_CSS = "calc(100vh - 18rem)";
const TIMELINE_CONTENT_MAX_WIDTH = 768;
const TIMELINE_MINIMAP_PERSISTENT_GUTTER = 48;
+/**
+ * Returns the collapsed label for a completed tool call, summarizing multiline
+ * commands and commands longer than 120 characters to keep the timeline compact.
+ */
function singleToolCallLabel(entry: WorkLogEntry): string {
const toolPresentation = resolveWorkEntryToolPresentation(entry, "completed");
if (toolPresentation) return toolPresentation.displayName;
const command = entry.command?.trim();
- if (command) return command;
+ if (command) {
+ return /[\r\n]/.test(command) || command.length > 120
+ ? liveWorkEntryLabel(entry, undefined, false)
+ : command;
+ }
const heading = normalizeCompactToolLabel(entry.toolTitle || entry.label);
return `${heading.charAt(0).toUpperCase()}${heading.slice(1)}`;
}
diff --git a/apps/web/src/components/chat/MessagesTimeline.test.tsx b/apps/web/src/components/chat/MessagesTimeline.test.tsx
index 655156048c1d..69fb7b30fa7d 100644
--- a/apps/web/src/components/chat/MessagesTimeline.test.tsx
+++ b/apps/web/src/components/chat/MessagesTimeline.test.tsx
@@ -1267,6 +1267,65 @@ describe("MessagesTimeline", () => {
expect(markup).not.toContain("C:/Users/mike/dev-stuff/t3code/apps/web/src/session-logic.ts");
});
+ it.each([
+ { command: "python3 - <<'PY'\nprint('verification complete')\nPY", detail: undefined },
+ { command: "python3 - <<'PY'\nprint('verification complete')\nPY", detail: "" },
+ {
+ command: "python3 - <<'PY'\r\nprint('verification complete')\r\nPY",
+ detail: "Script finished successfully",
+ },
+ { command: `python3 -c "print('${"x".repeat(120)}')"`, detail: undefined },
+ ])(
+ "keeps a long completed command compact and expandable with output $detail",
+ async ({ command, detail }) => {
+ vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
+ vi.stubGlobal("requestAnimationFrame", () => 0);
+ vi.stubGlobal("cancelAnimationFrame", () => {});
+ let renderer: ReactTestRenderer | undefined;
+ try {
+ await act(() => {
+ renderer = create(
+ ,
+ );
+ });
+ const toggle = renderer!.root.findAllByProps({
+ role: "button",
+ "aria-label": "Ran python3",
+ });
+ expect(toggle).toHaveLength(1);
+ expect(JSON.stringify(renderer!.toJSON())).not.toContain("print('");
+ await act(() => toggle[0]!.props.onClick());
+ const expandedText = renderer!.root
+ .findAllByType("pre")
+ .map((node) => node.children.join(""))
+ .join("\n");
+ expect(expandedText).toContain(command);
+ if (detail) expect(expandedText).toContain(detail);
+ } finally {
+ await act(() => renderer?.unmount());
+ }
+ },
+ );
+
it("keeps mixed-success tool groups neutral", () => {
const markup = renderToStaticMarkup(