diff --git a/apps/web/src/appSettings.ts b/apps/web/src/appSettings.ts index 14b6a6a92..e0c528de5 100644 --- a/apps/web/src/appSettings.ts +++ b/apps/web/src/appSettings.ts @@ -51,6 +51,7 @@ export const AppSettingsSchema = Schema.Struct({ codexHomePath: Schema.String.check(Schema.isMaxLength(4096)).pipe(withDefaults(() => "")), defaultThreadEnvMode: EnvMode.pipe(withDefaults(() => "local" as const satisfies EnvMode)), confirmThreadDelete: Schema.Boolean.pipe(withDefaults(() => true)), + diffWordWrap: Schema.Boolean.pipe(withDefaults(() => false)), enableAssistantStreaming: Schema.Boolean.pipe(withDefaults(() => false)), timestampFormat: TimestampFormat.pipe(withDefaults(() => DEFAULT_TIMESTAMP_FORMAT)), customCodexModels: Schema.Array(Schema.String).pipe(withDefaults(() => [])), diff --git a/apps/web/src/components/DiffPanel.tsx b/apps/web/src/components/DiffPanel.tsx index 34ad78881..96e021987 100644 --- a/apps/web/src/components/DiffPanel.tsx +++ b/apps/web/src/components/DiffPanel.tsx @@ -3,7 +3,13 @@ import { FileDiff, type FileDiffMetadata, Virtualizer } from "@pierre/diffs/reac import { useQuery } from "@tanstack/react-query"; import { useNavigate, useParams, useSearch } from "@tanstack/react-router"; import { ThreadId, type TurnId } from "@t3tools/contracts"; -import { ChevronLeftIcon, ChevronRightIcon, Columns2Icon, Rows3Icon } from "lucide-react"; +import { + ChevronLeftIcon, + ChevronRightIcon, + Columns2Icon, + Rows3Icon, + TextWrapIcon, +} from "lucide-react"; import { type WheelEvent as ReactWheelEvent, useCallback, @@ -162,8 +168,10 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { const { resolvedTheme } = useTheme(); const { settings } = useAppSettings(); const [diffRenderMode, setDiffRenderMode] = useState("stacked"); + const [diffWordWrap, setDiffWordWrap] = useState(settings.diffWordWrap); const patchViewportRef = useRef(null); const turnStripRef = useRef(null); + const previousDiffOpenRef = useRef(false); const [canScrollTurnStripLeft, setCanScrollTurnStripLeft] = useState(false); const [canScrollTurnStripRight, setCanScrollTurnStripRight] = useState(false); const routeThreadId = useParams({ @@ -171,6 +179,7 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { select: (params) => (params.threadId ? ThreadId.makeUnsafe(params.threadId) : null), }); const diffSearch = useSearch({ strict: false, select: (search) => parseDiffRouteSearch(search) }); + const diffOpen = diffSearch.diff === "1"; const activeThreadId = routeThreadId; const activeThread = useStore((store) => activeThreadId ? store.threads.find((thread) => thread.id === activeThreadId) : undefined, @@ -293,6 +302,13 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { ); }, [renderablePatch]); + useEffect(() => { + if (diffOpen && !previousDiffOpenRef.current) { + setDiffWordWrap(settings.diffWordWrap); + } + previousDiffOpenRef.current = diffOpen; + }, [diffOpen, settings.diffWordWrap]); + useEffect(() => { if (!selectedFilePath || !patchViewportRef.current) { return; @@ -490,25 +506,39 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { ))} - { - const next = value[0]; - if (next === "stacked" || next === "split") { - setDiffRenderMode(next); - } - }} - > - - - - - +
+ { + const next = value[0]; + if (next === "stacked" || next === "split") { + setDiffRenderMode(next); + } + }} + > + + + + + + + + { + setDiffWordWrap(Boolean(pressed)); + }} + > + - +
); @@ -582,6 +612,7 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { options={{ diffStyle: diffRenderMode === "split" ? "split" : "unified", lineDiffType: "none", + overflow: diffWordWrap ? "wrap" : "scroll", theme: resolveDiffThemeName(resolvedTheme), themeType: resolvedTheme as DiffThemeType, unsafeCSS: DIFF_PANEL_UNSAFE_CSS, @@ -595,7 +626,14 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {

{renderablePatch.reason}

-
+                  
                     {renderablePatch.text}
                   
diff --git a/apps/web/src/routes/_chat.settings.tsx b/apps/web/src/routes/_chat.settings.tsx index acc8763fb..c7d927546 100644 --- a/apps/web/src/routes/_chat.settings.tsx +++ b/apps/web/src/routes/_chat.settings.tsx @@ -279,6 +279,43 @@ function SettingsRouteView() {
) : null} + +
+
+

+ Wrap diff lines by default +

+

+ Sets the initial diff wrap state when the diff panel opens. The in-panel wrap + button only affects the current open diff session. +

+
+ + updateSettings({ + diffWordWrap: Boolean(checked), + }) + } + aria-label="Wrap diff lines by default" + /> +
+ + {settings.diffWordWrap !== defaults.diffWordWrap ? ( +
+ +
+ ) : null}