1import type { EnvironmentId, OrchestrationGetTurnDiffResult, ThreadId } from "@t3tools/contracts";
2import * as Effect from "effect/Effect";
3import { Atom, type AtomRegistry } from "effect/reactivity";
4
5const EMPTY_TURN_DIFFS = Object.freeze<Readonly<Record<number, string>>>({});
6
7// The last answered diff of each single turn, keyed by its turn count, so review keeps showing
8// turns it already loaded while the environment is disconnected and no turn query is mounted.
9const lastTurnDiffsFamily = Atom.family((key: string) =>
10 Atom.make(EMPTY_TURN_DIFFS).pipe(
11 Atom.keepAlive,
12 Atom.withLabel(`mobile:review:turn-diffs:${key}`),
13 ),
14);
15
16export function lastReviewTurnDiffsAtom(environmentId: EnvironmentId, threadId: ThreadId) {
17 return lastTurnDiffsFamily(JSON.stringify([environmentId, threadId]));
18}
19
20export const recordReviewTurnDiffAnswer = (
21 target: {
22 readonly environmentId: EnvironmentId;
23 readonly input: { readonly ignoreWhitespace?: boolean };
24 },
25 value: OrchestrationGetTurnDiffResult,
26 registry: AtomRegistry.AtomRegistry,
27) =>
28 Effect.sync(() => {
29 // Review shows one turn with whitespace changes; other ranges and whitespace-ignoring
30 // reads are not what a review section displays.
31 if (target.input.ignoreWhitespace === true || value.fromTurnCount !== value.toTurnCount - 1) {
32 return;
33 }
34 const atom = lastReviewTurnDiffsAtom(target.environmentId, value.threadId);
35 const current = registry.get(atom);
36 if (current[value.toTurnCount] === value.diff) return;
37 registry.set(atom, { ...current, [value.toTurnCount]: value.diff });
38 });