| @@ -4,6 +4,7 @@ import { · 3 unmodified lines |
| 4 | CommandId, | 4 | CommandId, |
| 5 | EnvironmentId, | 5 | EnvironmentId, |
| 6 | type OrchestrationV2ThreadProjection, | 6 | type OrchestrationV2ThreadProjection, |
| | 7 | type OrchestrationV2ThreadShell, |
| 7 | type Project, | 8 | type Project, |
| 8 | ProjectId, | 9 | ProjectId, |
| 9 | ProviderInstanceId, | 10 | ProviderInstanceId, |
| @@ -153,6 +154,12 @@ interface HarnessOptions { · 143 unmodified lines |
| 153 | readonly worktreePath: string | null; | 154 | readonly worktreePath: string | null; |
| 154 | readonly active?: boolean; | 155 | readonly active?: boolean; |
| 155 | }>; | 156 | }>; |
| | 157 | readonly archivedProjectThread?: { |
| | 158 | readonly id: ThreadId; |
| | 159 | readonly title: string; |
| | 160 | readonly branch: string | null; |
| | 161 | readonly worktreePath: string | null; |
| | 162 | }; |
| 156 | } | 163 | } |
| 157 | | 164 | |
| 158 | const makeHarness = (options: HarnessOptions = {}) => { | 165 | const makeHarness = (options: HarnessOptions = {}) => { |
| @@ -242,31 +249,52 @@ const makeHarness = (options: HarnessOptions = {}) => { · 83 unmodified lines |
| 242 | : Option.none(), | 249 | : Option.none(), |
| 243 | ), | 250 | ), |
| 244 | ); | 251 | ); |
| 245 | const listProjectThreads = vi.fn(() => | 252 | const projectThreadShells = ( |
| 246 | Effect.succeed( | 253 | options.projectThreads ?? [ |
| 247 | ( | 254 | { |
| 248 | options.projectThreads ?? [ | 255 | id: threadId, |
| | 256 | title: "Worktree test thread", |
| | 257 | branch: thread?.branch ?? null, |
| | 258 | worktreePath: thread?.worktreePath ?? null, |
| | 259 | }, |
| | 260 | ] |
| | 261 | ).map( |
| | 262 | (item) => |
| | 263 | ({ |
| | 264 | id: item.id, |
| | 265 | projectId, |
| | 266 | title: item.title, |
| | 267 | branch: item.branch, |
| | 268 | worktreePath: item.worktreePath, |
| | 269 | status: item.active === true ? "running" : "idle", |
| | 270 | activeRunId: item.active === true ? "run-active" : null, |
| | 271 | lineage: { relationshipToParent: "none" }, |
| | 272 | }) as OrchestrationV2ThreadShell, |
| | 273 | ); |
| | 274 | const archivedThreadShells = |
| | 275 | options.archivedProjectThread === undefined |
| | 276 | ? [] |
| | 277 | : ([ |
| 249 | { | 278 | { |
| 250 | id: threadId, | 279 | ...(projectThreadShells[0] ?? makeProjection({}).thread), |
| 251 | title: "Worktree test thread", | 280 | id: options.archivedProjectThread.id, |
| 252 | branch: thread?.branch ?? null, | | |
| 253 | worktreePath: thread?.worktreePath ?? null, | | |
| 254 | }, | | |
| 255 | ] | | |
| 256 | ).map( | | |
| 257 | (item) => | | |
| 258 | ({ | | |
| 259 | id: item.id, | | |
| 260 | projectId, | 281 | projectId, |
| 261 | title: item.title, | 282 | title: options.archivedProjectThread.title, |
| 262 | branch: item.branch, | 283 | branch: options.archivedProjectThread.branch, |
| 263 | worktreePath: item.worktreePath, | 284 | worktreePath: options.archivedProjectThread.worktreePath, |
| 264 | status: item.active === true ? "running" : "idle", | 285 | activeRunId: null, |
| 265 | activeRunId: item.active === true ? "run-active" : null, | 286 | archivedAt: "2026-01-02T00:00:00.000Z", |
| 266 | lineage: { relationshipToParent: "none" }, | 287 | lineage: { relationshipToParent: "none" }, |
| 267 | }) as never, | 288 | }, |
| 268 | ), | 289 | ] as ReadonlyArray<OrchestrationV2ThreadShell>); |
| 269 | ), | 290 | const listProjectThreads = vi.fn(() => Effect.succeed(projectThreadShells)); |
| | 291 | const getShellSnapshot = vi.fn(() => |
| | 292 | Effect.succeed({ |
| | 293 | schemaVersion: 1, |
| | 294 | snapshotSequence: 1, |
| | 295 | threads: projectThreadShells, |
| | 296 | archivedThreads: archivedThreadShells, |
| | 297 | } as never), |
| 270 | ); | 298 | ); |
| 271 | const removeWorktree = vi.fn((_: unknown) => | 299 | const removeWorktree = vi.fn((_: unknown) => |
| 272 | options.removeWorktreeFails | 300 | options.removeWorktreeFails |
| @@ -465,6 +493,7 @@ const makeHarness = (options: HarnessOptions = {}) => { · 192 unmodified lines |
| 465 | Layer.mergeAll( | 493 | Layer.mergeAll( |
| 466 | Layer.mock(ThreadManagementService)({ | 494 | Layer.mock(ThreadManagementService)({ |
| 467 | dispatch, | 495 | dispatch, |
| | 496 | getShellSnapshot, |
| 468 | getThreadProjection, | 497 | getThreadProjection, |
| 469 | listProjectThreads, | 498 | listProjectThreads, |
| 470 | sendToThread, | 499 | sendToThread, |
| @@ -1448,6 +1477,34 @@ describe("t3_worktree_list", () => { · 977 unmodified lines |
| 1448 | }); | 1477 | }); |
| 1449 | }); | 1478 | }); |
| 1450 | | 1479 | |
| | 1480 | it.effect("includes archived thread bindings retained on a physical checkout", () => { |
| | 1481 | const archivedThreadId = ThreadId.make("thread-archived-list-owner"); |
| | 1482 | const harness = makeHarness({ |
| | 1483 | worktrees: [{ path: workspaceRoot, refName: "dev" }], |
| | 1484 | archivedProjectThread: { |
| | 1485 | id: archivedThreadId, |
| | 1486 | title: "Archived checkout owner", |
| | 1487 | branch: "dev", |
| | 1488 | worktreePath: workspaceRoot, |
| | 1489 | }, |
| | 1490 | }); |
| | 1491 | return Effect.gen(function* () { |
| | 1492 | const result = yield* runList(harness, { limit: 1 }); |
| | 1493 | |
| | 1494 | expect(result.worktrees[0]).toMatchObject({ |
| | 1495 | path: workspaceRoot, |
| | 1496 | bindingCount: 2, |
| | 1497 | bindings: expect.arrayContaining([ |
| | 1498 | expect.objectContaining({ |
| | 1499 | threadId: archivedThreadId, |
| | 1500 | recordedWorktreePath: workspaceRoot, |
| | 1501 | active: false, |
| | 1502 | }), |
| | 1503 | ]), |
| | 1504 | }); |
| | 1505 | }); |
| | 1506 | }); |
| | 1507 | |
| 1451 | it.effect("attributes a nested recorded cwd to its physical worktree root", () => { | 1508 | it.effect("attributes a nested recorded cwd to its physical worktree root", () => { |
| 1452 | const nestedPath = `${workspaceRoot}/packages/app`; | 1509 | const nestedPath = `${workspaceRoot}/packages/app`; |
| 1453 | const harness = makeHarness({ | 1510 | const harness = makeHarness({ |