feat(ui): TaskRowView hierarchy indentation, chevron, badges, draft italic

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
mika kuns
2026-04-23 18:58:08 +02:00
parent 309f84b388
commit 42b208ff28
4 changed files with 193 additions and 118 deletions

View File

@@ -0,0 +1,15 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
namespace ClaudeDo.Ui.Converters;
public sealed class BoolToDraftOpacityConverter : IValueConverter
{
public static BoolToDraftOpacityConverter Instance { get; } = new();
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
=> value is true ? 0.7 : 1.0;
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotSupportedException();
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Media;
namespace ClaudeDo.Ui.Converters;
public sealed class BoolToItalicConverter : IValueConverter
{
public static BoolToItalicConverter Instance { get; } = new();
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
=> value is true ? FontStyle.Italic : FontStyle.Normal;
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotSupportedException();
}

View File

@@ -15,134 +15,176 @@
Background="{DynamicResource MossBrush}" CornerRadius="1"
IsVisible="{Binding DropHintAbove}"/>
<Border Grid.Row="1" Classes="task-row"
Margin="0"
Classes.selected="{Binding IsSelected}"
Classes.done="{Binding Done}">
<Border.ContextMenu>
<ContextMenu>
<MenuItem Header="Send to queue"
IsVisible="{Binding !IsQueued}"
Click="OnSendToQueueClick"/>
<MenuItem Header="Remove from queue"
<!-- Indent wrapper: col 0 = 24px child indent track, col 1 = content -->
<Grid Grid.Row="1" ColumnDefinitions="Auto,*">
<!-- Indent track (only visible for child tasks) -->
<Border Grid.Column="0" Width="24" IsVisible="{Binding IsChild}" VerticalAlignment="Stretch">
<Rectangle Width="1" Fill="{DynamicResource LineBrush}"
HorizontalAlignment="Right" Margin="0,4"/>
</Border>
<!-- Main task card -->
<Border Grid.Column="1" Classes="task-row"
Margin="0"
Classes.selected="{Binding IsSelected}"
Classes.done="{Binding Done}">
<Border.ContextMenu>
<ContextMenu>
<MenuItem Header="Send to queue"
IsVisible="{Binding !IsQueued}"
Click="OnSendToQueueClick"/>
<MenuItem Header="Remove from queue"
IsVisible="{Binding IsQueued}"
Click="OnRemoveFromQueueClick"/>
<Separator/>
<MenuItem Header="Schedule for..." Click="OnScheduleForClick"/>
<MenuItem Header="Clear schedule"
IsVisible="{Binding HasSchedule}"
Click="OnClearScheduleClick"/>
</ContextMenu>
</Border.ContextMenu>
<Grid ColumnDefinitions="0,18,32,*,Auto,32" Margin="6,8,10,8">
<!-- Chevron toggle (only for planning parent tasks) -->
<Button Grid.Column="1"
IsVisible="{Binding IsPlanningParent}"
Command="{Binding $parent[ItemsControl].((vm:TasksIslandViewModel)DataContext).ToggleExpandCommand}"
CommandParameter="{Binding}"
Classes="icon-btn"
Width="18" Height="18"
VerticalAlignment="Center">
<Panel>
<TextBlock Text="▾" FontSize="10" IsVisible="{Binding IsExpanded}"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBlock Text="▸" FontSize="10" IsVisible="{Binding !IsExpanded}"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Panel>
</Button>
<!-- Done toggle -->
<Button Grid.Column="2" Classes="flat" VerticalAlignment="Top"
Margin="0,2,0,0"
Command="{Binding $parent[ItemsControl].((vm:TasksIslandViewModel)DataContext).ToggleDoneCommand}"
CommandParameter="{Binding}">
<Ellipse Width="18" Height="18" Classes="task-check"
Classes.done="{Binding Done}"/>
</Button>
<!-- Title + chip row + live tail -->
<StackPanel Grid.Column="3" Spacing="6" VerticalAlignment="Center">
<StackPanel Orientation="Horizontal" Spacing="4" VerticalAlignment="Center">
<TextBlock Classes="task-title"
Text="{Binding Title}" FontSize="14"
Foreground="{DynamicResource TextBrush}"
FontStyle="{Binding IsDraft, Converter={StaticResource BoolToItalic}}"
Opacity="{Binding IsDraft, Converter={StaticResource BoolToDraftOpacity}}"
TextDecorations="{Binding Done, Converter={StaticResource StrikeIfTrue}}"/>
<!-- Badges: DRAFT and planning session -->
<StackPanel Orientation="Horizontal" Spacing="4" VerticalAlignment="Center">
<Border Classes="badge draft" IsVisible="{Binding IsDraft}">
<TextBlock Text="DRAFT"/>
</Border>
<Border Classes="badge planning" IsVisible="{Binding IsPlanningParent}">
<TextBlock Text="{Binding PlanningBadge}"/>
</Border>
</StackPanel>
</StackPanel>
<!-- Chip row -->
<StackPanel Orientation="Horizontal" Spacing="6">
<!-- Status chip -->
<Border Classes="chip"
Classes.running="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Running}"
Classes.review="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Done}"
Classes.error="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Failed}"
Classes.queued="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Queued}">
<TextBlock Text="{Binding Status}"/>
</Border>
<!-- Dequeue button (only when Queued) -->
<Button Classes="icon-btn dequeue-btn"
IsVisible="{Binding IsQueued}"
Click="OnRemoveFromQueueClick"/>
<Separator/>
<MenuItem Header="Schedule for..." Click="OnScheduleForClick"/>
<MenuItem Header="Clear schedule"
IsVisible="{Binding HasSchedule}"
Click="OnClearScheduleClick"/>
</ContextMenu>
</Border.ContextMenu>
<Grid ColumnDefinitions="0,32,*,32" Margin="6,8,10,8">
ToolTip.Tip="Remove from queue"
Command="{Binding $parent[ItemsControl].((vm:TasksIslandViewModel)DataContext).RemoveFromQueueCommand}"
CommandParameter="{Binding}">
<PathIcon Width="10" Height="10" Data="{StaticResource Icon.X}"/>
</Button>
<!-- Done toggle -->
<Button Grid.Column="1" Classes="flat" VerticalAlignment="Top"
Margin="0,2,0,0"
Command="{Binding $parent[ItemsControl].((vm:TasksIslandViewModel)DataContext).ToggleDoneCommand}"
CommandParameter="{Binding}">
<Ellipse Width="18" Height="18" Classes="task-check"
Classes.done="{Binding Done}"/>
</Button>
<!-- List chip with dot -->
<Border Classes="chip chip-list">
<StackPanel Orientation="Horizontal" Spacing="5" VerticalAlignment="Center">
<Ellipse Width="6" Height="6"
Fill="{DynamicResource MossBrush}"
VerticalAlignment="Center"/>
<TextBlock Text="{Binding ListName}"/>
</StackPanel>
</Border>
<!-- Title + chip row + live tail -->
<StackPanel Grid.Column="2" Spacing="6" VerticalAlignment="Center">
<TextBlock Classes="task-title"
Text="{Binding Title}" FontSize="14"
Foreground="{DynamicResource TextBrush}"
TextDecorations="{Binding Done, Converter={StaticResource StrikeIfTrue}}"/>
<!-- Branch chip -->
<Border Classes="chip chip-branch" IsVisible="{Binding HasBranch}">
<StackPanel Orientation="Horizontal" Spacing="4" VerticalAlignment="Center">
<PathIcon Width="10" Height="10"
Data="{StaticResource Icon.GitBranch}"
Foreground="{DynamicResource TextDimBrush}"/>
<TextBlock Text="{Binding Branch}"/>
</StackPanel>
</Border>
<!-- Chip row -->
<StackPanel Orientation="Horizontal" Spacing="6">
<!-- Diff chip -->
<Border Classes="chip chip-diff" IsVisible="{Binding HasDiff}">
<StackPanel Orientation="Horizontal" Spacing="4" VerticalAlignment="Center">
<TextBlock Classes="diff-add" Text="{Binding DiffAdditionsText}"/>
<TextBlock Classes="diff-del" Text="{Binding DiffDeletionsText}"/>
</StackPanel>
</Border>
<!-- Status chip -->
<Border Classes="chip"
Classes.running="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Running}"
Classes.review="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Done}"
Classes.error="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Failed}"
Classes.queued="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Queued}">
<TextBlock Text="{Binding Status}"/>
</Border>
<!-- Tag chips -->
<ItemsControl ItemsSource="{Binding Tags}" IsVisible="{Binding HasTags}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="6"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Classes="chip chip-tag">
<TextBlock Text="{Binding}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<!-- Dequeue button (only when Queued) -->
<Button Classes="icon-btn dequeue-btn"
IsVisible="{Binding IsQueued}"
ToolTip.Tip="Remove from queue"
Command="{Binding $parent[ItemsControl].((vm:TasksIslandViewModel)DataContext).RemoveFromQueueCommand}"
CommandParameter="{Binding}">
<PathIcon Width="10" Height="10" Data="{StaticResource Icon.X}"/>
</Button>
<!-- List chip with dot -->
<Border Classes="chip chip-list">
<StackPanel Orientation="Horizontal" Spacing="5" VerticalAlignment="Center">
<Ellipse Width="6" Height="6"
Fill="{DynamicResource MossBrush}"
VerticalAlignment="Center"/>
<TextBlock Text="{Binding ListName}"/>
<!-- Live-tail row (visible when running + has tail) -->
<Border Classes="task-live-tail" IsVisible="{Binding HasLiveTail}">
<StackPanel Spacing="3">
<TextBlock Text="{Binding LiveTail}"
TextTrimming="CharacterEllipsis" MaxLines="1"/>
<Grid Height="3" HorizontalAlignment="Stretch">
<Rectangle Fill="{DynamicResource Surface3Brush}"
HorizontalAlignment="Stretch" RadiusX="1.5" RadiusY="1.5"/>
<Rectangle Fill="{DynamicResource MossBrush}"
HorizontalAlignment="Left" Width="60" RadiusX="1.5" RadiusY="1.5"/>
</Grid>
</StackPanel>
</Border>
<!-- Branch chip -->
<Border Classes="chip chip-branch" IsVisible="{Binding HasBranch}">
<StackPanel Orientation="Horizontal" Spacing="4" VerticalAlignment="Center">
<PathIcon Width="10" Height="10"
Data="{StaticResource Icon.GitBranch}"
Foreground="{DynamicResource TextDimBrush}"/>
<TextBlock Text="{Binding Branch}"/>
</StackPanel>
</Border>
<!-- Diff chip -->
<Border Classes="chip chip-diff" IsVisible="{Binding HasDiff}">
<StackPanel Orientation="Horizontal" Spacing="4" VerticalAlignment="Center">
<TextBlock Classes="diff-add" Text="{Binding DiffAdditionsText}"/>
<TextBlock Classes="diff-del" Text="{Binding DiffDeletionsText}"/>
</StackPanel>
</Border>
<!-- Tag chips -->
<ItemsControl ItemsSource="{Binding Tags}" IsVisible="{Binding HasTags}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="6"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Classes="chip chip-tag">
<TextBlock Text="{Binding}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<!-- Live-tail row (visible when running + has tail) -->
<Border Classes="task-live-tail" IsVisible="{Binding HasLiveTail}">
<StackPanel Spacing="3">
<TextBlock Text="{Binding LiveTail}"
TextTrimming="CharacterEllipsis" MaxLines="1"/>
<Grid Height="3" HorizontalAlignment="Stretch">
<Rectangle Fill="{DynamicResource Surface3Brush}"
HorizontalAlignment="Stretch" RadiusX="1.5" RadiusY="1.5"/>
<Rectangle Fill="{DynamicResource MossBrush}"
HorizontalAlignment="Left" Width="60" RadiusX="1.5" RadiusY="1.5"/>
</Grid>
</StackPanel>
</Border>
</StackPanel>
<!-- Star toggle -->
<Button Grid.Column="5" Classes="icon-btn star-btn"
Classes.on="{Binding IsStarred}"
VerticalAlignment="Top" Margin="0,2,0,0"
Command="{Binding $parent[ItemsControl].((vm:TasksIslandViewModel)DataContext).ToggleStarCommand}"
CommandParameter="{Binding}">
<PathIcon Width="14" Height="14" Data="{StaticResource Icon.Star}"/>
</Button>
</Grid>
</Border>
<!-- Star toggle -->
<Button Grid.Column="3" Classes="icon-btn star-btn"
Classes.on="{Binding IsStarred}"
VerticalAlignment="Top" Margin="0,2,0,0"
Command="{Binding $parent[ItemsControl].((vm:TasksIslandViewModel)DataContext).ToggleStarCommand}"
CommandParameter="{Binding}">
<PathIcon Width="14" Height="14" Data="{StaticResource Icon.Star}"/>
</Button>
</Grid>
</Border>
</Grid>
<!-- Below-row indicator: only expands when visible (used for the last row of a section) -->
<Grid Grid.Row="2" Height="6" IsVisible="{Binding DropHintBelow}">