From 0ca60ee70d7d837f158686d89f66ece68817db87 Mon Sep 17 00:00:00 2001 From: VooDisss <41582720+VooDisss@users.noreply.github.com> Date: Thu, 9 Apr 2026 19:01:04 +0300 Subject: [PATCH 01/10] add compact Git Changes Monaco gutter snapshot Enable a first-pass compact unified gutter experiment for the Git Changes right-panel viewer while keeping the rest of the Monaco diff surfaces unchanged. The change is intentionally narrow: Git Changes opts into a compact unified Monaco configuration, but no CSS seam-hiding layer or new settings contract is introduced yet. This snapshot exists as a clean checkpoint from the redesign branch base so the next visual iteration can stay deliberate and easy to evaluate. If the compact shape proves acceptable, future work can layer on only the smallest additional adjustments needed for sign placement or classic-mode widening without carrying forward the earlier broad gutter override experiments. --- .../file-viewer/monaco-diff-viewer.tsx | 21 +++++++++++++++++-- .../shell/right-panel/tabs/GitChangesTab.tsx | 1 + 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx b/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx index 6856fb24..27bcefa5 100644 --- a/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx +++ b/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx @@ -15,6 +15,7 @@ interface MonacoDiffViewerProps { viewMode?: "split" | "unified" contextMode?: "expanded" | "collapsed" wordWrap?: "on" | "off" + compactUnifiedGutter?: boolean } export function MonacoDiffViewer(props: MonacoDiffViewerProps) { @@ -100,25 +101,41 @@ export function MonacoDiffViewer(props: MonacoDiffViewerProps) { const viewMode = props.viewMode === "unified" ? "unified" : "split" const contextMode = props.contextMode === "collapsed" ? "collapsed" : "expanded" const wordWrap = props.wordWrap === "on" ? "on" : "off" + const compactUnifiedGutter = Boolean(props.compactUnifiedGutter) && viewMode === "unified" diffEditor.updateOptions({ renderSideBySide: viewMode === "split", renderSideBySideInlineBreakpoint: 0, + compactMode: compactUnifiedGutter, + renderIndicators: true, + lineNumbersMinChars: compactUnifiedGutter ? 3 : 4, + lineDecorationsWidth: compactUnifiedGutter ? 9 : 12, hideUnchangedRegions: contextMode === "collapsed" ? { enabled: true } : { enabled: false }, wordWrap, + experimental: { + useTrueInlineView: compactUnifiedGutter, + }, }) try { - diffEditor.getOriginalEditor?.()?.updateOptions?.({ wordWrap }) + diffEditor.getOriginalEditor?.()?.updateOptions?.({ + wordWrap, + lineNumbersMinChars: compactUnifiedGutter ? 3 : 4, + lineDecorationsWidth: compactUnifiedGutter ? 9 : 12, + }) } catch { // ignore } try { - diffEditor.getModifiedEditor?.()?.updateOptions?.({ wordWrap }) + diffEditor.getModifiedEditor?.()?.updateOptions?.({ + wordWrap, + lineNumbersMinChars: compactUnifiedGutter ? 3 : 4, + lineDecorationsWidth: compactUnifiedGutter ? 9 : 12, + }) } catch { // ignore } diff --git a/packages/ui/src/components/instance/shell/right-panel/tabs/GitChangesTab.tsx b/packages/ui/src/components/instance/shell/right-panel/tabs/GitChangesTab.tsx index 019dce1d..18d2eef4 100644 --- a/packages/ui/src/components/instance/shell/right-panel/tabs/GitChangesTab.tsx +++ b/packages/ui/src/components/instance/shell/right-panel/tabs/GitChangesTab.tsx @@ -139,6 +139,7 @@ const GitChangesTab: Component = (props) => { viewMode={props.diffViewMode()} contextMode={props.diffContextMode()} wordWrap={props.diffWordWrapMode()} + compactUnifiedGutter={true} /> )} From 78ba2a858a948136dd3319e1ede10412a12fde82 Mon Sep 17 00:00:00 2001 From: VooDisss <41582720+VooDisss@users.noreply.github.com> Date: Thu, 9 Apr 2026 19:07:13 +0300 Subject: [PATCH 02/10] add classic Git Changes Monaco gutter snapshot Introduce a second unified gutter presentation mode for the Git Changes Monaco diff viewer on top of the compact checkpoint. Compact remains the narrow single-column-style inline presentation, while classic restores Monaco's wider unified gutter shape with the extra visible line-number lane that better matches the intended classic model. This stays intentionally local to Git Changes and does not yet promote the choice into shared settings or toolbar contracts. The goal of the snapshot is to preserve the now-validated visual distinction between compact and classic before any further cleanup or productization work changes the control surface. --- .../file-viewer/monaco-diff-viewer.tsx | 16 +++++++----- .../shell/right-panel/tabs/GitChangesTab.tsx | 26 +++++++++++++++++-- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx b/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx index 27bcefa5..c71cfd28 100644 --- a/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx +++ b/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx @@ -16,6 +16,7 @@ interface MonacoDiffViewerProps { contextMode?: "expanded" | "collapsed" wordWrap?: "on" | "off" compactUnifiedGutter?: boolean + classicUnifiedGutter?: boolean } export function MonacoDiffViewer(props: MonacoDiffViewerProps) { @@ -102,14 +103,17 @@ export function MonacoDiffViewer(props: MonacoDiffViewerProps) { const contextMode = props.contextMode === "collapsed" ? "collapsed" : "expanded" const wordWrap = props.wordWrap === "on" ? "on" : "off" const compactUnifiedGutter = Boolean(props.compactUnifiedGutter) && viewMode === "unified" + const classicUnifiedGutter = Boolean(props.classicUnifiedGutter) && viewMode === "unified" + const lineNumbersMinChars = compactUnifiedGutter ? 3 : classicUnifiedGutter ? 4 : 4 + const lineDecorationsWidth = compactUnifiedGutter ? 9 : classicUnifiedGutter ? 12 : 12 diffEditor.updateOptions({ renderSideBySide: viewMode === "split", renderSideBySideInlineBreakpoint: 0, compactMode: compactUnifiedGutter, renderIndicators: true, - lineNumbersMinChars: compactUnifiedGutter ? 3 : 4, - lineDecorationsWidth: compactUnifiedGutter ? 9 : 12, + lineNumbersMinChars, + lineDecorationsWidth, hideUnchangedRegions: contextMode === "collapsed" ? { enabled: true } @@ -123,8 +127,8 @@ export function MonacoDiffViewer(props: MonacoDiffViewerProps) { try { diffEditor.getOriginalEditor?.()?.updateOptions?.({ wordWrap, - lineNumbersMinChars: compactUnifiedGutter ? 3 : 4, - lineDecorationsWidth: compactUnifiedGutter ? 9 : 12, + lineNumbersMinChars, + lineDecorationsWidth, }) } catch { // ignore @@ -133,8 +137,8 @@ export function MonacoDiffViewer(props: MonacoDiffViewerProps) { try { diffEditor.getModifiedEditor?.()?.updateOptions?.({ wordWrap, - lineNumbersMinChars: compactUnifiedGutter ? 3 : 4, - lineDecorationsWidth: compactUnifiedGutter ? 9 : 12, + lineNumbersMinChars, + lineDecorationsWidth, }) } catch { // ignore diff --git a/packages/ui/src/components/instance/shell/right-panel/tabs/GitChangesTab.tsx b/packages/ui/src/components/instance/shell/right-panel/tabs/GitChangesTab.tsx index 18d2eef4..e06776e4 100644 --- a/packages/ui/src/components/instance/shell/right-panel/tabs/GitChangesTab.tsx +++ b/packages/ui/src/components/instance/shell/right-panel/tabs/GitChangesTab.tsx @@ -1,4 +1,4 @@ -import { For, Show, Suspense, createMemo, lazy, type Accessor, type Component, type JSX } from "solid-js" +import { For, Show, Suspense, createMemo, createSignal, lazy, type Accessor, type Component, type JSX } from "solid-js" import type { File as GitFileStatus } from "@opencode-ai/sdk/v2/client" import { RefreshCw } from "lucide-solid" @@ -48,6 +48,7 @@ interface GitChangesTabProps { } const GitChangesTab: Component = (props) => { + const [unifiedGutterStyle, setUnifiedGutterStyle] = createSignal<"compact" | "classic">("compact") const sessionId = createMemo(() => props.activeSessionId()) const hasSession = createMemo(() => Boolean(sessionId() && sessionId() !== "info")) const entries = createMemo(() => (hasSession() ? props.entries() : null)) @@ -139,7 +140,8 @@ const GitChangesTab: Component = (props) => { viewMode={props.diffViewMode()} contextMode={props.diffContextMode()} wordWrap={props.diffWordWrapMode()} - compactUnifiedGutter={true} + compactUnifiedGutter={unifiedGutterStyle() === "compact"} + classicUnifiedGutter={unifiedGutterStyle() === "classic"} /> )} @@ -265,6 +267,26 @@ const GitChangesTab: Component = (props) => { onContextModeChange={props.onContextModeChange} onWordWrapModeChange={props.onWordWrapModeChange} /> + + + + } list={{ panel: renderListPanel, overlay: renderListOverlay }} From 1b5b581978877f9e0fb46eca7d2d1e7f788048a0 Mon Sep 17 00:00:00 2001 From: VooDisss <41582720+VooDisss@users.noreply.github.com> Date: Thu, 9 Apr 2026 19:10:45 +0300 Subject: [PATCH 03/10] tune compact Monaco gutter line inset Refine the compact Git Changes Monaco unified gutter by explicitly left-aligning the line numbers and setting their left inset to 4px. This preserves the current compact/classic gutter model while correcting the remaining visual slack in compact mode that made the numbers feel too detached from the gutter edge. The change stays narrowly scoped to the compact unified Git Changes presentation: a small host data attribute marks the compact mode, and a matching right-panel CSS rule applies the 4px inset without changing classic mode or the underlying Monaco width configuration. --- .../ui/src/components/file-viewer/monaco-diff-viewer.tsx | 5 +++++ packages/ui/src/styles/panels/right-panel.css | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx b/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx index c71cfd28..4e1bdfe6 100644 --- a/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx +++ b/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx @@ -97,6 +97,11 @@ export function MonacoDiffViewer(props: MonacoDiffViewerProps) { monaco.editor.setTheme(isDark() ? "vs-dark" : "vs") }) + createEffect(() => { + if (!host) return + host.dataset.compactUnifiedGutter = props.compactUnifiedGutter ? "true" : "false" + }) + createEffect(() => { if (!ready() || !monaco || !diffEditor) return const viewMode = props.viewMode === "unified" ? "unified" : "split" diff --git a/packages/ui/src/styles/panels/right-panel.css b/packages/ui/src/styles/panels/right-panel.css index 8af0cac3..cd30e8d6 100644 --- a/packages/ui/src/styles/panels/right-panel.css +++ b/packages/ui/src/styles/panels/right-panel.css @@ -337,6 +337,11 @@ direction: ltr; } +.file-viewer-content--monaco .monaco-viewer[data-compact-unified-gutter="true"] .line-numbers { + text-align: left !important; + padding-left: 4px; +} + .file-viewer-empty { @apply flex flex-col items-center justify-center h-full gap-3 text-center; color: var(--text-muted); From 219b2fabf7e7cacc78873ad104350ff3e4a60b3f Mon Sep 17 00:00:00 2001 From: VooDisss <41582720+VooDisss@users.noreply.github.com> Date: Thu, 9 Apr 2026 19:13:55 +0300 Subject: [PATCH 04/10] reduce classic Monaco gutter number inset Apply the same left-edge tightening used in compact mode to the classic Git Changes Monaco unified gutter. Classic intentionally remains the wider Monaco-style unified presentation, but the first line-number column no longer wastes horizontal space with the default right-aligned slack on its left side. This snapshot keeps the change narrowly scoped to presentation only. The wider classic gutter model is preserved, while a small host data attribute plus matching scoped CSS realigns the visible number column to a 4px left inset so the gutter width is spent on useful content instead of empty margin. --- .../ui/src/components/file-viewer/monaco-diff-viewer.tsx | 1 + packages/ui/src/styles/panels/right-panel.css | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx b/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx index 4e1bdfe6..8bac7180 100644 --- a/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx +++ b/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx @@ -100,6 +100,7 @@ export function MonacoDiffViewer(props: MonacoDiffViewerProps) { createEffect(() => { if (!host) return host.dataset.compactUnifiedGutter = props.compactUnifiedGutter ? "true" : "false" + host.dataset.classicUnifiedGutter = props.classicUnifiedGutter ? "true" : "false" }) createEffect(() => { diff --git a/packages/ui/src/styles/panels/right-panel.css b/packages/ui/src/styles/panels/right-panel.css index cd30e8d6..b65a8b38 100644 --- a/packages/ui/src/styles/panels/right-panel.css +++ b/packages/ui/src/styles/panels/right-panel.css @@ -342,6 +342,11 @@ padding-left: 4px; } +.file-viewer-content--monaco .monaco-viewer[data-classic-unified-gutter="true"] .line-numbers { + text-align: left !important; + padding-left: 4px; +} + .file-viewer-empty { @apply flex flex-col items-center justify-center h-full gap-3 text-center; color: var(--text-muted); From d1dc1b6a66c8fecff7f80971d7231a11fe0e78be Mon Sep 17 00:00:00 2001 From: VooDisss <41582720+VooDisss@users.noreply.github.com> Date: Thu, 9 Apr 2026 19:41:15 +0300 Subject: [PATCH 05/10] refine dynamic Monaco unified gutter sizing Replace the earlier compact/classic gutter experiment wiring with a cleaner shared model and make the unified gutter widths truly respond to the diff's line-number scale. The previous implementation had the right intent, but it still behaved too statically in common cases and it forced classic mode's two visible number columns to share the same width budget, which wasted space when one side contained much shorter line numbers than the other. This snapshot consolidates gutter style selection into a single unifiedGutterStyle prop, keeps the Monaco-specific sizing logic inside monaco-diff-viewer.tsx, and introduces a shared helper that derives line-number budgets from the actual before/after line counts. Compact mode keeps a shared single-column-style width, while classic mode now sizes the original and modified number columns independently and expands the sign/decorations lane more aggressively as digit counts grow. The existing 4px left inset remains a presentation-only CSS rule keyed off the same unified gutter style attribute. The result is a cleaner ownership model and a more accurate classic gutter layout: small files stay tight, three-digit and four-digit ranges get additional room, and classic no longer makes both numeric columns equally wide when only one side needs the extra width. This commit serves as the architectural checkpoint before any further polish to edge cases such as sign-spacing behavior at larger digit counts or future productization of the compact/classic choice into a broader settings surface. --- .../file-viewer/monaco-diff-viewer.tsx | 82 ++++++++++++++++--- .../shell/right-panel/tabs/GitChangesTab.tsx | 3 +- packages/ui/src/styles/panels/right-panel.css | 4 +- 3 files changed, 74 insertions(+), 15 deletions(-) diff --git a/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx b/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx index 8bac7180..159e7051 100644 --- a/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx +++ b/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx @@ -15,8 +15,60 @@ interface MonacoDiffViewerProps { viewMode?: "split" | "unified" contextMode?: "expanded" | "collapsed" wordWrap?: "on" | "off" - compactUnifiedGutter?: boolean - classicUnifiedGutter?: boolean + unifiedGutterStyle?: "compact" | "classic" +} + +function getLineCount(value: string): number { + if (!value) return 1 + return value.split("\n").length +} + +function getDigitCount(value: number): number { + return String(Math.max(1, value)).length +} + +function getUnifiedGutterSizing(options: { + unifiedGutterStyle: "compact" | "classic" | null + before: string + after: string +}) { + const beforeLineCount = getLineCount(options.before) + const afterLineCount = getLineCount(options.after) + const beforeDigitCount = getDigitCount(beforeLineCount) + const afterDigitCount = getDigitCount(afterLineCount) + const maxDigitCount = Math.max(beforeDigitCount, afterDigitCount) + const extraDigits = Math.max(0, maxDigitCount - 2) + // Reserve one extra character so the number lane keeps a visible gap before + // the +/- indicator lane once the line numbers grow beyond trivial widths. + const beforeNumberChars = Math.max(2, beforeDigitCount + 1) + const afterNumberChars = Math.max(2, afterDigitCount + 1) + const fourDigitPenalty = Math.max(0, maxDigitCount - 3) + + if (options.unifiedGutterStyle === "compact") { + const sharedNumberChars = Math.max(beforeNumberChars, afterNumberChars) + return { + diffEditorLineNumbersMinChars: sharedNumberChars, + originalLineNumbersMinChars: sharedNumberChars, + modifiedLineNumbersMinChars: sharedNumberChars, + lineDecorationsWidth: 8 + extraDigits * 4 + fourDigitPenalty * 2, + } + } + + if (options.unifiedGutterStyle === "classic") { + return { + diffEditorLineNumbersMinChars: Math.max(beforeNumberChars, afterNumberChars), + originalLineNumbersMinChars: beforeNumberChars, + modifiedLineNumbersMinChars: afterNumberChars, + lineDecorationsWidth: 10 + extraDigits * 4 + fourDigitPenalty * 4, + } + } + + return { + diffEditorLineNumbersMinChars: 4, + originalLineNumbersMinChars: 4, + modifiedLineNumbersMinChars: 4, + lineDecorationsWidth: 12, + } } export function MonacoDiffViewer(props: MonacoDiffViewerProps) { @@ -99,8 +151,7 @@ export function MonacoDiffViewer(props: MonacoDiffViewerProps) { createEffect(() => { if (!host) return - host.dataset.compactUnifiedGutter = props.compactUnifiedGutter ? "true" : "false" - host.dataset.classicUnifiedGutter = props.classicUnifiedGutter ? "true" : "false" + host.dataset.unifiedGutterStyle = props.unifiedGutterStyle ?? "" }) createEffect(() => { @@ -108,17 +159,26 @@ export function MonacoDiffViewer(props: MonacoDiffViewerProps) { const viewMode = props.viewMode === "unified" ? "unified" : "split" const contextMode = props.contextMode === "collapsed" ? "collapsed" : "expanded" const wordWrap = props.wordWrap === "on" ? "on" : "off" - const compactUnifiedGutter = Boolean(props.compactUnifiedGutter) && viewMode === "unified" - const classicUnifiedGutter = Boolean(props.classicUnifiedGutter) && viewMode === "unified" - const lineNumbersMinChars = compactUnifiedGutter ? 3 : classicUnifiedGutter ? 4 : 4 - const lineDecorationsWidth = compactUnifiedGutter ? 9 : classicUnifiedGutter ? 12 : 12 + const unifiedGutterStyle = viewMode === "unified" ? props.unifiedGutterStyle ?? null : null + const { before, after } = resolvedContent() + const { + diffEditorLineNumbersMinChars, + originalLineNumbersMinChars, + modifiedLineNumbersMinChars, + lineDecorationsWidth, + } = getUnifiedGutterSizing({ + unifiedGutterStyle, + before, + after, + }) + const compactUnifiedGutter = unifiedGutterStyle === "compact" diffEditor.updateOptions({ renderSideBySide: viewMode === "split", renderSideBySideInlineBreakpoint: 0, compactMode: compactUnifiedGutter, renderIndicators: true, - lineNumbersMinChars, + lineNumbersMinChars: diffEditorLineNumbersMinChars, lineDecorationsWidth, hideUnchangedRegions: contextMode === "collapsed" @@ -133,7 +193,7 @@ export function MonacoDiffViewer(props: MonacoDiffViewerProps) { try { diffEditor.getOriginalEditor?.()?.updateOptions?.({ wordWrap, - lineNumbersMinChars, + lineNumbersMinChars: originalLineNumbersMinChars, lineDecorationsWidth, }) } catch { @@ -143,7 +203,7 @@ export function MonacoDiffViewer(props: MonacoDiffViewerProps) { try { diffEditor.getModifiedEditor?.()?.updateOptions?.({ wordWrap, - lineNumbersMinChars, + lineNumbersMinChars: modifiedLineNumbersMinChars, lineDecorationsWidth, }) } catch { diff --git a/packages/ui/src/components/instance/shell/right-panel/tabs/GitChangesTab.tsx b/packages/ui/src/components/instance/shell/right-panel/tabs/GitChangesTab.tsx index e06776e4..55012ec0 100644 --- a/packages/ui/src/components/instance/shell/right-panel/tabs/GitChangesTab.tsx +++ b/packages/ui/src/components/instance/shell/right-panel/tabs/GitChangesTab.tsx @@ -140,8 +140,7 @@ const GitChangesTab: Component = (props) => { viewMode={props.diffViewMode()} contextMode={props.diffContextMode()} wordWrap={props.diffWordWrapMode()} - compactUnifiedGutter={unifiedGutterStyle() === "compact"} - classicUnifiedGutter={unifiedGutterStyle() === "classic"} + unifiedGutterStyle={unifiedGutterStyle()} /> )} diff --git a/packages/ui/src/styles/panels/right-panel.css b/packages/ui/src/styles/panels/right-panel.css index b65a8b38..cbeaa972 100644 --- a/packages/ui/src/styles/panels/right-panel.css +++ b/packages/ui/src/styles/panels/right-panel.css @@ -337,12 +337,12 @@ direction: ltr; } -.file-viewer-content--monaco .monaco-viewer[data-compact-unified-gutter="true"] .line-numbers { +.file-viewer-content--monaco .monaco-viewer[data-unified-gutter-style="compact"] .line-numbers { text-align: left !important; padding-left: 4px; } -.file-viewer-content--monaco .monaco-viewer[data-classic-unified-gutter="true"] .line-numbers { +.file-viewer-content--monaco .monaco-viewer[data-unified-gutter-style="classic"] .line-numbers { text-align: left !important; padding-left: 4px; } From 9d794b0d86b3ae687ffcd529d9ac237887df3e75 Mon Sep 17 00:00:00 2001 From: VooDisss <41582720+VooDisss@users.noreply.github.com> Date: Thu, 9 Apr 2026 19:49:19 +0300 Subject: [PATCH 06/10] persist Git Changes unified gutter style Persist the Git Changes compact/classic unified gutter selection through the existing UI preferences store instead of keeping it in local component state. The previous redesign iterations proved out the Monaco gutter behavior, but the selected mode still reset to compact on reload because GitChangesTab owned the choice with a local signal rather than reading and writing a persisted preference. This snapshot adds a dedicated gitDiffUnifiedGutterStyle preference with compact as the default and normalizes stored values defensively. RightPanel now threads the persisted value and setter into GitChangesTab, and GitChangesTab no longer owns a competing local state copy. That makes preferences the single source of truth for the unified gutter style while keeping the control surface narrow and local to the Git Changes panel. The result is a cleaner ownership model and the expected product behavior: switching between compact and classic survives reloads, remounts, and future UI refactors that might recreate the tab component. This commit intentionally does not broaden the settings UI again; it only secures persistence for the mode that the redesign branch has already validated visually. --- .../instance/shell/right-panel/RightPanel.tsx | 3 +++ .../shell/right-panel/tabs/GitChangesTab.tsx | 19 +++++++++++-------- packages/ui/src/stores/preferences.tsx | 14 ++++++++++++++ 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/packages/ui/src/components/instance/shell/right-panel/RightPanel.tsx b/packages/ui/src/components/instance/shell/right-panel/RightPanel.tsx index c031ac87..91aae989 100644 --- a/packages/ui/src/components/instance/shell/right-panel/RightPanel.tsx +++ b/packages/ui/src/components/instance/shell/right-panel/RightPanel.tsx @@ -21,6 +21,7 @@ import type { BackgroundProcess } from "../../../../../../server/src/api-types" import type { Session } from "../../../../types/session" import type { DrawerViewState } from "../types" import type { DiffContextMode, DiffViewMode, DiffWordWrapMode, RightPanelTab } from "./types" +import { preferences, setGitDiffUnifiedGutterStyle } from "../../../../stores/preferences" import { getDefaultWorktreeSlug, getOrCreateWorktreeClient, getWorktreeSlugForSession } from "../../../../stores/worktrees" import { requestData } from "../../../../lib/opencode-api" @@ -921,9 +922,11 @@ const RightPanel: Component = (props) => { diffViewMode={diffViewMode} diffContextMode={diffContextMode} diffWordWrapMode={diffWordWrapMode} + unifiedGutterStyle={() => preferences().gitDiffUnifiedGutterStyle} onViewModeChange={setDiffViewMode} onContextModeChange={setDiffContextMode} onWordWrapModeChange={setDiffWordWrapMode} + onUnifiedGutterStyleChange={setGitDiffUnifiedGutterStyle} onOpenFile={(path: string) => void openGitFile(path)} onRefresh={() => void refreshGitStatus()} listOpen={gitChangesListOpen} diff --git a/packages/ui/src/components/instance/shell/right-panel/tabs/GitChangesTab.tsx b/packages/ui/src/components/instance/shell/right-panel/tabs/GitChangesTab.tsx index 55012ec0..7faa229e 100644 --- a/packages/ui/src/components/instance/shell/right-panel/tabs/GitChangesTab.tsx +++ b/packages/ui/src/components/instance/shell/right-panel/tabs/GitChangesTab.tsx @@ -1,4 +1,4 @@ -import { For, Show, Suspense, createMemo, createSignal, lazy, type Accessor, type Component, type JSX } from "solid-js" +import { For, Show, Suspense, createMemo, lazy, type Accessor, type Component, type JSX } from "solid-js" import type { File as GitFileStatus } from "@opencode-ai/sdk/v2/client" import { RefreshCw } from "lucide-solid" @@ -6,6 +6,7 @@ import { RefreshCw } from "lucide-solid" import DiffToolbar from "../components/DiffToolbar" import SplitFilePanel from "../components/SplitFilePanel" import type { DiffContextMode, DiffViewMode, DiffWordWrapMode } from "../types" +import type { GitDiffUnifiedGutterStyle } from "../../../../../stores/preferences" const LazyMonacoDiffViewer = lazy(() => import("../../../../file-viewer/monaco-diff-viewer").then((module) => ({ default: module.MonacoDiffViewer })), @@ -32,9 +33,11 @@ interface GitChangesTabProps { diffViewMode: Accessor diffContextMode: Accessor diffWordWrapMode: Accessor + unifiedGutterStyle: Accessor onViewModeChange: (mode: DiffViewMode) => void onContextModeChange: (mode: DiffContextMode) => void onWordWrapModeChange: (mode: DiffWordWrapMode) => void + onUnifiedGutterStyleChange: (style: GitDiffUnifiedGutterStyle) => void onOpenFile: (path: string) => void onRefresh: () => void @@ -48,7 +51,6 @@ interface GitChangesTabProps { } const GitChangesTab: Component = (props) => { - const [unifiedGutterStyle, setUnifiedGutterStyle] = createSignal<"compact" | "classic">("compact") const sessionId = createMemo(() => props.activeSessionId()) const hasSession = createMemo(() => Boolean(sessionId() && sessionId() !== "info")) const entries = createMemo(() => (hasSession() ? props.entries() : null)) @@ -140,7 +142,7 @@ const GitChangesTab: Component = (props) => { viewMode={props.diffViewMode()} contextMode={props.diffContextMode()} wordWrap={props.diffWordWrapMode()} - unifiedGutterStyle={unifiedGutterStyle()} + unifiedGutterStyle={props.unifiedGutterStyle()} /> )} @@ -270,20 +272,21 @@ const GitChangesTab: Component = (props) => { diff --git a/packages/ui/src/stores/preferences.tsx b/packages/ui/src/stores/preferences.tsx index 21209c75..f5b04813 100644 --- a/packages/ui/src/stores/preferences.tsx +++ b/packages/ui/src/stores/preferences.tsx @@ -26,6 +26,7 @@ export interface ModelPreference { } export type DiffViewMode = "split" | "unified" +export type GitDiffUnifiedGutterStyle = "compact" | "classic" export type ExpansionPreference = "expanded" | "collapsed" export type ToolInputsVisibilityPreference = "hidden" | "collapsed" | "expanded" export type ListeningMode = "local" | "all" @@ -59,6 +60,7 @@ export interface UiSettings { showPromptVoiceInput: boolean locale?: string diffViewMode: DiffViewMode + gitDiffUnifiedGutterStyle: GitDiffUnifiedGutterStyle toolOutputExpansion: ExpansionPreference diagnosticsExpansion: ExpansionPreference toolInputsVisibility: ToolInputsVisibilityPreference @@ -136,6 +138,7 @@ const defaultUiSettings: UiSettings = { promptSubmitOnEnter: false, showPromptVoiceInput: true, diffViewMode: "split", + gitDiffUnifiedGutterStyle: "compact", toolOutputExpansion: "expanded", diagnosticsExpansion: "expanded", toolInputsVisibility: "collapsed", @@ -160,6 +163,8 @@ const defaultSpeechSettings: SpeechSettings = { function normalizeUiSettings(input?: Partial | null): UiSettings { const sanitized = input ?? {} + const gitDiffUnifiedGutterStyle: GitDiffUnifiedGutterStyle = + sanitized.gitDiffUnifiedGutterStyle === "classic" ? "classic" : "compact" return { showThinkingBlocks: sanitized.showThinkingBlocks ?? defaultUiSettings.showThinkingBlocks, showKeyboardShortcutHints: @@ -170,6 +175,7 @@ function normalizeUiSettings(input?: Partial | null): UiSettings { showPromptVoiceInput: sanitized.showPromptVoiceInput ?? defaultUiSettings.showPromptVoiceInput, locale: sanitized.locale ?? defaultUiSettings.locale, diffViewMode: sanitized.diffViewMode ?? defaultUiSettings.diffViewMode, + gitDiffUnifiedGutterStyle, toolOutputExpansion: sanitized.toolOutputExpansion ?? defaultUiSettings.toolOutputExpansion, diagnosticsExpansion: sanitized.diagnosticsExpansion ?? defaultUiSettings.diagnosticsExpansion, toolInputsVisibility: @@ -622,6 +628,11 @@ function setDiffViewMode(mode: DiffViewMode): void { updateUiSettings({ diffViewMode: mode }) } +function setGitDiffUnifiedGutterStyle(style: GitDiffUnifiedGutterStyle): void { + if (preferences().gitDiffUnifiedGutterStyle === style) return + updateUiSettings({ gitDiffUnifiedGutterStyle: style }) +} + function setToolOutputExpansion(mode: ExpansionPreference): void { if (preferences().toolOutputExpansion === mode) return updateUiSettings({ toolOutputExpansion: mode }) @@ -742,6 +753,7 @@ interface ConfigContextValue { togglePromptSubmitOnEnter: typeof togglePromptSubmitOnEnter toggleShowPromptVoiceInput: typeof toggleShowPromptVoiceInput setDiffViewMode: typeof setDiffViewMode + setGitDiffUnifiedGutterStyle: typeof setGitDiffUnifiedGutterStyle setToolOutputExpansion: typeof setToolOutputExpansion setDiagnosticsExpansion: typeof setDiagnosticsExpansion setThinkingBlocksExpansion: typeof setThinkingBlocksExpansion @@ -793,6 +805,7 @@ const configContextValue: ConfigContextValue = { togglePromptSubmitOnEnter, toggleShowPromptVoiceInput, setDiffViewMode, + setGitDiffUnifiedGutterStyle, setToolOutputExpansion, setDiagnosticsExpansion, setThinkingBlocksExpansion, @@ -874,6 +887,7 @@ export { togglePromptSubmitOnEnter, toggleShowPromptVoiceInput, setDiffViewMode, + setGitDiffUnifiedGutterStyle, setToolOutputExpansion, setDiagnosticsExpansion, setThinkingBlocksExpansion, From 2aec71afef84e92fe737542fd93a9680f67d6160 Mon Sep 17 00:00:00 2001 From: VooDisss <41582720+VooDisss@users.noreply.github.com> Date: Thu, 9 Apr 2026 19:58:15 +0300 Subject: [PATCH 07/10] translate Git Changes gutter toggle labels Replace the remaining hardcoded user-facing strings in the Git Changes compact/classic gutter toggle with i18n-backed messages. The gutter redesign branch had already validated the Monaco behavior and persistence, but the toggle itself still rendered English-only labels and titles directly in GitChangesTab, which violated the repository rule that user-visible strings must go through the shared localization layer. This follow-up keeps the scope intentionally narrow. It adds only the four new gutter-toggle keys under the existing instanceShell.diff namespace in the locale instance.ts files and switches the button label, title, and aria-label lookups over to props.t(...). No broader locale-file cleanup or settings copy refactor is included in this branch. The result is that the compact/classic gutter control now meets the codebase's i18n expectations without changing any of the underlying Monaco behavior, persistence model, or product surface validated in the earlier redesign checkpoints. --- .../shell/right-panel/tabs/GitChangesTab.tsx | 12 +++++++----- packages/ui/src/lib/i18n/messages/en/instance.ts | 4 ++++ packages/ui/src/lib/i18n/messages/es/instance.ts | 4 ++++ packages/ui/src/lib/i18n/messages/fr/instance.ts | 4 ++++ packages/ui/src/lib/i18n/messages/he/instance.ts | 4 ++++ packages/ui/src/lib/i18n/messages/ja/instance.ts | 4 ++++ packages/ui/src/lib/i18n/messages/ru/instance.ts | 4 ++++ .../ui/src/lib/i18n/messages/zh-Hans/instance.ts | 4 ++++ 8 files changed, 35 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/components/instance/shell/right-panel/tabs/GitChangesTab.tsx b/packages/ui/src/components/instance/shell/right-panel/tabs/GitChangesTab.tsx index 7faa229e..94072a90 100644 --- a/packages/ui/src/components/instance/shell/right-panel/tabs/GitChangesTab.tsx +++ b/packages/ui/src/components/instance/shell/right-panel/tabs/GitChangesTab.tsx @@ -277,16 +277,18 @@ const GitChangesTab: Component = (props) => { props.onUnifiedGutterStyleChange(props.unifiedGutterStyle() === "compact" ? "classic" : "compact")} aria-label={ props.unifiedGutterStyle() === "compact" - ? "Switch unified gutter to classic" - : "Switch unified gutter to compact" + ? props.t("instanceShell.diff.switchToClassicGutter") + : props.t("instanceShell.diff.switchToCompactGutter") } title={ props.unifiedGutterStyle() === "compact" - ? "Switch unified gutter to classic" - : "Switch unified gutter to compact" + ? props.t("instanceShell.diff.switchToClassicGutter") + : props.t("instanceShell.diff.switchToCompactGutter") } > - {props.unifiedGutterStyle() === "compact" ? "Compact" : "Classic"} + {props.unifiedGutterStyle() === "compact" + ? props.t("instanceShell.diff.gutterStyleCompact") + : props.t("instanceShell.diff.gutterStyleClassic")} diff --git a/packages/ui/src/lib/i18n/messages/en/instance.ts b/packages/ui/src/lib/i18n/messages/en/instance.ts index d56f481c..9258aa9c 100644 --- a/packages/ui/src/lib/i18n/messages/en/instance.ts +++ b/packages/ui/src/lib/i18n/messages/en/instance.ts @@ -146,6 +146,10 @@ export const instanceMessages = { "instanceShell.diff.switchToUnified": "Switch to unified view", "instanceShell.diff.enableWordWrap": "Enable word wrap", "instanceShell.diff.disableWordWrap": "Disable word wrap", + "instanceShell.diff.switchToCompactGutter": "Switch unified gutter to compact", + "instanceShell.diff.switchToClassicGutter": "Switch unified gutter to classic", + "instanceShell.diff.gutterStyleCompact": "Compact", + "instanceShell.diff.gutterStyleClassic": "Classic", "instanceShell.worktree.create": "+ Create worktree", "instanceShell.plan.noSessionSelected": "Select a session to view plan.", diff --git a/packages/ui/src/lib/i18n/messages/es/instance.ts b/packages/ui/src/lib/i18n/messages/es/instance.ts index e0293f1f..56ad2360 100644 --- a/packages/ui/src/lib/i18n/messages/es/instance.ts +++ b/packages/ui/src/lib/i18n/messages/es/instance.ts @@ -130,6 +130,10 @@ export const instanceMessages = { "instanceShell.gitChanges.loading": "Cargando cambios de Git...", "instanceShell.gitChanges.empty": "Aún no hay cambios de Git.", "instanceShell.gitChanges.deleted": "Eliminado", + "instanceShell.diff.switchToCompactGutter": "Cambiar el gutter unificado a compacto", + "instanceShell.diff.switchToClassicGutter": "Cambiar el gutter unificado a clásico", + "instanceShell.diff.gutterStyleCompact": "Compacto", + "instanceShell.diff.gutterStyleClassic": "Clásico", "instanceShell.filesShell.fileListTitle": "Lista de archivos", "instanceShell.filesShell.mobileSelectorLabel": "Seleccionar archivo", diff --git a/packages/ui/src/lib/i18n/messages/fr/instance.ts b/packages/ui/src/lib/i18n/messages/fr/instance.ts index 26c56844..547c2bd8 100644 --- a/packages/ui/src/lib/i18n/messages/fr/instance.ts +++ b/packages/ui/src/lib/i18n/messages/fr/instance.ts @@ -130,6 +130,10 @@ export const instanceMessages = { "instanceShell.gitChanges.loading": "Chargement des changements Git...", "instanceShell.gitChanges.empty": "Aucun changement Git pour l'instant.", "instanceShell.gitChanges.deleted": "Supprimé", + "instanceShell.diff.switchToCompactGutter": "Passer la gouttière unifiée en mode compact", + "instanceShell.diff.switchToClassicGutter": "Passer la gouttière unifiée en mode classique", + "instanceShell.diff.gutterStyleCompact": "Compact", + "instanceShell.diff.gutterStyleClassic": "Classique", "instanceShell.filesShell.fileListTitle": "Liste des fichiers", "instanceShell.filesShell.mobileSelectorLabel": "Sélectionner un fichier", diff --git a/packages/ui/src/lib/i18n/messages/he/instance.ts b/packages/ui/src/lib/i18n/messages/he/instance.ts index 1db1b29c..f78f223b 100644 --- a/packages/ui/src/lib/i18n/messages/he/instance.ts +++ b/packages/ui/src/lib/i18n/messages/he/instance.ts @@ -144,6 +144,10 @@ export const instanceMessages = { "instanceShell.diff.switchToUnified": "עבור לתצוגה מאוחדת", "instanceShell.diff.enableWordWrap": "הפעל גלישת מילים", "instanceShell.diff.disableWordWrap": "כבה גלישת מילים", + "instanceShell.diff.switchToCompactGutter": "עבור לשוליים מאוחדים קומפקטיים", + "instanceShell.diff.switchToClassicGutter": "עבור לשוליים מאוחדים קלאסיים", + "instanceShell.diff.gutterStyleCompact": "קומפקטי", + "instanceShell.diff.gutterStyleClassic": "קלאסי", "instanceShell.worktree.create": "+ צור worktree", "instanceShell.plan.noSessionSelected": "בחר סשן לצפייה בתוכנית.", diff --git a/packages/ui/src/lib/i18n/messages/ja/instance.ts b/packages/ui/src/lib/i18n/messages/ja/instance.ts index 546a22ef..ae4afab3 100644 --- a/packages/ui/src/lib/i18n/messages/ja/instance.ts +++ b/packages/ui/src/lib/i18n/messages/ja/instance.ts @@ -130,6 +130,10 @@ export const instanceMessages = { "instanceShell.gitChanges.loading": "Git の変更を読み込み中...", "instanceShell.gitChanges.empty": "Git の変更はまだありません。", "instanceShell.gitChanges.deleted": "削除済み", + "instanceShell.diff.switchToCompactGutter": "統合ガターをコンパクト表示に切り替え", + "instanceShell.diff.switchToClassicGutter": "統合ガターをクラシック表示に切り替え", + "instanceShell.diff.gutterStyleCompact": "コンパクト", + "instanceShell.diff.gutterStyleClassic": "クラシック", "instanceShell.filesShell.fileListTitle": "ファイル一覧", "instanceShell.filesShell.mobileSelectorLabel": "ファイルを選択", diff --git a/packages/ui/src/lib/i18n/messages/ru/instance.ts b/packages/ui/src/lib/i18n/messages/ru/instance.ts index 8a4b6a89..1ceb2505 100644 --- a/packages/ui/src/lib/i18n/messages/ru/instance.ts +++ b/packages/ui/src/lib/i18n/messages/ru/instance.ts @@ -130,6 +130,10 @@ export const instanceMessages = { "instanceShell.gitChanges.loading": "Загрузка изменений Git...", "instanceShell.gitChanges.empty": "Изменений Git пока нет.", "instanceShell.gitChanges.deleted": "Удалено", + "instanceShell.diff.switchToCompactGutter": "Переключить объединённую область на компактный режим", + "instanceShell.diff.switchToClassicGutter": "Переключить объединённую область на классический режим", + "instanceShell.diff.gutterStyleCompact": "Компактный", + "instanceShell.diff.gutterStyleClassic": "Классический", "instanceShell.filesShell.fileListTitle": "Список файлов", "instanceShell.filesShell.mobileSelectorLabel": "Выбрать файл", diff --git a/packages/ui/src/lib/i18n/messages/zh-Hans/instance.ts b/packages/ui/src/lib/i18n/messages/zh-Hans/instance.ts index 10675d0a..0c62bad8 100644 --- a/packages/ui/src/lib/i18n/messages/zh-Hans/instance.ts +++ b/packages/ui/src/lib/i18n/messages/zh-Hans/instance.ts @@ -130,6 +130,10 @@ export const instanceMessages = { "instanceShell.gitChanges.loading": "正在加载 Git 更改...", "instanceShell.gitChanges.empty": "暂无 Git 更改。", "instanceShell.gitChanges.deleted": "已删除", + "instanceShell.diff.switchToCompactGutter": "切换统一边栏为紧凑模式", + "instanceShell.diff.switchToClassicGutter": "切换统一边栏为经典模式", + "instanceShell.diff.gutterStyleCompact": "紧凑", + "instanceShell.diff.gutterStyleClassic": "经典", "instanceShell.filesShell.fileListTitle": "文件列表", "instanceShell.filesShell.mobileSelectorLabel": "选择文件", From 8ccb9330fa44badc4b605e9f81ed219e67972f48 Mon Sep 17 00:00:00 2001 From: VooDisss <41582720+VooDisss@users.noreply.github.com> Date: Thu, 9 Apr 2026 20:13:39 +0300 Subject: [PATCH 08/10] refine split Monaco gutter sizing Extend the Git Changes Monaco gutter redesign into split view so the split editor no longer falls back to the earlier static-width assumptions. The previous work focused on unified mode and then proved out dynamic gutter sizing, persistence, and localization, but split mode still showed the same class of layout problems: wasted left inset on the original pane and overlap or over-allocation around the modified pane's line-number and sign lanes as digit counts grew. This snapshot introduces a dedicated split-mode sizing path in monaco-diff-viewer.tsx while reusing the same clean, line-count-driven design principles established for unified mode. Split view now computes digit-aware budgets for the original and modified panes independently, rather than inheriting a generic static gutter width. The modified pane therefore expands when large line-number ranges require more room, and the original pane no longer needs to pay for width it does not use. The split heuristic was then tuned to be less aggressive than the unified classic heuristic on the decorations lane, because the earlier formula fixed overlap but over-expanded the right-side gutter for four- and five-digit files. By reducing the growth rate for split-mode lineDecorationsWidth while preserving dynamic per-side number widths, the gutter stays readable without leaving conspicuous dead space. A small split-only CSS adjustment also aligns both split panes to the same 4px left inset used elsewhere, removing the wasted padding before the line numbers in the original pane. This commit captures the split-view checkpoint of the redesign: unified and split modes now share the same overall architectural approach, but each mode can still tune its own gutter heuristics where Monaco's layout behavior differs. It should provide a cleaner base for any final polish or future productization of the Monaco gutter behavior across the rest of the diff surfaces. --- .../file-viewer/monaco-diff-viewer.tsx | 34 ++++++++++++++++--- packages/ui/src/styles/panels/right-panel.css | 6 ++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx b/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx index 159e7051..26b7e6c0 100644 --- a/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx +++ b/packages/ui/src/components/file-viewer/monaco-diff-viewer.tsx @@ -71,6 +71,25 @@ function getUnifiedGutterSizing(options: { } } +function getSplitGutterSizing(options: { before: string; after: string }) { + const beforeLineCount = getLineCount(options.before) + const afterLineCount = getLineCount(options.after) + const beforeDigitCount = getDigitCount(beforeLineCount) + const afterDigitCount = getDigitCount(afterLineCount) + const maxDigitCount = Math.max(beforeDigitCount, afterDigitCount) + const extraDigits = Math.max(0, maxDigitCount - 2) + const beforeNumberChars = Math.max(2, beforeDigitCount + 1) + const afterNumberChars = Math.max(2, afterDigitCount + 1) + const fourDigitPenalty = Math.max(0, maxDigitCount - 3) + + return { + diffEditorLineNumbersMinChars: Math.max(beforeNumberChars, afterNumberChars), + originalLineNumbersMinChars: beforeNumberChars, + modifiedLineNumbersMinChars: afterNumberChars, + lineDecorationsWidth: 10 + extraDigits * 2 + fourDigitPenalty * 2, + } +} + export function MonacoDiffViewer(props: MonacoDiffViewerProps) { const { isDark } = useTheme() let host: HTMLDivElement | undefined @@ -151,6 +170,7 @@ export function MonacoDiffViewer(props: MonacoDiffViewerProps) { createEffect(() => { if (!host) return + host.dataset.viewMode = props.viewMode === "split" ? "split" : "unified" host.dataset.unifiedGutterStyle = props.unifiedGutterStyle ?? "" }) @@ -161,16 +181,20 @@ export function MonacoDiffViewer(props: MonacoDiffViewerProps) { const wordWrap = props.wordWrap === "on" ? "on" : "off" const unifiedGutterStyle = viewMode === "unified" ? props.unifiedGutterStyle ?? null : null const { before, after } = resolvedContent() + const sizing = + viewMode === "unified" + ? getUnifiedGutterSizing({ + unifiedGutterStyle, + before, + after, + }) + : getSplitGutterSizing({ before, after }) const { diffEditorLineNumbersMinChars, originalLineNumbersMinChars, modifiedLineNumbersMinChars, lineDecorationsWidth, - } = getUnifiedGutterSizing({ - unifiedGutterStyle, - before, - after, - }) + } = sizing const compactUnifiedGutter = unifiedGutterStyle === "compact" diffEditor.updateOptions({ diff --git a/packages/ui/src/styles/panels/right-panel.css b/packages/ui/src/styles/panels/right-panel.css index cbeaa972..1c7b28d7 100644 --- a/packages/ui/src/styles/panels/right-panel.css +++ b/packages/ui/src/styles/panels/right-panel.css @@ -347,6 +347,12 @@ padding-left: 4px; } +.file-viewer-content--monaco .monaco-viewer[data-view-mode="split"] .editor.original .line-numbers, +.file-viewer-content--monaco .monaco-viewer[data-view-mode="split"] .editor.modified .line-numbers { + text-align: left !important; + padding-left: 4px; +} + .file-viewer-empty { @apply flex flex-col items-center justify-center h-full gap-3 text-center; color: var(--text-muted); From fa3e01ee3fd6104ee00b4b542ba7a09052ba2343 Mon Sep 17 00:00:00 2001 From: VooDisss <41582720+VooDisss@users.noreply.github.com> Date: Thu, 9 Apr 2026 20:24:08 +0300 Subject: [PATCH 09/10] fix split original Monaco gutter inset Correct the remaining wasted left inset in the split-view original pane of the Git Changes Monaco diff viewer. Earlier split-mode work fixed the large-scale dynamic sizing issues and the modified pane overlap problems, but one visual defect remained: the original pane still showed an empty strip before the first visible line-number column even when the line numbers were only a single digit wide. The DOM for the original split pane showed that this inset was not caused by the shared 4px line-number padding rule or by the dynamic number-width heuristic. Monaco was still reserving an empty glyph-margin lane of 18px on the original side, and the line numbers themselves were positioned at left: 18px inside a 42px margin block. That meant the visible number column was shifted inward before any of our line-number styling could apply. This commit fixes that specific structural inset without touching the broader gutter-width heuristics again. It collapses the empty original-side glyph margin, realigns the original line numbers and delete sign to the same left-edge layout pattern used by the modified pane, shrinks the original pane margin wrappers accordingly, and shifts the original scrollable content left to match the corrected margin width. The change is intentionally scoped to split mode and the original pane only, because the modified pane did not exhibit the same empty leading lane. This snapshot captures the narrow DOM-rooted correction that was needed after the dynamic split sizing work. It keeps the earlier line-count-driven sizing model intact while removing the leftover Monaco structural offset that had still made the original split gutter feel wasteful and visually inconsistent. --- packages/ui/src/styles/panels/right-panel.css | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/ui/src/styles/panels/right-panel.css b/packages/ui/src/styles/panels/right-panel.css index 1c7b28d7..0352ec04 100644 --- a/packages/ui/src/styles/panels/right-panel.css +++ b/packages/ui/src/styles/panels/right-panel.css @@ -353,6 +353,29 @@ padding-left: 4px; } +.file-viewer-content--monaco .monaco-viewer[data-view-mode="split"] .editor.original .glyph-margin { + width: 0 !important; +} + +.file-viewer-content--monaco .monaco-viewer[data-view-mode="split"] .editor.original .line-numbers { + left: 0 !important; +} + +.file-viewer-content--monaco .monaco-viewer[data-view-mode="split"] .editor.original .cldr.delete-sign { + left: 14px !important; +} + +.file-viewer-content--monaco .monaco-viewer[data-view-mode="split"] .editor.original .margin, +.file-viewer-content--monaco .monaco-viewer[data-view-mode="split"] .editor.original .margin-view-zones, +.file-viewer-content--monaco .monaco-viewer[data-view-mode="split"] .editor.original .margin-view-overlays { + width: 24px !important; +} + +.file-viewer-content--monaco .monaco-viewer[data-view-mode="split"] .editor.original .editor-scrollable { + left: 24px !important; + width: calc(100% - 24px) !important; +} + .file-viewer-empty { @apply flex flex-col items-center justify-center h-full gap-3 text-center; color: var(--text-muted); From b2840c3419fee417fd14617a52e5b66108069d06 Mon Sep 17 00:00:00 2001 From: VooDisss <41582720+VooDisss@users.noreply.github.com> Date: Thu, 9 Apr 2026 20:39:52 +0300 Subject: [PATCH 10/10] move Git gutter mode into Appearance settings Move the Git Changes compact/classic Monaco gutter control out of the in-panel diff toolbar and into the Appearance settings section, where it fits better as a persisted presentation preference. The previous toolbar button was useful while iterating on the redesign, but once the gutter behavior stabilized it became clear that the choice belongs with other appearance and diff defaults rather than inside the Git Changes panel itself. This commit removes the local GitChangesTab toggle button entirely and reuses the already-persisted gitDiffUnifiedGutterStyle preference as the source of truth for a new settings row in Appearance. The selector is wired through the existing settings UI patterns and presents the two user-facing modes as Compact and Normal, matching the desired naming more closely than the earlier Compact and Classic terminology shown directly in the panel. The implementation intentionally focuses on the UI relocation rather than changing any of the underlying Monaco behavior. Unified and split gutter sizing, persistence, and the existing Monaco presentation heuristics stay exactly as they were. Only the control surface changes: users now choose the mode in Settings, and Git Changes simply consumes the persisted preference. This snapshot is also intentionally narrow in localization scope. It adds the new settings-row copy in English so the relocation can be reviewed, but the remaining locale settings files still need the same small follow-up translation pass before this should be considered fully complete for multilingual users. The branch already established the i18n pattern for the earlier toolbar labels; this commit captures the product-level move of the control itself. --- .../shell/right-panel/tabs/GitChangesTab.tsx | 22 -------- .../settings/appearance-settings-section.tsx | 53 +++++++++++++++++++ .../ui/src/lib/i18n/messages/en/settings.ts | 4 ++ 3 files changed, 57 insertions(+), 22 deletions(-) diff --git a/packages/ui/src/components/instance/shell/right-panel/tabs/GitChangesTab.tsx b/packages/ui/src/components/instance/shell/right-panel/tabs/GitChangesTab.tsx index 94072a90..c03d8b76 100644 --- a/packages/ui/src/components/instance/shell/right-panel/tabs/GitChangesTab.tsx +++ b/packages/ui/src/components/instance/shell/right-panel/tabs/GitChangesTab.tsx @@ -269,28 +269,6 @@ const GitChangesTab: Component = (props) => { onWordWrapModeChange={props.onWordWrapModeChange} /> - - - } list={{ panel: renderListPanel, overlay: renderListOverlay }} diff --git a/packages/ui/src/components/settings/appearance-settings-section.tsx b/packages/ui/src/components/settings/appearance-settings-section.tsx index 10974d0b..4a3495ab 100644 --- a/packages/ui/src/components/settings/appearance-settings-section.tsx +++ b/packages/ui/src/components/settings/appearance-settings-section.tsx @@ -5,6 +5,7 @@ import { useI18n } from "../../lib/i18n" import { useTheme, type ThemeMode } from "../../lib/theme" import { useConfig } from "../../stores/preferences" import { getBehaviorSettings, type BehaviorSetting } from "../../lib/settings/behavior-registry" +import type { GitDiffUnifiedGutterStyle } from "../../stores/preferences" const themeModeOptions: Array<{ value: ThemeMode; icon: typeof Laptop }> = [ { value: "system", icon: Laptop }, @@ -26,12 +27,21 @@ export const AppearanceSettingsSection: Component = () => { togglePromptSubmitOnEnter, toggleShowPromptVoiceInput, setDiffViewMode, + setGitDiffUnifiedGutterStyle, setToolOutputExpansion, setDiagnosticsExpansion, setThinkingBlocksExpansion, setToolInputsVisibility, } = useConfig() + const gitDiffGutterStyleOptions = createMemo>(() => [ + { value: "compact", label: t("settings.appearance.gitDiff.gutterMode.option.compact") }, + { value: "classic", label: t("settings.appearance.gitDiff.gutterMode.option.normal") }, + ]) + const selectedGitDiffGutterStyle = createMemo(() => + gitDiffGutterStyleOptions().find((option) => option.value === preferences().gitDiffUnifiedGutterStyle), + ) + const behaviorSettings = createMemo(() => getBehaviorSettings({ preferences, @@ -265,6 +275,49 @@ export const AppearanceSettingsSection: Component = () => {
{(setting) => } + +
+
+
{t("settings.appearance.gitDiff.gutterMode.title")}
+
{t("settings.appearance.gitDiff.gutterMode.subtitle")}
+
+ + value={selectedGitDiffGutterStyle()} + onChange={(opt) => { + if (!opt) return + setGitDiffUnifiedGutterStyle(opt.value) + }} + options={gitDiffGutterStyleOptions()} + optionValue="value" + optionTextValue="label" + itemComponent={(itemProps) => ( + + {itemProps.item.rawValue.label} + + )} + > + +
+ > + {(state) => ( + + {state.selectedOption()?.label} + + )} + +
+ + + +
+ + + + + + + +
diff --git a/packages/ui/src/lib/i18n/messages/en/settings.ts b/packages/ui/src/lib/i18n/messages/en/settings.ts index f77b7b48..10b9943c 100644 --- a/packages/ui/src/lib/i18n/messages/en/settings.ts +++ b/packages/ui/src/lib/i18n/messages/en/settings.ts @@ -137,6 +137,10 @@ export const settingsMessages = { "settings.behavior.diffView.subtitle": "Choose how tool-call diffs are displayed.", "settings.behavior.diffView.option.split": "Split", "settings.behavior.diffView.option.unified": "Unified", + "settings.appearance.gitDiff.gutterMode.title": "Git diff gutter mode", + "settings.appearance.gitDiff.gutterMode.subtitle": "Choose the Monaco gutter presentation used for Git Changes diffs.", + "settings.appearance.gitDiff.gutterMode.option.compact": "Compact", + "settings.appearance.gitDiff.gutterMode.option.normal": "Normal", "settings.behavior.toolOutputsDefault.title": "Tool outputs default", "settings.behavior.toolOutputsDefault.subtitle": "Choose whether tool outputs start expanded or collapsed.", "settings.behavior.diagnosticsDefault.title": "Diagnostics default",