From c4d1acc75b98cb53c21518e856d5228c58664e47 Mon Sep 17 00:00:00 2001 From: Mika Kuns Date: Fri, 19 Jun 2026 10:15:12 +0200 Subject: [PATCH] feat(merge): Rider-style 3-pane conflict editor view Replace the Base|Ours|Theirs read-only columns + single-conflict result with a whole-file 3-pane editor: Ours (read-only) | editable Result | Theirs (read-only), reconstructed from the active file's segments so the panes line up on stable text. - IBackgroundRenderer paints each conflict block (unresolved=blood, resolved=green) across all three panes. - Result document edits are gated by an IReadOnlySectionProvider (stable text is read-only; only conflict regions, tracked via TextAnchors, are editable); edits flow back to the owning block. - Between-pane gutters host inline accept controls (>/< ) positioned per conflict; click accepts ours/theirs into the result. - Proportional synced vertical scroll across the panes; file switcher + change-nav arrows (F8 / Shift+F8); active-file 'M conflicts - K resolved' readout. - Merge block tints + AmberBrush tokens; en/de keys for the new labels. Seam unchanged. App builds; Ui.Tests 128, Localization.Tests 16. --- src/ClaudeDo.Localization/locales/de.json | 15 +- src/ClaudeDo.Localization/locales/en.json | 15 +- src/ClaudeDo.Ui/Design/Tokens.axaml | 8 + .../Conflicts/ConflictResolverViewModel.cs | 6 + .../Conflicts/ConflictResolverView.axaml | 112 +++--- .../Conflicts/ConflictResolverView.axaml.cs | 373 ++++++++++++++++-- 6 files changed, 432 insertions(+), 97 deletions(-) diff --git a/src/ClaudeDo.Localization/locales/de.json b/src/ClaudeDo.Localization/locales/de.json index 1ddde29..36d1339 100644 --- a/src/ClaudeDo.Localization/locales/de.json +++ b/src/ClaudeDo.Localization/locales/de.json @@ -400,13 +400,14 @@ "windowTitle": "Merge-Konflikte lösen", "modalTitle": "KONFLIKTE LÖSEN", "loading": "Konflikte werden geladen…", - "current": "Aktuell (unsere)", - "incoming": "Eingehend (ihre)", - "mergedResult": "Zusammengeführtes Ergebnis", - "acceptCurrent": "Aktuelle übernehmen", - "acceptIncoming": "Eingehende übernehmen", - "acceptBoth": "Beide übernehmen", - "editManually": "Manuell bearbeiten", + "ours": "OURS · aktuell (Ziel-Branch)", + "result": "ERGEBNIS", + "theirs": "THEIRS · eingehend (Task)", + "binaryHint": "Binärdateien können hier nicht zusammengeführt werden — brich ab und löse sie in deinem Editor:", + "prevConflict": "Vorheriger Konflikt (Umschalt+F8)", + "nextConflict": "Nächster Konflikt (F8)", + "acceptOurs": "Ours ins Ergebnis übernehmen", + "acceptTheirs": "Theirs ins Ergebnis übernehmen", "continue": "Lösen & fortfahren", "abort": "Merge abbrechen" }, diff --git a/src/ClaudeDo.Localization/locales/en.json b/src/ClaudeDo.Localization/locales/en.json index e3b9d1e..0317a6b 100644 --- a/src/ClaudeDo.Localization/locales/en.json +++ b/src/ClaudeDo.Localization/locales/en.json @@ -400,13 +400,14 @@ "windowTitle": "Resolve merge conflicts", "modalTitle": "RESOLVE CONFLICTS", "loading": "Loading conflicts…", - "current": "Current (ours)", - "incoming": "Incoming (theirs)", - "mergedResult": "Merged result", - "acceptCurrent": "Accept Current", - "acceptIncoming": "Accept Incoming", - "acceptBoth": "Accept Both", - "editManually": "Edit manually", + "ours": "OURS · current (merge target)", + "result": "RESULT", + "theirs": "THEIRS · incoming (task)", + "binaryHint": "Binary files can't be merged here — abort and resolve them in your editor:", + "prevConflict": "Previous conflict (Shift+F8)", + "nextConflict": "Next conflict (F8)", + "acceptOurs": "Accept ours into result", + "acceptTheirs": "Accept theirs into result", "continue": "Resolve & continue", "abort": "Abort merge" }, diff --git a/src/ClaudeDo.Ui/Design/Tokens.axaml b/src/ClaudeDo.Ui/Design/Tokens.axaml index 8bafa08..d291080 100644 --- a/src/ClaudeDo.Ui/Design/Tokens.axaml +++ b/src/ClaudeDo.Ui/Design/Tokens.axaml @@ -100,6 +100,14 @@ + + + + + + + + diff --git a/src/ClaudeDo.Ui/ViewModels/Conflicts/ConflictResolverViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Conflicts/ConflictResolverViewModel.cs index 06548e8..732fcd4 100644 --- a/src/ClaudeDo.Ui/ViewModels/Conflicts/ConflictResolverViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Conflicts/ConflictResolverViewModel.cs @@ -57,6 +57,12 @@ public sealed partial class ConflictResolverViewModel : ObservableObject OnPropertyChanged(nameof(ActiveTheirsText)); OnPropertyChanged(nameof(ActiveResultText)); OnPropertyChanged(nameof(PositionText)); + // Keep the focused conflict inside the active file (e.g. when switched via the file picker). + if (value is not null && (Current is null || !value.Conflicts.Contains(Current))) + { + var idx = _flat.FindIndex(x => x.File == value); + if (idx >= 0) MoveTo(idx); + } } public string ActiveOursText => ActiveFile?.OursText ?? ""; diff --git a/src/ClaudeDo.Ui/Views/Conflicts/ConflictResolverView.axaml b/src/ClaudeDo.Ui/Views/Conflicts/ConflictResolverView.axaml index 0b9d8d6..7a023eb 100644 --- a/src/ClaudeDo.Ui/Views/Conflicts/ConflictResolverView.axaml +++ b/src/ClaudeDo.Ui/Views/Conflicts/ConflictResolverView.axaml @@ -7,7 +7,7 @@ x:DataType="vm:ConflictResolverViewModel" x:Class="ClaudeDo.Ui.Views.Conflicts.ConflictResolverView" Title="{loc:Tr conflictResolver.windowTitle}" - Width="1120" Height="760" MinWidth="840" MinHeight="540" + Width="1280" Height="820" MinWidth="960" MinHeight="560" CanResize="True" WindowDecorations="BorderOnly" ExtendClientAreaToDecorationsHint="True" @@ -17,6 +17,8 @@ + + @@ -24,13 +26,38 @@ - - + + + + + + @@ -49,7 +76,7 @@ - + + Text="{loc:Tr conflictResolver.binaryHint}"/> @@ -76,69 +103,64 @@ - - + -