diff --git a/src/ClaudeDo.Ui/ViewModels/Conflicts/ConflictModels.cs b/src/ClaudeDo.Ui/ViewModels/Conflicts/ConflictModels.cs
index 7212e24..e4db884 100644
--- a/src/ClaudeDo.Ui/ViewModels/Conflicts/ConflictModels.cs
+++ b/src/ClaudeDo.Ui/ViewModels/Conflicts/ConflictModels.cs
@@ -72,9 +72,11 @@ public sealed class MergeFile
/// A binary file can't be resolved in-app; a text file is done once every block is resolved.
public bool AllResolved => !IsBinary && Conflicts.All(c => c.IsResolved);
- /// Reassemble the file: stable text verbatim, each conflict replaced by its resolution.
+ /// Reassemble the file: stable text verbatim, each conflict replaced by its resolution
+ /// (empty when unresolved — the same "empty start" the editor shows; Continue is gated on
+ /// so an unresolved conflict never actually reaches here).
public string Compose() => string.Concat(
- Segments.Select(s => s.IsConflict ? (s.Conflict!.Resolution ?? s.Conflict.Ours) : s.StableText));
+ Segments.Select(s => s.IsConflict ? (s.Conflict!.Resolution ?? "") : s.StableText));
/// Left pane document: stable regions verbatim, conflict regions show Ours text.
public string OursText => string.Concat(
@@ -84,7 +86,8 @@ public sealed class MergeFile
public string TheirsText => string.Concat(
Segments.Select(s => s.IsConflict ? s.Conflict!.Theirs : s.StableText));
- /// Middle (result) pane document: stable regions verbatim, conflict regions show Resolution if set, else Ours.
+ /// Middle (result) pane document: stable regions verbatim, conflict regions show the
+ /// chosen Resolution, or empty when unresolved (the editor builds each conflict up from empty).
public string ResultText => string.Concat(
- Segments.Select(s => s.IsConflict ? (s.Conflict!.Resolution ?? s.Conflict.Ours) : s.StableText));
+ Segments.Select(s => s.IsConflict ? (s.Conflict!.Resolution ?? "") : s.StableText));
}
diff --git a/src/ClaudeDo.Ui/ViewModels/Conflicts/ConflictResolverViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Conflicts/ConflictResolverViewModel.cs
index fe6b3e3..a4018a8 100644
--- a/src/ClaudeDo.Ui/ViewModels/Conflicts/ConflictResolverViewModel.cs
+++ b/src/ClaudeDo.Ui/ViewModels/Conflicts/ConflictResolverViewModel.cs
@@ -81,7 +81,7 @@ public sealed partial class ConflictResolverViewModel : ObservableObject
if (ActiveFile is null || ActiveFile.Conflicts.Count == 0) return "No text conflicts";
var count = ActiveFile.Conflicts.Count;
var resolved = ActiveFile.Conflicts.Count(c => c.IsResolved);
- return $"{count} conflicts · {resolved} resolved";
+ return $"{count} {(count == 1 ? "conflict" : "conflicts")} · {resolved} resolved";
}
}
diff --git a/src/ClaudeDo.Ui/Views/Conflicts/ConflictResolverView.axaml.cs b/src/ClaudeDo.Ui/Views/Conflicts/ConflictResolverView.axaml.cs
index 6bda13a..2e33714 100644
--- a/src/ClaudeDo.Ui/Views/Conflicts/ConflictResolverView.axaml.cs
+++ b/src/ClaudeDo.Ui/Views/Conflicts/ConflictResolverView.axaml.cs
@@ -40,6 +40,7 @@ public partial class ConflictResolverView : Window
private bool _applyingAccept;
private bool _syncing;
private bool _gutterPending;
+ private int _gutterRetries;
public ConflictResolverView()
{
@@ -109,6 +110,7 @@ public partial class ConflictResolverView : Window
{
if (_vm is null) return;
_rebuilding = true;
+ _gutterRetries = 0; // fresh retry budget for this file's gutter layout
try
{
ClearGutters();
@@ -257,9 +259,12 @@ public partial class ConflictResolverView : Window
var tv = ResultEditor.TextArea.TextView;
if (!tv.VisualLinesValid)
{
- QueueGutters();
+ // Retry until the editor is laid out, but bounded so a never-laid-out editor
+ // (e.g. minimized window) can't busy-loop the dispatcher.
+ if (_gutterRetries++ < 40) QueueGutters();
return;
}
+ _gutterRetries = 0;
var doc = ResultEditor.Document;
foreach (var region in _resultRegions)
diff --git a/tests/ClaudeDo.Ui.Tests/ViewModels/ConflictResolverViewModelTests.cs b/tests/ClaudeDo.Ui.Tests/ViewModels/ConflictResolverViewModelTests.cs
index 6839823..2d295b6 100644
--- a/tests/ClaudeDo.Ui.Tests/ViewModels/ConflictResolverViewModelTests.cs
+++ b/tests/ClaudeDo.Ui.Tests/ViewModels/ConflictResolverViewModelTests.cs
@@ -238,7 +238,7 @@ public class ConflictResolverViewModelTests
Assert.Equal("a\no\nz\n", file.OursText);
Assert.Equal("a\nt\nz\n", file.TheirsText);
- Assert.Equal("a\no\nz\n", file.ResultText); // unresolved seeds Ours
+ Assert.Equal("a\nz\n", file.ResultText); // unresolved conflicts start empty
// After resolving: ResultText reflects the resolution
file.Conflicts[0].Resolution = "r\n";
@@ -286,7 +286,7 @@ public class ConflictResolverViewModelTests
// Switch to file B
vm.SelectFileCommand.Execute(vm.Files[1]);
Assert.Equal("b.cs", vm.ActiveFile!.Path);
- Assert.Equal("ours-b\n", vm.ActiveResultText); // unresolved seeds Ours
+ Assert.Equal("", vm.ActiveResultText); // unresolved conflicts start empty
// Switch back to file A
vm.SelectFileCommand.Execute(vm.Files[0]);
@@ -303,11 +303,11 @@ public class ConflictResolverViewModelTests
await vm.OpenAsync("main");
// 1 conflict, 0 resolved
- Assert.Equal("1 conflicts · 0 resolved", vm.PositionText);
+ Assert.Equal("1 conflict · 0 resolved", vm.PositionText);
vm.Current!.AcceptOursCommand.Execute(null);
// 1 conflict, 1 resolved
- Assert.Equal("1 conflicts · 1 resolved", vm.PositionText);
+ Assert.Equal("1 conflict · 1 resolved", vm.PositionText);
}
}