The tab body ran flush into the console's rounded bottom corner, so the final log line was shaved off. Inset the tab body from the bottom so the scroll viewport ends above the corner and ScrollToEnd reveals the whole last line. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
264 lines
13 KiB
XML
264 lines
13 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:loc="using:ClaudeDo.Ui.Localization"
|
|
x:DataType="vm:DetailsIslandViewModel"
|
|
x:Class="ClaudeDo.Ui.Views.Islands.Detail.WorkConsole">
|
|
|
|
<UserControl.Styles>
|
|
<Style Selector="Button.tab-btn">
|
|
<Setter Property="Background" Value="Transparent" />
|
|
<Setter Property="BorderThickness" Value="0" />
|
|
<Setter Property="Padding" Value="12,8" />
|
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
|
<Setter Property="FontSize" Value="{StaticResource FontSizeMono}" />
|
|
<Setter Property="Foreground" Value="{StaticResource TextMuteBrush}" />
|
|
<Setter Property="CornerRadius" Value="0" />
|
|
</Style>
|
|
<Style Selector="Button.tab-btn:pointerover /template/ ContentPresenter">
|
|
<Setter Property="Background" Value="{StaticResource Surface2Brush}" />
|
|
<Setter Property="TextElement.Foreground" Value="{StaticResource TextDimBrush}" />
|
|
</Style>
|
|
<Style Selector="Button.tab-btn.active /template/ ContentPresenter">
|
|
<Setter Property="Background" Value="{StaticResource Surface3Brush}" />
|
|
<Setter Property="TextElement.Foreground" Value="{StaticResource AccentBrush}" />
|
|
</Style>
|
|
</UserControl.Styles>
|
|
|
|
<!-- Outer terminal card — Padding="0" so header/strip span edge-to-edge;
|
|
ClipToBounds keeps tab content inside the rounded corners (no bottom clip). -->
|
|
<Border Classes="terminal" Padding="0" ClipToBounds="True">
|
|
<DockPanel LastChildFill="True">
|
|
|
|
<!-- ── Title bar ── -->
|
|
<Grid DockPanel.Dock="Top" ColumnDefinitions="Auto,*,Auto"
|
|
Background="{DynamicResource Surface2Brush}" Height="28">
|
|
|
|
<!-- Traffic-light dots -->
|
|
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="6"
|
|
Margin="12,0" VerticalAlignment="Center">
|
|
<Ellipse Classes="dot-red" />
|
|
<Ellipse Classes="dot-yellow" />
|
|
<Ellipse Classes="dot-green" />
|
|
</StackPanel>
|
|
|
|
<!-- Right cluster: info header (model · turns · diff) + status chip -->
|
|
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="12"
|
|
Margin="0,0,8,0" VerticalAlignment="Center">
|
|
|
|
<StackPanel Orientation="Horizontal" Spacing="6" VerticalAlignment="Center">
|
|
<TextBlock Classes="meta" Text="{Binding Model}"
|
|
Foreground="{DynamicResource TextMuteBrush}" />
|
|
<TextBlock Classes="meta" Text="·"
|
|
Foreground="{DynamicResource TextFaintBrush}" />
|
|
<TextBlock Classes="meta" Text="{Binding TurnsText}"
|
|
Foreground="{DynamicResource TextMuteBrush}" />
|
|
<TextBlock Classes="meta" Text="·"
|
|
Foreground="{DynamicResource TextFaintBrush}" />
|
|
<TextBlock Classes="diff-add" Text="{Binding DiffAddText}" />
|
|
<TextBlock Classes="diff-del" Text="{Binding DiffDelText}" />
|
|
</StackPanel>
|
|
|
|
<Panel VerticalAlignment="Center">
|
|
<Border Classes="live-chip pulsing"
|
|
IsVisible="{Binding IsRunning}">
|
|
<StackPanel Orientation="Horizontal" Spacing="5" VerticalAlignment="Center">
|
|
<Ellipse VerticalAlignment="Center" />
|
|
<TextBlock Text="{loc:Tr session.chipLive}" VerticalAlignment="Center" />
|
|
</StackPanel>
|
|
</Border>
|
|
<Border Classes="live-chip done"
|
|
IsVisible="{Binding IsDone}">
|
|
<StackPanel Orientation="Horizontal" Spacing="5" VerticalAlignment="Center">
|
|
<Ellipse VerticalAlignment="Center" Fill="{DynamicResource MossBrush}" />
|
|
<TextBlock Text="{loc:Tr session.chipDone}" VerticalAlignment="Center"
|
|
Foreground="{DynamicResource MossBrush}" />
|
|
</StackPanel>
|
|
</Border>
|
|
<Border Classes="live-chip failed"
|
|
IsVisible="{Binding IsFailed}">
|
|
<StackPanel Orientation="Horizontal" Spacing="5" VerticalAlignment="Center">
|
|
<Ellipse VerticalAlignment="Center" Fill="{DynamicResource BloodBrush}" />
|
|
<TextBlock Text="{loc:Tr session.chipFailed}" VerticalAlignment="Center"
|
|
Foreground="{DynamicResource BloodBrush}" />
|
|
</StackPanel>
|
|
</Border>
|
|
</Panel>
|
|
|
|
</StackPanel>
|
|
|
|
</Grid>
|
|
|
|
<!-- ── Roadblock band ── -->
|
|
<Border DockPanel.Dock="Top"
|
|
IsVisible="{Binding ShowRoadblock}"
|
|
Background="{DynamicResource ErrorTintBrush}"
|
|
BorderBrush="{DynamicResource BloodBrush}"
|
|
BorderThickness="0,1"
|
|
Padding="14,8">
|
|
<StackPanel Spacing="6">
|
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
|
<PathIcon Data="{StaticResource Icon.Warning}"
|
|
Foreground="{DynamicResource BloodBrush}"
|
|
Width="14" Height="14" VerticalAlignment="Center" />
|
|
<TextBlock Classes="meta" Text="{Binding RoadblockMessage}"
|
|
Foreground="{DynamicResource BloodBrush}"
|
|
TextWrapping="Wrap" VerticalAlignment="Center" />
|
|
</StackPanel>
|
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
|
<Button Classes="btn accent" Content="Continue"
|
|
Command="{Binding ContinueCommand}"
|
|
IsVisible="{Binding ShowContinue}" />
|
|
<Button Classes="btn" Content="Reset & Retry"
|
|
Command="{Binding ResetAndRetryCommand}"
|
|
IsVisible="{Binding ShowResetAndRetry}" />
|
|
</StackPanel>
|
|
</StackPanel>
|
|
</Border>
|
|
|
|
<!-- ── Tab strip ── -->
|
|
<Border DockPanel.Dock="Top"
|
|
Background="{DynamicResource Surface2Brush}"
|
|
BorderBrush="{DynamicResource LineBrush}"
|
|
BorderThickness="0,0,0,1">
|
|
<StackPanel Orientation="Horizontal">
|
|
<Button Classes="tab-btn"
|
|
Classes.active="{Binding IsOutputTab}"
|
|
Content="Output"
|
|
Command="{Binding SelectTabCommand}"
|
|
CommandParameter="output" />
|
|
<Button Classes="tab-btn"
|
|
Classes.active="{Binding IsSessionTab}"
|
|
Content="Session"
|
|
Command="{Binding SelectTabCommand}"
|
|
CommandParameter="session" />
|
|
</StackPanel>
|
|
</Border>
|
|
|
|
<!-- ── Tab body (bottom inset keeps content clear of the rounded corner) ── -->
|
|
<Grid Margin="0,0,0,8">
|
|
|
|
<!-- Output: log rendered directly on the console body (no nested card) -->
|
|
<ScrollViewer Name="LogScroll"
|
|
IsVisible="{Binding IsOutputTab}"
|
|
VerticalScrollBarVisibility="Visible"
|
|
AllowAutoHide="False"
|
|
Padding="12,8,12,4">
|
|
<ItemsControl ItemsSource="{Binding Log}">
|
|
<ItemsControl.ItemTemplate>
|
|
<DataTemplate DataType="vm:LogLineViewModel">
|
|
<Grid ColumnDefinitions="60,*" Margin="0,1">
|
|
<TextBlock Grid.Column="0"
|
|
Classes="log-ts"
|
|
Text="{Binding TimestampFormatted}" />
|
|
<SelectableTextBlock Grid.Column="1"
|
|
Text="{Binding Text}" Tag="{Binding ClassName}"
|
|
Foreground="{DynamicResource TextDimBrush}"
|
|
TextWrapping="Wrap" />
|
|
</Grid>
|
|
</DataTemplate>
|
|
</ItemsControl.ItemTemplate>
|
|
</ItemsControl>
|
|
</ScrollViewer>
|
|
|
|
<!-- Session: review (top) + merge/worktree + outcomes — each gated on state -->
|
|
<ScrollViewer IsVisible="{Binding IsSessionTab}" Padding="14,10">
|
|
<StackPanel Spacing="14">
|
|
|
|
<!-- Review controls -->
|
|
<StackPanel Spacing="8" IsVisible="{Binding IsWaitingForReview}">
|
|
<TextBlock Classes="section-label" Text="REVIEW" />
|
|
<TextBlock Classes="field-label" Text="Feedback" />
|
|
<TextBox Text="{Binding ReviewFeedback, Mode=TwoWay}"
|
|
AcceptsReturn="True"
|
|
TextWrapping="Wrap"
|
|
MinHeight="60"
|
|
MaxHeight="180"
|
|
PlaceholderText="Optional feedback for the next run…"
|
|
Padding="8"
|
|
Background="{DynamicResource Surface2Brush}"
|
|
BorderBrush="{DynamicResource LineBrush}"
|
|
BorderThickness="1"
|
|
CornerRadius="8" />
|
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
|
<Button Classes="btn accent" Content="Approve"
|
|
Command="{Binding ApproveReviewCommand}" />
|
|
<Button Classes="btn" Content="Reject"
|
|
Command="{Binding RejectReviewCommand}" />
|
|
<Button Classes="btn" Content="Park"
|
|
Command="{Binding ParkReviewCommand}" />
|
|
<Button Classes="btn" Content="Cancel"
|
|
Command="{Binding CancelReviewCommand}" />
|
|
</StackPanel>
|
|
</StackPanel>
|
|
|
|
<!-- Merge & worktree management -->
|
|
<StackPanel Spacing="10" IsVisible="{Binding ShowMergeSection}">
|
|
<TextBlock Classes="section-label" Text="MERGE & WORKTREE" />
|
|
<StackPanel Spacing="4">
|
|
<TextBlock Classes="field-label" Text="Merge target" />
|
|
<ComboBox ItemsSource="{Binding MergeTargetBranches}"
|
|
SelectedItem="{Binding SelectedMergeTarget, Mode=TwoWay}"
|
|
HorizontalAlignment="Stretch" />
|
|
</StackPanel>
|
|
<WrapPanel Orientation="Horizontal">
|
|
<Button Classes="btn" Content="Open Diff" Margin="0,0,8,8"
|
|
Command="{Binding OpenDiffCommand}" />
|
|
<Button Classes="btn" Margin="0,0,8,8"
|
|
Command="{Binding OpenWorktreeCommand}">
|
|
<StackPanel Orientation="Horizontal" Spacing="5">
|
|
<TextBlock Text="Worktree" />
|
|
<PathIcon Data="{StaticResource Icon.ArrowOut}" Width="11" Height="11" />
|
|
</StackPanel>
|
|
</Button>
|
|
<Button Classes="btn" Content="Review Combined Diff" Margin="0,0,8,8"
|
|
Command="{Binding ReviewCombinedDiffCommand}" />
|
|
<Button Classes="btn accent" Content="Merge All Subtasks" Margin="0,0,0,8"
|
|
Command="{Binding MergeAllCommand}"
|
|
IsEnabled="{Binding CanMergeAll}"
|
|
ToolTip.Tip="{Binding MergeAllDisabledReason}" />
|
|
</WrapPanel>
|
|
<TextBlock Text="{Binding MergeAllError}"
|
|
Foreground="{DynamicResource BloodBrush}"
|
|
TextWrapping="Wrap"
|
|
IsVisible="{Binding MergeAllError,
|
|
Converter={x:Static ObjectConverters.IsNotNull}}" />
|
|
</StackPanel>
|
|
|
|
<!-- Child outcomes -->
|
|
<StackPanel Spacing="6" IsVisible="{Binding HasChildOutcomes}">
|
|
<TextBlock Classes="section-label" Text="OUTCOMES" />
|
|
<ItemsControl ItemsSource="{Binding ChildOutcomes}">
|
|
<ItemsControl.ItemTemplate>
|
|
<DataTemplate x:DataType="vm:ChildOutcomeRowViewModel">
|
|
<Grid ColumnDefinitions="*,Auto,Auto" Margin="0,2">
|
|
<TextBlock Grid.Column="0" Text="{Binding Title}"
|
|
TextTrimming="CharacterEllipsis"
|
|
VerticalAlignment="Center" />
|
|
<TextBlock Grid.Column="1" Text="{Binding RoadblockText}"
|
|
IsVisible="{Binding HasRoadblock}"
|
|
Foreground="#E0A030"
|
|
Margin="8,0" VerticalAlignment="Center" />
|
|
<TextBlock Grid.Column="2" Text="{Binding StatusLabel}"
|
|
Opacity="0.75" VerticalAlignment="Center" />
|
|
</Grid>
|
|
</DataTemplate>
|
|
</ItemsControl.ItemTemplate>
|
|
</ItemsControl>
|
|
</StackPanel>
|
|
|
|
<!-- Empty state: nothing to manage yet -->
|
|
<TextBlock IsVisible="{Binding ShowSessionEmpty}"
|
|
Classes="meta"
|
|
Foreground="{DynamicResource TextMuteBrush}"
|
|
TextWrapping="Wrap"
|
|
Text="Nothing to manage yet — review and merge controls appear here once the run finishes." />
|
|
|
|
</StackPanel>
|
|
</ScrollViewer>
|
|
|
|
</Grid>
|
|
</DockPanel>
|
|
</Border>
|
|
</UserControl>
|