Files
ClaudeDo/src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml
T
mika kuns b81d6e0b4f feat(ui): render dependency-chain rail, step badge, and after-chip in TaskRowView
Reuses the existing 24px indent track (now gated on ShowAsChild OR
ShowAsChainMember) instead of a second mechanism, adds a round step-number
badge centered on the rail line, and a dimmed "after X" chip for chain
members whose head isn't in view or that render as a planning child.
2026-08-11 15:14:39 +02:00

275 lines
14 KiB
XML

<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:ClaudeDo.Ui.ViewModels.Islands"
xmlns:ctl="using:ClaudeDo.Ui.Views.Controls"
xmlns:loc="using:ClaudeDo.Ui.Localization"
x:Class="ClaudeDo.Ui.Views.Islands.TaskRowView"
x:DataType="vm:TaskRowViewModel">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Above-row indicator: dashed placeholder gap showing where the dragged row will land -->
<Grid Grid.Row="0" Height="52" IsVisible="{Binding DropHintAbove}">
<Rectangle Margin="4,3" Stroke="{DynamicResource MossBrush}" StrokeThickness="1.5"
StrokeDashArray="4,3" Fill="Transparent" RadiusX="8" RadiusY="8"/>
</Grid>
<!-- Indent wrapper: col 0 = 24px child/chain indent track, col 1 = content -->
<Grid Grid.Row="1" ColumnDefinitions="24,*">
<!-- Indent track (only while the parent/chain-head shares this view; orphaned rows render flat) -->
<Border Grid.Column="0" VerticalAlignment="Stretch">
<Border.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.Or}">
<Binding Path="ShowAsChild"/>
<Binding Path="ShowAsChainMember"/>
</MultiBinding>
</Border.IsVisible>
<Rectangle Width="1" Fill="{DynamicResource LineBrush}"
HorizontalAlignment="Right" Margin="0,4"/>
</Border>
<!-- Chain step badge: centered on the rail line, half laid over the card's left edge -->
<Border Grid.Column="0" Grid.ColumnSpan="2" Classes="chain-step-badge"
IsVisible="{Binding ShowAsChainMember}"
HorizontalAlignment="Left" VerticalAlignment="Center"
Margin="16,0,0,0">
<TextBlock Text="{Binding ChainStep}"/>
</Border>
<!-- Main task card -->
<Border Grid.Column="1" Classes="task-row"
Margin="0,2"
Classes.selected="{Binding IsSelected}"
Classes.dragging="{Binding IsDragging}"
Classes.drop-target="{Binding IsDropTarget}"
Classes.done="{Binding Done}"
ContextRequested="OnRowContextRequested">
<Grid ColumnDefinitions="0,18,32,*,Auto,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"
ToolTip.Tip="{loc:Tr tasks.toggleSubtasksTip}">
<Panel>
<PathIcon Width="12" Height="12" Data="{StaticResource Icon.ChevronDown}"
Foreground="{DynamicResource TextDimBrush}" IsVisible="{Binding IsExpanded}"/>
<PathIcon Width="12" Height="12" Data="{StaticResource Icon.ChevronRight}"
Foreground="{DynamicResource TextDimBrush}" IsVisible="{Binding !IsExpanded}"/>
</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">
<Grid ColumnDefinitions="*,Auto" VerticalAlignment="Center">
<Grid Grid.Column="0" ColumnDefinitions="Auto,*" VerticalAlignment="Center">
<TextBlock Grid.Column="0" Classes="meta" Text="{Binding Number, StringFormat='#{0}'}"
VerticalAlignment="Center" Margin="0,0,6,0"/>
<TextBlock Grid.Column="1"
Classes="task-title"
Text="{Binding Title}" FontSize="{StaticResource FontSizeTaskTitle}"
Foreground="{DynamicResource TextBrush}"
TextWrapping="Wrap"
FontStyle="{Binding IsDraft, Converter={StaticResource BoolToItalic}}"
Opacity="{Binding IsDraft, Converter={StaticResource BoolToDraftOpacity}}"
TextDecorations="{Binding Done, Converter={StaticResource StrikeIfTrue}}"/>
</Grid>
<!-- Badges: DRAFT and planning session -->
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="4"
VerticalAlignment="Center" Margin="4,0,0,0">
<Border Classes="badge draft" IsVisible="{Binding IsDraft}">
<TextBlock Text="{loc:Tr tasks.badgeDraft}"/>
</Border>
<Border Classes="badge planned" IsVisible="{Binding IsPlanned}">
<TextBlock Text="{loc:Tr tasks.badgePlanned}"/>
</Border>
<Border Classes="badge"
Classes.planning="{Binding IsPlanActive}"
Classes.planned="{Binding IsPlanFinalized}"
IsVisible="{Binding IsPlanningParent}">
<TextBlock Text="{Binding PlanningBadge}"/>
</Border>
<Border Classes="badge manual" IsVisible="{Binding IsManual}"
ToolTip.Tip="{loc:Tr tasks.manualTip}">
<TextBlock Text="{Binding ManualBadge}"/>
</Border>
</StackPanel>
</Grid>
<!-- Chip row -->
<StackPanel Orientation="Horizontal" Spacing="6">
<!-- Roadblock badge -->
<PathIcon Width="13" Height="13" VerticalAlignment="Center"
Data="{StaticResource Icon.Warning}"
Foreground="#E0A800"
IsVisible="{Binding HasRoadblock}"
ToolTip.Tip="{Binding RoadblockTooltip}"/>
<!-- Agent-suggested badge -->
<PathIcon Width="10" Height="10" VerticalAlignment="Center"
Data="{StaticResource Icon.AgentSuggested}"
Foreground="#5C8FA8"
IsVisible="{Binding IsAgentSuggested}"
ToolTip.Tip="{loc:Tr tasks.agentSuggestedTip}"/>
<!-- Status chip. When a ConPTY session is open it reads "Interactive" and is tappable —
it jumps to that pane in Mission Control. -->
<Border Classes="chip"
IsVisible="{Binding ShowStatusChip}"
Tapped="OnStatusChipTapped"
ToolTip.Tip="{Binding StatusChipTooltip}"
Classes.interactive="{Binding HasInteractiveSession}"
Classes.parked="{Binding IsParked}"
Classes.running="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Running}"
Classes.review="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=WaitingForReview}"
Classes.children="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=WaitingForChildren}"
Classes.done="{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 StatusLabel}"/>
</Border>
<!-- Dequeue button (visible when row is Queued, or planning parent has queued subtasks) -->
<Button Classes="icon-btn dequeue-btn"
IsVisible="{Binding CanRemoveFromQueue}"
ToolTip.Tip="{loc:Tr tasks.removeFromQueueTip}"
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" IsVisible="{Binding ShowListChip}">
<StackPanel Orientation="Horizontal" Spacing="5" VerticalAlignment="Center">
<Ellipse Width="6" Height="6"
Fill="{DynamicResource MossBrush}"
VerticalAlignment="Center"/>
<TextBlock Text="{Binding ListName}"/>
</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>
<!-- Chain-after chip: the head-relative fallback when this row isn't rendered as a
rail member (planning child, or the head is filtered out of this view). -->
<Border Classes="chip chip-tag"
IsVisible="{Binding ChainAfterLabel, Converter={StaticResource NotNullToBool}}">
<TextBlock>
<Run Text="{loc:Tr tasks.chainAfterPrefix}"/>
<Run Text=" "/>
<Run Text="{Binding ChainAfterLabel}"/>
</TextBlock>
</Border>
</StackPanel>
</StackPanel>
<!-- Refine button, replaced by a spinner while the refine run is in flight -->
<Panel Grid.Column="5">
<Button Classes="icon-btn refine-btn"
IsVisible="{Binding CanRefine}"
VerticalAlignment="Top" Margin="0,2,0,0"
Command="{Binding $parent[ItemsControl].((vm:TasksIslandViewModel)DataContext).RefineTaskCommand}"
CommandParameter="{Binding}"
ToolTip.Tip="{loc:Tr tasks.refineTip}">
<Viewbox Width="16" Height="16">
<Path Classes="plan-icon" Data="{StaticResource Icon.Refine}"/>
</Viewbox>
</Button>
<Ellipse Classes="spinner"
IsVisible="{Binding IsRefining}"
VerticalAlignment="Top" Margin="0,4,2,0"
ToolTip.Tip="{loc:Tr tasks.refiningTip}"/>
</Panel>
<!-- Star toggle -->
<Button Grid.Column="6" 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}"
ToolTip.Tip="{loc:Tr details.starTip}">
<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="52" IsVisible="{Binding DropHintBelow}">
<Rectangle Margin="4,3" Stroke="{DynamicResource MossBrush}" StrokeThickness="1.5"
StrokeDashArray="4,3" Fill="Transparent" RadiusX="8" RadiusY="8"/>
</Grid>
<!-- Hidden schedule anchor (its Flyout is shown from the context menu) -->
<Button Grid.Row="1" x:Name="ScheduleAnchor"
Width="1" Height="1" Opacity="0"
HorizontalAlignment="Left" VerticalAlignment="Top"
IsHitTestVisible="False" Focusable="False">
<Button.Flyout>
<Flyout Placement="Bottom" ShowMode="Standard">
<Border Background="{DynamicResource Surface2Brush}"
BorderBrush="{DynamicResource LineBrush}"
BorderThickness="1" CornerRadius="10"
Padding="16" Width="300">
<StackPanel Spacing="12">
<TextBlock Classes="title" Text="{loc:Tr tasks.scheduleTitle}"/>
<StackPanel Spacing="6">
<TextBlock Classes="eyebrow" Text="{loc:Tr tasks.scheduleWhen}"
Foreground="{DynamicResource TextDimBrush}" Opacity="0.6"/>
<ctl:ThemedDatePicker x:Name="ScheduleDate" ShowTime="True"
HorizontalAlignment="Stretch"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="8"
HorizontalAlignment="Right" Margin="0,4,0,0">
<Button Classes="btn" Content="{loc:Tr tasks.cancel}" Click="OnScheduleCancelClick" MinWidth="76"/>
<Button Content="{loc:Tr tasks.scheduleConfirm}" Classes="accent" Click="OnScheduleSetClick" MinWidth="76"/>
</StackPanel>
</StackPanel>
</Border>
</Flyout>
</Button.Flyout>
</Button>
</Grid>
</UserControl>