authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-19 01:34:11-08:00
committergravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-19 01:34:11-08:00
logb253d1f8ca8f107933034ed76abc2cb20dbaa52b
tree7aab4114f91af36a1a8a1fc446d7c21c0f867fb5
parent564474f92711bbd496376b8abe4ee7500c2a8d5f

Retarget diff panel to active route thread on visit

- Prefer route thread id over stored diff thread when rendering diffs - When visiting a different thread with diff open, retarget diff state and clear stale turn/file selection - Add reducer test coverage for open-diff retargeting behavior

3 files changed, 58 insertions(+), 1 deletions(-)

apps/web/src/components/DiffPanel.tsx+12-1
......@@ -107,7 +107,7 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {
107107 const patchViewportRef = useRef<HTMLDivElement>(null);
108108 const params = useParams({ strict: false });
109109 const routeThreadId = typeof params.threadId === "string" ? params.threadId : null;
110 const activeThreadId = state.diffThreadId ?? routeThreadId;
110 const activeThreadId = routeThreadId ?? state.diffThreadId;
111111 const activeThread = state.threads.find((thread) => thread.id === activeThreadId);
112112 const activeThreadRuntimeId =
113113 activeThread?.codexThreadId ?? activeThread?.session?.threadId ?? null;
......@@ -216,6 +216,17 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {
216216 target?.scrollIntoView({ block: "nearest" });
217217 }, [selectedFilePath, renderableFiles]);
218218
219 useEffect(() => {
220 if (!state.diffOpen) return;
221 if (!routeThreadId) return;
222 if (state.diffThreadId === routeThreadId) return;
223
224 dispatch({
225 type: "SET_DIFF_TARGET",
226 threadId: routeThreadId,
227 });
228 }, [dispatch, routeThreadId, state.diffOpen, state.diffThreadId]);
229
219230 const selectTurn = (turnId: string) => {
220231 if (!activeThread) return;
221232 dispatch({
apps/web/src/store.test.ts+42
......@@ -833,6 +833,48 @@ describe("store reducer thread continuity", () => {
833833 expect(next.threads[0]?.lastVisitedAt).toBe(nextVisitedAt);
834834 });
835835
836 it("retargets open diff state to the visited thread", () => {
837 const state: AppState = {
838 ...makeState(
839 makeThread({
840 id: "thread-local-1",
841 latestTurnCompletedAt: "2026-02-08T10:00:10.000Z",
842 lastVisitedAt: "2026-02-08T10:00:00.000Z",
843 }),
844 ),
845 threads: [
846 makeThread({
847 id: "thread-local-1",
848 latestTurnCompletedAt: "2026-02-08T10:00:10.000Z",
849 lastVisitedAt: "2026-02-08T10:00:00.000Z",
850 }),
851 makeThread({
852 id: "thread-local-2",
853 session: makeSession({ sessionId: "sess-2" }),
854 latestTurnCompletedAt: "2026-02-08T09:00:10.000Z",
855 lastVisitedAt: "2026-02-08T09:00:00.000Z",
856 }),
857 ],
858 diffOpen: true,
859 diffThreadId: "thread-local-1",
860 diffTurnId: "turn-1",
861 diffFilePath: "src/old.ts",
862 };
863
864 const nextVisitedAt = "2026-02-08T10:00:20.000Z";
865 const next = reducer(state, {
866 type: "MARK_THREAD_VISITED",
867 threadId: "thread-local-2",
868 visitedAt: nextVisitedAt,
869 });
870
871 expect(next.threads[1]?.lastVisitedAt).toBe(nextVisitedAt);
872 expect(next.diffOpen).toBe(true);
873 expect(next.diffThreadId).toBe("thread-local-2");
874 expect(next.diffTurnId).toBeNull();
875 expect(next.diffFilePath).toBeNull();
876 });
877
836878 it("reverts thread state to a checkpoint snapshot", () => {
837879 const state = makeState(
838880 makeThread({
apps/web/src/store.ts+4
......@@ -1078,12 +1078,16 @@ export function reducer(state: AppState, action: Action): AppState {
10781078
10791079 case "MARK_THREAD_VISITED": {
10801080 const visitedAt = action.visitedAt ?? new Date().toISOString();
1081 const shouldRetargetOpenDiff = state.diffOpen && state.diffThreadId !== action.threadId;
10811082 return {
10821083 ...state,
10831084 threads: updateThread(state.threads, action.threadId, (t) => ({
10841085 ...t,
10851086 lastVisitedAt: visitedAt,
10861087 })),
1088 diffThreadId: shouldRetargetOpenDiff ? action.threadId : state.diffThreadId,
1089 diffTurnId: shouldRetargetOpenDiff ? null : state.diffTurnId,
1090 diffFilePath: shouldRetargetOpenDiff ? null : state.diffFilePath,
10871091 };
10881092 }
10891093