11import { ProjectId , ThreadId , TurnId } from "@t3tools/contracts" ;
22import { afterEach , describe , expect , it , vi } from "vitest" ;
33import { useStore } from "../store" ;
4+ import { type Thread } from "../types" ;
45
56import {
67 MAX_HIDDEN_MOUNTED_TERMINAL_THREADS ,
@@ -178,7 +179,7 @@ const makeThread = (input?: {
178179 startedAt : string | null ;
179180 completedAt : string | null ;
180181 } | null ;
181- } ) => ( {
182+ } ) : Thread => ( {
182183 id : input ?. id ?? ThreadId . makeUnsafe ( "thread-1" ) ,
183184 codexThreadId : null ,
184185 projectId : ProjectId . makeUnsafe ( "project-1" ) ,
@@ -205,94 +206,172 @@ const makeThread = (input?: {
205206 activities : [ ] ,
206207} ) ;
207208
209+ function setStoreThreads ( threads : ReadonlyArray < ReturnType < typeof makeThread > > ) {
210+ const projectId = ProjectId . makeUnsafe ( "project-1" ) ;
211+ useStore . setState ( {
212+ projectIds : [ projectId ] ,
213+ projectById : {
214+ [ projectId ] : {
215+ id : projectId ,
216+ name : "Project" ,
217+ cwd : "/tmp/project" ,
218+ defaultModelSelection : {
219+ provider : "codex" ,
220+ model : "gpt-5.4" ,
221+ } ,
222+ createdAt : "2026-03-29T00:00:00.000Z" ,
223+ updatedAt : "2026-03-29T00:00:00.000Z" ,
224+ scripts : [ ] ,
225+ } ,
226+ } ,
227+ threadIds : threads . map ( ( thread ) => thread . id ) ,
228+ threadIdsByProjectId : {
229+ [ projectId ] : threads . map ( ( thread ) => thread . id ) ,
230+ } ,
231+ threadShellById : Object . fromEntries (
232+ threads . map ( ( thread ) => [
233+ thread . id ,
234+ {
235+ id : thread . id ,
236+ codexThreadId : thread . codexThreadId ,
237+ projectId : thread . projectId ,
238+ title : thread . title ,
239+ modelSelection : thread . modelSelection ,
240+ runtimeMode : thread . runtimeMode ,
241+ interactionMode : thread . interactionMode ,
242+ error : thread . error ,
243+ createdAt : thread . createdAt ,
244+ archivedAt : thread . archivedAt ,
245+ updatedAt : thread . updatedAt ,
246+ branch : thread . branch ,
247+ worktreePath : thread . worktreePath ,
248+ } ,
249+ ] ) ,
250+ ) ,
251+ threadSessionById : Object . fromEntries ( threads . map ( ( thread ) => [ thread . id , thread . session ] ) ) ,
252+ threadTurnStateById : Object . fromEntries (
253+ threads . map ( ( thread ) => [
254+ thread . id ,
255+ {
256+ latestTurn : thread . latestTurn ,
257+ ...( thread . pendingSourceProposedPlan
258+ ? { pendingSourceProposedPlan : thread . pendingSourceProposedPlan }
259+ : { } ) ,
260+ } ,
261+ ] ) ,
262+ ) ,
263+ messageIdsByThreadId : Object . fromEntries (
264+ threads . map ( ( thread ) => [ thread . id , thread . messages . map ( ( message ) => message . id ) ] ) ,
265+ ) ,
266+ messageByThreadId : Object . fromEntries (
267+ threads . map ( ( thread ) => [
268+ thread . id ,
269+ Object . fromEntries ( thread . messages . map ( ( message ) => [ message . id , message ] ) ) ,
270+ ] ) ,
271+ ) ,
272+ activityIdsByThreadId : Object . fromEntries (
273+ threads . map ( ( thread ) => [ thread . id , thread . activities . map ( ( activity ) => activity . id ) ] ) ,
274+ ) ,
275+ activityByThreadId : Object . fromEntries (
276+ threads . map ( ( thread ) => [
277+ thread . id ,
278+ Object . fromEntries ( thread . activities . map ( ( activity ) => [ activity . id , activity ] ) ) ,
279+ ] ) ,
280+ ) ,
281+ proposedPlanIdsByThreadId : Object . fromEntries (
282+ threads . map ( ( thread ) => [ thread . id , thread . proposedPlans . map ( ( plan ) => plan . id ) ] ) ,
283+ ) ,
284+ proposedPlanByThreadId : Object . fromEntries (
285+ threads . map ( ( thread ) => [
286+ thread . id ,
287+ Object . fromEntries ( thread . proposedPlans . map ( ( plan ) => [ plan . id , plan ] ) ) ,
288+ ] ) ,
289+ ) ,
290+ turnDiffIdsByThreadId : Object . fromEntries (
291+ threads . map ( ( thread ) => [
292+ thread . id ,
293+ thread . turnDiffSummaries . map ( ( summary ) => summary . turnId ) ,
294+ ] ) ,
295+ ) ,
296+ turnDiffSummaryByThreadId : Object . fromEntries (
297+ threads . map ( ( thread ) => [
298+ thread . id ,
299+ Object . fromEntries ( thread . turnDiffSummaries . map ( ( summary ) => [ summary . turnId , summary ] ) ) ,
300+ ] ) ,
301+ ) ,
302+ sidebarThreadSummaryById : { } ,
303+ bootstrapComplete : true ,
304+ } ) ;
305+ }
306+
208307afterEach ( ( ) => {
209308 vi . useRealTimers ( ) ;
210309 vi . restoreAllMocks ( ) ;
211- useStore . setState ( ( state ) => ( {
212- ...state ,
213- projects : [ ] ,
214- threads : [ ] ,
215- bootstrapComplete : true ,
216- } ) ) ;
310+ setStoreThreads ( [ ] ) ;
217311} ) ;
218312
219313describe ( "waitForStartedServerThread" , ( ) => {
220314 it ( "resolves immediately when the thread is already started" , async ( ) => {
221315 const threadId = ThreadId . makeUnsafe ( "thread-started" ) ;
222- useStore . setState ( ( state ) => ( {
223- ...state ,
224- threads : [
225- makeThread ( {
226- id : threadId ,
227- latestTurn : {
228- turnId : TurnId . makeUnsafe ( "turn-started" ) ,
229- state : "running" ,
230- requestedAt : "2026-03-29T00:00:01.000Z" ,
231- startedAt : "2026-03-29T00:00:01.000Z" ,
232- completedAt : null ,
233- } ,
234- } ) ,
235- ] ,
236- } ) ) ;
316+ setStoreThreads ( [
317+ makeThread ( {
318+ id : threadId ,
319+ latestTurn : {
320+ turnId : TurnId . makeUnsafe ( "turn-started" ) ,
321+ state : "running" ,
322+ requestedAt : "2026-03-29T00:00:01.000Z" ,
323+ startedAt : "2026-03-29T00:00:01.000Z" ,
324+ completedAt : null ,
325+ } ,
326+ } ) ,
327+ ] ) ;
237328
238329 await expect ( waitForStartedServerThread ( threadId ) ) . resolves . toBe ( true ) ;
239330 } ) ;
240331
241332 it ( "waits for the thread to start via subscription updates" , async ( ) => {
242333 const threadId = ThreadId . makeUnsafe ( "thread-wait" ) ;
243- useStore . setState ( ( state ) => ( {
244- ...state ,
245- threads : [ makeThread ( { id : threadId } ) ] ,
246- } ) ) ;
334+ setStoreThreads ( [ makeThread ( { id : threadId } ) ] ) ;
247335
248336 const promise = waitForStartedServerThread ( threadId , 500 ) ;
249337
250- useStore . setState ( ( state ) => ( {
251- ...state ,
252- threads : [
253- makeThread ( {
254- id : threadId ,
255- latestTurn : {
256- turnId : TurnId . makeUnsafe ( "turn-started" ) ,
257- state : "running" ,
258- requestedAt : "2026-03-29T00:00:01.000Z" ,
259- startedAt : "2026-03-29T00:00:01.000Z" ,
260- completedAt : null ,
261- } ,
262- } ) ,
263- ] ,
264- } ) ) ;
338+ setStoreThreads ( [
339+ makeThread ( {
340+ id : threadId ,
341+ latestTurn : {
342+ turnId : TurnId . makeUnsafe ( "turn-started" ) ,
343+ state : "running" ,
344+ requestedAt : "2026-03-29T00:00:01.000Z" ,
345+ startedAt : "2026-03-29T00:00:01.000Z" ,
346+ completedAt : null ,
347+ } ,
348+ } ) ,
349+ ] ) ;
265350
266351 await expect ( promise ) . resolves . toBe ( true ) ;
267352 } ) ;
268353
269354 it ( "handles the thread starting between the initial read and subscription setup" , async ( ) => {
270355 const threadId = ThreadId . makeUnsafe ( "thread-race" ) ;
271- useStore . setState ( ( state ) => ( {
272- ...state ,
273- threads : [ makeThread ( { id : threadId } ) ] ,
274- } ) ) ;
356+ setStoreThreads ( [ makeThread ( { id : threadId } ) ] ) ;
275357
276358 const originalSubscribe = useStore . subscribe . bind ( useStore ) ;
277359 let raced = false ;
278360 vi . spyOn ( useStore , "subscribe" ) . mockImplementation ( ( listener ) => {
279361 if ( ! raced ) {
280362 raced = true ;
281- useStore . setState ( ( state ) => ( {
282- ...state ,
283- threads : [
284- makeThread ( {
285- id : threadId ,
286- latestTurn : {
287- turnId : TurnId . makeUnsafe ( "turn-race" ) ,
288- state : "running" ,
289- requestedAt : "2026-03-29T00:00:01.000Z" ,
290- startedAt : "2026-03-29T00:00:01.000Z" ,
291- completedAt : null ,
292- } ,
293- } ) ,
294- ] ,
295- } ) ) ;
363+ setStoreThreads ( [
364+ makeThread ( {
365+ id : threadId ,
366+ latestTurn : {
367+ turnId : TurnId . makeUnsafe ( "turn-race" ) ,
368+ state : "running" ,
369+ requestedAt : "2026-03-29T00:00:01.000Z" ,
370+ startedAt : "2026-03-29T00:00:01.000Z" ,
371+ completedAt : null ,
372+ } ,
373+ } ) ,
374+ ] ) ;
296375 }
297376 return originalSubscribe ( listener ) ;
298377 } ) ;
@@ -304,10 +383,7 @@ describe("waitForStartedServerThread", () => {
304383 vi . useFakeTimers ( ) ;
305384
306385 const threadId = ThreadId . makeUnsafe ( "thread-timeout" ) ;
307- useStore . setState ( ( state ) => ( {
308- ...state ,
309- threads : [ makeThread ( { id : threadId } ) ] ,
310- } ) ) ;
386+ setStoreThreads ( [ makeThread ( { id : threadId } ) ] ) ;
311387 const promise = waitForStartedServerThread ( threadId , 500 ) ;
312388
313389 await vi . advanceTimersByTimeAsync ( 500 ) ;
0 commit comments