diff --git a/apps/web/src/components/DiffPanel.tsx b/apps/web/src/components/DiffPanel.tsx index a2678e03fcd39cd083a724f01645e5a492131f5e..43b19188671f31b4914797125cd4a413c9403a82 100644 --- a/apps/web/src/components/DiffPanel.tsx +++ b/apps/web/src/components/DiffPanel.tsx @@ -107,7 +107,7 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { const patchViewportRef = useRef(null); const params = useParams({ strict: false }); const routeThreadId = typeof params.threadId === "string" ? params.threadId : null; - const activeThreadId = state.diffThreadId ?? routeThreadId; + const activeThreadId = routeThreadId ?? state.diffThreadId; const activeThread = state.threads.find((thread) => thread.id === activeThreadId); const activeThreadRuntimeId = activeThread?.codexThreadId ?? activeThread?.session?.threadId ?? null; @@ -216,6 +216,17 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { target?.scrollIntoView({ block: "nearest" }); }, [selectedFilePath, renderableFiles]); + useEffect(() => { + if (!state.diffOpen) return; + if (!routeThreadId) return; + if (state.diffThreadId === routeThreadId) return; + + dispatch({ + type: "SET_DIFF_TARGET", + threadId: routeThreadId, + }); + }, [dispatch, routeThreadId, state.diffOpen, state.diffThreadId]); + const selectTurn = (turnId: string) => { if (!activeThread) return; dispatch({ diff --git a/apps/web/src/store.test.ts b/apps/web/src/store.test.ts index 5b3c7a81117c465e86caa5a4ad7579b87c0886aa..e0fa075b3097aabc70f0082dae68473dbc8a3c9f 100644 --- a/apps/web/src/store.test.ts +++ b/apps/web/src/store.test.ts @@ -833,6 +833,48 @@ describe("store reducer thread continuity", () => { expect(next.threads[0]?.lastVisitedAt).toBe(nextVisitedAt); }); + it("retargets open diff state to the visited thread", () => { + const state: AppState = { + ...makeState( + makeThread({ + id: "thread-local-1", + latestTurnCompletedAt: "2026-02-08T10:00:10.000Z", + lastVisitedAt: "2026-02-08T10:00:00.000Z", + }), + ), + threads: [ + makeThread({ + id: "thread-local-1", + latestTurnCompletedAt: "2026-02-08T10:00:10.000Z", + lastVisitedAt: "2026-02-08T10:00:00.000Z", + }), + makeThread({ + id: "thread-local-2", + session: makeSession({ sessionId: "sess-2" }), + latestTurnCompletedAt: "2026-02-08T09:00:10.000Z", + lastVisitedAt: "2026-02-08T09:00:00.000Z", + }), + ], + diffOpen: true, + diffThreadId: "thread-local-1", + diffTurnId: "turn-1", + diffFilePath: "src/old.ts", + }; + + const nextVisitedAt = "2026-02-08T10:00:20.000Z"; + const next = reducer(state, { + type: "MARK_THREAD_VISITED", + threadId: "thread-local-2", + visitedAt: nextVisitedAt, + }); + + expect(next.threads[1]?.lastVisitedAt).toBe(nextVisitedAt); + expect(next.diffOpen).toBe(true); + expect(next.diffThreadId).toBe("thread-local-2"); + expect(next.diffTurnId).toBeNull(); + expect(next.diffFilePath).toBeNull(); + }); + it("reverts thread state to a checkpoint snapshot", () => { const state = makeState( makeThread({ diff --git a/apps/web/src/store.ts b/apps/web/src/store.ts index f470a8c83bd5dba8c8eaa3a850e3e6e7c8bc60ab..2784547fe2436413b0db8403f34ec1464491b563 100644 --- a/apps/web/src/store.ts +++ b/apps/web/src/store.ts @@ -1078,12 +1078,16 @@ export function reducer(state: AppState, action: Action): AppState { case "MARK_THREAD_VISITED": { const visitedAt = action.visitedAt ?? new Date().toISOString(); + const shouldRetargetOpenDiff = state.diffOpen && state.diffThreadId !== action.threadId; return { ...state, threads: updateThread(state.threads, action.threadId, (t) => ({ ...t, lastVisitedAt: visitedAt, })), + diffThreadId: shouldRetargetOpenDiff ? action.threadId : state.diffThreadId, + diffTurnId: shouldRetargetOpenDiff ? null : state.diffTurnId, + diffFilePath: shouldRetargetOpenDiff ? null : state.diffFilePath, }; }