History

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

Julius Marminge committed 8 months ago

Commit b253d1f1 parent 564474f3 files · +58 −1Browse files

Unified · Split

3 files
  1. apps/web/src/components/DiffPanel.tsx +12 −1
  2. apps/web/src/store.test.ts +42 −0
  3. apps/web/src/store.ts +4 −0
apps/web/src/components/DiffPanel.tsx+12−1
@@ -107,7 +107,7 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { · 106 unmodified lines
107 const patchViewportRef = useRef<HTMLDivElement>(null);107 const patchViewportRef = useRef<HTMLDivElement>(null);
108 const params = useParams({ strict: false });108 const params = useParams({ strict: false });
109 const routeThreadId = typeof params.threadId === "string" ? params.threadId : null;109 const routeThreadId = typeof params.threadId === "string" ? params.threadId : null;
110 const activeThreadId = state.diffThreadId ?? routeThreadId;110 const activeThreadId = routeThreadId ?? state.diffThreadId;
111 const activeThread = state.threads.find((thread) => thread.id === activeThreadId);111 const activeThread = state.threads.find((thread) => thread.id === activeThreadId);
112 const activeThreadRuntimeId =112 const activeThreadRuntimeId =
113 activeThread?.codexThreadId ?? activeThread?.session?.threadId ?? null;113 activeThread?.codexThreadId ?? activeThread?.session?.threadId ?? null;
@@ -216,6 +216,17 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { · 102 unmodified lines
216 target?.scrollIntoView({ block: "nearest" });216 target?.scrollIntoView({ block: "nearest" });
217 }, [selectedFilePath, renderableFiles]);217 }, [selectedFilePath, renderableFiles]);
218 218
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
219 const selectTurn = (turnId: string) => {230 const selectTurn = (turnId: string) => {
220 if (!activeThread) return;231 if (!activeThread) return;
221 dispatch({232 dispatch({
apps/web/src/store.test.ts+42−0
@@ -833,6 +833,48 @@ describe("store reducer thread continuity", () => { · 832 unmodified lines
833 expect(next.threads[0]?.lastVisitedAt).toBe(nextVisitedAt);833 expect(next.threads[0]?.lastVisitedAt).toBe(nextVisitedAt);
834 });834 });
835 835
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
836 it("reverts thread state to a checkpoint snapshot", () => {878 it("reverts thread state to a checkpoint snapshot", () => {
837 const state = makeState(879 const state = makeState(
838 makeThread({880 makeThread({
apps/web/src/store.ts+4−0
@@ -1078,12 +1078,16 @@ export function reducer(state: AppState, action: Action): AppState { · 1077 unmodified lines
1078 1078
1079 case "MARK_THREAD_VISITED": {1079 case "MARK_THREAD_VISITED": {
1080 const visitedAt = action.visitedAt ?? new Date().toISOString();1080 const visitedAt = action.visitedAt ?? new Date().toISOString();
1081 const shouldRetargetOpenDiff = state.diffOpen && state.diffThreadId !== action.threadId;
1081 return {1082 return {
1082 ...state,1083 ...state,
1083 threads: updateThread(state.threads, action.threadId, (t) => ({1084 threads: updateThread(state.threads, action.threadId, (t) => ({
1084 ...t,1085 ...t,
1085 lastVisitedAt: visitedAt,1086 lastVisitedAt: visitedAt,
1086 })),1087 })),
1088 diffThreadId: shouldRetargetOpenDiff ? action.threadId : state.diffThreadId,
1089 diffTurnId: shouldRetargetOpenDiff ? null : state.diffTurnId,
1090 diffFilePath: shouldRetargetOpenDiff ? null : state.diffFilePath,
1087 };1091 };
1088 }1092 }
1089 1093

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t