MCPcopy
hub / github.com/codeaashu/claude-code / TasksV2Store

Class TasksV2Store

src/hooks/useTasksV2.ts:29–199  ·  view source on GitHub ↗

* Singleton store for the TodoV2 task list. Owns the file watcher, timers, * and cached task list. Multiple hook instances (REPL, Spinner, * PromptInputFooterLeftSide) subscribe to one shared store instead of each * setting up their own fs.watch on the same directory. The Spinner mounts/ * unmou

Source from the content-addressed store, hash-verified

27 * Implements the useSyncExternalStore contract: subscribe/getSnapshot.
28 */
29class TasksV2Store {
30 /** Stable array reference; replaced only on fetch. undefined until started. */
31 #tasks: Task[] | undefined = undefined
32 /**
33 * Set when the hide timer has elapsed (all tasks completed for >5s), or
34 * when the task list is empty. Starts false so the first fetch runs the
35 * "all completed → schedule 5s hide" path (matches original behavior:
36 * resuming a session with completed tasks shows them briefly).
37 */
38 #hidden = false
39 #watcher: FSWatcher | null = null
40 #watchedDir: string | null = null
41 #hideTimer: ReturnType<typeof setTimeout> | null = null
42 #debounceTimer: ReturnType<typeof setTimeout> | null = null
43 #pollTimer: ReturnType<typeof setTimeout> | null = null
44 #unsubscribeTasksUpdated: (() => void) | null = null
45 #changed = createSignal()
46 #subscriberCount = 0
47 #started = false
48
49 /**
50 * useSyncExternalStore snapshot. Returns the same Task[] reference between
51 * updates (required for Object.is stability). Returns undefined when hidden.
52 */
53 getSnapshot = (): Task[] | undefined => {
54 return this.#hidden ? undefined : this.#tasks
55 }
56
57 subscribe = (fn: () => void): (() => void) => {
58 // Lazy init on first subscriber. useSyncExternalStore calls this
59 // post-commit, so I/O here is safe (no render-phase side effects).
60 // REPL.tsx keeps a subscription alive for the whole session, so
61 // Spinner mount/unmount churn never drives the count to zero.
62 const unsubscribe = this.#changed.subscribe(fn)
63 this.#subscriberCount++
64 if (!this.#started) {
65 this.#started = true
66 this.#unsubscribeTasksUpdated = onTasksUpdated(this.#debouncedFetch)
67 // Fire-and-forget: subscribe is called post-commit (not in render),
68 // and the store notifies subscribers when the fetch resolves.
69 void this.#fetch()
70 }
71 let unsubscribed = false
72 return () => {
73 if (unsubscribed) return
74 unsubscribed = true
75 unsubscribe()
76 this.#subscriberCount--
77 if (this.#subscriberCount === 0) this.#stop()
78 }
79 }
80
81 #notify(): void {
82 this.#changed.emit()
83 }
84
85 /**
86 * Point the file watcher at the current tasks directory. Called on start

Callers

nothing calls this directly

Calls 8

#stopMethod · 0.95
#rewatchMethod · 0.95
#clearHideTimerMethod · 0.95
#notifyMethod · 0.95
createSignalFunction · 0.85
getTaskListIdFunction · 0.85
getTasksDirFunction · 0.85
listTasksFunction · 0.85

Tested by

no test coverage detected