import { describe, expect, it } from "vite-plus/test"; import { EventId, MessageId, ProjectId, ProviderInstanceId, ThreadId, TurnId, type OrchestrationThread, type OrchestrationThreadActivity, } from "@t3tools/contracts"; import { buildThreadFeed, deriveThreadFeedPresentation, type ThreadFeedActivity, type ThreadFeedEntry, } from "./threadActivity"; function makeActivity( input: Partial & Pick, ): OrchestrationThreadActivity { return { tone: "info", payload: {}, turnId: null, ...input, }; } function makeThread( input: Partial & Pick, ): OrchestrationThread { return { modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4" }, runtimeMode: "full-access", interactionMode: "default", branch: null, worktreePath: null, latestTurn: null, createdAt: "2026-04-01T00:00:00.000Z", updatedAt: "2026-04-01T00:00:00.000Z", archivedAt: null, deletedAt: null, messages: [], proposedPlans: [], activities: [], checkpoints: [], session: null, ...input, settledOverride: input.settledOverride ?? null, settledAt: input.settledAt ?? null, }; } describe("buildThreadFeed", () => { it("keeps historic work entries attributed to their turns", () => { const thread = makeThread({ id: ThreadId.make("thread-1"), projectId: ProjectId.make("project-1"), title: "Runtime warning thread", latestTurn: { turnId: TurnId.make("turn-latest"), state: "running", requestedAt: "2026-04-01T00:00:00.000Z", startedAt: "2026-04-01T00:00:01.000Z", completedAt: null, assistantMessageId: null, }, activities: [ makeActivity({ id: EventId.make("activity-old"), kind: "runtime.warning", summary: "Runtime warning", createdAt: "2026-04-01T00:00:02.000Z", turnId: TurnId.make("turn-old"), payload: { message: "Old warning", }, }), makeActivity({ id: EventId.make("activity-latest"), kind: "runtime.warning", summary: "Runtime warning", createdAt: "2026-04-01T00:00:03.000Z", turnId: TurnId.make("turn-latest"), payload: { message: "Latest warning", }, }), ], }); const feed = buildThreadFeed(thread); expect(feed).toMatchObject([ { type: "activity-group", turnId: "turn-old", activities: [{ id: "activity-old", turnId: "turn-old" }], }, { type: "activity-group", turnId: "turn-latest", activities: [{ id: "activity-latest", turnId: "turn-latest" }], }, ]); }); it("collapses matching tool lifecycle rows like desktop", () => { const thread = makeThread({ id: ThreadId.make("thread-2"), projectId: ProjectId.make("project-1"), title: "Collapsed tools", latestTurn: { turnId: TurnId.make("turn-1"), state: "completed", requestedAt: "2026-04-01T00:00:00.000Z", startedAt: "2026-04-01T00:00:01.000Z", completedAt: "2026-04-01T00:00:03.000Z", assistantMessageId: null, }, activities: [ makeActivity({ id: EventId.make("tool-updated"), kind: "tool.updated", tone: "tool", summary: "Run tests", createdAt: "2026-04-01T00:00:01.000Z", turnId: TurnId.make("turn-1"), payload: { title: "Run tests", itemType: "command_execution", detail: "/bin/zsh -lc 'bun run test'", }, }), makeActivity({ id: EventId.make("tool-completed"), kind: "tool.completed", tone: "tool", summary: "Run tests completed", createdAt: "2026-04-01T00:00:02.000Z", turnId: TurnId.make("turn-1"), payload: { title: "Run tests", itemType: "command_execution", detail: "/bin/zsh -lc 'bun run test'", }, }), ], }); const feed = buildThreadFeed(thread); const group = feed[0]; expect(group).toMatchObject({ type: "activity-group", }); if (!group || group.type !== "activity-group") { return; } expect(group.activities).toEqual([ { id: "tool-completed", createdAt: "2026-04-01T00:00:02.000Z", turnId: "turn-1", summary: "Run tests", detail: "bun run test", fullDetail: "/bin/zsh -lc 'bun run test'", copyText: "Run tests\nbun run test\n/bin/zsh -lc 'bun run test'", icon: "command", toolLike: true, status: "success", }, ]); }); it("keeps MCP inputs available to expanded mobile work rows", () => { const turnId = TurnId.make("turn-mcp"); const thread = makeThread({ id: ThreadId.make("thread-mcp"), projectId: ProjectId.make("project-1"), title: "Expandable MCP call", latestTurn: { turnId, state: "completed", requestedAt: "2026-04-01T00:00:00.000Z", startedAt: "2026-04-01T00:00:01.000Z", completedAt: "2026-04-01T00:00:03.000Z", assistantMessageId: null, }, activities: [ makeActivity({ id: EventId.make("mcp-completed"), kind: "tool.completed", tone: "tool", summary: "Call repository tool", createdAt: "2026-04-01T00:00:02.000Z", turnId, payload: { title: "Call repository tool", itemType: "mcp_tool_call", detail: "repository.search", status: "completed", data: { item: { server: "repository", tool: "search", arguments: { query: "work log" }, }, }, }, }), ], }); const group = buildThreadFeed(thread)[0]; expect(group).toMatchObject({ type: "activity-group" }); if (!group || group.type !== "activity-group") { return; } expect(group.activities[0]?.icon).toBe("wrench"); expect(group.activities[0]?.fullDetail).toContain('"query": "work log"'); expect(group.activities[0]?.fullDetail).toContain("repository.search"); }); it("folds settled turn work while leaving the terminal answer visible", () => { const turnId = TurnId.make("turn-1"); const thread = makeThread({ id: ThreadId.make("thread-3"), projectId: ProjectId.make("project-1"), title: "Folded work", latestTurn: { turnId, state: "completed", requestedAt: "2026-04-01T00:00:00.000Z", startedAt: "2026-04-01T00:00:01.000Z", completedAt: "2026-04-01T00:00:18.000Z", assistantMessageId: MessageId.make("assistant-final"), }, messages: [ { id: MessageId.make("assistant-commentary"), role: "assistant", text: "I am checking.", turnId, streaming: false, createdAt: "2026-04-01T00:00:02.000Z", updatedAt: "2026-04-01T00:00:03.000Z", }, { id: MessageId.make("assistant-final"), role: "assistant", text: "Done.", turnId, streaming: false, createdAt: "2026-04-01T00:00:17.000Z", updatedAt: "2026-04-01T00:00:18.000Z", }, ], activities: [ makeActivity({ id: EventId.make("tool-completed"), kind: "tool.completed", tone: "tool", summary: "Read files", createdAt: "2026-04-01T00:00:05.000Z", turnId, payload: { title: "Read files", itemType: "file_read", status: "completed", }, }), ], }); const feed = buildThreadFeed(thread); const collapsed = deriveThreadFeedPresentation(feed, thread.latestTurn, new Set()); expect(collapsed.map((entry) => entry.id)).toEqual(["turn-fold:turn-1", "assistant-final"]); expect(collapsed[0]).toMatchObject({ type: "turn-fold", label: "Worked for 17s", expanded: false, }); const expanded = deriveThreadFeedPresentation(feed, thread.latestTurn, new Set([turnId])); expect(expanded.map((entry) => entry.id)).toEqual([ "turn-fold:turn-1", "assistant-commentary", "tool-completed", "assistant-final", ]); }); it("measures a steer-superseded turn from its user boundary through trailing work", () => { const firstTurnId = TurnId.make("turn-1"); const secondTurnId = TurnId.make("turn-2"); const thread = makeThread({ id: ThreadId.make("thread-steered"), projectId: ProjectId.make("project-1"), title: "Steered work", latestTurn: { turnId: secondTurnId, state: "running", requestedAt: "2026-04-01T00:00:14.000Z", startedAt: "2026-04-01T00:00:14.000Z", completedAt: null, assistantMessageId: MessageId.make("assistant-next"), }, messages: [ { id: MessageId.make("user-1"), role: "user", text: "Do it once more.", turnId: null, streaming: false, createdAt: "2026-04-01T00:00:00.000Z", updatedAt: "2026-04-01T00:00:00.000Z", }, { id: MessageId.make("assistant-commentary"), role: "assistant", text: "Kicking off call 1.", turnId: firstTurnId, streaming: false, createdAt: "2026-04-01T00:00:09.000Z", updatedAt: "2026-04-01T00:00:09.000Z", }, { id: MessageId.make("user-2"), role: "user", text: "Actually do 15.", turnId: null, streaming: false, createdAt: "2026-04-01T00:00:14.000Z", updatedAt: "2026-04-01T00:00:14.000Z", }, { id: MessageId.make("assistant-next"), role: "assistant", text: "One down - adjusting.", turnId: secondTurnId, streaming: true, createdAt: "2026-04-01T00:00:17.000Z", updatedAt: "2026-04-01T00:00:17.000Z", }, ], activities: [ makeActivity({ id: EventId.make("work-1"), kind: "tool.completed", tone: "tool", summary: "Ran command", createdAt: "2026-04-01T00:00:12.000Z", turnId: firstTurnId, payload: { title: "Ran command", itemType: "command_execution", status: "completed", }, }), ], }); const feed = buildThreadFeed(thread); const collapsed = deriveThreadFeedPresentation(feed, thread.latestTurn, new Set()); expect(collapsed.find((entry) => entry.type === "turn-fold")).toMatchObject({ turnId: firstTurnId, label: "Worked for 12s", }); }); it("keeps an active turn expanded and classifies error-shaped tool output", () => { const turnId = TurnId.make("turn-running"); const thread = makeThread({ id: ThreadId.make("thread-4"), projectId: ProjectId.make("project-1"), title: "Running work", latestTurn: { turnId, state: "running", requestedAt: "2026-04-01T00:00:00.000Z", startedAt: "2026-04-01T00:00:01.000Z", completedAt: null, assistantMessageId: null, }, activities: [ makeActivity({ id: EventId.make("tool-failed"), kind: "tool.completed", tone: "tool", summary: "Run command", createdAt: "2026-04-01T00:00:05.000Z", turnId, payload: { title: "Run command", itemType: "command_execution", detail: "zsh: command not found: nope", status: "completed", }, }), ], }); const feed = buildThreadFeed(thread); expect(deriveThreadFeedPresentation(feed, thread.latestTurn, new Set())).toEqual(feed); expect(feed[0]).toMatchObject({ type: "activity-group", activities: [{ status: "failure" }], }); }); it("appends active work as a normal timeline row", () => { const startedAt = "2026-04-01T00:00:01.000Z"; const presented = deriveThreadFeedPresentation([], null, new Set(), new Set(), startedAt); expect(presented).toEqual([ { type: "working", id: "working-indicator-row", createdAt: startedAt, }, ]); expect(deriveThreadFeedPresentation(presented, null, new Set())).toEqual([]); }); it("models work-log overflow as list rows", () => { const activity = ( id: string, createdAt: string, status: ThreadFeedActivity["status"] = "success", ): ThreadFeedActivity => ({ id, createdAt, turnId: null, summary: `Tool ${id}`, detail: null, fullDetail: null, copyText: id, icon: "command", toolLike: true, status, }); const feed: ThreadFeedEntry[] = [ { type: "activity-group", id: "work-group-1", createdAt: "2026-04-01T00:00:01.000Z", turnId: null, activities: [ activity("activity-1", "2026-04-01T00:00:01.000Z"), activity("activity-neutral", "2026-04-01T00:00:02.000Z", "neutral"), activity("activity-2", "2026-04-01T00:00:03.000Z"), activity("activity-3", "2026-04-01T00:00:04.000Z"), ], }, ]; const collapsed = deriveThreadFeedPresentation(feed, null, new Set()); expect(collapsed.map((entry) => entry.id)).toEqual(["activity-3", "work-toggle:work-group-1"]); expect(collapsed[1]).toMatchObject({ type: "work-toggle", groupId: "work-group-1", hiddenCount: 2, expanded: false, }); const expanded = deriveThreadFeedPresentation(feed, null, new Set(), new Set(["work-group-1"])); expect(expanded.map((entry) => entry.id)).toEqual([ "activity-1", "activity-2", "activity-3", "work-toggle:work-group-1", ]); expect(expanded.at(-1)).toMatchObject({ type: "work-toggle", expanded: true, }); }); });