diff --git a/src/ClaudeDo.Ui/Views/MissionControl/MissionControlView.axaml b/src/ClaudeDo.Ui/Views/MissionControl/MissionControlView.axaml
index 0fb052d2..02ae99f5 100644
--- a/src/ClaudeDo.Ui/Views/MissionControl/MissionControlView.axaml
+++ b/src/ClaudeDo.Ui/Views/MissionControl/MissionControlView.axaml
@@ -51,85 +51,98 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+ HorizontalAlignment="Center" VerticalAlignment="Center" />
+
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ClaudeDo.Ui/Views/MissionControl/MissionControlView.axaml.cs b/src/ClaudeDo.Ui/Views/MissionControl/MissionControlView.axaml.cs
index 34c1c6d4..2f544297 100644
--- a/src/ClaudeDo.Ui/Views/MissionControl/MissionControlView.axaml.cs
+++ b/src/ClaudeDo.Ui/Views/MissionControl/MissionControlView.axaml.cs
@@ -1,5 +1,10 @@
+using System;
+using System.Collections.Specialized;
+using System.ComponentModel;
using Avalonia.Controls;
using Avalonia.Interactivity;
+using Avalonia.Layout;
+using Avalonia.Media;
using Avalonia.Platform.Storage;
using ClaudeDo.Ui.ViewModels;
@@ -7,9 +12,20 @@ namespace ClaudeDo.Ui.Views.MissionControl;
public partial class MissionControlView : UserControl
{
+ private const double SplitterSize = 6;
+ private const double DefaultQueueWidth = 210;
+ private const double MinQueueWidth = 160;
+ private const double MinPaneWidth = 220;
+ private const double MinPaneHeight = 140;
+
+ private MissionControlViewModel? _vm;
+ private GridLength _queueWidth = new(DefaultQueueWidth, GridUnitType.Pixel);
+
public MissionControlView()
{
InitializeComponent();
+ DataContextChanged += OnDataContextChanged;
+ QueueSplitter.DragCompleted += (_, _) => _queueWidth = BodyGrid.ColumnDefinitions[2].Width;
}
// Ad-hoc ConPTY session: the view owns the folder picker, the VM only takes the chosen path.
@@ -28,4 +44,127 @@ public partial class MissionControlView : UserControl
await vm.OpenAdHocConPtySessionAsync(folders[0].Path.LocalPath);
}
+
+ private void OnDataContextChanged(object? sender, EventArgs e)
+ {
+ if (_vm is not null)
+ {
+ _vm.Panes.CollectionChanged -= OnPanesChanged;
+ _vm.PropertyChanged -= OnViewModelPropertyChanged;
+ }
+
+ _vm = DataContext as MissionControlViewModel;
+
+ if (_vm is not null)
+ {
+ _vm.Panes.CollectionChanged += OnPanesChanged;
+ _vm.PropertyChanged += OnViewModelPropertyChanged;
+ }
+
+ RebuildOverviewGrid();
+ UpdateQueueColumn();
+ }
+
+ private void OnPanesChanged(object? sender, NotifyCollectionChangedEventArgs e) => RebuildOverviewGrid();
+
+ private void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(MissionControlViewModel.ColumnCount))
+ RebuildOverviewGrid();
+ else if (e.PropertyName == nameof(MissionControlViewModel.HasQueued))
+ UpdateQueueColumn();
+ }
+
+ // The queue column is Pixel-widthed (so the splitter can drag it) — when nothing is
+ // queued it collapses to 0 instead of leaving a blank gap, and remembers the last
+ // dragged width for the rest of this window's lifetime (not persisted beyond it).
+ private void UpdateQueueColumn()
+ {
+ var queueColumn = BodyGrid.ColumnDefinitions[2];
+ if (_vm?.HasQueued == true)
+ {
+ queueColumn.MinWidth = MinQueueWidth;
+ queueColumn.Width = _queueWidth;
+ }
+ else
+ {
+ queueColumn.MinWidth = 0;
+ queueColumn.Width = new GridLength(0, GridUnitType.Pixel);
+ }
+ }
+
+ // Replaces the old UniformGrid with a real Grid + GridSplitters so panes resize
+ // individually. Column splitters span every row (resize that whole column vs. its
+ // neighbor); row splitters span every column. Rebuilt from scratch whenever the pane
+ // count or column count changes — per-pane sizes are intentionally not preserved across
+ // a rebuild (session-only resizing, reset to the default split when panes come/go).
+ private void RebuildOverviewGrid()
+ {
+ OverviewGrid.Children.Clear();
+ OverviewGrid.ColumnDefinitions.Clear();
+ OverviewGrid.RowDefinitions.Clear();
+
+ var panes = _vm?.Panes;
+ if (panes is not { Count: > 0 }) return;
+
+ var columns = Math.Max(1, _vm!.ColumnCount);
+ var rows = (int)Math.Ceiling(panes.Count / (double)columns);
+
+ for (var c = 0; c < columns; c++)
+ {
+ if (c > 0) OverviewGrid.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Auto));
+ OverviewGrid.ColumnDefinitions.Add(new ColumnDefinition(new GridLength(1, GridUnitType.Star)) { MinWidth = MinPaneWidth });
+ }
+
+ for (var r = 0; r < rows; r++)
+ {
+ if (r > 0) OverviewGrid.RowDefinitions.Add(new RowDefinition(GridLength.Auto));
+ OverviewGrid.RowDefinitions.Add(new RowDefinition(new GridLength(1, GridUnitType.Star)) { MinHeight = MinPaneHeight });
+ }
+
+ var totalGridRows = rows * 2 - 1;
+ var totalGridCols = columns * 2 - 1;
+
+ for (var c = 1; c < columns; c++)
+ {
+ var splitter = new GridSplitter
+ {
+ Width = SplitterSize,
+ Background = Brushes.Transparent,
+ HorizontalAlignment = HorizontalAlignment.Stretch,
+ VerticalAlignment = VerticalAlignment.Stretch,
+ ResizeDirection = GridResizeDirection.Columns,
+ ResizeBehavior = GridResizeBehavior.PreviousAndNext,
+ };
+ Grid.SetColumn(splitter, c * 2 - 1);
+ Grid.SetRow(splitter, 0);
+ Grid.SetRowSpan(splitter, totalGridRows);
+ OverviewGrid.Children.Add(splitter);
+ }
+
+ for (var r = 1; r < rows; r++)
+ {
+ var splitter = new GridSplitter
+ {
+ Height = SplitterSize,
+ Background = Brushes.Transparent,
+ HorizontalAlignment = HorizontalAlignment.Stretch,
+ VerticalAlignment = VerticalAlignment.Stretch,
+ ResizeDirection = GridResizeDirection.Rows,
+ ResizeBehavior = GridResizeBehavior.PreviousAndNext,
+ };
+ Grid.SetColumn(splitter, 0);
+ Grid.SetColumnSpan(splitter, totalGridCols);
+ Grid.SetRow(splitter, r * 2 - 1);
+ OverviewGrid.Children.Add(splitter);
+ }
+
+ for (var i = 0; i < panes.Count; i++)
+ {
+ var presenter = new ContentControl { Content = panes[i] };
+ Grid.SetColumn(presenter, (i % columns) * 2);
+ Grid.SetRow(presenter, (i / columns) * 2);
+ OverviewGrid.Children.Add(presenter);
+ }
+ }
}