From 540a3e89ed15a78129ff6bc91f561abddba8f6d8 Mon Sep 17 00:00:00 2001 From: Alex Southwell Date: Tue, 13 Oct 2026 15:17:52 +1100 Subject: [PATCH] perf(web): collect active tool calls without array shifts (#18853) Co-authored-by: SAPHID Co-authored-by: GPT-6.1 Sol --- .../MessagesTimeline.active-tools.test.ts | 51 +++++++++++++++++++ .../components/chat/MessagesTimeline.logic.ts | 3 +- 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 apps/web/src/components/chat/MessagesTimeline.active-tools.test.ts diff --git a/apps/web/src/components/chat/MessagesTimeline.active-tools.test.ts b/apps/web/src/components/chat/MessagesTimeline.active-tools.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..b689f11c66016e0dbca062e891d2417d122ab36c --- /dev/null +++ b/apps/web/src/components/chat/MessagesTimeline.active-tools.test.ts @@ -0,0 +1,51 @@ +import { RunId } from "@t3tools/contracts"; +import { describe, expect, it } from "vite-plus/test"; +import { deriveMessagesTimelineRows, liveWorkEntryLabel } from "./MessagesTimeline.logic"; + +describe("long active tool groups", () => { + it.each([false, true])( + "preserves chronological order and the latest activity, expanded=%s", + (expanded) => { + const runId = RunId.make("long-active-run"); + const createdAt = "2026-10-12T00:00:00Z"; + const timelineEntries = Array.from({ length: 1_000 }, (_, index) => ({ + id: `work-entry-${index}`, + kind: "work" as const, + createdAt, + entry: { + id: `work-${index}`, + createdAt, + runId, + label: `Tool ${index}`, + tone: index % 2 === 0 ? ("thinking" as const) : ("tool" as const), + itemType: index % 2 === 0 ? ("reasoning" as const) : ("command_execution" as const), + ...(index % 2 === 0 ? { detail: `Thought ${index}` } : {}), + toolLifecycleStatus: index === 999 ? ("inProgress" as const) : ("completed" as const), + }, + })); + const rows = deriveMessagesTimelineRows({ + timelineEntries, + isWorking: true, + runningRunId: runId, + activeTurnStartedAt: createdAt, + turnDiffSummaries: [], + supportsConversationRollback: false, + expandedWorkGroupIds: new Set(expanded ? ["work-group:work-entry-0"] : []), + }); + const live = rows.find((row) => row.kind === "work-live"); + expect(live).toBeDefined(); + expect(live?.entry.id).toBe("work-999"); + expect(live && liveWorkEntryLabel(live.entry, undefined, live.active)).toContain("999"); + expect(live?.thought?.id).toBe("work-998"); + expect(live?.groupedEntries.map(({ id }) => id)).toEqual( + timelineEntries.map(({ entry }) => entry.id), + ); + expect(live?.groupId).toBe("work-group:work-entry-0"); + const details = rows.filter((row) => row.kind === "work"); + expect(details).toHaveLength(expanded ? 1 : 0); + if (expanded) { + expect(details[0]?.groupedEntries).toEqual(live?.groupedEntries); + } + }, + ); +}); diff --git a/apps/web/src/components/chat/MessagesTimeline.logic.ts b/apps/web/src/components/chat/MessagesTimeline.logic.ts index 6eeda65512c1673c5eb55725f184a616900ea2dc..17d54c8f74386e4b0fc55ae28f8ffc76d1cd2da0 100644 --- a/apps/web/src/components/chat/MessagesTimeline.logic.ts +++ b/apps/web/src/components/chat/MessagesTimeline.logic.ts @@ -1504,9 +1504,10 @@ export function deriveMessagesTimelineRows(input: { } else if ((entry.attempt?.id ?? null) !== tailAttemptId) { break; } - activeToolEntries.unshift(entry); + activeToolEntries.push(entry); } } + activeToolEntries.reverse(); const visibleActiveToolEntries = activeToolEntries.filter((entry) => workEntryIsVisibleInGroup(entry.entry, true), ); -- 2.54.0