diff --git a/docs/explore-notes/review-merge.md b/docs/explore-notes/review-merge.md index b01365f9..775f055a 100644 --- a/docs/explore-notes/review-merge.md +++ b/docs/explore-notes/review-merge.md @@ -1,7 +1,9 @@ # Review, merge & conflict resolution > **Explore-note — verify before trusting.** Distilled map of a subsystem, not authoritative. -> Last verified against commit `0d1e3b9` (2026-08-06). +> Last verified against branch `worktree-diff-side-by-side` @ 2026-08-07, the commit right after +> `cf80fe3` that reworks Planning mode and retires `DiffLinesView` (exact SHA not known at write +> time — see `git log` on that branch). > Drift check: `git log --oneline 20bce9b..HEAD -- src/ClaudeDo.Worker/Lifecycle src/ClaudeDo.Worker/State src/ClaudeDo.Worker/Planning src/ClaudeDo.Ui/ViewModels/Conflicts src/ClaudeDo.Worker/External` > Stable structure only (no line numbers). See docs/explore-notes/README.md. @@ -219,15 +221,20 @@ Review **Approve** on conflict, and the **Merge** button in the Diff window (a c ## Diff stack (UI) `UnifiedDiffParser` (static) parses `git diff` output into `DiffFileViewModel`s, detecting -added/deleted/renamed/binary files and per-line numbers; `Flatten` injects file-header rows for -a combined single-pane view. `DiffModels.cs` holds the shared types (`DiffLineViewModel`, -`DiffFileViewModel`, `DiffLineKind`, `DiffFileStatus`, `SubtaskDiffRow`, +added/deleted/renamed/binary files and per-line numbers. `DiffModels.cs` holds the shared types +(`DiffLineViewModel`, `DiffFileViewModel`, `DiffLineKind`, `DiffFileStatus`, `SubtaskDiffRow`, `DiffTreeNodeViewModel`, `DiffTree`). `DiffViewerViewModel` is one unified read-only viewer with two modes: - **Files** — dirty worktree / branch-vs-base / commit-range. Loads via `GitService`, folder file-tree left + per-file diff pane right, Merge button for a live branch source. -- **Planning** — per-subtask diffs via `GetPlanningAggregateAsync`, subtask list left + flat - diff right, combined integration-branch toggle. +- **Planning** — per-subtask diffs via `GetPlanningAggregateAsync`, subtask list left + one + editor per file right (`PlanningFiles`), combined integration-branch toggle. -`DiffLinesView` renders per-file content with binary/empty placeholders. +Both modes render through `DiffAlignment` (pure — pairs diff lines into side-by-side rows and +computes word-diff spans) and `DiffTextView` (AvaloniaEdit + TextMate highlighting keyed off the +file extension, unified/split layout, optional line wrap, synced scrolling). Files mode hosts one +`DiffTextView` for the selected file; Planning mode hosts one per file in `PlanningFiles` so each +gets its own grammar (one editor can only carry one TextMate grammar). The split/wrap toggles +persist to `ui.config.json` via `AppSettings` and reach the per-file editors in Planning mode +too. diff --git a/src/ClaudeDo.Ui/CLAUDE.md b/src/ClaudeDo.Ui/CLAUDE.md index 542e0688..e06e38f0 100644 --- a/src/ClaudeDo.Ui/CLAUDE.md +++ b/src/ClaudeDo.Ui/CLAUDE.md @@ -31,7 +31,7 @@ ViewModels/ Conflicts/ — ConflictResolverViewModel + ConflictModels Views/ — mirrors the VM layout; Islands/Detail/ holds TaskHeaderBar, DescriptionStepsCard, WorkConsole; plus SessionTerminalView -Views/Controls/ — MarkdownView, ModalShell, ThemedDatePicker, DiffLinesView, InheritedBadge, +Views/Controls/ — MarkdownView, ModalShell, ThemedDatePicker, DiffTextView, InheritedBadge, AgentConfigEditor Design/ — Tokens.axaml (design tokens; merged before styles) + IslandStyles.axaml (component styles + the filled icon geometry library) @@ -68,7 +68,10 @@ warn/error filter), `WorkerConnectionModalViewModel`, `AboutModalViewModel`. ## Diff & Conflicts `UnifiedDiffParser` (static) + `DiffModels.cs` shared types + `DiffViewerViewModel` (one unified -read-only viewer, Files and Planning modes) + `DiffLinesView`. +read-only viewer, Files and Planning modes) render through `DiffAlignment` (pure — pairs diff +lines into side-by-side rows and computes word-diff spans) and `DiffTextView` (AvaloniaEdit + +TextMate highlighting, unified/split layout, optional line wrap, synced scrolling). The +split/wrap toggles persist to `ui.config.json` via `AppSettings`. `ConflictResolverViewModel` is an in-app Rider-style 3-pane AvaloniaEdit merge editor for both single-task and planning unit-merge conflicts. Full detail → [review-merge](../../docs/explore-notes/review-merge.md). diff --git a/src/ClaudeDo.Ui/ViewModels/Modals/DiffModels.cs b/src/ClaudeDo.Ui/ViewModels/Modals/DiffModels.cs index 9f9dd0b5..b49570cc 100644 --- a/src/ClaudeDo.Ui/ViewModels/Modals/DiffModels.cs +++ b/src/ClaudeDo.Ui/ViewModels/Modals/DiffModels.cs @@ -3,7 +3,7 @@ using CommunityToolkit.Mvvm.ComponentModel; namespace ClaudeDo.Ui.ViewModels.Modals; -// Shared diff models used by UnifiedDiffParser, DiffLinesView and DiffViewerViewModel. +// Shared diff models used by UnifiedDiffParser, DiffTextView and DiffViewerViewModel. public enum DiffLineKind { Add, Del, Ctx, File } diff --git a/src/ClaudeDo.Ui/ViewModels/Modals/DiffViewerViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Modals/DiffViewerViewModel.cs index 455e1be4..ef11ff9b 100644 --- a/src/ClaudeDo.Ui/ViewModels/Modals/DiffViewerViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Modals/DiffViewerViewModel.cs @@ -56,7 +56,8 @@ public sealed partial class DiffViewerViewModel : ViewModelBase // ── Right pane ────────────────────────────────────────────────────────── [ObservableProperty] private DiffFileViewModel? _selectedFile; // Files mode - public ObservableCollection DiffLines { get; } = new(); // Planning mode + // Planning mode: one entry per file so each gets its own editor and grammar. + public ObservableCollection PlanningFiles { get; } = new(); [ObservableProperty] private string _displayedDiff = ""; [ObservableProperty] private string? _statusMessage; @@ -250,9 +251,9 @@ public sealed partial class DiffViewerViewModel : ViewModelBase partial void OnDisplayedDiffChanged(string value) { - DiffLines.Clear(); - foreach (var line in UnifiedDiffParser.Flatten(UnifiedDiffParser.Parse(value))) - DiffLines.Add(line); + PlanningFiles.Clear(); + foreach (var file in UnifiedDiffParser.Parse(value)) + PlanningFiles.Add(file); } // ── Merge (Files mode, branch source) ─────────────────────────────────── diff --git a/src/ClaudeDo.Ui/ViewModels/Modals/UnifiedDiffParser.cs b/src/ClaudeDo.Ui/ViewModels/Modals/UnifiedDiffParser.cs index bc49d9b5..9cecf0e1 100644 --- a/src/ClaudeDo.Ui/ViewModels/Modals/UnifiedDiffParser.cs +++ b/src/ClaudeDo.Ui/ViewModels/Modals/UnifiedDiffParser.cs @@ -132,21 +132,6 @@ public static class UnifiedDiffParser return files; } - /// Flattens multiple parsed files into a single line stream, inserting a - /// file-header row before each file so boundaries are visible in a - /// single-pane (combined) view. - public static List Flatten(IEnumerable files) - { - var lines = new List(); - foreach (var file in files) - { - lines.Add(new DiffLineViewModel { Kind = DiffLineKind.File, Text = file.Path }); - foreach (var line in file.Lines) - lines.Add(line); - } - return lines; - } - private static void ParseHunkHeader(string header, out int oldStart, out int newStart) { oldStart = 1; newStart = 1; diff --git a/src/ClaudeDo.Ui/Views/Controls/DiffLinesView.axaml b/src/ClaudeDo.Ui/Views/Controls/DiffLinesView.axaml deleted file mode 100644 index d2149b04..00000000 --- a/src/ClaudeDo.Ui/Views/Controls/DiffLinesView.axaml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/ClaudeDo.Ui/Views/Controls/DiffLinesView.axaml.cs b/src/ClaudeDo.Ui/Views/Controls/DiffLinesView.axaml.cs deleted file mode 100644 index 340541c3..00000000 --- a/src/ClaudeDo.Ui/Views/Controls/DiffLinesView.axaml.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections; -using Avalonia; -using Avalonia.Controls; - -namespace ClaudeDo.Ui.Views.Controls; - -public partial class DiffLinesView : UserControl -{ - public static readonly StyledProperty LinesProperty = - AvaloniaProperty.Register(nameof(Lines)); - - public IEnumerable? Lines - { - get => GetValue(LinesProperty); - set => SetValue(LinesProperty, value); - } - - public DiffLinesView() => InitializeComponent(); -} diff --git a/src/ClaudeDo.Ui/Views/Modals/DiffViewerView.axaml b/src/ClaudeDo.Ui/Views/Modals/DiffViewerView.axaml index 5d66da0c..003cdaf2 100644 --- a/src/ClaudeDo.Ui/Views/Modals/DiffViewerView.axaml +++ b/src/ClaudeDo.Ui/Views/Modals/DiffViewerView.axaml @@ -162,10 +162,24 @@ - + - - + + + + + + + + + + + + +