fix(ui): Worktrees-Uebersicht zeigt Task-Nummern, DIFF-Spalte trimmt statt abzuschneiden

Befund F7 aus der Sichtpruefung 2026-09-16. TaskNumber war bereits im
WorktreeMaintenanceService-Join und in WorktreeOverviewRow vorhanden, fehlte
aber im Wire-DTO und in der UI-Kette; jetzt bis zur Row-/Conflict-Anzeige
durchgereicht (#<number> dimmed vor dem Titel, ausgeblendet bei 0). DIFF-Spalte
bekommt CharacterEllipsis-Trimming + Tooltip statt hartem Cut, Spalte von 80
auf 110px verbreitert (Header und Row synchron).
This commit is contained in:
Mika Kuns
2026-09-16 15:43:38 +02:00
committed by mika kuns
parent 56161e4e9b
commit 1325ab9438
5 changed files with 50 additions and 9 deletions
+2 -1
View File
@@ -72,7 +72,8 @@ public record WorktreeOverviewDto(
WorktreeState State,
string? DiffStat,
DateTime CreatedAt,
bool PathExistsOnDisk);
bool PathExistsOnDisk,
int TaskNumber = 0);
public record ForceRemoveResultDto(bool Removed, string? Reason);
@@ -16,6 +16,7 @@ public enum BatchMergeOutcome { None, Merging, Merged, Conflict, Blocked, Verify
public sealed partial class WorktreeOverviewRowViewModel : ViewModelBase
{
[ObservableProperty] private string _taskId = "";
[ObservableProperty] private int _taskNumber;
[ObservableProperty] private string _taskTitle = "";
[ObservableProperty][NotifyPropertyChangedFor(nameof(IsRunning))] private TaskStatus _taskStatus;
[ObservableProperty] private string _listId = "";
@@ -49,6 +50,9 @@ public sealed partial class WorktreeOverviewRowViewModel : ViewModelBase
public bool IsKept => State == WorktreeState.Kept;
public bool IsRunning => TaskStatus == TaskStatus.Running;
// 0 = row without an allocated number (test seeds); a bare "#0" would be meaningless.
public bool ShowNumberBadge => TaskNumber > 0;
private static string FormatAge(TimeSpan ts)
{
if (ts.TotalDays >= 1) return $"{(int)ts.TotalDays}d ago";
@@ -366,7 +370,7 @@ public sealed partial class WorktreesOverviewModalViewModel : ViewModelBase
private static WorktreeOverviewRowViewModel Map(WorktreeOverviewDto d) => new()
{
TaskId = d.TaskId, TaskTitle = d.TaskTitle, TaskStatus = d.TaskStatus,
TaskId = d.TaskId, TaskNumber = d.TaskNumber, TaskTitle = d.TaskTitle, TaskStatus = d.TaskStatus,
ListId = d.ListId, ListName = d.ListName,
Path = d.Path, BranchName = d.BranchName, BaseCommit = d.BaseCommit, State = d.State,
DiffStat = d.DiffStat, CreatedAt = d.CreatedAt, PathExistsOnDisk = d.PathExistsOnDisk,
@@ -58,13 +58,18 @@
CommandParameter="{Binding}"/>
</ContextMenu>
</Border.ContextMenu>
<Grid ColumnDefinitions="Auto,*,90,90,80,80">
<Grid ColumnDefinitions="Auto,*,90,90,110,80">
<CheckBox Grid.Column="0" VerticalAlignment="Center" Margin="0,0,8,0"
IsChecked="{Binding IsChecked, Mode=TwoWay}"
IsEnabled="{Binding IsActive}"
IsVisible="{Binding IsActive}"/>
<StackPanel Grid.Column="1" Orientation="Vertical" Spacing="2">
<TextBlock Classes="title" Text="{Binding TaskTitle}"/>
<Grid ColumnDefinitions="Auto,*">
<TextBlock Grid.Column="0" Classes="meta" Text="{Binding TaskNumber, StringFormat='#{0}'}"
IsVisible="{Binding ShowNumberBadge}"
VerticalAlignment="Center" Margin="0,0,6,0"/>
<TextBlock Grid.Column="1" Classes="title" Text="{Binding TaskTitle}"/>
</Grid>
<StackPanel Orientation="Horizontal" Spacing="4">
<TextBlock Classes="meta" Text="{Binding TaskStatus}"/>
<TextBlock Classes="meta" Text="•"
@@ -85,7 +90,8 @@
HorizontalAlignment="Left" VerticalAlignment="Center">
<TextBlock Text="{Binding State}"/>
</Border>
<TextBlock Grid.Column="4" Classes="meta" Text="{Binding DiffStat}" VerticalAlignment="Center"/>
<TextBlock Grid.Column="4" Classes="meta" Text="{Binding DiffStat}" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis" ToolTip.Tip="{Binding DiffStat}"/>
<TextBlock Grid.Column="5" Classes="meta" Text="{Binding AgeText}" VerticalAlignment="Center"/>
</Grid>
</Border>
@@ -146,8 +152,11 @@
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:WorktreeOverviewRowViewModel">
<Grid ColumnDefinitions="*,Auto" Margin="0,2">
<TextBlock Grid.Column="0" Classes="meta" VerticalAlignment="Center"
Text="{Binding TaskTitle}"/>
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="4" VerticalAlignment="Center">
<TextBlock Classes="meta" Text="{Binding TaskNumber, StringFormat='#{0}'}"
IsVisible="{Binding ShowNumberBadge}"/>
<TextBlock Classes="meta" Text="{Binding TaskTitle}"/>
</StackPanel>
<Button Grid.Column="1" Classes="btn"
Content="{loc:Tr modals.worktreesOverview.resolve}"
Command="{Binding $parent[Window].((vm:WorktreesOverviewModalViewModel)DataContext).ResolveConflictCommand}"
@@ -159,7 +168,7 @@
</StackPanel>
</Border>
<!-- Column headers -->
<Grid ColumnDefinitions="Auto,*,90,90,80,80" Margin="12,0,12,4">
<Grid ColumnDefinitions="Auto,*,90,90,110,80" Margin="12,0,12,4">
<TextBlock Grid.Column="1" Classes="eyebrow" Text="{loc:Tr modals.worktreesOverview.columnTask}"/>
<TextBlock Grid.Column="2" Classes="eyebrow" Text="{loc:Tr modals.worktreesOverview.columnOutcome}"/>
<TextBlock Grid.Column="3" Classes="eyebrow" Text="{loc:Tr modals.worktreesOverview.columnState}"/>
+2 -1
View File
@@ -449,7 +449,8 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
var rows = await _wtMaintenance.GetOverviewAsync(listId, Context.ConnectionAborted);
return rows.Select(r => new WorktreeOverviewDto(
r.TaskId, r.TaskTitle, r.TaskStatus, r.ListId, r.ListName,
r.Path, r.BranchName, r.BaseCommit, r.State, r.DiffStat, r.CreatedAt, r.PathExistsOnDisk)).ToList();
r.Path, r.BranchName, r.BaseCommit, r.State, r.DiffStat, r.CreatedAt, r.PathExistsOnDisk,
r.TaskNumber)).ToList();
}
public async Task<bool> SetWorktreeState(string taskId, WorktreeState newState)
@@ -114,6 +114,32 @@ public class WorktreesOverviewReconcileTickTests
Assert.Equal(1, vm.SelectedCount);
}
[Fact]
public async Task Map_carries_the_task_number_and_hides_the_badge_when_zero()
{
var worker = new FakeWorker
{
Worktrees =
{
Wt("a") with { TaskNumber = 42 },
Wt("b") with { TaskNumber = 0 },
},
};
var vm = new WorktreesOverviewModalViewModel(
worker, () => throw new InvalidOperationException("no diff vm in this test"),
new ClaudeDo.Ui.Services.MergeCoordinator());
vm.Configure("L1", "Work");
await vm.LoadAsync();
var withNumber = vm.Rows.Single(r => r.TaskId == "a");
var withoutNumber = vm.Rows.Single(r => r.TaskId == "b");
Assert.Equal(42, withNumber.TaskNumber);
Assert.True(withNumber.ShowNumberBadge);
Assert.Equal(0, withoutNumber.TaskNumber);
Assert.False(withoutNumber.ShowNumberBadge);
}
[Theory]
[InlineData("refresh")]
[InlineData("cleanup")]