| author | |
| committer | |
| log | 9409dd20a9fbce491d49d09c79b289d8fb8bfe3e |
| tree | e9e39232d05cca883563024b7ce8bb2c15aa4741 |
| parent | 12f1fc427efad4b683835a72bda6f74bebb5d641 |
| signature | Signed by PGP key B5690EEEBB952194 |
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"; |
| 44 | import { useWorkspaceMutationRefresh } from "../hooks/useWorkspaceMutationRefresh"; | 44 | import { useWorkspaceMutationRefresh } from "../hooks/useWorkspaceMutationRefresh"; |
| 45 | import { useProject, useThread } from "../state/entities"; | 45 | import { useProject, useThread } from "../state/entities"; |
| 46 | import { resolveThreadRouteRef } from "../threadRoutes"; | 46 | import { resolveThreadRouteRef } from "../threadRoutes"; |
| 47 | import { useClientSettings } from "../hooks/useSettings"; | 47 | import { useClientSettings, useUpdateClientSettings } from "../hooks/useSettings"; |
| 48 | import { formatShortTimestamp } from "../timestampFormat"; | 48 | import { formatShortTimestamp } from "../timestampFormat"; |
| 49 | import { DiffFilePathCopyButton } from "./DiffFilePathCopyButton"; | 49 | import { DiffFilePathCopyButton } from "./DiffFilePathCopyButton"; |
| 50 | import { DiffPanelLoadingState, DiffPanelShell, type DiffPanelMode } from "./DiffPanelShell"; | 50 | import { 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 | <ToggleGroup | 768 | <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 { |
| 26 | import { useAtomRefresh } from "@effect/atom-react"; | 26 | import { useAtomRefresh } from "@effect/atom-react"; |
| 27 | import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react"; | 27 | import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react"; |
| 28 | 28 | ||
| 29 | import { useClientSettings } from "~/hooks/useSettings"; | 29 | import { useClientSettings, useUpdateClientSettings } from "~/hooks/useSettings"; |
| 30 | import { useTheme } from "~/hooks/useTheme"; | 30 | import { useTheme } from "~/hooks/useTheme"; |
| 31 | import { areAllDiffFilesCollapsed } from "~/lib/diffCollapse"; | 31 | import { areAllDiffFilesCollapsed } from "~/lib/diffCollapse"; |
| 32 | import { pullRequestFindingKey, type PullRequestFinding } from "./pullRequestDetail.logic"; | 32 | import { 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({ |
| 740 | 741 | ||
| 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 | ); |
| 770 | 763 | ||
| 771 | const runThreadCommand = useCallback( | 764 | const runThreadCommand = useCallback( |
| ... | @@ -1122,11 +1115,11 @@ export function PullRequestCodeTab({ | ... | @@ -1122,11 +1115,11 @@ export function PullRequestCodeTab({ |
| 1122 | <ToggleGroup | 1115 | <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 { |
| 19 | import { | 19 | import { |
| 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; |
| 171 | 172 | ||
| 173 | const DIFF_LAYOUT_LABELS: Record<DiffLayout, string> = { | ||
| 174 | stacked: "Stacked", | ||
| 175 | split: "Split", | ||
| 176 | }; | ||
| 177 | |||
| 172 | const QUIT_CONFIRMATION_MODE_LABELS: Record<QuitConfirmationMode, string> = { | 178 | const 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.diffIgnoreWhitespace | 533 | ...(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.proactivePanelsEnabled | 537 | ...(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 | /> |
| 2225 | 2234 | ||
| 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 | <SettingsRow | 2270 | <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 | ); |
| 17 | 16 | ||
| 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 }; |
| 12 | 12 | ||
| 13 | export type DiffRenderMode = "stacked" | "split"; | ||
| 14 | |||
| 15 | const DEFAULT_SELECTION: DiffPanelSelection = { kind: "branch", baseRef: null }; | 13 | const DEFAULT_SELECTION: DiffPanelSelection = { kind: "branch", baseRef: null }; |
| 16 | const DEFAULT_WORKING_TREE_SELECTION: DiffPanelSelection = { kind: "unstaged" }; | 14 | const DEFAULT_WORKING_TREE_SELECTION: DiffPanelSelection = { kind: "unstaged" }; |
| 17 | 15 | ||
| 18 | interface DiffPanelStoreState { | 16 | interface 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"]) |
| 31 | export type TimestampFormat = typeof TimestampFormat.Type; | 31 | export type TimestampFormat = typeof TimestampFormat.Type; |
| 32 | export const DEFAULT_TIMESTAMP_FORMAT: TimestampFormat = "locale"; | 32 | export const DEFAULT_TIMESTAMP_FORMAT: TimestampFormat = "locale"; |
| 33 | 33 | ||
| 34 | export const DiffLayout = Schema.Literals(["stacked", "split"]); | ||
| 35 | export type DiffLayout = typeof DiffLayout.Type; | ||
| 36 | export const DEFAULT_DIFF_LAYOUT: DiffLayout = "stacked"; | ||
| 37 | |||
| 34 | export const SidebarProjectSortOrder = Schema.Literals(["updated_at", "created_at", "manual"]); | 38 | export const SidebarProjectSortOrder = Schema.Literals(["updated_at", "created_at", "manual"]); |
| 35 | export type SidebarProjectSortOrder = typeof SidebarProjectSortOrder.Type; | 39 | export type SidebarProjectSortOrder = typeof SidebarProjectSortOrder.Type; |
| 36 | export const DEFAULT_SIDEBAR_PROJECT_SORT_ORDER: SidebarProjectSortOrder = "updated_at"; | 40 | export 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), |