authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-18 01:30:38-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-02-18 21:59:17-08:00
log6aea858ed85fc3e8e4b41d6164b428a80895dcc7
tree5b874cdb67a5f7bb0aabdb7f5a996115d94126b0
parentf07b6ac8e6d17efde13fe3135bb52159962b5bdc

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

4 files changed, 64 insertions(+), 0 deletions(-)

apps/web/src/components/ChatView.tsx+1
...@@ -529,6 +529,7 @@ export default function ChatView() {...@@ -529,6 +529,7 @@ export default function ChatView() {
529 threadRuntimeId: activeThreadRuntimeId,529 threadRuntimeId: activeThreadRuntimeId,
530 fromTurnCount: target.fromTurnCount,530 fromTurnCount: target.fromTurnCount,
531 toTurnCount: target.toTurnCount,531 toTurnCount: target.toTurnCount,
532 cacheScope: `turn:${target.turnId}`,
532 }),533 }),
533 ),534 ),
534 });535 });
apps/web/src/components/DiffPanel.tsx+8
...@@ -138,12 +138,19 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {...@@ -138,12 +138,19 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {
138 const activeCheckpointRange = selectedTurn138 const activeCheckpointRange = selectedTurn
139 ? selectedCheckpointRange139 ? selectedCheckpointRange
140 : conversationCheckpointRange;140 : conversationCheckpointRange;
141 const conversationCacheScope = useMemo(() => {
142 if (selectedTurn || turnDiffSummaries.length === 0) {
143 return null;
144 }
145 return `conversation:${turnDiffSummaries.map((summary) => summary.turnId).join(",")}`;
146 }, [selectedTurn, turnDiffSummaries]);
141 const activeCheckpointDiffQuery = useQuery(147 const activeCheckpointDiffQuery = useQuery(
142 checkpointDiffQueryOptions(api, {148 checkpointDiffQueryOptions(api, {
143 sessionId: activeSessionId,149 sessionId: activeSessionId,
144 threadRuntimeId: activeThreadRuntimeId,150 threadRuntimeId: activeThreadRuntimeId,
145 fromTurnCount: activeCheckpointRange?.fromTurnCount ?? null,151 fromTurnCount: activeCheckpointRange?.fromTurnCount ?? null,
146 toTurnCount: activeCheckpointRange?.toTurnCount ?? null,152 toTurnCount: activeCheckpointRange?.toTurnCount ?? null,
153 cacheScope: selectedTurn ? `turn:${selectedTurn.turnId}` : conversationCacheScope,
147 }),154 }),
148 );155 );
149 const selectedTurnCheckpointDiff = selectedTurn156 const selectedTurnCheckpointDiff = selectedTurn
...@@ -184,6 +191,7 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {...@@ -184,6 +191,7 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {
184 threadRuntimeId: activeThreadRuntimeId,191 threadRuntimeId: activeThreadRuntimeId,
185 fromTurnCount: Math.max(0, checkpointTurnCount - 1),192 fromTurnCount: Math.max(0, checkpointTurnCount - 1),
186 toTurnCount: checkpointTurnCount,193 toTurnCount: checkpointTurnCount,
194 cacheScope: `turn:${summary.turnId}`,
187 }),195 }),
188 )?.diff;196 )?.diff;
189 if (checkpointPatch) {197 if (checkpointPatch) {
apps/web/src/lib/providerReactQuery.test.ts created+53
...@@ -0,0 +1,53 @@
1import type { NativeApi } from "@t3tools/contracts";
2import { describe, expect, it, vi } from "vitest";
3import { checkpointDiffQueryOptions, providerQueryKeys } from "./providerReactQuery";
4
5describe("providerQueryKeys.checkpointDiff", () => {
6 it("includes cacheScope so reused turn counts do not collide", () => {
7 const baseInput = {
8 sessionId: "session-id",
9 threadRuntimeId: "thread-id",
10 fromTurnCount: 1,
11 toTurnCount: 2,
12 } as const;
13
14 expect(
15 providerQueryKeys.checkpointDiff({
16 ...baseInput,
17 cacheScope: "turn:old-turn",
18 }),
19 ).not.toEqual(
20 providerQueryKeys.checkpointDiff({
21 ...baseInput,
22 cacheScope: "turn:new-turn",
23 }),
24 );
25 });
26});
27
28describe("checkpointDiffQueryOptions", () => {
29 it("forwards checkpoint range to the provider API", async () => {
30 const getCheckpointDiff = vi.fn().mockResolvedValue({ diff: "patch" });
31 const api = {
32 providers: {
33 getCheckpointDiff,
34 },
35 } as unknown as NativeApi;
36
37 const options = checkpointDiffQueryOptions(api, {
38 sessionId: "session-id",
39 threadRuntimeId: "thread-id",
40 fromTurnCount: 3,
41 toTurnCount: 4,
42 cacheScope: "turn:abc",
43 });
44
45 await options.queryFn();
46
47 expect(getCheckpointDiff).toHaveBeenCalledWith({
48 sessionId: "session-id",
49 fromTurnCount: 3,
50 toTurnCount: 4,
51 });
52 });
53});
apps/web/src/lib/providerReactQuery.ts+2
...@@ -6,6 +6,7 @@ export interface CheckpointDiffQueryInput {...@@ -6,6 +6,7 @@ export interface CheckpointDiffQueryInput {
6 threadRuntimeId: string | null;6 threadRuntimeId: string | null;
7 fromTurnCount: number | null;7 fromTurnCount: number | null;
8 toTurnCount: number | null;8 toTurnCount: number | null;
9 cacheScope?: string | null;
9}10}
1011
11export const providerQueryKeys = {12export const providerQueryKeys = {
...@@ -18,6 +19,7 @@ export const providerQueryKeys = {...@@ -18,6 +19,7 @@ export const providerQueryKeys = {
18 input.threadRuntimeId,19 input.threadRuntimeId,
19 input.fromTurnCount,20 input.fromTurnCount,
20 input.toTurnCount,21 input.toTurnCount,
22 input.cacheScope ?? null,
21 ] as const,23 ] as const,
22};24};
2325