1import { ThreadId, type WorktreeSetupSnapshot } from "@t3tools/contracts";
2import { resolveVisibleWorktreeSetup } from "@t3tools/client-runtime/worktree-setup";
3import { describe, expect, it } from "vite-plus/test";
4import { resolveWorktreeSetupSnapshot } from "./worktree-setup-state";
5
6const threadId = ThreadId.make("setup-thread");
7const running: WorktreeSetupSnapshot = {
8 threadId,
9 sequence: 1,
10 phase: "running",
11 startedAt: "2026-09-17T00:00:00.000Z",
12 endedAt: null,
13 branch: "feature",
14 baseRef: "main",
15 worktreePath: null,
16 setupScript: null,
17 stages: [],
18 error: null,
19};
20
21describe("mobile worktree setup handoff", () => {
22 it("keeps the setup identity after its card retires so the working header can take over", () => {
23 const done = { ...running, phase: "done" as const, sequence: 2 };
24 const held = resolveWorktreeSetupSnapshot(threadId, done, running);
25 expect(held).toBe(done);
26 expect(
27 resolveVisibleWorktreeSetup({
28 live: held,
29 recorded: null,
30 turnStarted: false,
31 followUpSent: false,
32 }),
33 ).toBe(done);
34 expect(
35 resolveVisibleWorktreeSetup({
36 live: held,
37 recorded: null,
38 turnStarted: true,
39 followUpSent: false,
40 }),
41 ).toBeNull();
42 // Closing the live query must not lose the handoff anchor.
43 expect(resolveWorktreeSetupSnapshot(threadId, undefined, held)).toBe(done);
44 });
45
46 it.each(["failed", "cancelled"] as const)(
47 "retains %s details after the stream closes",
48 (phase) => {
49 const settled = { ...running, phase, sequence: 3 };
50 const held = resolveWorktreeSetupSnapshot(threadId, settled, running);
51 expect(resolveWorktreeSetupSnapshot(threadId, null, held)).toBe(settled);
52 expect(resolveWorktreeSetupSnapshot(threadId, running, held)).toBe(settled);
53 expect(
54 resolveVisibleWorktreeSetup({
55 live: held,
56 recorded: null,
57 turnStarted: false,
58 followUpSent: false,
59 }),
60 ).toBe(settled);
61 expect(
62 resolveVisibleWorktreeSetup({
63 live: held,
64 recorded: null,
65 turnStarted: false,
66 followUpSent: true,
67 }),
68 ).toBeNull();
69 },
70 );
71
72 it("does not carry a previous thread's progress across navigation", () => {
73 const otherId = ThreadId.make("other-thread");
74 expect(resolveWorktreeSetupSnapshot(otherId, running, running)).toBeNull();
75 const other = { ...running, threadId: otherId };
76 expect(resolveWorktreeSetupSnapshot(otherId, other, running)).toBe(other);
77 });
78});