authorgravatar for encriptandost@gmail.comLeonel Rivas <encriptandost@gmail.com> 2026-08-08 14:02:51-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-08 17:02:51-04:00
log89ee692bf0436505d008c1d70215e70836eba4e2
tree4cdc065aa2725df741f2ff3dc7954193cd9b45a7
parenta20923ce463335e89e92f5983d98a180536e8e7d
signature Signed by PGP key B5690EEEBB952194

fix(web): persist diff view mode (#5731)


3 files changed, 36 insertions(+), 3 deletions(-)

apps/web/src/components/DiffPanel.tsx+2-2
...@@ -76,7 +76,6 @@ import { reviewEnvironment } from "../state/review";...@@ -76,7 +76,6 @@ import { reviewEnvironment } from "../state/review";
76import { vcsEnvironment } from "../state/vcs";76import { vcsEnvironment } from "../state/vcs";
77import { buildBaseRefChoices, filterBaseRefChoices } from "../lib/baseRefChoices";77import { buildBaseRefChoices, filterBaseRefChoices } from "../lib/baseRefChoices";
7878
79type DiffRenderMode = "stacked" | "split";
80type DiffThemeType = "light" | "dark";79type DiffThemeType = "light" | "dark";
81const AUTOMATIC_BASE_REF = "__automatic_base_ref__";80const AUTOMATIC_BASE_REF = "__automatic_base_ref__";
8281
...@@ -312,7 +311,8 @@ export default function DiffPanel({...@@ -312,7 +311,8 @@ export default function DiffPanel({
312 const { resolvedTheme } = useTheme();311 const { resolvedTheme } = useTheme();
313 const settings = useClientSettings();312 const settings = useClientSettings();
314 const [initialGitScope] = useState(initialGitScopeProp);313 const [initialGitScope] = useState(initialGitScopeProp);
315 const [diffRenderMode, setDiffRenderMode] = useState<DiffRenderMode>("stacked");314 const diffRenderMode = useDiffPanelStore((state) => state.diffRenderMode);
315 const setDiffRenderMode = useDiffPanelStore((state) => state.setDiffRenderMode);
316 const [wordWrap, setWordWrap] = useState(settings.wordWrap);316 const [wordWrap, setWordWrap] = useState(settings.wordWrap);
317 const [diffIgnoreWhitespace, setDiffIgnoreWhitespace] = useState(settings.diffIgnoreWhitespace);317 const [diffIgnoreWhitespace, setDiffIgnoreWhitespace] = useState(settings.diffIgnoreWhitespace);
318 const [baseRefQuery, setBaseRefQuery] = useState("");318 const [baseRefQuery, setBaseRefQuery] = useState("");
apps/web/src/diffPanelStore.test.ts+27-1
...@@ -7,7 +7,33 @@ import { selectThreadDiffPanelSelection, useDiffPanelStore } from "./diffPanelSt...@@ -7,7 +7,33 @@ import { selectThreadDiffPanelSelection, useDiffPanelStore } from "./diffPanelSt
7const THREAD_REF = scopeThreadRef(EnvironmentId.make("environment-1"), ThreadId.make("thread-1"));7const THREAD_REF = scopeThreadRef(EnvironmentId.make("environment-1"), ThreadId.make("thread-1"));
88
9describe("diffPanelStore", () => {9describe("diffPanelStore", () => {
10 beforeEach(() => useDiffPanelStore.setState({ byThreadKey: {}, branchBaseRefByThreadKey: {} }));10 beforeEach(() =>
11 useDiffPanelStore.setState({
12 byThreadKey: {},
13 branchBaseRefByThreadKey: {},
14 diffRenderMode: "stacked",
15 }),
16 );
17
18 it("keeps the selected render mode in panel and persisted state", async () => {
19 useDiffPanelStore.getState().setDiffRenderMode("split");
20
21 expect(useDiffPanelStore.getState().diffRenderMode).toBe("split");
22 expect(
23 useDiffPanelStore.persist.getOptions().partialize?.(useDiffPanelStore.getState()),
24 ).toMatchObject({ diffRenderMode: "split" });
25
26 const { name, storage } = useDiffPanelStore.persist.getOptions();
27 if (!name) throw new Error("Expected diff panel persistence to have a storage name");
28 const persisted = await storage?.getItem(name);
29 expect(persisted?.state).toMatchObject({ diffRenderMode: "split" });
30
31 useDiffPanelStore.setState({ diffRenderMode: "stacked" });
32 if (persisted) await storage?.setItem(name, persisted);
33 await useDiffPanelStore.persist.rehydrate();
34
35 expect(useDiffPanelStore.getState().diffRenderMode).toBe("split");
36 });
1137
12 it("defaults each thread to branch changes when the working tree is clean", () => {38 it("defaults each thread to branch changes when the working tree is clean", () => {
13 expect(39 expect(
apps/web/src/diffPanelStore.ts+7
...@@ -10,12 +10,16 @@ export type DiffPanelSelection =...@@ -10,12 +10,16 @@ export type DiffPanelSelection =
10 | { kind: "unstaged" }10 | { kind: "unstaged" }
11 | { kind: "turn"; turnId: TurnId; filePath: string | null; revealRequestId: number };11 | { kind: "turn"; turnId: TurnId; filePath: string | null; revealRequestId: number };
1212
13export type DiffRenderMode = "stacked" | "split";
14
13const DEFAULT_SELECTION: DiffPanelSelection = { kind: "branch", baseRef: null };15const DEFAULT_SELECTION: DiffPanelSelection = { kind: "branch", baseRef: null };
14const DEFAULT_WORKING_TREE_SELECTION: DiffPanelSelection = { kind: "unstaged" };16const DEFAULT_WORKING_TREE_SELECTION: DiffPanelSelection = { kind: "unstaged" };
1517
16interface DiffPanelStoreState {18interface DiffPanelStoreState {
17 byThreadKey: Record<string, DiffPanelSelection>;19 byThreadKey: Record<string, DiffPanelSelection>;
18 branchBaseRefByThreadKey: Record<string, string | null>;20 branchBaseRefByThreadKey: Record<string, string | null>;
21 diffRenderMode: DiffRenderMode;
22 setDiffRenderMode: (mode: DiffRenderMode) => void;
19 selectGitScope: (ref: ScopedThreadRef, scope: "branch" | "unstaged") => void;23 selectGitScope: (ref: ScopedThreadRef, scope: "branch" | "unstaged") => void;
20 selectBranchBaseRef: (ref: ScopedThreadRef, baseRef: string | null) => void;24 selectBranchBaseRef: (ref: ScopedThreadRef, baseRef: string | null) => void;
21 selectTurn: (ref: ScopedThreadRef, turnId: TurnId, filePath?: string) => void;25 selectTurn: (ref: ScopedThreadRef, turnId: TurnId, filePath?: string) => void;
...@@ -33,6 +37,8 @@ export const useDiffPanelStore = create<DiffPanelStoreState>()(...@@ -33,6 +37,8 @@ export const useDiffPanelStore = create<DiffPanelStoreState>()(
33 (set) => ({37 (set) => ({
34 byThreadKey: {},38 byThreadKey: {},
35 branchBaseRefByThreadKey: {},39 branchBaseRefByThreadKey: {},
40 diffRenderMode: "stacked",
41 setDiffRenderMode: (diffRenderMode) => set({ diffRenderMode }),
36 selectGitScope: (ref, scope) =>42 selectGitScope: (ref, scope) =>
37 set((state) => {43 set((state) => {
38 const threadKey = scopedThreadKey(ref);44 const threadKey = scopedThreadKey(ref);
...@@ -126,6 +132,7 @@ export const useDiffPanelStore = create<DiffPanelStoreState>()(...@@ -126,6 +132,7 @@ export const useDiffPanelStore = create<DiffPanelStoreState>()(
126 partialize: (state) => ({132 partialize: (state) => ({
127 byThreadKey: state.byThreadKey,133 byThreadKey: state.byThreadKey,
128 branchBaseRefByThreadKey: state.branchBaseRefByThreadKey,134 branchBaseRefByThreadKey: state.branchBaseRefByThreadKey,
135 diffRenderMode: state.diffRenderMode,
129 }),136 }),
130 },137 },
131 ),138 ),