From 6aea858ed85fc3e8e4b41d6164b428a80895dcc7 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 18 Feb 2026 01:30:38 -0800 Subject: [PATCH] Scope checkpoint diff cache keys to avoid collisions - Include `cacheScope` in checkpoint diff query keys - Pass turn/conversation-specific scopes from `ChatView` and `DiffPanel` - Add tests for key separation and API argument forwarding --- apps/web/src/components/ChatView.tsx | 1 + apps/web/src/components/DiffPanel.tsx | 8 ++++ apps/web/src/lib/providerReactQuery.test.ts | 53 +++++++++++++++++++++ apps/web/src/lib/providerReactQuery.ts | 2 + 4 files changed, 64 insertions(+) create mode 100644 apps/web/src/lib/providerReactQuery.test.ts diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index a8efd1e7fea448f4e4b967ce631648348dfaa88c..f01f226cc28629ec9bb18c893df7ba644e3c58b3 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -529,6 +529,7 @@ export default function ChatView() { threadRuntimeId: activeThreadRuntimeId, fromTurnCount: target.fromTurnCount, toTurnCount: target.toTurnCount, + cacheScope: `turn:${target.turnId}`, }), ), }); diff --git a/apps/web/src/components/DiffPanel.tsx b/apps/web/src/components/DiffPanel.tsx index 638c5dc7033cad5577765557b31578df7751ffff..c6ad5eabc4cab01e611ff54dc9887bd2e9171e0f 100644 --- a/apps/web/src/components/DiffPanel.tsx +++ b/apps/web/src/components/DiffPanel.tsx @@ -138,12 +138,19 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { const activeCheckpointRange = selectedTurn ? selectedCheckpointRange : conversationCheckpointRange; + const conversationCacheScope = useMemo(() => { + if (selectedTurn || turnDiffSummaries.length === 0) { + return null; + } + return `conversation:${turnDiffSummaries.map((summary) => summary.turnId).join(",")}`; + }, [selectedTurn, turnDiffSummaries]); const activeCheckpointDiffQuery = useQuery( checkpointDiffQueryOptions(api, { sessionId: activeSessionId, threadRuntimeId: activeThreadRuntimeId, fromTurnCount: activeCheckpointRange?.fromTurnCount ?? null, toTurnCount: activeCheckpointRange?.toTurnCount ?? null, + cacheScope: selectedTurn ? `turn:${selectedTurn.turnId}` : conversationCacheScope, }), ); const selectedTurnCheckpointDiff = selectedTurn @@ -184,6 +191,7 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { threadRuntimeId: activeThreadRuntimeId, fromTurnCount: Math.max(0, checkpointTurnCount - 1), toTurnCount: checkpointTurnCount, + cacheScope: `turn:${summary.turnId}`, }), )?.diff; if (checkpointPatch) { diff --git a/apps/web/src/lib/providerReactQuery.test.ts b/apps/web/src/lib/providerReactQuery.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..45d822aae8e44686f4259223ef776601976ed7f3 --- /dev/null +++ b/apps/web/src/lib/providerReactQuery.test.ts @@ -0,0 +1,53 @@ +import type { NativeApi } from "@t3tools/contracts"; +import { describe, expect, it, vi } from "vitest"; +import { checkpointDiffQueryOptions, providerQueryKeys } from "./providerReactQuery"; + +describe("providerQueryKeys.checkpointDiff", () => { + it("includes cacheScope so reused turn counts do not collide", () => { + const baseInput = { + sessionId: "session-id", + threadRuntimeId: "thread-id", + fromTurnCount: 1, + toTurnCount: 2, + } as const; + + expect( + providerQueryKeys.checkpointDiff({ + ...baseInput, + cacheScope: "turn:old-turn", + }), + ).not.toEqual( + providerQueryKeys.checkpointDiff({ + ...baseInput, + cacheScope: "turn:new-turn", + }), + ); + }); +}); + +describe("checkpointDiffQueryOptions", () => { + it("forwards checkpoint range to the provider API", async () => { + const getCheckpointDiff = vi.fn().mockResolvedValue({ diff: "patch" }); + const api = { + providers: { + getCheckpointDiff, + }, + } as unknown as NativeApi; + + const options = checkpointDiffQueryOptions(api, { + sessionId: "session-id", + threadRuntimeId: "thread-id", + fromTurnCount: 3, + toTurnCount: 4, + cacheScope: "turn:abc", + }); + + await options.queryFn(); + + expect(getCheckpointDiff).toHaveBeenCalledWith({ + sessionId: "session-id", + fromTurnCount: 3, + toTurnCount: 4, + }); + }); +}); diff --git a/apps/web/src/lib/providerReactQuery.ts b/apps/web/src/lib/providerReactQuery.ts index 31f6bdf1e1fa445ba34cc4b481d0da1395c24e2e..1dc837b7bcf4d34a66ad8704df9cf1b1d627d72b 100644 --- a/apps/web/src/lib/providerReactQuery.ts +++ b/apps/web/src/lib/providerReactQuery.ts @@ -6,6 +6,7 @@ export interface CheckpointDiffQueryInput { threadRuntimeId: string | null; fromTurnCount: number | null; toTurnCount: number | null; + cacheScope?: string | null; } export const providerQueryKeys = { @@ -18,6 +19,7 @@ export const providerQueryKeys = { input.threadRuntimeId, input.fromTurnCount, input.toTurnCount, + input.cacheScope ?? null, ] as const, }; -- 2.54.0