Files
ClaudeDo/src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml
T
mika kuns 589b9e75f3 fix(ui): unfreeze the operation spinner and attribute rebind churn
Three causes behind the stuck indicator:
- OperationStatus.End did not retire the generation, so tick work posted
  while the dispatcher was blocked drained afterwards and set
  ShowIndicator back to true on a finished operation.
- The startup update check ran in Task.Run; OperationStatus writes its
  observable properties on the calling thread, so the final
  ShowIndicator=false never reached the binding.
- OperationIndicator overwrote its own DataContext from the Status
  property, which re-targeted the call-site binding. It now scopes the
  DataContext to an inner panel and collapses itself when Status is
  null; every call site switched from DataContext= to Status=.

Also gates the footer connection pill in the command body instead of
CanExecute (a disabled command greyed out the ONLINE chip), and extends
OperationTiming: pid per line, 4 MB rollover, fast successes dropped
(failures always kept), and DetailsIsland.BindAsync now carries the
selection trigger via TasksIslandViewModel.SelectFrom.
2026-08-12 13:50:05 +02:00

200 lines
9.4 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:islands="using:ClaudeDo.Ui.Views.Islands"
xmlns:detail="using:ClaudeDo.Ui.Views.Islands.Detail"
xmlns:ctl="using:ClaudeDo.Ui.Views.Controls"
xmlns:loc="using:ClaudeDo.Ui.Localization"
x:Class="ClaudeDo.Ui.Views.Islands.DetailsIslandView"
x:DataType="vm:DetailsIslandViewModel"
DragDrop.AllowDrop="True">
<Panel>
<DockPanel>
<!-- ── Metadata footer (sticky bottom) — created-at + close — task detail only ── -->
<Border DockPanel.Dock="Bottom"
IsVisible="{Binding IsTaskDetailVisible}"
BorderBrush="{DynamicResource LineBrush}"
BorderThickness="0,1,0,0"
Padding="14,8">
<Grid ColumnDefinitions="*,Auto">
<TextBlock Grid.Column="0"
Classes="meta"
Text="{Binding Task.CreatedAtFormatted}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<Button Grid.Column="1" Classes="icon-btn"
Command="{Binding CloseDetailsCommand}"
ToolTip.Tip="{loc:Tr details.closeTip}"
VerticalAlignment="Center">
<PathIcon Data="{StaticResource Icon.X}" Width="14" Height="14"/>
</Button>
</Grid>
</Border>
<!-- ── Header (sticky top): id · title · trash/skull · gear — task detail only ── -->
<Border DockPanel.Dock="Top" Classes="island-header"
IsVisible="{Binding IsTaskDetailVisible}">
<detail:TaskHeaderBar/>
</Border>
<!-- ── Body: task details (normal), notes editor (notes mode), or prep log (prep mode) ── -->
<Grid>
<!-- Task detail: description/steps card (upper) + pinned work console (lower) -->
<Grid x:Name="DetailBodyGrid"
IsVisible="{Binding IsTaskDetailVisible}"
Margin="14,12,14,12">
<Grid.RowDefinitions>
<!-- Auto: the description sizes to its content so the console takes
every spare pixel when it's short. Row limits are proportional
and set in code-behind (UpdateRowLimits): the description row is
capped at 2/3 of the island and the console row floored at 1/3,
so the console can be dragged down to (but not below) 1/3 and a
long description never spills over the footer. -->
<RowDefinition Height="Auto" MinHeight="90"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<detail:DescriptionStepsCard x:Name="DescriptionCard" Grid.Row="0"/>
<!-- Console row also hosts the roadblock card (docked above the console)
so it surfaces at a glance between Details and Output. Keeping it
inside row 1 leaves the desc/console resize model untouched. -->
<DockPanel Grid.Row="1" Margin="0,10,0,0">
<Border DockPanel.Dock="Top"
IsVisible="{Binding ShowRoadblockCard}"
Margin="0,0,0,10" Padding="12,10"
Background="{DynamicResource ReviewTintBrush}"
BorderBrush="{DynamicResource ReviewTintBorderBrush}"
BorderThickness="1" CornerRadius="10">
<StackPanel Spacing="6">
<StackPanel Orientation="Horizontal" Spacing="8">
<PathIcon Data="{StaticResource Icon.Warning}"
Foreground="{DynamicResource StatusReviewBrush}"
Width="14" Height="14" VerticalAlignment="Center"/>
<TextBlock Classes="section-label" Text="ROADBLOCK"
Foreground="{DynamicResource StatusReviewBrush}"
VerticalAlignment="Center"/>
</StackPanel>
<SelectableTextBlock Text="{Binding Roadblocks}"
TextWrapping="Wrap"
Foreground="{DynamicResource TextDimBrush}"/>
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="6">
<TextBox Grid.Column="0"
Text="{Binding RoadblockReplyDraft, UpdateSourceTrigger=PropertyChanged}"
PlaceholderText="{loc:Tr details.roadblockReply.placeholder}"
IsEnabled="{Binding CanReplyToRoadblock}"
AcceptsReturn="False">
<TextBox.KeyBindings>
<KeyBinding Gesture="Enter" Command="{Binding SendRoadblockReplyCommand}"/>
</TextBox.KeyBindings>
</TextBox>
<Button Grid.Column="1"
Content="{loc:Tr details.roadblockReply.send}"
Command="{Binding SendRoadblockReplyCommand}"/>
</Grid>
<TextBlock Classes="meta"
Text="{loc:Tr details.roadblockReply.noSession}"
IsVisible="{Binding !CanReplyToRoadblock}"
TextWrapping="Wrap"
Foreground="{DynamicResource TextDimBrush}"/>
</StackPanel>
</Border>
<!-- Question prompt (AskUser): answer the running task's question inline. -->
<Border DockPanel.Dock="Top"
IsVisible="{Binding Monitor.HasPendingQuestion}"
Margin="0,0,0,10" Padding="12,8"
Background="{DynamicResource AccentSoftBrush}"
BorderBrush="{DynamicResource AccentBrush}"
BorderThickness="1" CornerRadius="10">
<StackPanel Spacing="6">
<TextBlock Classes="meta"
Text="{loc:Tr missionControl.question.title}"
Foreground="{DynamicResource AccentBrush}" FontWeight="SemiBold"/>
<TextBlock Text="{Binding Monitor.PendingQuestion}" TextWrapping="Wrap"
Foreground="{DynamicResource TextBrush}"/>
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="6">
<TextBox Grid.Column="0"
Text="{Binding Monitor.AnswerDraft, UpdateSourceTrigger=PropertyChanged}"
PlaceholderText="{loc:Tr missionControl.question.placeholder}"
AcceptsReturn="False">
<TextBox.KeyBindings>
<KeyBinding Gesture="Enter" Command="{Binding Monitor.SubmitAnswerCommand}"/>
</TextBox.KeyBindings>
</TextBox>
<Button Grid.Column="1"
Content="{loc:Tr missionControl.question.send}"
Command="{Binding Monitor.SubmitAnswerCommand}"/>
</Grid>
</StackPanel>
</Border>
<detail:WorkConsole/>
</DockPanel>
<!-- Resize by dragging the console's top edge — a transparent splitter
over the gap above the console; no standalone separator bar.
Stays draggable while maximized. -->
<GridSplitter x:Name="DetailSplitter" Grid.Row="1"
VerticalAlignment="Top"
Height="10"
HorizontalAlignment="Stretch"
ResizeDirection="Rows"
Background="Transparent"
DragStarted="OnSplitterDragStarted"
DragCompleted="OnSplitterDragCompleted"/>
</Grid>
<!-- Notes mode -->
<Panel IsVisible="{Binding IsNotesMode}">
<islands:NotesEditorView DataContext="{Binding Notes}"/>
</Panel>
<!-- Daily-prep mode -->
<Panel IsVisible="{Binding IsPrepMode}">
<DockPanel>
<Border DockPanel.Dock="Top" Padding="12,8">
<StackPanel Orientation="Horizontal" Spacing="{StaticResource SpaceSm}">
<Button Classes="btn primary"
Command="{Binding Prep.PlanDayCommand}"
IsEnabled="{Binding Prep.IsPlanDayEnabled}"
Content="{loc:Tr details.planDay}"/>
<ctl:OperationIndicator Status="{Binding Prep.PrepOperation}"/>
</StackPanel>
</Border>
<Panel>
<islands:SessionTerminalView
Margin="18,8,18,0"
Entries="{Binding Prep.PrepLog}" Label="daily-prep"
IsRunning="{Binding Prep.IsPrepRunning}"/>
<TextBlock IsVisible="{Binding Prep.ShowPrepEmptyState}"
HorizontalAlignment="Center" VerticalAlignment="Center"
Foreground="{DynamicResource TextMuteBrush}"
Text="{loc:Tr details.prepEmpty}"/>
</Panel>
</DockPanel>
</Panel>
</Grid>
</DockPanel>
<!-- Drop overlay — shown while dragging files over the pane -->
<Border IsVisible="{Binding IsDragOver}"
Background="{DynamicResource AccentSoftBrush}"
BorderBrush="{DynamicResource AccentBrush}"
BorderThickness="2"
CornerRadius="14"
IsHitTestVisible="False">
<TextBlock Text="{loc:Tr details.attachments.dropToAttach}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{DynamicResource AccentBrush}"
FontSize="16"
FontWeight="Medium"/>
</Border>
</Panel>
</UserControl>