feat(ui): show status messages and real diff-stats in DiffModal

- Count additions/deletions per file as lines are parsed.
- Surface load failures and empty-diff states via StatusMessage.
- Pass the worktree base commit so diffs render against the branch
  base, not just the working-tree HEAD.
This commit is contained in:
Mika Kuns
2026-04-22 11:03:37 +02:00
parent 07dee31847
commit 31420574db
3 changed files with 31 additions and 11 deletions

View File

@@ -262,6 +262,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
Notes = entity.Notes ?? ""; Notes = entity.Notes ?? "";
Model = entity.Model; Model = entity.Model;
WorktreePath = entity.Worktree?.Path; WorktreePath = entity.Worktree?.Path;
WorktreeBaseCommit = entity.Worktree?.BaseCommit;
WorktreeStateLabel = entity.Worktree?.State.ToString(); WorktreeStateLabel = entity.Worktree?.State.ToString();
BranchLine = entity.Worktree is { } w ? $"{w.BranchName} \u2190 main" : null; BranchLine = entity.Worktree is { } w ? $"{w.BranchName} \u2190 main" : null;
AgentStatusLabel = entity.Status.ToString(); AgentStatusLabel = entity.Status.ToString();

View File

@@ -31,8 +31,8 @@ public sealed class DiffLineViewModel
public sealed class DiffFileViewModel public sealed class DiffFileViewModel
{ {
public required string Path { get; init; } public required string Path { get; init; }
public int Additions { get; init; } public int Additions { get; set; }
public int Deletions { get; init; } public int Deletions { get; set; }
public ObservableCollection<DiffLineViewModel> Lines { get; } = new(); public ObservableCollection<DiffLineViewModel> Lines { get; } = new();
} }
@@ -50,6 +50,7 @@ public sealed partial class DiffModalViewModel : ViewModelBase
public ObservableCollection<DiffFileViewModel> Files { get; } = new(); public ObservableCollection<DiffFileViewModel> Files { get; } = new();
[ObservableProperty] private DiffFileViewModel? _selectedFile; [ObservableProperty] private DiffFileViewModel? _selectedFile;
[ObservableProperty] private string? _statusMessage;
// Injected action to close the owning Window // Injected action to close the owning Window
public Action? CloseAction { get; set; } public Action? CloseAction { get; set; }
@@ -79,6 +80,7 @@ public sealed partial class DiffModalViewModel : ViewModelBase
public async Task LoadAsync(CancellationToken ct = default) public async Task LoadAsync(CancellationToken ct = default)
{ {
Files.Clear(); Files.Clear();
StatusMessage = null;
string raw; string raw;
try try
@@ -87,9 +89,17 @@ public sealed partial class DiffModalViewModel : ViewModelBase
? await _git.GetBranchDiffAsync(WorktreePath, BaseRef, ct) ? await _git.GetBranchDiffAsync(WorktreePath, BaseRef, ct)
: await _git.GetDiffAsync(WorktreePath, ct); : await _git.GetDiffAsync(WorktreePath, ct);
} }
catch { return; } catch (Exception ex)
{
StatusMessage = $"Failed to load diff: {ex.Message}";
return;
}
if (string.IsNullOrWhiteSpace(raw)) return; if (string.IsNullOrWhiteSpace(raw))
{
StatusMessage = "No changes to show.";
return;
}
// Parse unified diff — state machine over lines // Parse unified diff — state machine over lines
DiffFileViewModel? current = null; DiffFileViewModel? current = null;
@@ -134,7 +144,7 @@ public sealed partial class DiffModalViewModel : ViewModelBase
NewNo = newLine++, NewNo = newLine++,
Text = line.Length > 1 ? line[1..] : "", Text = line.Length > 1 ? line[1..] : "",
}); });
// Count additions on the file VM current.Additions++;
} }
else if (line.StartsWith('-')) else if (line.StartsWith('-'))
{ {
@@ -144,6 +154,7 @@ public sealed partial class DiffModalViewModel : ViewModelBase
OldNo = oldLine++, OldNo = oldLine++,
Text = line.Length > 1 ? line[1..] : "", Text = line.Length > 1 ? line[1..] : "",
}); });
current.Deletions++;
} }
else if (line.StartsWith(' ')) else if (line.StartsWith(' '))
{ {
@@ -158,6 +169,7 @@ public sealed partial class DiffModalViewModel : ViewModelBase
} }
SelectedFile = Files.Count > 0 ? Files[0] : null; SelectedFile = Files.Count > 0 ? Files[0] : null;
if (Files.Count == 0) StatusMessage = "No changes to show.";
} }
private static void ParseHunkHeader(string header, out int oldStart, out int newStart) private static void ParseHunkHeader(string header, out int oldStart, out int newStart)

View File

@@ -96,7 +96,7 @@
FontFamily="{StaticResource MonoFamily}" FontFamily="{StaticResource MonoFamily}"
FontSize="11" FontSize="11"
Foreground="{StaticResource TextDimBrush}" Foreground="{StaticResource TextDimBrush}"
TextTrimming="LeadingEllipsis"/> TextTrimming="PrefixCharacterEllipsis"/>
<StackPanel Orientation="Horizontal" Spacing="6"> <StackPanel Orientation="Horizontal" Spacing="6">
<Border Classes="chip" Padding="5,2"> <Border Classes="chip" Padding="5,2">
<TextBlock Foreground="{StaticResource MossBrightBrush}" <TextBlock Foreground="{StaticResource MossBrightBrush}"
@@ -119,10 +119,16 @@
</Border> </Border>
<!-- Diff content --> <!-- Diff content -->
<ScrollViewer Grid.Column="1" <Grid Grid.Column="1" Background="{StaticResource VoidBrush}">
HorizontalScrollBarVisibility="Auto" <TextBlock Text="{Binding StatusMessage}"
VerticalScrollBarVisibility="Auto" IsVisible="{Binding StatusMessage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Background="{StaticResource VoidBrush}"> HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{StaticResource TextDimBrush}"
FontFamily="{StaticResource MonoFamily}"
FontSize="12"/>
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding SelectedFile.Lines}"> <ItemsControl ItemsSource="{Binding SelectedFile.Lines}">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:DiffLineViewModel"> <DataTemplate x:DataType="vm:DiffLineViewModel">
@@ -169,5 +175,6 @@
</ScrollViewer> </ScrollViewer>
</Grid> </Grid>
</Grid> </Grid>
</Grid>
</Border> </Border>
</Window> </Window>