1import { useAtomValue } from "@effect/atom-react";
2import type { EnvironmentId, ProjectId } from "@t3tools/contracts";
3import { Atom } from "effect/reactivity";
4
5import { environmentThreadShells, olderSettledThreads } from "./threads";
6
7/**
8 * Whether a project has any thread, loaded or only counted as older settled.
9 * A per-project boolean, so readers re-render when it flips, not on every
10 * thread-shell change (which is frequent while agents work).
11 */
12export function makeProjectHasThreadsAtom(sources: {
13 readonly threadRefsByProject: (
14 environmentId: EnvironmentId,
15 ) => Atom.Atom<ReadonlyMap<ProjectId, ReadonlyArray<unknown>>>;
16 readonly olderSettled: (
17 environmentId: EnvironmentId,
18 ) => Atom.Atom<{ readonly counts: ReadonlyMap<ProjectId, number> } | null>;
19}) {
20 return Atom.family((environmentId: EnvironmentId) =>
21 Atom.family((projectId: ProjectId) =>
22 Atom.make(
23 (get) =>
24 (get(sources.olderSettled(environmentId))?.counts.get(projectId) ?? 0) > 0 ||
25 (get(sources.threadRefsByProject(environmentId)).get(projectId)?.length ?? 0) > 0,
26 ).pipe(Atom.withLabel(`mobile:project-has-threads:${environmentId}:${projectId}`)),
27 ),
28 );
29}
30
31const projectHasThreadsAtom = makeProjectHasThreadsAtom({
32 threadRefsByProject: environmentThreadShells.environmentThreadRefsByProjectAtom,
33 olderSettled: olderSettledThreads.olderSettledAtom,
34});
35
36const NO_PROJECT_HAS_THREADS = Atom.make(false).pipe(
37 Atom.withLabel("mobile:project-has-threads:none"),
38);
39
40export function useProjectHasThreads(
41 project: { readonly environmentId: EnvironmentId; readonly id: ProjectId } | null,
42): boolean {
43 return useAtomValue(
44 project === null
45 ? NO_PROJECT_HAS_THREADS
46 : projectHasThreadsAtom(project.environmentId)(project.id),
47 );
48}