1import { describe, expect, it } from "vite-plus/test";
2import {
3 CommandId,
4 ComposerContextId,
5 EnvironmentId,
6 MessageId,
7 ThreadId,
8} from "@t3tools/contracts";
9import type { QueuedThreadMessage } from "../../state/thread-outbox-model";
10import { appendPendingThreadMessages } from "./pending-thread-feed";
11
12const pending = (id: string): QueuedThreadMessage => ({
13 environmentId: EnvironmentId.make("env"),
14 threadId: ThreadId.make("thread"),
15 messageId: MessageId.make(id),
16 commandId: CommandId.make(id),
17 text: id,
18 attachments: [],
19 createdAt: "2026-09-06T10:00:00.000Z",
20});
21
22describe("pending timeline messages", () => {
23 it("retains context records while a message is waiting for delivery", () => {
24 const record = {
25 version: 1 as const,
26 kind: "mention" as const,
27 contextId: ComposerContextId.make("setup-file"),
28 label: "Checkout.tsx",
29 path: "src/Checkout.tsx",
30 };
31 const context = { version: 1 as const, records: [record] };
32 const text = "[Checkout.tsx](t3-context://v1/mention/setup-file)";
33 const entries = appendPendingThreadMessages([], [], [{ ...pending("context"), text, context }]);
34 const entry = entries[0];
35 expect(entry?.type).toBe("message");
36 if (entry?.type !== "message") throw new Error("Expected a pending message");
37 expect(entry.message.text).toBe(text);
38 expect(entry.message.context).toEqual(context);
39 });
40
41 it("keeps pending messages after newer agent activity in queue order", () => {
42 const activity = {
43 type: "thinking",
44 runId: null,
45 id: "thinking",
46 createdAt: "2026-09-06T11:00:00.000Z",
47 } as const;
48 const entries = appendPendingThreadMessages(
49 [activity],
50 [],
51 [pending("first"), pending("second")],
52 );
53 expect(entries.map((entry) => entry.id)).toEqual(["thinking", "first", "second"]);
54 expect(entries[1]?.pendingMessage?.text).toBe("first");
55 });
56
57 it("reuses the message id and suppresses the pending copy when delivery appears", () => {
58 const queued = pending("sent");
59 const optimistic = appendPendingThreadMessages([], [], [queued])[0]!;
60 const delivered = { ...optimistic, pendingMessage: undefined };
61 expect(appendPendingThreadMessages([delivered], [delivered], [queued])).toEqual([delivered]);
62 // Folded messages still count as delivered even when absent from the presented rows.
63 expect(appendPendingThreadMessages([], [delivered], [queued])).toEqual([]);
64 });
65});