import { scopeThreadRef } from "@t3tools/client-runtime";import { CheckpointRef, DEFAULT_MODEL_BY_PROVIDER, EnvironmentId, EventId, MessageId, ProjectId, ThreadId, TurnId, type OrchestrationEvent, type OrchestrationReadModel,} from "@t3tools/contracts";import { describe, expect, it } from "vitest"; import { applyOrchestrationEvent, applyOrchestrationEvents, selectEnvironmentState, selectProjectsAcrossEnvironments, setThreadBranch, selectThreadsAcrossEnvironments, syncServerReadModel, type AppState, type EnvironmentState,} from "./store";import { DEFAULT_INTERACTION_MODE, DEFAULT_RUNTIME_MODE, type Thread } from "./types"; const localEnvironmentId = EnvironmentId.makeUnsafe("environment-local");const remoteEnvironmentId = EnvironmentId.makeUnsafe("environment-remote"); function withActiveEnvironmentState( environmentState: EnvironmentState, overrides: Partial<AppState & EnvironmentState> = {},): AppState { const { activeEnvironmentId: overrideActiveEnvironmentId, environmentStateById: overrideEnvironmentStateById, ...environmentOverrides } = overrides; const activeEnvironmentId = overrideActiveEnvironmentId ?? localEnvironmentId; const mergedEnvironmentState = { ...environmentState, ...environmentOverrides, }; const environmentStateById = overrideEnvironmentStateById ?? (activeEnvironmentId ? { [activeEnvironmentId]: mergedEnvironmentState, } : {}); return { activeEnvironmentId, environmentStateById, };} function makeThread(overrides: Partial<Thread> = {}): Thread { return { id: ThreadId.makeUnsafe("thread-1"), environmentId: localEnvironmentId, codexThreadId: null, projectId: ProjectId.makeUnsafe("project-1"), title: "Thread", modelSelection: { provider: "codex", model: "gpt-5-codex", }, runtimeMode: DEFAULT_RUNTIME_MODE, interactionMode: DEFAULT_INTERACTION_MODE, session: null, messages: [], turnDiffSummaries: [], activities: [], proposedPlans: [], error: null, createdAt: "2026-02-13T00:00:00.000Z", archivedAt: null, latestTurn: null, branch: null, worktreePath: null, ...overrides, };} function makeState(thread: Thread): AppState { const projectId = ProjectId.makeUnsafe("project-1"); const project = { id: projectId, environmentId: thread.environmentId, name: "Project", cwd: "/tmp/project", defaultModelSelection: { provider: "codex" as const, model: "gpt-5-codex", }, createdAt: "2026-02-13T00:00:00.000Z", updatedAt: "2026-02-13T00:00:00.000Z", scripts: [], }; const threadIdsByProjectId: EnvironmentState["threadIdsByProjectId"] = { [thread.projectId]: [thread.id], }; const environmentState = { projectIds: [projectId], projectById: { [projectId]: project, }, threadIds: [thread.id], threadIdsByProjectId, threadShellById: { [thread.id]: { id: thread.id, environmentId: thread.environmentId, codexThreadId: thread.codexThreadId, projectId: thread.projectId, title: thread.title, modelSelection: thread.modelSelection, runtimeMode: thread.runtimeMode, interactionMode: thread.interactionMode, error: thread.error, createdAt: thread.createdAt, archivedAt: thread.archivedAt, updatedAt: thread.updatedAt, branch: thread.branch, worktreePath: thread.worktreePath, }, }, threadSessionById: { [thread.id]: thread.session, }, threadTurnStateById: { [thread.id]: { latestTurn: thread.latestTurn, ...(thread.pendingSourceProposedPlan ? { pendingSourceProposedPlan: thread.pendingSourceProposedPlan } : {}), }, }, messageIdsByThreadId: { [thread.id]: thread.messages.map((message) => message.id), }, messageByThreadId: { [thread.id]: Object.fromEntries( thread.messages.map((message) => [message.id, message] as const), ) as EnvironmentState["messageByThreadId"][ThreadId], }, activityIdsByThreadId: { [thread.id]: thread.activities.map((activity) => activity.id), }, activityByThreadId: { [thread.id]: Object.fromEntries( thread.activities.map((activity) => [activity.id, activity] as const), ) as EnvironmentState["activityByThreadId"][ThreadId], }, proposedPlanIdsByThreadId: { [thread.id]: thread.proposedPlans.map((plan) => plan.id), }, proposedPlanByThreadId: { [thread.id]: Object.fromEntries( thread.proposedPlans.map((plan) => [plan.id, plan] as const), ) as EnvironmentState["proposedPlanByThreadId"][ThreadId], }, turnDiffIdsByThreadId: { [thread.id]: thread.turnDiffSummaries.map((summary) => summary.turnId), }, turnDiffSummaryByThreadId: { [thread.id]: Object.fromEntries( thread.turnDiffSummaries.map((summary) => [summary.turnId, summary] as const), ) as EnvironmentState["turnDiffSummaryByThreadId"][ThreadId], }, sidebarThreadSummaryById: {}, bootstrapComplete: true, }; return withActiveEnvironmentState(environmentState, { activeEnvironmentId: thread.environmentId, });} function makeEmptyState(overrides: Partial<AppState & EnvironmentState> = {}): AppState { const environmentState: EnvironmentState = { projectIds: [], projectById: {}, threadIds: [], threadIdsByProjectId: {}, threadShellById: {}, threadSessionById: {}, threadTurnStateById: {}, messageIdsByThreadId: {}, messageByThreadId: {}, activityIdsByThreadId: {}, activityByThreadId: {}, proposedPlanIdsByThreadId: {}, proposedPlanByThreadId: {}, turnDiffIdsByThreadId: {}, turnDiffSummaryByThreadId: {}, sidebarThreadSummaryById: {}, bootstrapComplete: true, }; return withActiveEnvironmentState(environmentState, overrides);} function localEnvironmentStateOf(state: AppState): EnvironmentState { return selectEnvironmentState(state, localEnvironmentId);} function environmentStateOf(state: AppState, environmentId: EnvironmentId): EnvironmentState { return selectEnvironmentState(state, environmentId);} function projectsOf(state: AppState) { return selectProjectsAcrossEnvironments(state);} function threadsOf(state: AppState) { return selectThreadsAcrossEnvironments(state);} function makeEvent<T extends OrchestrationEvent["type"]>( type: T, payload: Extract<OrchestrationEvent, { type: T }>["payload"], overrides: Partial<Extract<OrchestrationEvent, { type: T }>> = {},): Extract<OrchestrationEvent, { type: T }> { const sequence = overrides.sequence ?? 1; return { sequence, eventId: EventId.makeUnsafe(`event-${sequence}`), aggregateKind: "thread", aggregateId: "threadId" in payload ? payload.threadId : "projectId" in payload ? payload.projectId : ProjectId.makeUnsafe("project-1"), occurredAt: "2026-02-27T00:00:00.000Z", commandId: null, causationEventId: null, correlationId: null, metadata: {}, type, payload, ...overrides, } as Extract<OrchestrationEvent, { type: T }>;} function makeReadModelThread(overrides: Partial<OrchestrationReadModel["threads"][number]>) { return { id: ThreadId.makeUnsafe("thread-1"), projectId: ProjectId.makeUnsafe("project-1"), title: "Thread", modelSelection: { provider: "codex", model: "gpt-5.3-codex", }, runtimeMode: DEFAULT_RUNTIME_MODE, interactionMode: DEFAULT_INTERACTION_MODE, branch: null, worktreePath: null, latestTurn: null, createdAt: "2026-02-27T00:00:00.000Z", updatedAt: "2026-02-27T00:00:00.000Z", archivedAt: null, deletedAt: null, messages: [], activities: [], proposedPlans: [], checkpoints: [], session: null, ...overrides, } satisfies OrchestrationReadModel["threads"][number];} function makeReadModel(thread: OrchestrationReadModel["threads"][number]): OrchestrationReadModel { return { snapshotSequence: 1, updatedAt: "2026-02-27T00:00:00.000Z", projects: [ { id: ProjectId.makeUnsafe("project-1"), title: "Project", workspaceRoot: "/tmp/project", defaultModelSelection: { provider: "codex", model: "gpt-5.3-codex", }, createdAt: "2026-02-27T00:00:00.000Z", updatedAt: "2026-02-27T00:00:00.000Z", deletedAt: null, scripts: [], }, ], threads: [thread], };} function makeReadModelProject( overrides: Partial<OrchestrationReadModel["projects"][number]>,): OrchestrationReadModel["projects"][number] { return { id: ProjectId.makeUnsafe("project-1"), title: "Project", workspaceRoot: "/tmp/project", defaultModelSelection: { provider: "codex", model: "gpt-5.3-codex", }, createdAt: "2026-02-27T00:00:00.000Z", updatedAt: "2026-02-27T00:00:00.000Z", deletedAt: null, scripts: [], ...overrides, };} describe("setThreadBranch", () => { it("updates only the scoped thread environment", () => { const sharedThreadId = ThreadId.makeUnsafe("thread-shared"); const localThread = makeThread({ id: sharedThreadId, environmentId: localEnvironmentId, branch: "local-branch", }); const remoteThread = makeThread({ id: sharedThreadId, environmentId: remoteEnvironmentId, branch: "remote-branch", }); const state: AppState = { activeEnvironmentId: localEnvironmentId, environmentStateById: { [localEnvironmentId]: environmentStateOf(makeState(localThread), localEnvironmentId), [remoteEnvironmentId]: environmentStateOf(makeState(remoteThread), remoteEnvironmentId), }, }; const next = setThreadBranch( state, scopeThreadRef(remoteEnvironmentId, sharedThreadId), "remote-next", "/tmp/remote-worktree", ); expect( environmentStateOf(next, localEnvironmentId).threadShellById[sharedThreadId]?.branch, ).toBe("local-branch"); expect( environmentStateOf(next, remoteEnvironmentId).threadShellById[sharedThreadId]?.branch, ).toBe("remote-next"); expect( environmentStateOf(next, remoteEnvironmentId).threadShellById[sharedThreadId]?.worktreePath, ).toBe("/tmp/remote-worktree"); });}); describe("store read model sync", () => { it("marks bootstrap complete after snapshot sync", () => { const initialState = withActiveEnvironmentState( localEnvironmentStateOf(makeState(makeThread())), { bootstrapComplete: false, }, ); const next = syncServerReadModel( initialState, makeReadModel(makeReadModelThread({})), localEnvironmentId, ); expect(localEnvironmentStateOf(next).bootstrapComplete).toBe(true); }); it("preserves claude model slugs without an active session", () => { const initialState = makeState(makeThread()); const readModel = makeReadModel( makeReadModelThread({ modelSelection: { provider: "claudeAgent", model: "claude-opus-4-6", }, }), ); const next = syncServerReadModel(initialState, readModel, localEnvironmentId); expect(threadsOf(next)[0]?.modelSelection.model).toBe("claude-opus-4-6"); }); it("resolves claude aliases when session provider is claudeAgent", () => { const initialState = makeState(makeThread()); const readModel = makeReadModel( makeReadModelThread({ modelSelection: { provider: "claudeAgent", model: "sonnet", }, session: { threadId: ThreadId.makeUnsafe("thread-1"), status: "ready", providerName: "claudeAgent", runtimeMode: "approval-required", activeTurnId: null, lastError: null, updatedAt: "2026-02-27T00:00:00.000Z", }, }), ); const next = syncServerReadModel(initialState, readModel, localEnvironmentId); expect(threadsOf(next)[0]?.modelSelection.model).toBe("claude-sonnet-4-6"); }); it("preserves project and thread updatedAt timestamps from the read model", () => { const initialState = makeState(makeThread()); const readModel = makeReadModel( makeReadModelThread({ updatedAt: "2026-02-27T00:05:00.000Z", }), ); const next = syncServerReadModel(initialState, readModel, localEnvironmentId); expect(projectsOf(next)[0]?.updatedAt).toBe("2026-02-27T00:00:00.000Z"); expect(threadsOf(next)[0]?.updatedAt).toBe("2026-02-27T00:05:00.000Z"); }); it("maps archivedAt from the read model", () => { const initialState = makeState(makeThread()); const archivedAt = "2026-02-28T00:00:00.000Z"; const next = syncServerReadModel( initialState, makeReadModel( makeReadModelThread({ archivedAt, }), ), localEnvironmentId, ); expect(threadsOf(next)[0]?.archivedAt).toBe(archivedAt); }); it("replaces projects using snapshot order during recovery", () => { const project1 = ProjectId.makeUnsafe("project-1"); const project2 = ProjectId.makeUnsafe("project-2"); const project3 = ProjectId.makeUnsafe("project-3"); const initialState: AppState = makeEmptyState({ projectIds: [project2, project1], projectById: { [project2]: { id: project2, environmentId: localEnvironmentId, name: "Project 2", cwd: "/tmp/project-2", defaultModelSelection: { provider: "codex", model: DEFAULT_MODEL_BY_PROVIDER.codex, }, createdAt: "2026-02-27T00:00:00.000Z", updatedAt: "2026-02-27T00:00:00.000Z", scripts: [], }, [project1]: { id: project1, environmentId: localEnvironmentId, name: "Project 1", cwd: "/tmp/project-1", defaultModelSelection: { provider: "codex", model: DEFAULT_MODEL_BY_PROVIDER.codex, }, createdAt: "2026-02-27T00:00:00.000Z", updatedAt: "2026-02-27T00:00:00.000Z", scripts: [], }, }, }); const readModel: OrchestrationReadModel = { snapshotSequence: 2, updatedAt: "2026-02-27T00:00:00.000Z", projects: [ makeReadModelProject({ id: project1, title: "Project 1", workspaceRoot: "/tmp/project-1", }), makeReadModelProject({ id: project2, title: "Project 2", workspaceRoot: "/tmp/project-2", }), makeReadModelProject({ id: project3, title: "Project 3", workspaceRoot: "/tmp/project-3", }), ],Lines 1–500 of 1117. Load more
import { scopeThreadRef } from "@t3tools/client-runtime"; import { CheckpointRef, DEFAULT_MODEL_BY_PROVIDER, EnvironmentId, EventId, MessageId, ProjectId, ThreadId, TurnId, type OrchestrationEvent, type OrchestrationReadModel, } from "@t3tools/contracts"; import { describe, expect, it } from "vitest"; import { applyOrchestrationEvent, applyOrchestrationEvents, selectEnvironmentState, selectProjectsAcrossEnvironments, setThreadBranch, selectThreadsAcrossEnvironments, syncServerReadModel, type AppState, type EnvironmentState, } from "./store"; import { DEFAULT_INTERACTION_MODE, DEFAULT_RUNTIME_MODE, type Thread } from "./types"; const localEnvironmentId = EnvironmentId.makeUnsafe("environment-local"); const remoteEnvironmentId = EnvironmentId.makeUnsafe("environment-remote"); function withActiveEnvironmentState( environmentState: EnvironmentState, overrides: Partial<AppState & EnvironmentState> = {}, ): AppState { const { activeEnvironmentId: overrideActiveEnvironmentId, environmentStateById: overrideEnvironmentStateById, ...environmentOverrides } = overrides; const activeEnvironmentId = overrideActiveEnvironmentId ?? localEnvironmentId; const mergedEnvironmentState = { ...environmentState, ...environmentOverrides, }; const environmentStateById = overrideEnvironmentStateById ?? (activeEnvironmentId ? { [activeEnvironmentId]: mergedEnvironmentState, } : {}); return { activeEnvironmentId, environmentStateById, }; } function makeThread(overrides: Partial<Thread> = {}): Thread { return { id: ThreadId.makeUnsafe("thread-1"), environmentId: localEnvironmentId, codexThreadId: null, projectId: ProjectId.makeUnsafe("project-1"), title: "Thread", modelSelection: { provider: "codex", model: "gpt-5-codex", }, runtimeMode: DEFAULT_RUNTIME_MODE, interactionMode: DEFAULT_INTERACTION_MODE, session: null, messages: [], turnDiffSummaries: [], activities: [], proposedPlans: [], error: null, createdAt: "2026-02-13T00:00:00.000Z", archivedAt: null, latestTurn: null, branch: null, worktreePath: null, ...overrides, }; } function makeState(thread: Thread): AppState { const projectId = ProjectId.makeUnsafe("project-1"); const project = { id: projectId, environmentId: thread.environmentId, name: "Project", cwd: "/tmp/project", defaultModelSelection: { provider: "codex" as const, model: "gpt-5-codex", }, createdAt: "2026-02-13T00:00:00.000Z", updatedAt: "2026-02-13T00:00:00.000Z", scripts: [], }; const threadIdsByProjectId: EnvironmentState["threadIdsByProjectId"] = { [thread.projectId]: [thread.id], }; const environmentState = { projectIds: [projectId], projectById: { [projectId]: project, }, threadIds: [thread.id], threadIdsByProjectId, threadShellById: { [thread.id]: { id: thread.id, environmentId: thread.environmentId, codexThreadId: thread.codexThreadId, projectId: thread.projectId, title: thread.title, modelSelection: thread.modelSelection, runtimeMode: thread.runtimeMode, interactionMode: thread.interactionMode, error: thread.error, createdAt: thread.createdAt, archivedAt: thread.archivedAt, updatedAt: thread.updatedAt, branch: thread.branch, worktreePath: thread.worktreePath, }, }, threadSessionById: { [thread.id]: thread.session, }, threadTurnStateById: { [thread.id]: { latestTurn: thread.latestTurn, ...(thread.pendingSourceProposedPlan ? { pendingSourceProposedPlan: thread.pendingSourceProposedPlan } : {}), }, }, messageIdsByThreadId: { [thread.id]: thread.messages.map((message) => message.id), }, messageByThreadId: { [thread.id]: Object.fromEntries( thread.messages.map((message) => [message.id, message] as const), ) as EnvironmentState["messageByThreadId"][ThreadId], }, activityIdsByThreadId: { [thread.id]: thread.activities.map((activity) => activity.id), }, activityByThreadId: { [thread.id]: Object.fromEntries( thread.activities.map((activity) => [activity.id, activity] as const), ) as EnvironmentState["activityByThreadId"][ThreadId], }, proposedPlanIdsByThreadId: { [thread.id]: thread.proposedPlans.map((plan) => plan.id), }, proposedPlanByThreadId: { [thread.id]: Object.fromEntries( thread.proposedPlans.map((plan) => [plan.id, plan] as const), ) as EnvironmentState["proposedPlanByThreadId"][ThreadId], }, turnDiffIdsByThreadId: { [thread.id]: thread.turnDiffSummaries.map((summary) => summary.turnId), }, turnDiffSummaryByThreadId: { [thread.id]: Object.fromEntries( thread.turnDiffSummaries.map((summary) => [summary.turnId, summary] as const), ) as EnvironmentState["turnDiffSummaryByThreadId"][ThreadId], }, sidebarThreadSummaryById: {}, bootstrapComplete: true, }; return withActiveEnvironmentState(environmentState, { activeEnvironmentId: thread.environmentId, }); } function makeEmptyState(overrides: Partial<AppState & EnvironmentState> = {}): AppState { const environmentState: EnvironmentState = { projectIds: [], projectById: {}, threadIds: [], threadIdsByProjectId: {}, threadShellById: {}, threadSessionById: {}, threadTurnStateById: {}, messageIdsByThreadId: {}, messageByThreadId: {}, activityIdsByThreadId: {}, activityByThreadId: {}, proposedPlanIdsByThreadId: {}, proposedPlanByThreadId: {}, turnDiffIdsByThreadId: {}, turnDiffSummaryByThreadId: {}, sidebarThreadSummaryById: {}, bootstrapComplete: true, }; return withActiveEnvironmentState(environmentState, overrides); } function localEnvironmentStateOf(state: AppState): EnvironmentState { return selectEnvironmentState(state, localEnvironmentId); } function environmentStateOf(state: AppState, environmentId: EnvironmentId): EnvironmentState { return selectEnvironmentState(state, environmentId); } function projectsOf(state: AppState) { return selectProjectsAcrossEnvironments(state); } function threadsOf(state: AppState) { return selectThreadsAcrossEnvironments(state); } function makeEvent<T extends OrchestrationEvent["type"]>( type: T, payload: Extract<OrchestrationEvent, { type: T }>["payload"], overrides: Partial<Extract<OrchestrationEvent, { type: T }>> = {}, ): Extract<OrchestrationEvent, { type: T }> { const sequence = overrides.sequence ?? 1; return { sequence, eventId: EventId.makeUnsafe(`event-${sequence}`), aggregateKind: "thread", aggregateId: "threadId" in payload ? payload.threadId : "projectId" in payload ? payload.projectId : ProjectId.makeUnsafe("project-1"), occurredAt: "2026-02-27T00:00:00.000Z", commandId: null, causationEventId: null, correlationId: null, metadata: {}, type, payload, ...overrides, } as Extract<OrchestrationEvent, { type: T }>; } function makeReadModelThread(overrides: Partial<OrchestrationReadModel["threads"][number]>) { return { id: ThreadId.makeUnsafe("thread-1"), projectId: ProjectId.makeUnsafe("project-1"), title: "Thread", modelSelection: { provider: "codex", model: "gpt-5.3-codex", }, runtimeMode: DEFAULT_RUNTIME_MODE, interactionMode: DEFAULT_INTERACTION_MODE, branch: null, worktreePath: null, latestTurn: null, createdAt: "2026-02-27T00:00:00.000Z", updatedAt: "2026-02-27T00:00:00.000Z", archivedAt: null, deletedAt: null, messages: [], activities: [], proposedPlans: [], checkpoints: [], session: null, ...overrides, } satisfies OrchestrationReadModel["threads"][number]; } function makeReadModel(thread: OrchestrationReadModel["threads"][number]): OrchestrationReadModel { return { snapshotSequence: 1, updatedAt: "2026-02-27T00:00:00.000Z", projects: [ { id: ProjectId.makeUnsafe("project-1"), title: "Project", workspaceRoot: "/tmp/project", defaultModelSelection: { provider: "codex", model: "gpt-5.3-codex", }, createdAt: "2026-02-27T00:00:00.000Z", updatedAt: "2026-02-27T00:00:00.000Z", deletedAt: null, scripts: [], }, ], threads: [thread], }; } function makeReadModelProject( overrides: Partial<OrchestrationReadModel["projects"][number]>, ): OrchestrationReadModel["projects"][number] { return { id: ProjectId.makeUnsafe("project-1"), title: "Project", workspaceRoot: "/tmp/project", defaultModelSelection: { provider: "codex", model: "gpt-5.3-codex", }, createdAt: "2026-02-27T00:00:00.000Z", updatedAt: "2026-02-27T00:00:00.000Z", deletedAt: null, scripts: [], ...overrides, }; } describe("setThreadBranch", () => { it("updates only the scoped thread environment", () => { const sharedThreadId = ThreadId.makeUnsafe("thread-shared"); const localThread = makeThread({ id: sharedThreadId, environmentId: localEnvironmentId, branch: "local-branch", }); const remoteThread = makeThread({ id: sharedThreadId, environmentId: remoteEnvironmentId, branch: "remote-branch", }); const state: AppState = { activeEnvironmentId: localEnvironmentId, environmentStateById: { [localEnvironmentId]: environmentStateOf(makeState(localThread), localEnvironmentId), [remoteEnvironmentId]: environmentStateOf(makeState(remoteThread), remoteEnvironmentId), }, }; const next = setThreadBranch( state, scopeThreadRef(remoteEnvironmentId, sharedThreadId), "remote-next", "/tmp/remote-worktree", ); expect( environmentStateOf(next, localEnvironmentId).threadShellById[sharedThreadId]?.branch, ).toBe("local-branch"); expect( environmentStateOf(next, remoteEnvironmentId).threadShellById[sharedThreadId]?.branch, ).toBe("remote-next"); expect( environmentStateOf(next, remoteEnvironmentId).threadShellById[sharedThreadId]?.worktreePath, ).toBe("/tmp/remote-worktree"); }); }); describe("store read model sync", () => { it("marks bootstrap complete after snapshot sync", () => { const initialState = withActiveEnvironmentState( localEnvironmentStateOf(makeState(makeThread())), { bootstrapComplete: false, }, ); const next = syncServerReadModel( initialState, makeReadModel(makeReadModelThread({})), localEnvironmentId, ); expect(localEnvironmentStateOf(next).bootstrapComplete).toBe(true); }); it("preserves claude model slugs without an active session", () => { const initialState = makeState(makeThread()); const readModel = makeReadModel( makeReadModelThread({ modelSelection: { provider: "claudeAgent", model: "claude-opus-4-6", }, }), ); const next = syncServerReadModel(initialState, readModel, localEnvironmentId); expect(threadsOf(next)[0]?.modelSelection.model).toBe("claude-opus-4-6"); }); it("resolves claude aliases when session provider is claudeAgent", () => { const initialState = makeState(makeThread()); const readModel = makeReadModel( makeReadModelThread({ modelSelection: { provider: "claudeAgent", model: "sonnet", }, session: { threadId: ThreadId.makeUnsafe("thread-1"), status: "ready", providerName: "claudeAgent", runtimeMode: "approval-required", activeTurnId: null, lastError: null, updatedAt: "2026-02-27T00:00:00.000Z", }, }), ); const next = syncServerReadModel(initialState, readModel, localEnvironmentId); expect(threadsOf(next)[0]?.modelSelection.model).toBe("claude-sonnet-4-6"); }); it("preserves project and thread updatedAt timestamps from the read model", () => { const initialState = makeState(makeThread()); const readModel = makeReadModel( makeReadModelThread({ updatedAt: "2026-02-27T00:05:00.000Z", }), ); const next = syncServerReadModel(initialState, readModel, localEnvironmentId); expect(projectsOf(next)[0]?.updatedAt).toBe("2026-02-27T00:00:00.000Z"); expect(threadsOf(next)[0]?.updatedAt).toBe("2026-02-27T00:05:00.000Z"); }); it("maps archivedAt from the read model", () => { const initialState = makeState(makeThread()); const archivedAt = "2026-02-28T00:00:00.000Z"; const next = syncServerReadModel( initialState, makeReadModel( makeReadModelThread({ archivedAt, }), ), localEnvironmentId, ); expect(threadsOf(next)[0]?.archivedAt).toBe(archivedAt); }); it("replaces projects using snapshot order during recovery", () => { const project1 = ProjectId.makeUnsafe("project-1"); const project2 = ProjectId.makeUnsafe("project-2"); const project3 = ProjectId.makeUnsafe("project-3"); const initialState: AppState = makeEmptyState({ projectIds: [project2, project1], projectById: { [project2]: { id: project2, environmentId: localEnvironmentId, name: "Project 2", cwd: "/tmp/project-2", defaultModelSelection: { provider: "codex", model: DEFAULT_MODEL_BY_PROVIDER.codex, }, createdAt: "2026-02-27T00:00:00.000Z", updatedAt: "2026-02-27T00:00:00.000Z", scripts: [], }, [project1]: { id: project1, environmentId: localEnvironmentId, name: "Project 1", cwd: "/tmp/project-1", defaultModelSelection: { provider: "codex", model: DEFAULT_MODEL_BY_PROVIDER.codex, }, createdAt: "2026-02-27T00:00:00.000Z", updatedAt: "2026-02-27T00:00:00.000Z", scripts: [], }, }, }); const readModel: OrchestrationReadModel = { snapshotSequence: 2, updatedAt: "2026-02-27T00:00:00.000Z", projects: [ makeReadModelProject({ id: project1, title: "Project 1", workspaceRoot: "/tmp/project-1", }), makeReadModelProject({ id: project2, title: "Project 2", workspaceRoot: "/tmp/project-2", }), makeReadModelProject({ id: project3, title: "Project 3", workspaceRoot: "/tmp/project-3", }), ],