authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-02 20:16:25-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-02 20:16:25-07:00
log9409dd20a9fbce491d49d09c79b289d8fb8bfe3e
treee9e39232d05cca883563024b7ce8bb2c15aa4741
parent12f1fc427efad4b683835a72bda6f74bebb5d641
signature Signed by PGP key B5690EEEBB952194

fix(web): make the diff layout toggle a persisted setting (#9326)

Co-authored-by: Claude Code <noreply@anthropic.com>

9 files changed, 83 insertions(+), 48 deletions(-)

apps/desktop/src/settings/DesktopClientSettings.test.ts+1
...@@ -29,6 +29,7 @@ const clientSettings: ClientSettings = {...@@ -29,6 +29,7 @@ const clientSettings: ClientSettings = {
29 contextWindowMeterEnabled: false,29 contextWindowMeterEnabled: false,
30 dismissedProviderUpdateNotificationKeys: [],30 dismissedProviderUpdateNotificationKeys: [],
31 diffIgnoreWhitespace: true,31 diffIgnoreWhitespace: true,
32 diffLayout: "stacked",
32 environmentIdentificationMode: "artwork",33 environmentIdentificationMode: "artwork",
33 favorites: [],34 favorites: [],
34 fontFamilyCode: "",35 fontFamilyCode: "",
apps/web/src/clientPersistenceStorage.test.ts+13
...@@ -90,4 +90,17 @@ describe("clientPersistenceStorage", () => {...@@ -90,4 +90,17 @@ describe("clientPersistenceStorage", () => {
90 expect(settings).not.toHaveProperty("chatWordWrap");90 expect(settings).not.toHaveProperty("chatWordWrap");
91 expect(settings).not.toHaveProperty("diffWordWrap");91 expect(settings).not.toHaveProperty("diffWordWrap");
92 });92 });
93
94 it("keeps the diff layout across reloads and defaults it to stacked", async () => {
95 const testWindow = getTestWindow();
96 const { readBrowserClientSettings, writeBrowserClientSettings } =
97 await import("./clientPersistenceStorage");
98
99 expect(readBrowserClientSettings()).toBeNull();
100 testWindow.localStorage.setItem("t3code:client-settings:v1", JSON.stringify({}));
101 expect(readBrowserClientSettings()?.diffLayout).toBe("stacked");
102
103 writeBrowserClientSettings({ ...DEFAULT_CLIENT_SETTINGS, diffLayout: "split" });
104 expect(readBrowserClientSettings()?.diffLayout).toBe("split");
105 });
93});106});
apps/web/src/components/DiffPanel.tsx+6-6
...@@ -44,7 +44,7 @@ import { useTurnDiffSummaries } from "../hooks/useTurnDiffSummaries";...@@ -44,7 +44,7 @@ import { useTurnDiffSummaries } from "../hooks/useTurnDiffSummaries";
44import { useWorkspaceMutationRefresh } from "../hooks/useWorkspaceMutationRefresh";44import { useWorkspaceMutationRefresh } from "../hooks/useWorkspaceMutationRefresh";
45import { useProject, useThread } from "../state/entities";45import { useProject, useThread } from "../state/entities";
46import { resolveThreadRouteRef } from "../threadRoutes";46import { resolveThreadRouteRef } from "../threadRoutes";
47import { useClientSettings } from "../hooks/useSettings";47import { useClientSettings, useUpdateClientSettings } from "../hooks/useSettings";
48import { formatShortTimestamp } from "../timestampFormat";48import { formatShortTimestamp } from "../timestampFormat";
49import { DiffFilePathCopyButton } from "./DiffFilePathCopyButton";49import { DiffFilePathCopyButton } from "./DiffFilePathCopyButton";
50import { DiffPanelLoadingState, DiffPanelShell, type DiffPanelMode } from "./DiffPanelShell";50import { DiffPanelLoadingState, DiffPanelShell, type DiffPanelMode } from "./DiffPanelShell";
...@@ -108,8 +108,8 @@ export default function DiffPanel({...@@ -108,8 +108,8 @@ export default function DiffPanel({
108 const { resolvedTheme } = useTheme();108 const { resolvedTheme } = useTheme();
109 const settings = useClientSettings();109 const settings = useClientSettings();
110 const [initialGitScope] = useState(initialGitScopeProp);110 const [initialGitScope] = useState(initialGitScopeProp);
111 const diffRenderMode = useDiffPanelStore((state) => state.diffRenderMode);111 const diffLayout = settings.diffLayout;
112 const setDiffRenderMode = useDiffPanelStore((state) => state.setDiffRenderMode);112 const updateClientSettings = useUpdateClientSettings();
113 const [wordWrap, setWordWrap] = useState(settings.wordWrap);113 const [wordWrap, setWordWrap] = useState(settings.wordWrap);
114 const [diffIgnoreWhitespace, setDiffIgnoreWhitespace] = useState(settings.diffIgnoreWhitespace);114 const [diffIgnoreWhitespace, setDiffIgnoreWhitespace] = useState(settings.diffIgnoreWhitespace);
115 const [baseRefQuery, setBaseRefQuery] = useState("");115 const [baseRefQuery, setBaseRefQuery] = useState("");
...@@ -768,11 +768,11 @@ export default function DiffPanel({...@@ -768,11 +768,11 @@ export default function DiffPanel({
768 <ToggleGroup768 <ToggleGroup
769 className="shrink-0 gap-1"769 className="shrink-0 gap-1"
770 size="sm"770 size="sm"
771 value={[diffRenderMode]}771 value={[diffLayout]}
772 onValueChange={(value) => {772 onValueChange={(value) => {
773 const next = value[0];773 const next = value[0];
774 if (next === "stacked" || next === "split") {774 if (next === "stacked" || next === "split") {
775 setDiffRenderMode(next);775 updateClientSettings({ diffLayout: next });
776 }776 }
777 }}777 }}
778 >778 >
...@@ -959,7 +959,7 @@ export default function DiffPanel({...@@ -959,7 +959,7 @@ export default function DiffPanel({
959 );959 );
960 }}960 }}
961 options={{961 options={{
962 diffStyle: diffRenderMode === "split" ? "split" : "unified",962 diffStyle: diffLayout === "split" ? "split" : "unified",
963 lineDiffType: "none",963 lineDiffType: "none",
964 overflow: wordWrap ? "wrap" : "scroll",964 overflow: wordWrap ? "wrap" : "scroll",
965 theme: resolveDiffThemeName(resolvedTheme),965 theme: resolveDiffThemeName(resolvedTheme),
apps/web/src/components/pullRequest/PullRequestCodeTab.tsx+7-14
...@@ -26,7 +26,7 @@ import {...@@ -26,7 +26,7 @@ import {
26import { useAtomRefresh } from "@effect/atom-react";26import { useAtomRefresh } from "@effect/atom-react";
27import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";27import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
2828
29import { useClientSettings } from "~/hooks/useSettings";29import { useClientSettings, useUpdateClientSettings } from "~/hooks/useSettings";
30import { useTheme } from "~/hooks/useTheme";30import { useTheme } from "~/hooks/useTheme";
31import { areAllDiffFilesCollapsed } from "~/lib/diffCollapse";31import { areAllDiffFilesCollapsed } from "~/lib/diffCollapse";
32import { pullRequestFindingKey, type PullRequestFinding } from "./pullRequestDetail.logic";32import { pullRequestFindingKey, type PullRequestFinding } from "./pullRequestDetail.logic";
...@@ -217,7 +217,8 @@ export function PullRequestCodeTab({...@@ -217,7 +217,8 @@ export function PullRequestCodeTab({
217 const [visibleCommitCount, setVisibleCommitCount] = useState(COMMIT_PAGE_SIZE);217 const [visibleCommitCount, setVisibleCommitCount] = useState(COMMIT_PAGE_SIZE);
218 /** Set once the reader has asked for every file at once, until they pick a file apart again. */218 /** Set once the reader has asked for every file at once, until they pick a file apart again. */
219 const [foldOverride, setFoldOverride] = useState<DiffFoldOverride>(null);219 const [foldOverride, setFoldOverride] = useState<DiffFoldOverride>(null);
220 const [diffRenderMode, setDiffRenderMode] = useState<"stacked" | "split">("stacked");220 const diffLayout = settings.diffLayout;
221 const updateClientSettings = useUpdateClientSettings();
221 const [wordWrap, setWordWrap] = useState(settings.wordWrap);222 const [wordWrap, setWordWrap] = useState(settings.wordWrap);
222 const [selectedLines, setSelectedLines] = useState<{223 const [selectedLines, setSelectedLines] = useState<{
223 id: string;224 id: string;
...@@ -740,7 +741,7 @@ export function PullRequestCodeTab({...@@ -740,7 +741,7 @@ export function PullRequestCodeTab({
740741
741 const diffViewOptions = useMemo(742 const diffViewOptions = useMemo(
742 () => ({743 () => ({
743 diffStyle: diffRenderMode === "split" ? ("split" as const) : ("unified" as const),744 diffStyle: diffLayout === "split" ? ("split" as const) : ("unified" as const),
744 lineDiffType: "none" as const,745 lineDiffType: "none" as const,
745 overflow: wordWrap ? ("wrap" as const) : ("scroll" as const),746 overflow: wordWrap ? ("wrap" as const) : ("scroll" as const),
746 theme: resolveDiffThemeName(resolvedTheme),747 theme: resolveDiffThemeName(resolvedTheme),
...@@ -757,15 +758,7 @@ export function PullRequestCodeTab({...@@ -757,15 +758,7 @@ export function PullRequestCodeTab({
757 onGutterUtilityClick: beginComment,758 onGutterUtilityClick: beginComment,
758 onLineSelectionEnd: beginComment,759 onLineSelectionEnd: beginComment,
759 }),760 }),
760 [761 [diffLayout, wordWrap, resolvedTheme, loadDiffFiles, canCommentOnLines, draft, beginComment],
761 diffRenderMode,
762 wordWrap,
763 resolvedTheme,
764 loadDiffFiles,
765 canCommentOnLines,
766 draft,
767 beginComment,
768 ],
769 );762 );
770763
771 const runThreadCommand = useCallback(764 const runThreadCommand = useCallback(
...@@ -1122,11 +1115,11 @@ export function PullRequestCodeTab({...@@ -1122,11 +1115,11 @@ export function PullRequestCodeTab({
1122 <ToggleGroup1115 <ToggleGroup
1123 className="shrink-0 gap-1"1116 className="shrink-0 gap-1"
1124 size="sm"1117 size="sm"
1125 value={[diffRenderMode]}1118 value={[diffLayout]}
1126 onValueChange={(value) => {1119 onValueChange={(value) => {
1127 const next = value[0];1120 const next = value[0];
1128 if (next === "stacked" || next === "split") {1121 if (next === "stacked" || next === "split") {
1129 setDiffRenderMode(next);1122 updateClientSettings({ diffLayout: next });
1130 }1123 }
1131 }}1124 }}
1132 >1125 >
apps/web/src/components/settings/SettingsPanels.tsx+44
...@@ -19,6 +19,7 @@ import {...@@ -19,6 +19,7 @@ import {
19import {19import {
20 DEFAULT_ENVIRONMENT_IDENTIFICATION_MODE,20 DEFAULT_ENVIRONMENT_IDENTIFICATION_MODE,
21 DEFAULT_UNIFIED_SETTINGS,21 DEFAULT_UNIFIED_SETTINGS,
22 type DiffLayout,
22 type EnvironmentIdentificationMode,23 type EnvironmentIdentificationMode,
23 MAX_APPEARANCE_CONTRAST,24 MAX_APPEARANCE_CONTRAST,
24 MAX_CODE_FONT_SIZE,25 MAX_CODE_FONT_SIZE,
...@@ -169,6 +170,11 @@ const TIMESTAMP_FORMAT_LABELS = {...@@ -169,6 +170,11 @@ const TIMESTAMP_FORMAT_LABELS = {
169 "24-hour": "24-hour",170 "24-hour": "24-hour",
170} as const;171} as const;
171172
173const DIFF_LAYOUT_LABELS: Record<DiffLayout, string> = {
174 stacked: "Stacked",
175 split: "Split",
176};
177
172const QUIT_CONFIRMATION_MODE_LABELS: Record<QuitConfirmationMode, string> = {178const QUIT_CONFIRMATION_MODE_LABELS: Record<QuitConfirmationMode, string> = {
173 direct: "Direct",179 direct: "Direct",
174 hold: "Hold",180 hold: "Hold",
...@@ -527,6 +533,7 @@ export function useSettingsRestore(onRestored?: () => void) {...@@ -527,6 +533,7 @@ export function useSettingsRestore(onRestored?: () => void) {
527 ...(settings.diffIgnoreWhitespace !== DEFAULT_UNIFIED_SETTINGS.diffIgnoreWhitespace533 ...(settings.diffIgnoreWhitespace !== DEFAULT_UNIFIED_SETTINGS.diffIgnoreWhitespace
528 ? ["Diff whitespace changes"]534 ? ["Diff whitespace changes"]
529 : []),535 : []),
536 ...(settings.diffLayout !== DEFAULT_UNIFIED_SETTINGS.diffLayout ? ["Diff layout"] : []),
530 ...(settings.proactivePanelsEnabled !== DEFAULT_UNIFIED_SETTINGS.proactivePanelsEnabled537 ...(settings.proactivePanelsEnabled !== DEFAULT_UNIFIED_SETTINGS.proactivePanelsEnabled
531 ? ["Proactive panels"]538 ? ["Proactive panels"]
532 : []),539 : []),
...@@ -593,6 +600,7 @@ export function useSettingsRestore(onRestored?: () => void) {...@@ -593,6 +600,7 @@ export function useSettingsRestore(onRestored?: () => void) {
593 settings.defaultThreadEnvMode,600 settings.defaultThreadEnvMode,
594 settings.newWorktreesStartFromOrigin,601 settings.newWorktreesStartFromOrigin,
595 settings.diffIgnoreWhitespace,602 settings.diffIgnoreWhitespace,
603 settings.diffLayout,
596 settings.proactivePanelsEnabled,604 settings.proactivePanelsEnabled,
597 settings.environmentIdentificationMode,605 settings.environmentIdentificationMode,
598 settings.contextWindowMeterEnabled,606 settings.contextWindowMeterEnabled,
...@@ -689,6 +697,7 @@ export function useSettingsRestore(onRestored?: () => void) {...@@ -689,6 +697,7 @@ export function useSettingsRestore(onRestored?: () => void) {
689 timestampFormat: DEFAULT_UNIFIED_SETTINGS.timestampFormat,697 timestampFormat: DEFAULT_UNIFIED_SETTINGS.timestampFormat,
690 wordWrap: DEFAULT_UNIFIED_SETTINGS.wordWrap,698 wordWrap: DEFAULT_UNIFIED_SETTINGS.wordWrap,
691 diffIgnoreWhitespace: DEFAULT_UNIFIED_SETTINGS.diffIgnoreWhitespace,699 diffIgnoreWhitespace: DEFAULT_UNIFIED_SETTINGS.diffIgnoreWhitespace,
700 diffLayout: DEFAULT_UNIFIED_SETTINGS.diffLayout,
692 proactivePanelsEnabled: DEFAULT_UNIFIED_SETTINGS.proactivePanelsEnabled,701 proactivePanelsEnabled: DEFAULT_UNIFIED_SETTINGS.proactivePanelsEnabled,
693 showSkillsInSlashMenu: DEFAULT_UNIFIED_SETTINGS.showSkillsInSlashMenu,702 showSkillsInSlashMenu: DEFAULT_UNIFIED_SETTINGS.showSkillsInSlashMenu,
694 contextWindowMeterEnabled: DEFAULT_UNIFIED_SETTINGS.contextWindowMeterEnabled,703 contextWindowMeterEnabled: DEFAULT_UNIFIED_SETTINGS.contextWindowMeterEnabled,
...@@ -2223,6 +2232,41 @@ export function GeneralSettingsPanel() {...@@ -2223,6 +2232,41 @@ export function GeneralSettingsPanel() {
2223 }2232 }
2224 />2233 />
22252234
2235 <SettingsRow
2236 {...searchableSetting("diff-layout")}
2237 description="Show diffs stacked or side by side. The toggle in the diff toolbar changes this too."
2238 resetAction={
2239 settings.diffLayout !== DEFAULT_UNIFIED_SETTINGS.diffLayout ? (
2240 <SettingResetButton
2241 label="diff layout"
2242 onClick={() => updateSettings({ diffLayout: DEFAULT_UNIFIED_SETTINGS.diffLayout })}
2243 />
2244 ) : null
2245 }
2246 control={
2247 <Select
2248 value={settings.diffLayout}
2249 onValueChange={(value) => {
2250 if (value === "stacked" || value === "split") {
2251 updateSettings({ diffLayout: value });
2252 }
2253 }}
2254 >
2255 <SelectTrigger size="sm" className="w-full sm:w-40" aria-label="Diff layout">
2256 <SelectValue>{DIFF_LAYOUT_LABELS[settings.diffLayout]}</SelectValue>
2257 </SelectTrigger>
2258 <SelectPopup align="end" alignItemWithTrigger={false}>
2259 <SelectItem hideIndicator value="stacked">
2260 {DIFF_LAYOUT_LABELS.stacked}
2261 </SelectItem>
2262 <SelectItem hideIndicator value="split">
2263 {DIFF_LAYOUT_LABELS.split}
2264 </SelectItem>
2265 </SelectPopup>
2266 </Select>
2267 }
2268 />
2269
2226 <SettingsRow2270 <SettingsRow
2227 {...searchableSetting("proactive-panels")}2271 {...searchableSetting("proactive-panels")}
2228 description="Automatically open the linked pull request when it appears and the turn diff when agent work finishes."2272 description="Automatically open the linked pull request when it appears and the turn diff when agent work finishes."
apps/web/src/components/settings/settingsSearch.ts+6
...@@ -184,6 +184,12 @@ export const SETTINGS_SEARCH_ITEMS = [...@@ -184,6 +184,12 @@ export const SETTINGS_SEARCH_ITEMS = [
184 to: "/settings/general",184 to: "/settings/general",
185 searchTerms: ["diff ignore spaces edits default"],185 searchTerms: ["diff ignore spaces edits default"],
186 },186 },
187 {
188 id: "diff-layout",
189 title: "Diff layout",
190 to: "/settings/general",
191 searchTerms: ["stacked split side by side unified inline view"],
192 },
187 {193 {
188 id: "proactive-panels",194 id: "proactive-panels",
189 title: "Proactive panels",195 title: "Proactive panels",
apps/web/src/diffPanelStore.test.ts-21
...@@ -11,30 +11,9 @@ describe("diffPanelStore", () => {...@@ -11,30 +11,9 @@ describe("diffPanelStore", () => {
11 useDiffPanelStore.setState({11 useDiffPanelStore.setState({
12 byThreadKey: {},12 byThreadKey: {},
13 branchBaseRefByThreadKey: {},13 branchBaseRefByThreadKey: {},
14 diffRenderMode: "stacked",
15 }),14 }),
16 );15 );
1716
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 });
37
38 it("defaults each thread to branch changes when the working tree is clean", () => {17 it("defaults each thread to branch changes when the working tree is clean", () => {
39 expect(18 expect(
40 selectThreadDiffPanelSelection(useDiffPanelStore.getState().byThreadKey, THREAD_REF),19 selectThreadDiffPanelSelection(useDiffPanelStore.getState().byThreadKey, THREAD_REF),
apps/web/src/diffPanelStore.ts-7
...@@ -10,16 +10,12 @@ export type DiffPanelSelection =...@@ -10,16 +10,12 @@ 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
15const DEFAULT_SELECTION: DiffPanelSelection = { kind: "branch", baseRef: null };13const DEFAULT_SELECTION: DiffPanelSelection = { kind: "branch", baseRef: null };
16const DEFAULT_WORKING_TREE_SELECTION: DiffPanelSelection = { kind: "unstaged" };14const DEFAULT_WORKING_TREE_SELECTION: DiffPanelSelection = { kind: "unstaged" };
1715
18interface DiffPanelStoreState {16interface DiffPanelStoreState {
19 byThreadKey: Record<string, DiffPanelSelection>;17 byThreadKey: Record<string, DiffPanelSelection>;
20 branchBaseRefByThreadKey: Record<string, string | null>;18 branchBaseRefByThreadKey: Record<string, string | null>;
21 diffRenderMode: DiffRenderMode;
22 setDiffRenderMode: (mode: DiffRenderMode) => void;
23 selectGitScope: (ref: ScopedThreadRef, scope: "branch" | "unstaged") => void;19 selectGitScope: (ref: ScopedThreadRef, scope: "branch" | "unstaged") => void;
24 selectBranchBaseRef: (ref: ScopedThreadRef, baseRef: string | null) => void;20 selectBranchBaseRef: (ref: ScopedThreadRef, baseRef: string | null) => void;
25 selectTurn: (ref: ScopedThreadRef, turnId: TurnId, filePath?: string) => void;21 selectTurn: (ref: ScopedThreadRef, turnId: TurnId, filePath?: string) => void;
...@@ -37,8 +33,6 @@ export const useDiffPanelStore = create<DiffPanelStoreState>()(...@@ -37,8 +33,6 @@ export const useDiffPanelStore = create<DiffPanelStoreState>()(
37 (set) => ({33 (set) => ({
38 byThreadKey: {},34 byThreadKey: {},
39 branchBaseRefByThreadKey: {},35 branchBaseRefByThreadKey: {},
40 diffRenderMode: "stacked",
41 setDiffRenderMode: (diffRenderMode) => set({ diffRenderMode }),
42 selectGitScope: (ref, scope) =>36 selectGitScope: (ref, scope) =>
43 set((state) => {37 set((state) => {
44 const threadKey = scopedThreadKey(ref);38 const threadKey = scopedThreadKey(ref);
...@@ -132,7 +126,6 @@ export const useDiffPanelStore = create<DiffPanelStoreState>()(...@@ -132,7 +126,6 @@ export const useDiffPanelStore = create<DiffPanelStoreState>()(
132 partialize: (state) => ({126 partialize: (state) => ({
133 byThreadKey: state.byThreadKey,127 byThreadKey: state.byThreadKey,
134 branchBaseRefByThreadKey: state.branchBaseRefByThreadKey,128 branchBaseRefByThreadKey: state.branchBaseRefByThreadKey,
135 diffRenderMode: state.diffRenderMode,
136 }),129 }),
137 },130 },
138 ),131 ),
packages/contracts/src/settings.ts+6
...@@ -31,6 +31,10 @@ export const TimestampFormat = Schema.Literals(["locale", "12-hour", "24-hour"])...@@ -31,6 +31,10 @@ export const TimestampFormat = Schema.Literals(["locale", "12-hour", "24-hour"])
31export type TimestampFormat = typeof TimestampFormat.Type;31export type TimestampFormat = typeof TimestampFormat.Type;
32export const DEFAULT_TIMESTAMP_FORMAT: TimestampFormat = "locale";32export const DEFAULT_TIMESTAMP_FORMAT: TimestampFormat = "locale";
3333
34export const DiffLayout = Schema.Literals(["stacked", "split"]);
35export type DiffLayout = typeof DiffLayout.Type;
36export const DEFAULT_DIFF_LAYOUT: DiffLayout = "stacked";
37
34export const SidebarProjectSortOrder = Schema.Literals(["updated_at", "created_at", "manual"]);38export const SidebarProjectSortOrder = Schema.Literals(["updated_at", "created_at", "manual"]);
35export type SidebarProjectSortOrder = typeof SidebarProjectSortOrder.Type;39export type SidebarProjectSortOrder = typeof SidebarProjectSortOrder.Type;
36export const DEFAULT_SIDEBAR_PROJECT_SORT_ORDER: SidebarProjectSortOrder = "updated_at";40export const DEFAULT_SIDEBAR_PROJECT_SORT_ORDER: SidebarProjectSortOrder = "updated_at";
...@@ -242,6 +246,7 @@ export const ClientSettingsSchema = Schema.Struct({...@@ -242,6 +246,7 @@ export const ClientSettingsSchema = Schema.Struct({
242 Schema.withDecodingDefault(Effect.succeed([])),246 Schema.withDecodingDefault(Effect.succeed([])),
243 ),247 ),
244 diffIgnoreWhitespace: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),248 diffIgnoreWhitespace: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),
249 diffLayout: DiffLayout.pipe(Schema.withDecodingDefault(Effect.succeed(DEFAULT_DIFF_LAYOUT))),
245 environmentIdentificationMode: EnvironmentIdentificationMode.pipe(250 environmentIdentificationMode: EnvironmentIdentificationMode.pipe(
246 Schema.withDecodingDefault(Effect.succeed(DEFAULT_ENVIRONMENT_IDENTIFICATION_MODE)),251 Schema.withDecodingDefault(Effect.succeed(DEFAULT_ENVIRONMENT_IDENTIFICATION_MODE)),
247 ),252 ),
...@@ -1007,6 +1012,7 @@ export const ClientSettingsPatch = Schema.Struct({...@@ -1007,6 +1012,7 @@ export const ClientSettingsPatch = Schema.Struct({
1007 confirmThreadUnpin: Schema.optionalKey(Schema.Boolean),1012 confirmThreadUnpin: Schema.optionalKey(Schema.Boolean),
1008 continueThreadsAfterServerUpdate: Schema.optionalKey(Schema.Boolean),1013 continueThreadsAfterServerUpdate: Schema.optionalKey(Schema.Boolean),
1009 diffIgnoreWhitespace: Schema.optionalKey(Schema.Boolean),1014 diffIgnoreWhitespace: Schema.optionalKey(Schema.Boolean),
1015 diffLayout: Schema.optionalKey(DiffLayout),
1010 environmentIdentificationMode: Schema.optionalKey(EnvironmentIdentificationMode),1016 environmentIdentificationMode: Schema.optionalKey(EnvironmentIdentificationMode),
1011 glassOpacity: Schema.optionalKey(GlassOpacity),1017 glassOpacity: Schema.optionalKey(GlassOpacity),
1012 fontSizeInterface: Schema.optionalKey(InterfaceFontSize),1018 fontSizeInterface: Schema.optionalKey(InterfaceFontSize),