From c6a514af20f8d6a63a06b6971ace9a7900be67d6 Mon Sep 17 00:00:00 2001 From: Zortos Date: Sun, 15 Mar 2026 14:27:55 +0100 Subject: [PATCH] fix(web): keep prior tool calls visible in thread timeline --- .../web/src/components/ChatView.logic.test.ts | 52 +++++++++++++++++++ apps/web/src/components/ChatView.logic.ts | 14 ++++- apps/web/src/components/ChatView.tsx | 6 +-- 3 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 apps/web/src/components/ChatView.logic.test.ts diff --git a/apps/web/src/components/ChatView.logic.test.ts b/apps/web/src/components/ChatView.logic.test.ts new file mode 100644 index 000000000..50dd3f8a0 --- /dev/null +++ b/apps/web/src/components/ChatView.logic.test.ts @@ -0,0 +1,52 @@ +import { EventId, TurnId, type OrchestrationThreadActivity } from "@t3tools/contracts"; +import { describe, expect, it } from "vitest"; + +import { deriveVisibleThreadWorkLogEntries } from "./ChatView.logic"; + +function makeActivity(overrides: { + id?: string; + createdAt?: string; + kind?: string; + summary?: string; + tone?: OrchestrationThreadActivity["tone"]; + payload?: Record; + turnId?: string; + sequence?: number; +}): OrchestrationThreadActivity { + return { + id: EventId.makeUnsafe(overrides.id ?? crypto.randomUUID()), + createdAt: overrides.createdAt ?? "2026-02-23T00:00:00.000Z", + kind: overrides.kind ?? "tool.started", + summary: overrides.summary ?? "Tool call", + tone: overrides.tone ?? "tool", + payload: overrides.payload ?? {}, + turnId: overrides.turnId ? TurnId.makeUnsafe(overrides.turnId) : null, + ...(overrides.sequence !== undefined ? { sequence: overrides.sequence } : {}), + }; +} + +describe("deriveVisibleThreadWorkLogEntries", () => { + it("keeps completed tool calls from previous turns visible in the thread timeline", () => { + const activities: OrchestrationThreadActivity[] = [ + makeActivity({ + id: "tool-1", + createdAt: "2026-02-23T00:00:01.000Z", + turnId: "turn-1", + kind: "tool.completed", + summary: "First tool call", + }), + makeActivity({ + id: "tool-2", + createdAt: "2026-02-23T00:00:03.000Z", + turnId: "turn-2", + kind: "tool.completed", + summary: "Second tool call", + }), + ]; + + expect(deriveVisibleThreadWorkLogEntries(activities).map((entry) => entry.id)).toEqual([ + "tool-1", + "tool-2", + ]); + }); +}); diff --git a/apps/web/src/components/ChatView.logic.ts b/apps/web/src/components/ChatView.logic.ts index 59e290431..ba30e3927 100644 --- a/apps/web/src/components/ChatView.logic.ts +++ b/apps/web/src/components/ChatView.logic.ts @@ -1,9 +1,15 @@ -import { ProjectId, type ProviderKind, type ThreadId } from "@t3tools/contracts"; +import { + ProjectId, + type OrchestrationThreadActivity, + type ProviderKind, + type ThreadId, +} from "@t3tools/contracts"; import { type ChatMessage, type Thread } from "../types"; import { randomUUID } from "~/lib/utils"; import { getAppModelOptions } from "../appSettings"; import { type ComposerImageAttachment, type DraftThreadState } from "../composerDraftStore"; import { Schema } from "effect"; +import { deriveWorkLogEntries, type WorkLogEntry } from "../session-logic"; export const LAST_INVOKED_SCRIPT_BY_PROJECT_KEY = "t3code:last-invoked-script-by-project"; const WORKTREE_BRANCH_PREFIX = "t3code"; @@ -123,3 +129,9 @@ export function getCustomModelOptionsByProvider(settings: { codex: getAppModelOptions("codex", settings.customCodexModels), }; } + +export function deriveVisibleThreadWorkLogEntries( + activities: ReadonlyArray, +): WorkLogEntry[] { + return deriveWorkLogEntries(activities, undefined); +} diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 52637695e..ad5250519 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -54,7 +54,6 @@ import { deriveActiveWorkStartedAt, deriveActivePlanState, findLatestProposedPlan, - deriveWorkLogEntries, hasToolActivityForTurn, isLatestTurnSettled, formatElapsed, @@ -149,6 +148,7 @@ import { buildTemporaryWorktreeBranchName, cloneComposerImageForRetry, collectUserMessageBlobPreviewUrls, + deriveVisibleThreadWorkLogEntries, getCustomModelOptionsByProvider, LAST_INVOKED_SCRIPT_BY_PROJECT_KEY, LastInvokedScriptByProjectSchema, @@ -578,8 +578,8 @@ export default function ChatView({ threadId }: ChatViewProps) { ); const threadActivities = activeThread?.activities ?? EMPTY_ACTIVITIES; const workLogEntries = useMemo( - () => deriveWorkLogEntries(threadActivities, activeLatestTurn?.turnId ?? undefined), - [activeLatestTurn?.turnId, threadActivities], + () => deriveVisibleThreadWorkLogEntries(threadActivities), + [threadActivities], ); const latestTurnHasToolActivity = useMemo( () => hasToolActivityForTurn(threadActivities, activeLatestTurn?.turnId),