refactor(diff): render planning mode per file and retire DiffLinesView
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
# Review, merge & conflict resolution
|
# Review, merge & conflict resolution
|
||||||
|
|
||||||
> **Explore-note — verify before trusting.** Distilled map of a subsystem, not authoritative.
|
> **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`
|
> 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.
|
> 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)
|
## Diff stack (UI)
|
||||||
|
|
||||||
`UnifiedDiffParser` (static) parses `git diff` output into `DiffFileViewModel`s, detecting
|
`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
|
added/deleted/renamed/binary files and per-line numbers. `DiffModels.cs` holds the shared types
|
||||||
a combined single-pane view. `DiffModels.cs` holds the shared types (`DiffLineViewModel`,
|
(`DiffLineViewModel`, `DiffFileViewModel`, `DiffLineKind`, `DiffFileStatus`, `SubtaskDiffRow`,
|
||||||
`DiffFileViewModel`, `DiffLineKind`, `DiffFileStatus`, `SubtaskDiffRow`,
|
|
||||||
`DiffTreeNodeViewModel`, `DiffTree`).
|
`DiffTreeNodeViewModel`, `DiffTree`).
|
||||||
|
|
||||||
`DiffViewerViewModel` is one unified read-only viewer with two modes:
|
`DiffViewerViewModel` is one unified read-only viewer with two modes:
|
||||||
- **Files** — dirty worktree / branch-vs-base / commit-range. Loads via `GitService`, folder
|
- **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.
|
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
|
- **Planning** — per-subtask diffs via `GetPlanningAggregateAsync`, subtask list left + one
|
||||||
diff right, combined integration-branch toggle.
|
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.
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ ViewModels/
|
|||||||
Conflicts/ — ConflictResolverViewModel + ConflictModels
|
Conflicts/ — ConflictResolverViewModel + ConflictModels
|
||||||
Views/ — mirrors the VM layout; Islands/Detail/ holds TaskHeaderBar,
|
Views/ — mirrors the VM layout; Islands/Detail/ holds TaskHeaderBar,
|
||||||
DescriptionStepsCard, WorkConsole; plus SessionTerminalView
|
DescriptionStepsCard, WorkConsole; plus SessionTerminalView
|
||||||
Views/Controls/ — MarkdownView, ModalShell, ThemedDatePicker, DiffLinesView, InheritedBadge,
|
Views/Controls/ — MarkdownView, ModalShell, ThemedDatePicker, DiffTextView, InheritedBadge,
|
||||||
AgentConfigEditor
|
AgentConfigEditor
|
||||||
Design/ — Tokens.axaml (design tokens; merged before styles)
|
Design/ — Tokens.axaml (design tokens; merged before styles)
|
||||||
+ IslandStyles.axaml (component styles + the filled icon geometry library)
|
+ IslandStyles.axaml (component styles + the filled icon geometry library)
|
||||||
@@ -68,7 +68,10 @@ warn/error filter), `WorkerConnectionModalViewModel`, `AboutModalViewModel`.
|
|||||||
## Diff & Conflicts
|
## Diff & Conflicts
|
||||||
|
|
||||||
`UnifiedDiffParser` (static) + `DiffModels.cs` shared types + `DiffViewerViewModel` (one unified
|
`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
|
`ConflictResolverViewModel` is an in-app Rider-style 3-pane AvaloniaEdit merge editor for both
|
||||||
single-task and planning unit-merge conflicts. Full detail →
|
single-task and planning unit-merge conflicts. Full detail →
|
||||||
[review-merge](../../docs/explore-notes/review-merge.md).
|
[review-merge](../../docs/explore-notes/review-merge.md).
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
|||||||
|
|
||||||
namespace ClaudeDo.Ui.ViewModels.Modals;
|
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 }
|
public enum DiffLineKind { Add, Del, Ctx, File }
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,8 @@ public sealed partial class DiffViewerViewModel : ViewModelBase
|
|||||||
|
|
||||||
// ── Right pane ──────────────────────────────────────────────────────────
|
// ── Right pane ──────────────────────────────────────────────────────────
|
||||||
[ObservableProperty] private DiffFileViewModel? _selectedFile; // Files mode
|
[ObservableProperty] private DiffFileViewModel? _selectedFile; // Files mode
|
||||||
public ObservableCollection<DiffLineViewModel> DiffLines { get; } = new(); // Planning mode
|
// Planning mode: one entry per file so each gets its own editor and grammar.
|
||||||
|
public ObservableCollection<DiffFileViewModel> PlanningFiles { get; } = new();
|
||||||
[ObservableProperty] private string _displayedDiff = "";
|
[ObservableProperty] private string _displayedDiff = "";
|
||||||
[ObservableProperty] private string? _statusMessage;
|
[ObservableProperty] private string? _statusMessage;
|
||||||
|
|
||||||
@@ -250,9 +251,9 @@ public sealed partial class DiffViewerViewModel : ViewModelBase
|
|||||||
|
|
||||||
partial void OnDisplayedDiffChanged(string value)
|
partial void OnDisplayedDiffChanged(string value)
|
||||||
{
|
{
|
||||||
DiffLines.Clear();
|
PlanningFiles.Clear();
|
||||||
foreach (var line in UnifiedDiffParser.Flatten(UnifiedDiffParser.Parse(value)))
|
foreach (var file in UnifiedDiffParser.Parse(value))
|
||||||
DiffLines.Add(line);
|
PlanningFiles.Add(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Merge (Files mode, branch source) ───────────────────────────────────
|
// ── Merge (Files mode, branch source) ───────────────────────────────────
|
||||||
|
|||||||
@@ -132,21 +132,6 @@ public static class UnifiedDiffParser
|
|||||||
return files;
|
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<DiffLineViewModel> Flatten(IEnumerable<DiffFileViewModel> files)
|
|
||||||
{
|
|
||||||
var lines = new List<DiffLineViewModel>();
|
|
||||||
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)
|
private static void ParseHunkHeader(string header, out int oldStart, out int newStart)
|
||||||
{
|
{
|
||||||
oldStart = 1; newStart = 1;
|
oldStart = 1; newStart = 1;
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
<UserControl xmlns="https://github.com/avaloniaui"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:vm="using:ClaudeDo.Ui.ViewModels.Modals"
|
|
||||||
x:Class="ClaudeDo.Ui.Views.Controls.DiffLinesView"
|
|
||||||
x:Name="Root">
|
|
||||||
|
|
||||||
<UserControl.Styles>
|
|
||||||
<!-- diff line row tints via Tag selector (compiled-binding-friendly) -->
|
|
||||||
<Style Selector="Border.diff-line[Tag=add]">
|
|
||||||
<Setter Property="Background" Value="{StaticResource RunningTintBrush}"/>
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Border.diff-line[Tag=del]">
|
|
||||||
<Setter Property="Background" Value="{StaticResource ErrorTintBrush}"/>
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Border.diff-line[Tag=ctx]">
|
|
||||||
<Setter Property="Background" Value="Transparent"/>
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Border.diff-line[Tag=file]">
|
|
||||||
<Setter Property="Background" Value="{StaticResource Surface3Brush}"/>
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Border.diff-line[Tag=add] TextBlock.diff-sign">
|
|
||||||
<Setter Property="Foreground" Value="{StaticResource MossBrightBrush}"/>
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Border.diff-line[Tag=del] TextBlock.diff-sign">
|
|
||||||
<Setter Property="Foreground" Value="{StaticResource BloodBrush}"/>
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Border.diff-line[Tag=ctx] TextBlock.diff-sign">
|
|
||||||
<Setter Property="Foreground" Value="{StaticResource TextFaintBrush}"/>
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Border.diff-line[Tag=add] TextBlock.diff-text">
|
|
||||||
<Setter Property="Foreground" Value="{StaticResource MossBrightBrush}"/>
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Border.diff-line[Tag=del] TextBlock.diff-text">
|
|
||||||
<Setter Property="Foreground" Value="{StaticResource BloodBrush}"/>
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Border.diff-line[Tag=ctx] TextBlock.diff-text">
|
|
||||||
<Setter Property="Foreground" Value="{StaticResource TextDimBrush}"/>
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Border.diff-line[Tag=file] TextBlock.diff-text">
|
|
||||||
<Setter Property="Foreground" Value="{StaticResource TextBrush}"/>
|
|
||||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
|
||||||
</Style>
|
|
||||||
</UserControl.Styles>
|
|
||||||
|
|
||||||
<ItemsControl ItemsSource="{Binding #Root.Lines}">
|
|
||||||
<ItemsControl.ItemTemplate>
|
|
||||||
<DataTemplate x:DataType="vm:DiffLineViewModel">
|
|
||||||
<Border Classes="diff-line"
|
|
||||||
Tag="{Binding ClassName}"
|
|
||||||
Padding="4,1">
|
|
||||||
<Grid ColumnDefinitions="48,48,16,*">
|
|
||||||
<!-- Old line number -->
|
|
||||||
<TextBlock Grid.Column="0"
|
|
||||||
Text="{Binding OldNo}"
|
|
||||||
Classes="diff-lineno"
|
|
||||||
HorizontalAlignment="Right"
|
|
||||||
Margin="0,0,8,0"/>
|
|
||||||
<!-- New line number -->
|
|
||||||
<TextBlock Grid.Column="1"
|
|
||||||
Text="{Binding NewNo}"
|
|
||||||
Classes="diff-lineno"
|
|
||||||
HorizontalAlignment="Right"
|
|
||||||
Margin="0,0,8,0"/>
|
|
||||||
<!-- Sign -->
|
|
||||||
<TextBlock Grid.Column="2"
|
|
||||||
Classes="diff-sign"
|
|
||||||
Text="{Binding Sign}"
|
|
||||||
FontFamily="{DynamicResource MonoFont}"
|
|
||||||
FontSize="{StaticResource FontSizeMono}"/>
|
|
||||||
<!-- Line text -->
|
|
||||||
<TextBlock Grid.Column="3"
|
|
||||||
Classes="diff-text"
|
|
||||||
Text="{Binding Text}"
|
|
||||||
FontFamily="{DynamicResource MonoFont}"
|
|
||||||
FontSize="{StaticResource FontSizeMono}"
|
|
||||||
TextWrapping="NoWrap"/>
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
</DataTemplate>
|
|
||||||
</ItemsControl.ItemTemplate>
|
|
||||||
</ItemsControl>
|
|
||||||
</UserControl>
|
|
||||||
@@ -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<IEnumerable?> LinesProperty =
|
|
||||||
AvaloniaProperty.Register<DiffLinesView, IEnumerable?>(nameof(Lines));
|
|
||||||
|
|
||||||
public IEnumerable? Lines
|
|
||||||
{
|
|
||||||
get => GetValue(LinesProperty);
|
|
||||||
set => SetValue(LinesProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DiffLinesView() => InitializeComponent();
|
|
||||||
}
|
|
||||||
@@ -162,10 +162,24 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
|
|
||||||
<!-- Planning mode: flat aggregate/combined diff -->
|
<!-- Planning mode: one editor per file so each gets its own grammar -->
|
||||||
<Grid Background="{DynamicResource VoidBrush}" IsVisible="{Binding IsPlanning}">
|
<Grid Background="{DynamicResource VoidBrush}" IsVisible="{Binding IsPlanning}">
|
||||||
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
|
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||||
<ctl:DiffLinesView Lines="{Binding DiffLines}"/>
|
<ItemsControl ItemsSource="{Binding PlanningFiles}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="vm:DiffFileViewModel">
|
||||||
|
<StackPanel Margin="0,0,0,12">
|
||||||
|
<Border Classes="island-header">
|
||||||
|
<TextBlock Classes="path-mono" Text="{Binding HeaderPath}"
|
||||||
|
TextTrimming="PrefixCharacterEllipsis"/>
|
||||||
|
</Border>
|
||||||
|
<ctl:DiffTextView File="{Binding}"
|
||||||
|
IsSplit="{Binding $parent[Window].((vm:DiffViewerViewModel)DataContext).IsSplitView}"
|
||||||
|
WrapLines="{Binding $parent[Window].((vm:DiffViewerViewModel)DataContext).WrapLines}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user