Skip to content
Source
Sign inSign up

FileMetadataThreadProvider.tsx

63 lines2.8 KB

1import { useAtomValue } from "@effect/atom-react";
2import type { OrchestrationV2ThreadProjection, ScopedThreadRef } from "@t3tools/contracts";
3import { resolveWorkspaceFilePath } from "@t3tools/shared/path";
4import { Atom } from "effect/reactivity";
5import { createContext, useEffect, useRef, type ReactNode } from "react";
6​
7import { projectEnvironment } from "../state/projects";
8import { useAtomCommand } from "../state/use-atom-command";
9​
10export const FileMetadataThreadContext = createContext<ScopedThreadRef | null>(null);
11const EMPTY_RETENTION = Atom.make(undefined);
12​
13/** Keeps fetched metadata across virtualized message-row unmounts. */
14export function FileMetadataThreadProvider(props: {
15 threadRef: ScopedThreadRef | null;
16 cwd: string | undefined;
17 checkpoints: OrchestrationV2ThreadProjection["checkpoints"] | undefined;
18 children?: ReactNode;
19}) {
20 const { threadRef, cwd, checkpoints, children } = props;
21 useAtomValue(threadRef ? projectEnvironment.fileMetadataThread(threadRef) : EMPTY_RETENTION);
22 const invalidate = useAtomCommand(projectEnvironment.invalidateFileMetadata, {
23 reportFailure: false,
24 });
25 const previous = useRef<{
26 environmentId: ScopedThreadRef["environmentId"];
27 threadId: ScopedThreadRef["threadId"];
28 cwd: string;
29 checkpoints: OrchestrationV2ThreadProjection["checkpoints"];
30 } | null>(null);
31​
32 // oxlint-disable-next-line t3code/no-unreasoned-state-sync -- invalidates the server file metadata cache for files whose checkpoints changed
33 useEffect(() => {
34 if (!threadRef || !cwd || !checkpoints) {
35 previous.current = null;
36 return;
37 }
38 const before = previous.current;
39 previous.current = { ...threadRef, cwd, checkpoints };
40 if (
41 !before ||
42 before.environmentId !== threadRef.environmentId ||
43 before.threadId !== threadRef.threadId ||
44 before.cwd !== cwd
45 )
46 return;
47 const oldById = new Map(before.checkpoints.map((checkpoint) => [checkpoint.id, checkpoint]));
48 const paths = new Set<string>();
49 for (const checkpoint of checkpoints) {
50 const old = oldById.get(checkpoint.id);
51 oldById.delete(checkpoint.id);
52 if (old?.files === checkpoint.files && old?.status === checkpoint.status) continue;
53 for (const file of checkpoint.files) paths.add(resolveWorkspaceFilePath(file.path, cwd));
54 for (const file of old?.files ?? []) paths.add(resolveWorkspaceFilePath(file.path, cwd));
55 }
56 for (const checkpoint of oldById.values())
57 for (const file of checkpoint.files) paths.add(resolveWorkspaceFilePath(file.path, cwd));
58 if (paths.size > 0)
59 void invalidate({ environmentId: threadRef.environmentId, input: { paths: [...paths] } });
60 }, [threadRef, cwd, checkpoints, invalidate]);
61​
62 return <FileMetadataThreadContext value={threadRef}>{children}</FileMetadataThreadContext>;