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.
309 lines
14 KiB
XML
309 lines
14 KiB
XML
<Window xmlns="https://github.com/avaloniaui"
|
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
xmlns:vm="using:ClaudeDo.Ui.ViewModels"
|
|
xmlns:islands="using:ClaudeDo.Ui.Views.Islands"
|
|
xmlns:controls="using:ClaudeDo.Ui.Views.Controls"
|
|
xmlns:converters="using:ClaudeDo.Ui.Converters"
|
|
xmlns:loc="using:ClaudeDo.Ui.Localization"
|
|
x:Class="ClaudeDo.Ui.Views.MainWindow"
|
|
x:DataType="vm:IslandsShellViewModel"
|
|
Title="ClaudeDo"
|
|
Width="1280" Height="820" MinWidth="780" MinHeight="600"
|
|
Background="{DynamicResource VoidBrush}"
|
|
Icon="avares://ClaudeDo.Ui/Assets/ClaudeTask.ico"
|
|
CanResize="True"
|
|
WindowDecorations="BorderOnly"
|
|
ExtendClientAreaToDecorationsHint="True"
|
|
ExtendClientAreaTitleBarHeightHint="-1">
|
|
<Window.Resources>
|
|
<converters:WorkerLogLevelToBrushConverter x:Key="WorkerLogLevelToBrush"/>
|
|
</Window.Resources>
|
|
<Window.KeyBindings>
|
|
<!-- OemQuestion is the '#' key on a German layout: binding it here swallowed the
|
|
character app-wide. Ctrl+K is what the search box advertises anyway. -->
|
|
<KeyBinding Gesture="Ctrl+K" Command="{Binding FocusSearchCommand}"/>
|
|
<KeyBinding Gesture="Ctrl+N" Command="{Binding FocusAddTaskCommand}"/>
|
|
</Window.KeyBindings>
|
|
<Grid x:Name="RootGrid" RowDefinitions="36,*,22">
|
|
<!-- Custom title bar -->
|
|
<Border Grid.Row="0"
|
|
Background="{DynamicResource DeepBrush}"
|
|
BorderBrush="{DynamicResource LineBrush}"
|
|
BorderThickness="0,0,0,1">
|
|
<Grid ColumnDefinitions="Auto,*,Auto">
|
|
|
|
<!-- Left: brand block -->
|
|
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="8"
|
|
VerticalAlignment="Center" Margin="14,0,0,0">
|
|
<!-- App icon (matches taskbar) -->
|
|
<Image Source="avares://ClaudeDo.Ui/Assets/ClaudeTask.ico"
|
|
Width="16" Height="16"
|
|
VerticalAlignment="Center"
|
|
RenderOptions.BitmapInterpolationMode="HighQuality"/>
|
|
<!-- CLAUDEDO label -->
|
|
<TextBlock Classes="title-brand-name eyebrow"
|
|
Text="CLAUDEDO"
|
|
Foreground="{DynamicResource TextBrush}"
|
|
LetterSpacing="1.4"
|
|
VerticalAlignment="Center"/>
|
|
<!-- separator dot -->
|
|
<TextBlock Classes="meta"
|
|
Text="·"
|
|
VerticalAlignment="Center"/>
|
|
<!-- current list name -->
|
|
<TextBlock Classes="meta"
|
|
Text="{Binding Lists.SelectedList.Name, Converter={StaticResource UpperCase}}"
|
|
LetterSpacing="1.4"
|
|
VerticalAlignment="Center"/>
|
|
<!-- Help menu -->
|
|
<Menu Margin="12,0,0,0"
|
|
Background="Transparent"
|
|
VerticalAlignment="Center">
|
|
<MenuItem Header="{loc:Tr shell.menu.worker}"
|
|
FontSize="{StaticResource FontSizeMono}"
|
|
Foreground="{DynamicResource TextDimBrush}">
|
|
<MenuItem Header="{loc:Tr shell.menu.restartWorker}"
|
|
Command="{Binding RestartWorkerCommand}"/>
|
|
<MenuItem Header="{loc:Tr shell.menu.checkForUpdates}"
|
|
Command="{Binding CheckForUpdatesCommand}"/>
|
|
<MenuItem Header="{loc:Tr shell.menu.usageMonitor}"
|
|
Command="{Binding OpenUsageMonitorCommand}"/>
|
|
</MenuItem>
|
|
<MenuItem Header="{loc:Tr shell.menu.repositories}"
|
|
FontSize="{StaticResource FontSizeMono}"
|
|
Foreground="{DynamicResource TextDimBrush}">
|
|
<MenuItem Header="{loc:Tr shell.menu.addRepos}" Command="{Binding OpenRepoImportCommand}"/>
|
|
<MenuItem Header="{loc:Tr shell.menu.worktrees}"
|
|
Command="{Binding OpenWorktreesOverviewGlobalCommand}"/>
|
|
</MenuItem>
|
|
<MenuItem Header="{loc:Tr shell.menu.help}"
|
|
FontSize="{StaticResource FontSizeMono}"
|
|
Foreground="{DynamicResource TextDimBrush}">
|
|
<MenuItem Header="{loc:Tr shell.menu.weeklyReport}" Command="{Binding OpenWeeklyReportCommand}"/>
|
|
<MenuItem Header="{loc:Tr shell.menu.about}" Command="{Binding OpenAboutCommand}"/>
|
|
</MenuItem>
|
|
</Menu>
|
|
<controls:OperationIndicator Status="{Binding UpdateCheck.Op}" Margin="8,0,0,0"/>
|
|
</StackPanel>
|
|
|
|
<!-- Middle: draggable strip -->
|
|
<Border Grid.Column="1" Background="Transparent"
|
|
PointerPressed="OnTitleBarPressed" />
|
|
|
|
<!-- Right: window controls -->
|
|
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="0"
|
|
VerticalAlignment="Center" Margin="0,0,4,0">
|
|
<Button Classes="title-ctrl"
|
|
Command="{Binding OpenMissionControlCommand}"
|
|
ToolTip.Tip="{loc:Tr missionControl.windowTitle}">
|
|
<PathIcon Data="{StaticResource Icon.Grid}" Width="12" Height="12"/>
|
|
</Button>
|
|
<Button Classes="title-ctrl" Click="OnMinimize">
|
|
<PathIcon Data="{StaticResource Icon.WinMin}" Width="10" Height="10"/>
|
|
</Button>
|
|
<Button Classes="title-ctrl" Click="OnToggleMax">
|
|
<PathIcon x:Name="MaxIcon" Data="{StaticResource Icon.WinMax}" Width="10" Height="10"/>
|
|
</Button>
|
|
<Button Classes="title-ctrl close" Click="OnClose">
|
|
<PathIcon Data="{StaticResource Icon.WinClose}" Width="10" Height="10"/>
|
|
</Button>
|
|
</StackPanel>
|
|
|
|
</Grid>
|
|
</Border>
|
|
|
|
<!-- Background gradient layer -->
|
|
<Border Grid.Row="1">
|
|
<Border.Background>
|
|
<RadialGradientBrush Center="50%,50%" GradientOrigin="50%,50%" RadiusX="70%" RadiusY="70%">
|
|
<GradientStop Offset="0" Color="{StaticResource DeepColor}" />
|
|
<GradientStop Offset="1" Color="{StaticResource VoidColor}" />
|
|
</RadialGradientBrush>
|
|
</Border.Background>
|
|
</Border>
|
|
|
|
<!-- Three islands (user-resizable) -->
|
|
<Grid Grid.Row="1" Margin="7">
|
|
<Grid.ColumnDefinitions>
|
|
<ColumnDefinition Width="260" MinWidth="200"/>
|
|
<ColumnDefinition Width="Auto"/>
|
|
<ColumnDefinition Width="*" MinWidth="320"/>
|
|
<ColumnDefinition Width="Auto"/>
|
|
<ColumnDefinition Width="460" MinWidth="280"/>
|
|
</Grid.ColumnDefinitions>
|
|
|
|
<Border Grid.Column="0" Classes="island" Margin="3">
|
|
<islands:ListsIslandView DataContext="{Binding Lists}"/>
|
|
</Border>
|
|
|
|
<GridSplitter Grid.Column="1"
|
|
Width="3"
|
|
Background="Transparent"
|
|
HorizontalAlignment="Stretch"
|
|
VerticalAlignment="Stretch"
|
|
ResizeDirection="Columns"
|
|
ResizeBehavior="PreviousAndNext"/>
|
|
|
|
<Border Grid.Column="2" Classes="island" Margin="3">
|
|
<islands:TasksIslandView DataContext="{Binding Tasks}"/>
|
|
</Border>
|
|
|
|
<GridSplitter Grid.Column="3"
|
|
Width="3"
|
|
Background="Transparent"
|
|
HorizontalAlignment="Stretch"
|
|
VerticalAlignment="Stretch"
|
|
ResizeDirection="Columns"
|
|
ResizeBehavior="PreviousAndNext"
|
|
IsVisible="{Binding ShowDetails}"/>
|
|
|
|
<Border Grid.Column="4" Classes="island" Margin="3"
|
|
IsVisible="{Binding ShowDetails}">
|
|
<islands:DetailsIslandView DataContext="{Binding Details}"/>
|
|
</Border>
|
|
</Grid>
|
|
|
|
<!-- Footer: connection status -->
|
|
<Border Grid.Row="2"
|
|
Background="{DynamicResource DeepBrush}"
|
|
BorderBrush="{DynamicResource LineBrush}"
|
|
BorderThickness="0,1,0,0">
|
|
<DockPanel LastChildFill="True" Margin="14,0">
|
|
<!-- Left: connection pill (click to open worker help) -->
|
|
<Button DockPanel.Dock="Left"
|
|
Command="{Binding OpenWorkerConnectionHelpCommand}"
|
|
Background="Transparent" BorderThickness="0" Padding="0"
|
|
Cursor="Hand" VerticalAlignment="Center">
|
|
<StackPanel Orientation="Horizontal" Spacing="7" VerticalAlignment="Center">
|
|
<Ellipse Width="7" Height="7" Fill="{DynamicResource StatusRunningBrush}"
|
|
IsVisible="{Binding Worker.IsConnected}"/>
|
|
<Ellipse Width="7" Height="7" Fill="{DynamicResource StatusReviewBrush}"
|
|
IsVisible="{Binding Worker.IsReconnecting}"/>
|
|
<Ellipse Width="7" Height="7" Fill="{DynamicResource StatusErrorBrush}"
|
|
IsVisible="{Binding IsOffline}"/>
|
|
<TextBlock Classes="eyebrow"
|
|
Text="{Binding ConnectionText, Converter={StaticResource UpperCase}}"
|
|
LetterSpacing="1.4"
|
|
VerticalAlignment="Center"/>
|
|
</StackPanel>
|
|
</Button>
|
|
|
|
<!-- Left: usage pill (click opens the usage monitor, wired in a later subtask) -->
|
|
<controls:UsagePill DockPanel.Dock="Left"
|
|
DataContext="{Binding UsagePill}"
|
|
Margin="14,0,0,0"
|
|
VerticalAlignment="Center"/>
|
|
|
|
<!-- Right: worker log line — click to open the Log Visualizer overlay -->
|
|
<Button DockPanel.Dock="Right"
|
|
Command="{Binding OpenLogVisualizerCommand}"
|
|
Background="Transparent" BorderThickness="0" Padding="6,0"
|
|
Cursor="Hand" VerticalAlignment="Center"
|
|
ToolTip.Tip="{loc:Tr modals.logVisualizer.openTooltip}">
|
|
<Panel>
|
|
<TextBlock Classes="meta"
|
|
Text="{loc:Tr modals.logVisualizer.footerHint}"
|
|
IsVisible="{Binding !IsWorkerLogVisible}"
|
|
Foreground="{DynamicResource TextMuteBrush}"
|
|
LetterSpacing="1.4"
|
|
VerticalAlignment="Center"/>
|
|
<TextBlock Classes="meta"
|
|
Text="{Binding WorkerLogText}"
|
|
IsVisible="{Binding IsWorkerLogVisible}"
|
|
Foreground="{Binding WorkerLogLevel, Converter={StaticResource WorkerLogLevelToBrush}}"
|
|
LetterSpacing="1.4"
|
|
TextTrimming="CharacterEllipsis"
|
|
VerticalAlignment="Center"/>
|
|
</Panel>
|
|
</Button>
|
|
|
|
<!-- Right: prime status notification -->
|
|
<TextBlock DockPanel.Dock="Right"
|
|
Classes="meta"
|
|
Text="{Binding PrimeStatus}"
|
|
VerticalAlignment="Center"
|
|
Margin="12,0,0,0"
|
|
IsVisible="{Binding PrimeStatus, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
|
|
|
<!-- Spacer between pill and log -->
|
|
<Panel/>
|
|
</DockPanel>
|
|
</Border>
|
|
<!-- Update popup card — bottom-right corner, only when update available -->
|
|
<Border Grid.Row="0" Grid.RowSpan="3"
|
|
VerticalAlignment="Bottom" HorizontalAlignment="Right"
|
|
Margin="0,0,16,30"
|
|
ZIndex="100"
|
|
IsVisible="{Binding IsUpdateBannerVisible}"
|
|
Background="{DynamicResource DeepBrush}"
|
|
BorderBrush="{DynamicResource LineBrush}"
|
|
BorderThickness="1"
|
|
CornerRadius="6"
|
|
Padding="14,10">
|
|
<StackPanel Spacing="8" Width="260">
|
|
<TextBlock Classes="body">
|
|
<Run Text="{loc:Tr shell.update.available}"/>
|
|
<Run Text=" v"/>
|
|
<Run Text="{Binding UpdateCheck.CurrentVersion}"/>
|
|
<Run Text=" → v"/>
|
|
<Run Text="{Binding UpdateBannerLatestVersion}"/>
|
|
</TextBlock>
|
|
<StackPanel Orientation="Horizontal" Spacing="8" HorizontalAlignment="Right">
|
|
<Button Classes="btn"
|
|
Content="{loc:Tr shell.update.dismiss}"
|
|
Command="{Binding DismissBannerCommand}"/>
|
|
<Button Classes="btn"
|
|
Content="{loc:Tr shell.update.updateNow}"
|
|
Command="{Binding UpdateNowCommand}"/>
|
|
</StackPanel>
|
|
</StackPanel>
|
|
</Border>
|
|
|
|
<!-- Stale-worker notice — bottom-right corner, above the update popup. Persistent
|
|
(no auto-dismiss timer): the running worker predates the selected list's merged
|
|
HEAD, so a restart is needed before "verified" claims against it are trustworthy. -->
|
|
<Border Grid.Row="0" Grid.RowSpan="3"
|
|
VerticalAlignment="Bottom" HorizontalAlignment="Right"
|
|
Margin="0,0,16,100"
|
|
ZIndex="100"
|
|
IsVisible="{Binding IsStaleWorkerBannerVisible}"
|
|
Background="{DynamicResource DeepBrush}"
|
|
BorderBrush="{DynamicResource LineBrush}"
|
|
BorderThickness="1"
|
|
CornerRadius="6"
|
|
Padding="14,10">
|
|
<StackPanel Spacing="8" Width="260">
|
|
<TextBlock Classes="body" Text="{loc:Tr shell.staleWorker.message}" TextWrapping="Wrap"/>
|
|
<StackPanel Orientation="Horizontal" Spacing="8" HorizontalAlignment="Right">
|
|
<Button Classes="btn"
|
|
Content="{loc:Tr shell.menu.restartWorker}"
|
|
Command="{Binding RestartWorkerCommand}"/>
|
|
</StackPanel>
|
|
</StackPanel>
|
|
</Border>
|
|
|
|
<!-- External merge popup card — bottom-left corner, while an MCP session owns an
|
|
unresolved unit-merge conflict. Does not auto-dismiss; stays until the merge ends. -->
|
|
<Border Grid.Row="0" Grid.RowSpan="3"
|
|
VerticalAlignment="Bottom" HorizontalAlignment="Left"
|
|
Margin="16,0,0,30"
|
|
ZIndex="100"
|
|
IsVisible="{Binding IsExternalMergeBannerVisible}"
|
|
Background="{DynamicResource DeepBrush}"
|
|
BorderBrush="{DynamicResource PeatBrush}"
|
|
BorderThickness="1"
|
|
CornerRadius="6"
|
|
Padding="14,10">
|
|
<StackPanel Spacing="8" Width="300">
|
|
<TextBlock Classes="body" Text="{loc:Tr shell.externalMerge.banner}" TextWrapping="Wrap"/>
|
|
<StackPanel Orientation="Horizontal" Spacing="8" HorizontalAlignment="Right">
|
|
<Button Classes="btn"
|
|
Content="{loc:Tr shell.externalMerge.open}"
|
|
Command="{Binding OpenExternalMergeConflictCommand}"/>
|
|
</StackPanel>
|
|
</StackPanel>
|
|
</Border>
|
|
|
|
</Grid>
|
|
</Window>
|