From 60769065a0cd0b662b358cd5976baa98c338e314 Mon Sep 17 00:00:00 2001 From: hiimchinh Date: Wed, 9 Sep 2026 12:25:03 +0700 Subject: [PATCH 1/2] fix(web): keep long completed commands compact and expandable --- .../components/chat/MessagesTimeline.logic.ts | 6 +- .../components/chat/MessagesTimeline.test.tsx | 59 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/apps/web/src/components/chat/MessagesTimeline.logic.ts b/apps/web/src/components/chat/MessagesTimeline.logic.ts index a87531673506..0dc95474987f 100644 --- a/apps/web/src/components/chat/MessagesTimeline.logic.ts +++ b/apps/web/src/components/chat/MessagesTimeline.logic.ts @@ -41,7 +41,11 @@ 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( Date: Wed, 9 Sep 2026 13:56:27 +0700 Subject: [PATCH 2/2] docs(web): explain compact completed command labels --- apps/web/src/components/chat/MessagesTimeline.logic.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/web/src/components/chat/MessagesTimeline.logic.ts b/apps/web/src/components/chat/MessagesTimeline.logic.ts index 0dc95474987f..24970f8a4245 100644 --- a/apps/web/src/components/chat/MessagesTimeline.logic.ts +++ b/apps/web/src/components/chat/MessagesTimeline.logic.ts @@ -37,6 +37,10 @@ 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;