Nine interfaces had exactly one implementation and no test double — they existed only to be named twice in a DI registration: IFindingsStore, IFindingsStoreLocator, IPrimeScheduleSignal, IRefineRunner, IWeekReportService, IMergeCoordinator, IMissionControlPane, IOnlineLoginService, ITaskListFilter. Consumers now depend on the concrete type; the DTO records that shared those files moved next to their implementation. IInteractiveLaunchSpecService stays — it carries 54 lines of contract documentation, which is not ceremony. IMergeCoordinator in particular had a redundant null object: MergeCoordinator with a null Handler already no-ops, and every test used the real class with Handler set. Filtering/ collapses from 8 files to 1. ITaskListFilter and TaskListFilterBase were a double abstraction over four predicates, with MatchesAsContext => false declared in both. SmartFlagFilter also compiled its expression twice (its own _flag plus the inherited Matches cache) — it now uses the cache. StaticTokenAuthProvider was in src but production uses ZitadelAuthProvider; it is a test double, so it moves to the test project. Its own test goes away with it.
152 lines
7.5 KiB
XML
152 lines
7.5 KiB
XML
<UserControl xmlns="https://github.com/avaloniaui"
|
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
xmlns:vm="using:ClaudeDo.Ui.ViewModels"
|
|
xmlns:vmm="using:ClaudeDo.Ui.ViewModels.MissionControl"
|
|
xmlns:mc="using:ClaudeDo.Ui.Views.MissionControl"
|
|
xmlns:controls="using:ClaudeDo.Ui.Views.Controls"
|
|
xmlns:loc="using:ClaudeDo.Ui.Localization"
|
|
x:DataType="vm:MissionControlViewModel"
|
|
x:Class="ClaudeDo.Ui.Views.MissionControl.MissionControlView">
|
|
<UserControl.DataTemplates>
|
|
<!-- ConPtyPaneHost reuses the pane's one long-lived view across layout rebuilds and
|
|
tab switches — instantiating ConPtyPaneView here directly would detach (and, without
|
|
reparent suppression, kill) the running session every time a pane is added. -->
|
|
<DataTemplate DataType="vmm:ConPtyPaneViewModel">
|
|
<mc:ConPtyPaneHost Margin="6" />
|
|
</DataTemplate>
|
|
</UserControl.DataTemplates>
|
|
<DockPanel LastChildFill="True" Background="{DynamicResource VoidBrush}">
|
|
|
|
<!-- Header -->
|
|
<Border DockPanel.Dock="Top"
|
|
Background="{DynamicResource DeepBrush}"
|
|
BorderBrush="{DynamicResource LineBrush}"
|
|
BorderThickness="0,0,0,1" Padding="14,8">
|
|
<Grid ColumnDefinitions="*,Auto">
|
|
<TextBlock Grid.Column="0" Classes="eyebrow"
|
|
Text="{loc:Tr missionControl.windowTitle}"
|
|
Foreground="{DynamicResource TextBrush}"
|
|
LetterSpacing="1.4" VerticalAlignment="Center" />
|
|
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="8"
|
|
VerticalAlignment="Center">
|
|
<controls:UsagePill DataContext="{Binding UsagePill}"
|
|
Margin="0,0,4,0"
|
|
VerticalAlignment="Center"/>
|
|
<Button Classes="icon-btn"
|
|
Click="OnNewSessionClicked"
|
|
ToolTip.Tip="{loc:Tr missionControl.newSession}">
|
|
<PathIcon Data="{StaticResource Icon.Plus}" Width="15" Height="15"
|
|
Foreground="{DynamicResource TextMuteBrush}"/>
|
|
</Button>
|
|
<Button Classes="icon-btn"
|
|
Command="{Binding ToggleLayoutCommand}"
|
|
ToolTip.Tip="{Binding LayoutToggleLabel}">
|
|
<PathIcon Data="{StaticResource Icon.Grid}" Width="15" Height="15"
|
|
Foreground="{DynamicResource TextMuteBrush}"/>
|
|
</Button>
|
|
<Button Classes="icon-btn"
|
|
Command="{Binding OpenSettingsCommand}"
|
|
ToolTip.Tip="{loc:Tr missionControl.settings}">
|
|
<PathIcon Data="{StaticResource Icon.Settings}" Width="15" Height="15"
|
|
Foreground="{DynamicResource TextMuteBrush}"/>
|
|
</Button>
|
|
</StackPanel>
|
|
</Grid>
|
|
</Border>
|
|
|
|
<!-- Body: main content | queue splitter | queue strip (widths managed in code-behind
|
|
so the queue column collapses to zero when nothing is queued). -->
|
|
<Grid x:Name="BodyGrid">
|
|
<Grid.ColumnDefinitions>
|
|
<ColumnDefinition Width="*"/>
|
|
<ColumnDefinition Width="Auto"/>
|
|
<ColumnDefinition Width="0"/>
|
|
</Grid.ColumnDefinitions>
|
|
|
|
<!-- Grid / tabs / empty state -->
|
|
<Panel Grid.Column="0" Margin="6">
|
|
<!-- Overview mode: a real Grid built in code-behind so each pane is individually
|
|
resizable via GridSplitters, instead of a UniformGrid where every cell is forced
|
|
to the same size. -->
|
|
<Grid x:Name="OverviewGrid" IsVisible="{Binding !IsFocusMode}" />
|
|
|
|
<!-- Focus mode: one pane large, switched via tabs -->
|
|
<TabControl ItemsSource="{Binding Panes}"
|
|
SelectedItem="{Binding FocusedPane}"
|
|
IsVisible="{Binding IsFocusMode}">
|
|
<TabControl.ItemTemplate>
|
|
<DataTemplate x:DataType="vmm:ConPtyPaneViewModel">
|
|
<TextBlock Text="{Binding DisplayTitle}" TextTrimming="CharacterEllipsis" MaxWidth="160" />
|
|
</DataTemplate>
|
|
</TabControl.ItemTemplate>
|
|
</TabControl>
|
|
|
|
<TextBlock IsVisible="{Binding !HasPanes}"
|
|
Text="{loc:Tr missionControl.empty}"
|
|
Foreground="{DynamicResource TextMuteBrush}"
|
|
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
|
</Panel>
|
|
|
|
<GridSplitter x:Name="QueueSplitter" Grid.Column="1"
|
|
IsVisible="{Binding HasQueued}"
|
|
Width="6"
|
|
Background="Transparent"
|
|
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
|
ResizeDirection="Columns"
|
|
ResizeBehavior="PreviousAndNext"/>
|
|
|
|
<!-- Read-only queue strip — collapses when nothing is queued -->
|
|
<Border Grid.Column="2"
|
|
IsVisible="{Binding HasQueued}"
|
|
Background="{DynamicResource DeepBrush}"
|
|
BorderBrush="{DynamicResource LineBrush}"
|
|
BorderThickness="1,0,0,0">
|
|
<DockPanel LastChildFill="True" Margin="10,10">
|
|
<TextBlock DockPanel.Dock="Top" Classes="eyebrow"
|
|
Text="{loc:Tr missionControl.queue}"
|
|
Foreground="{DynamicResource TextMuteBrush}"
|
|
LetterSpacing="1.4" Margin="0,0,0,8" />
|
|
<ScrollViewer>
|
|
<ItemsControl ItemsSource="{Binding Queued}">
|
|
<ItemsControl.ItemTemplate>
|
|
<DataTemplate x:DataType="vm:QueuedTaskViewModel">
|
|
<Button Command="{Binding OpenInAppCommand}"
|
|
Padding="0" Margin="0,0,0,4"
|
|
Background="Transparent" BorderThickness="0"
|
|
HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
|
|
Cursor="Hand">
|
|
<Panel>
|
|
<Border Background="{DynamicResource SurfaceBrush}"
|
|
BorderBrush="{DynamicResource LineBrush}"
|
|
BorderThickness="1" CornerRadius="6" />
|
|
<Border Background="{DynamicResource RunningTintBrush}"
|
|
BorderBrush="{DynamicResource RunningTintBorderBrush}"
|
|
BorderThickness="1" CornerRadius="6"
|
|
IsVisible="{Binding IsRunning}" />
|
|
<StackPanel Margin="8,6" Spacing="2">
|
|
<TextBlock Text="{Binding Title}"
|
|
TextTrimming="CharacterEllipsis"
|
|
ToolTip.Tip="{Binding Title}"
|
|
Foreground="{DynamicResource TextDimBrush}" />
|
|
<TextBlock Classes="meta"
|
|
Text="{loc:Tr missionControl.blocked}"
|
|
IsVisible="{Binding IsBlocked}"
|
|
Foreground="{DynamicResource AmberBrush}" />
|
|
<TextBlock Classes="meta"
|
|
Text="{loc:Tr missionControl.running}"
|
|
IsVisible="{Binding IsRunning}"
|
|
Foreground="{DynamicResource StatusRunningBrush}" />
|
|
</StackPanel>
|
|
</Panel>
|
|
</Button>
|
|
</DataTemplate>
|
|
</ItemsControl.ItemTemplate>
|
|
</ItemsControl>
|
|
</ScrollViewer>
|
|
</DockPanel>
|
|
</Border>
|
|
</Grid>
|
|
|
|
</DockPanel>
|
|
</UserControl>
|