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:
@@ -72,7 +72,8 @@ public record WorktreeOverviewDto(
|
|||||||
WorktreeState State,
|
WorktreeState State,
|
||||||
string? DiffStat,
|
string? DiffStat,
|
||||||
DateTime CreatedAt,
|
DateTime CreatedAt,
|
||||||
bool PathExistsOnDisk);
|
bool PathExistsOnDisk,
|
||||||
|
int TaskNumber = 0);
|
||||||
|
|
||||||
public record ForceRemoveResultDto(bool Removed, string? Reason);
|
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
|
public sealed partial class WorktreeOverviewRowViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
[ObservableProperty] private string _taskId = "";
|
[ObservableProperty] private string _taskId = "";
|
||||||
|
[ObservableProperty] private int _taskNumber;
|
||||||
[ObservableProperty] private string _taskTitle = "";
|
[ObservableProperty] private string _taskTitle = "";
|
||||||
[ObservableProperty][NotifyPropertyChangedFor(nameof(IsRunning))] private TaskStatus _taskStatus;
|
[ObservableProperty][NotifyPropertyChangedFor(nameof(IsRunning))] private TaskStatus _taskStatus;
|
||||||
[ObservableProperty] private string _listId = "";
|
[ObservableProperty] private string _listId = "";
|
||||||
@@ -49,6 +50,9 @@ public sealed partial class WorktreeOverviewRowViewModel : ViewModelBase
|
|||||||
public bool IsKept => State == WorktreeState.Kept;
|
public bool IsKept => State == WorktreeState.Kept;
|
||||||
public bool IsRunning => TaskStatus == TaskStatus.Running;
|
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)
|
private static string FormatAge(TimeSpan ts)
|
||||||
{
|
{
|
||||||
if (ts.TotalDays >= 1) return $"{(int)ts.TotalDays}d ago";
|
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()
|
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,
|
ListId = d.ListId, ListName = d.ListName,
|
||||||
Path = d.Path, BranchName = d.BranchName, BaseCommit = d.BaseCommit, State = d.State,
|
Path = d.Path, BranchName = d.BranchName, BaseCommit = d.BaseCommit, State = d.State,
|
||||||
DiffStat = d.DiffStat, CreatedAt = d.CreatedAt, PathExistsOnDisk = d.PathExistsOnDisk,
|
DiffStat = d.DiffStat, CreatedAt = d.CreatedAt, PathExistsOnDisk = d.PathExistsOnDisk,
|
||||||
|
|||||||
@@ -58,13 +58,18 @@
|
|||||||
CommandParameter="{Binding}"/>
|
CommandParameter="{Binding}"/>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
</Border.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"
|
<CheckBox Grid.Column="0" VerticalAlignment="Center" Margin="0,0,8,0"
|
||||||
IsChecked="{Binding IsChecked, Mode=TwoWay}"
|
IsChecked="{Binding IsChecked, Mode=TwoWay}"
|
||||||
IsEnabled="{Binding IsActive}"
|
IsEnabled="{Binding IsActive}"
|
||||||
IsVisible="{Binding IsActive}"/>
|
IsVisible="{Binding IsActive}"/>
|
||||||
<StackPanel Grid.Column="1" Orientation="Vertical" Spacing="2">
|
<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">
|
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||||
<TextBlock Classes="meta" Text="{Binding TaskStatus}"/>
|
<TextBlock Classes="meta" Text="{Binding TaskStatus}"/>
|
||||||
<TextBlock Classes="meta" Text="•"
|
<TextBlock Classes="meta" Text="•"
|
||||||
@@ -85,7 +90,8 @@
|
|||||||
HorizontalAlignment="Left" VerticalAlignment="Center">
|
HorizontalAlignment="Left" VerticalAlignment="Center">
|
||||||
<TextBlock Text="{Binding State}"/>
|
<TextBlock Text="{Binding State}"/>
|
||||||
</Border>
|
</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"/>
|
<TextBlock Grid.Column="5" Classes="meta" Text="{Binding AgeText}" VerticalAlignment="Center"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
@@ -146,8 +152,11 @@
|
|||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<DataTemplate x:DataType="vm:WorktreeOverviewRowViewModel">
|
<DataTemplate x:DataType="vm:WorktreeOverviewRowViewModel">
|
||||||
<Grid ColumnDefinitions="*,Auto" Margin="0,2">
|
<Grid ColumnDefinitions="*,Auto" Margin="0,2">
|
||||||
<TextBlock Grid.Column="0" Classes="meta" VerticalAlignment="Center"
|
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="4" VerticalAlignment="Center">
|
||||||
Text="{Binding TaskTitle}"/>
|
<TextBlock Classes="meta" Text="{Binding TaskNumber, StringFormat='#{0}'}"
|
||||||
|
IsVisible="{Binding ShowNumberBadge}"/>
|
||||||
|
<TextBlock Classes="meta" Text="{Binding TaskTitle}"/>
|
||||||
|
</StackPanel>
|
||||||
<Button Grid.Column="1" Classes="btn"
|
<Button Grid.Column="1" Classes="btn"
|
||||||
Content="{loc:Tr modals.worktreesOverview.resolve}"
|
Content="{loc:Tr modals.worktreesOverview.resolve}"
|
||||||
Command="{Binding $parent[Window].((vm:WorktreesOverviewModalViewModel)DataContext).ResolveConflictCommand}"
|
Command="{Binding $parent[Window].((vm:WorktreesOverviewModalViewModel)DataContext).ResolveConflictCommand}"
|
||||||
@@ -159,7 +168,7 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
<!-- Column headers -->
|
<!-- 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="1" Classes="eyebrow" Text="{loc:Tr modals.worktreesOverview.columnTask}"/>
|
||||||
<TextBlock Grid.Column="2" Classes="eyebrow" Text="{loc:Tr modals.worktreesOverview.columnOutcome}"/>
|
<TextBlock Grid.Column="2" Classes="eyebrow" Text="{loc:Tr modals.worktreesOverview.columnOutcome}"/>
|
||||||
<TextBlock Grid.Column="3" Classes="eyebrow" Text="{loc:Tr modals.worktreesOverview.columnState}"/>
|
<TextBlock Grid.Column="3" Classes="eyebrow" Text="{loc:Tr modals.worktreesOverview.columnState}"/>
|
||||||
|
|||||||
@@ -449,7 +449,8 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
|
|||||||
var rows = await _wtMaintenance.GetOverviewAsync(listId, Context.ConnectionAborted);
|
var rows = await _wtMaintenance.GetOverviewAsync(listId, Context.ConnectionAborted);
|
||||||
return rows.Select(r => new WorktreeOverviewDto(
|
return rows.Select(r => new WorktreeOverviewDto(
|
||||||
r.TaskId, r.TaskTitle, r.TaskStatus, r.ListId, r.ListName,
|
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)
|
public async Task<bool> SetWorktreeState(string taskId, WorktreeState newState)
|
||||||
|
|||||||
@@ -114,6 +114,32 @@ public class WorktreesOverviewReconcileTickTests
|
|||||||
Assert.Equal(1, vm.SelectedCount);
|
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]
|
[Theory]
|
||||||
[InlineData("refresh")]
|
[InlineData("refresh")]
|
||||||
[InlineData("cleanup")]
|
[InlineData("cleanup")]
|
||||||
|
|||||||
Reference in New Issue
Block a user