feat(ui): surface review actions and WaitingForReview status in task rows

Adds Approve/Reject/Park/Cancel buttons with a feedback flyout, a review
status chip, and a friendly status label for WaitingForReview tasks.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
mika kuns
2026-06-02 07:46:37 +02:00
parent 21f1cf2a85
commit 6c27ffbdca
8 changed files with 152 additions and 9 deletions

View File

@@ -131,12 +131,30 @@
<!-- Status chip -->
<Border Classes="chip"
Classes.running="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Running}"
Classes.review="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Done}"
Classes.review="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=WaitingForReview}"
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 Status}"/>
<TextBlock Text="{Binding StatusLabel}"/>
</Border>
<!-- Review actions (visible when WaitingForReview) -->
<StackPanel Orientation="Horizontal" Spacing="4"
IsVisible="{Binding IsWaitingForReview}">
<Button Classes="btn" Content="Approve" MinWidth="0" Padding="8,2"
ToolTip.Tip="Approve — mark Done"
Click="OnApproveReviewClick"/>
<Button Classes="btn" Content="Reject" MinWidth="0" Padding="8,2"
ToolTip.Tip="Reject with feedback and re-run"
Click="OnRejectReviewClick"/>
<Button Classes="btn" Content="Park" MinWidth="0" Padding="8,2"
ToolTip.Tip="Send back to Idle for manual editing"
Click="OnParkReviewClick"/>
<Button Classes="btn" Content="Cancel" MinWidth="0" Padding="8,2"
ToolTip.Tip="Cancel this task"
Click="OnCancelReviewClick"/>
</StackPanel>
<!-- Dequeue button (visible when row is Queued, or planning parent has queued subtasks) -->
<Button Classes="icon-btn dequeue-btn"
IsVisible="{Binding CanRemoveFromQueue}"
@@ -227,5 +245,36 @@
</Flyout>
</Button.Flyout>
</Button>
<!-- Hidden reject-feedback anchor (its Flyout is shown from the Reject button) -->
<Button Grid.Row="1" x:Name="RejectAnchor"
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="320">
<StackPanel Spacing="12">
<TextBlock Classes="title" Text="Reject &amp; re-run"/>
<StackPanel Spacing="6">
<TextBlock Classes="eyebrow" Text="FEEDBACK FOR THE AGENT"
Foreground="{DynamicResource TextDimBrush}" Opacity="0.6"/>
<TextBox x:Name="RejectFeedback"
AcceptsReturn="True" TextWrapping="Wrap"
MinHeight="80" PlaceholderText="What should the agent fix?"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="8"
HorizontalAlignment="Right" Margin="0,4,0,0">
<Button Classes="btn" Content="Cancel" Click="OnRejectCancelClick" MinWidth="76"/>
<Button Content="Re-run" Classes="accent" Click="OnRejectConfirmClick" MinWidth="76"/>
</StackPanel>
</StackPanel>
</Border>
</Flyout>
</Button.Flyout>
</Button>
</Grid>
</UserControl>

View File

@@ -88,6 +88,43 @@ public partial class TaskRowView : UserControl
await vm.SetStatusOnRowAsync(row, status);
}
private async void OnApproveReviewClick(object? sender, RoutedEventArgs e)
{
if (DataContext is TaskRowViewModel row && FindTasksVm() is { } vm)
await vm.ApproveReviewCommand.ExecuteAsync(row);
}
private async void OnParkReviewClick(object? sender, RoutedEventArgs e)
{
if (DataContext is TaskRowViewModel row && FindTasksVm() is { } vm)
await vm.RejectReviewToIdleCommand.ExecuteAsync(row);
}
private async void OnCancelReviewClick(object? sender, RoutedEventArgs e)
{
if (DataContext is TaskRowViewModel row && FindTasksVm() is { } vm)
await vm.CancelReviewCommand.ExecuteAsync(row);
}
private void OnRejectReviewClick(object? sender, RoutedEventArgs e)
{
if (DataContext is not TaskRowViewModel) return;
RejectFeedback.Text = "";
RejectAnchor.Flyout?.ShowAt(RejectAnchor);
}
private async void OnRejectConfirmClick(object? sender, RoutedEventArgs e)
{
RejectAnchor.Flyout?.Hide();
if (DataContext is not TaskRowViewModel row || FindTasksVm() is not { } vm) return;
var feedback = RejectFeedback.Text ?? "";
if (string.IsNullOrWhiteSpace(feedback)) return;
await vm.RejectReviewToQueueAsync(row, feedback);
}
private void OnRejectCancelClick(object? sender, RoutedEventArgs e)
=> RejectAnchor.Flyout?.Hide();
private void OnScheduleForClick(object? sender, RoutedEventArgs e)
{
if (DataContext is not TaskRowViewModel row) return;