Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion apps/web/src/components/chat/MessagesTimeline.logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)}`;
}
Expand Down
59 changes: 59 additions & 0 deletions apps/web/src/components/chat/MessagesTimeline.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<MessagesTimeline
{...buildProps()}
timelineEntries={[
{
id: "completed-script",
kind: "work",
createdAt: MESSAGE_CREATED_AT,
entry: {
id: "completed-script-work",
createdAt: MESSAGE_CREATED_AT,
label: "Ran command",
tone: "tool",
itemType: "command_execution",
toolLifecycleStatus: "completed",
command,
...(detail === undefined ? {} : { detail }),
},
},
]}
/>,
);
});
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(
<MessagesTimeline
Expand Down
Loading