chore(ui): remove dead Mission Control monitor-pane stack
Auto-seeding of monitor tiles was disabled in 724814f; Mission Control now only shows ConPTY panes. Removes Monitors/EnsureMonitor/SeedActive, the detach/re-dock machinery, MonitorPaneView and the detached monitor window, plus their tests and orphaned localization keys. TaskMonitorViewModel itself stays: DetailsIslandViewModel still uses it as the backing state for the task Log/AgentState/AskUser-question UI, so only its Mission-Control-only members (Title/DisplayTitle, detach, cancel command, IMissionControlPane) were stripped. IMissionControlPane/Panes were kept (now a 1:1 mirror of ConPtySessions) rather than dissolved, to avoid churning the still-live ConPTY pane tests and AXAML for a single-implementer interface. Visual verification still open: Mission Control with several open ConPTY tiles, and the Focus/Overview toggle.
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using ClaudeDo.Ui.ViewModels;
|
||||
using ClaudeDo.Ui.ViewModels.Conflicts;
|
||||
using ClaudeDo.Ui.ViewModels.Islands;
|
||||
using ClaudeDo.Ui.ViewModels.Modals;
|
||||
|
||||
namespace ClaudeDo.Ui.Services;
|
||||
@@ -37,7 +35,4 @@ public interface IDialogService
|
||||
|
||||
/// <summary>Show (or re-show + focus) the modeless Mission Control window. Lazily created; hides on close.</summary>
|
||||
void ShowMissionControl(MissionControlViewModel vm);
|
||||
|
||||
/// <summary>Show a detached monitor in its own window; <paramref name="onClosed"/> re-docks it when that window closes.</summary>
|
||||
void ShowDetachedMonitor(TaskMonitorViewModel monitor, Action onClosed);
|
||||
}
|
||||
|
||||
@@ -8,12 +8,11 @@ using ClaudeDo.Data.Repositories;
|
||||
using ClaudeDo.Ui.Helpers;
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using ClaudeDo.Ui.Services;
|
||||
using ClaudeDo.Ui.ViewModels.MissionControl;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace ClaudeDo.Ui.ViewModels.Islands;
|
||||
|
||||
public sealed partial class TaskMonitorViewModel : ViewModelBase, IMissionControlPane, IDisposable
|
||||
public sealed partial class TaskMonitorViewModel : ViewModelBase, IDisposable
|
||||
{
|
||||
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
|
||||
private readonly IWorkerClient _worker;
|
||||
@@ -28,13 +27,6 @@ public sealed partial class TaskMonitorViewModel : ViewModelBase, IMissionContro
|
||||
|
||||
[ObservableProperty] private string _agentState = "idle";
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(DisplayTitle))]
|
||||
private string? _title;
|
||||
|
||||
public string DisplayTitle =>
|
||||
string.IsNullOrWhiteSpace(Title) ? (SubscribedTaskId ?? "task") : Title!;
|
||||
|
||||
public string AgentStatusLabel => Loc.T($"vm.agentStatus.{AgentState}");
|
||||
public bool IsIdle => AgentState == "idle";
|
||||
public bool IsQueued => AgentState == "queued";
|
||||
@@ -205,43 +197,6 @@ public sealed partial class TaskMonitorViewModel : ViewModelBase, IMissionContro
|
||||
ClearPendingQuestion();
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(DetachTooltip))]
|
||||
private bool _isDetached;
|
||||
|
||||
// Localized tooltip for the detach/re-dock toggle button.
|
||||
public string DetachTooltip => Loc.T(IsDetached ? "missionControl.redock" : "missionControl.detach");
|
||||
|
||||
// Set by the detached window so the re-dock action can close it.
|
||||
public Action? CloseWindowRequested { get; set; }
|
||||
|
||||
// Set by the host (e.g. Mission Control) to navigate the main app to this task.
|
||||
public Action<string>? OpenInAppRequested { get; set; }
|
||||
|
||||
// Set by the host (Mission Control) to pop this monitor out into its own window.
|
||||
public Action<TaskMonitorViewModel>? DetachRequested { get; set; }
|
||||
|
||||
[RelayCommand]
|
||||
private void Detach()
|
||||
{
|
||||
if (IsDetached) CloseWindowRequested?.Invoke(); // re-dock: close the detached window
|
||||
else DetachRequested?.Invoke(this); // detach: pop out to its own window
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenInApp()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_subscribedTaskId))
|
||||
OpenInAppRequested?.Invoke(_subscribedTaskId);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async System.Threading.Tasks.Task CancelTask()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_subscribedTaskId) && (IsRunning || IsQueued))
|
||||
await _worker.CancelTaskAsync(_subscribedTaskId);
|
||||
}
|
||||
|
||||
public void SetTaskId(string id) => _subscribedTaskId = id;
|
||||
|
||||
public void ApplyState(ClaudeDo.Data.Models.TaskStatus status) =>
|
||||
|
||||
@@ -213,7 +213,6 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable
|
||||
Lists = lists; Tasks = tasks; Details = details; Worker = worker;
|
||||
MissionControl = missionControl;
|
||||
MissionControl.OpenInApp = id => _ = RevealTaskAsync(id);
|
||||
MissionControl.ShowDetached = (monitor, reDock) => Dialogs?.ShowDetachedMonitor(monitor, reDock);
|
||||
MissionControl.OpenSettingsRequested = () => Lists.OpenSettingsCommand.Execute(null);
|
||||
MissionControl.ErrorReported += FlashFooterError;
|
||||
// Keep the task rows' "Interactive" chip in step with Mission Control's open ConPTY panes.
|
||||
|
||||
@@ -9,9 +9,7 @@ namespace ClaudeDo.Ui.ViewModels.MissionControl;
|
||||
/// <summary>
|
||||
/// Command Center pane hosting an embedded ConPTY terminal for either one task's interactive
|
||||
/// Claude session, or an ad-hoc/free session in a user-chosen directory (no task, <see cref="TaskId"/>
|
||||
/// is null — ad-hoc panes are never deduped, unlike task-based ones). Distinct from the streamed-log
|
||||
/// <see cref="ClaudeDo.Ui.ViewModels.Islands.TaskMonitorViewModel"/> pane; the two coexist until
|
||||
/// the streaming interactive stack is removed.
|
||||
/// is null — ad-hoc panes are never deduped, unlike task-based ones).
|
||||
/// </summary>
|
||||
public sealed partial class ConPtyPaneViewModel : ViewModelBase, IMissionControlPane, IDisposable
|
||||
{
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
namespace ClaudeDo.Ui.ViewModels.MissionControl;
|
||||
|
||||
/// <summary>
|
||||
/// Common contract for anything hosted as a pane in the Command Center — implemented by both
|
||||
/// the streamed-log <see cref="ClaudeDo.Ui.ViewModels.Islands.TaskMonitorViewModel"/> and the
|
||||
/// embedded ConPTY <see cref="ConPtyPaneViewModel"/> — so the layout toggle (grid/tabs) can
|
||||
/// bind one heterogeneous pane collection.
|
||||
/// Common contract for anything hosted as a pane in the Command Center — currently only
|
||||
/// <see cref="ConPtyPaneViewModel"/>, kept as its own abstraction so the grid/tabs layout
|
||||
/// toggle binds a pane collection rather than a concrete ConPTY-specific type.
|
||||
/// </summary>
|
||||
public interface IMissionControlPane
|
||||
{
|
||||
|
||||
@@ -5,10 +5,8 @@ using System.Linq;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using ClaudeDo.Data;
|
||||
using ClaudeDo.Data.Repositories;
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using ClaudeDo.Ui.Services;
|
||||
using ClaudeDo.Ui.ViewModels.Islands;
|
||||
using ClaudeDo.Ui.ViewModels.MissionControl;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@@ -23,14 +21,12 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
|
||||
private readonly Action<string> _onTaskUpdated;
|
||||
private readonly Action _onConnectionRestored;
|
||||
|
||||
public ObservableCollection<TaskMonitorViewModel> Monitors { get; } = new();
|
||||
|
||||
// Embedded ConPTY sessions (task-based only) — a manual cockpit detached from the
|
||||
// review/merge/status machinery. Mirrored into Panes alongside the streamed-log Monitors.
|
||||
// review/merge/status machinery.
|
||||
public ObservableCollection<ConPtyPaneViewModel> ConPtySessions { get; } = new();
|
||||
|
||||
// Unified view of Monitors ++ ConPtySessions (in that order) so the layout toggle can
|
||||
// present one heterogeneous collection as either a grid or tabs.
|
||||
// Mirror of ConPtySessions typed as the pane abstraction so the layout toggle (grid/tabs)
|
||||
// binds one contract rather than a ConPTY-specific type.
|
||||
public ObservableCollection<IMissionControlPane> Panes { get; } = new();
|
||||
|
||||
[ObservableProperty] private int _columnCount = 1;
|
||||
@@ -47,25 +43,11 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
|
||||
/// wires this into the footer error strip, same as the island view models' ErrorReported.</summary>
|
||||
public event Action<string>? ErrorReported;
|
||||
|
||||
private Action<string>? _openInApp;
|
||||
public Action<string>? OpenInApp
|
||||
{
|
||||
get => _openInApp;
|
||||
set
|
||||
{
|
||||
_openInApp = value;
|
||||
foreach (var m in Monitors) m.OpenInAppRequested = value;
|
||||
}
|
||||
}
|
||||
|
||||
// View-layer seam: show a detached monitor in its own window. Second arg is the re-dock callback
|
||||
// invoked when that window closes.
|
||||
public Action<TaskMonitorViewModel, Action>? ShowDetached { get; set; }
|
||||
public Action<string>? OpenInApp { get; set; }
|
||||
|
||||
// View-layer seam: open the app Settings modal from the Mission Control window.
|
||||
public Action? OpenSettingsRequested { get; set; }
|
||||
|
||||
public bool HasMonitors => Monitors.Count > 0;
|
||||
public bool HasPanes => Panes.Count > 0;
|
||||
|
||||
// Read-only view of the worker queue (tasks waiting to run), shown as a side strip.
|
||||
@@ -77,7 +59,6 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
|
||||
_dbFactory = dbFactory;
|
||||
_worker = worker;
|
||||
|
||||
Monitors.CollectionChanged += OnMonitorsChanged;
|
||||
ConPtySessions.CollectionChanged += OnConPtySessionsChanged;
|
||||
Panes.CollectionChanged += OnPanesChanged;
|
||||
|
||||
@@ -119,7 +100,7 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
|
||||
Title = r.Title ?? string.Empty,
|
||||
IsBlocked = r.BlockedByTaskId != null,
|
||||
IsRunning = r.Status == ClaudeDo.Data.Models.TaskStatus.Running,
|
||||
OpenInAppCommand = new RelayCommand(() => _openInApp?.Invoke(id)),
|
||||
OpenInAppCommand = new RelayCommand(() => OpenInApp?.Invoke(id)),
|
||||
});
|
||||
}
|
||||
OnPropertyChanged(nameof(HasQueued));
|
||||
@@ -147,88 +128,12 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
|
||||
await RefreshQueueAsync();
|
||||
}
|
||||
|
||||
// Kept intentionally — unused since auto-seeding was disabled (2026-07-29).
|
||||
internal void SeedActive()
|
||||
{
|
||||
foreach (var a in _worker.GetActiveTasks())
|
||||
EnsureMonitor(a.TaskId);
|
||||
}
|
||||
|
||||
// Kept intentionally — unused since auto-seeding was disabled (2026-07-29).
|
||||
internal void EnsureMonitor(string taskId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(taskId)) return;
|
||||
if (Monitors.Any(m => m.SubscribedTaskId == taskId)) return;
|
||||
|
||||
var monitor = new TaskMonitorViewModel(_dbFactory, _worker);
|
||||
monitor.SetTaskId(taskId);
|
||||
monitor.OpenInAppRequested = _openInApp;
|
||||
monitor.DetachRequested = Detach;
|
||||
Monitors.Add(monitor);
|
||||
_ = HydrateAsync(monitor, taskId);
|
||||
}
|
||||
|
||||
private void Detach(TaskMonitorViewModel monitor)
|
||||
{
|
||||
if (!Monitors.Contains(monitor)) return;
|
||||
monitor.IsDetached = true;
|
||||
Monitors.Remove(monitor); // drop from grid — do NOT dispose; it keeps streaming
|
||||
ShowDetached?.Invoke(monitor, () => ReDock(monitor));
|
||||
}
|
||||
|
||||
private void ReDock(TaskMonitorViewModel monitor)
|
||||
{
|
||||
monitor.IsDetached = false;
|
||||
if (!Monitors.Contains(monitor) && monitor.SubscribedTaskId is not null)
|
||||
Monitors.Add(monitor); // back into the grid
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task HydrateAsync(TaskMonitorViewModel monitor, string taskId)
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var ctx = await _dbFactory.CreateDbContextAsync();
|
||||
var entity = await ctx.Tasks.AsNoTracking().FirstOrDefaultAsync(t => t.Id == taskId);
|
||||
if (entity is null || monitor.SubscribedTaskId != taskId) return;
|
||||
monitor.ApplyState(entity.Status);
|
||||
monitor.Title = entity.Title;
|
||||
var latestRun = await new TaskRunRepository(ctx).GetLatestByTaskIdAsync(taskId);
|
||||
monitor.ApplyOutcome(entity.Result, latestRun?.ErrorMarkdown);
|
||||
await monitor.ReplayLogFileAsync(entity.LogPath, CancellationToken.None);
|
||||
|
||||
// Re-attach: if the task is blocked on an AskUser question right now, surface it.
|
||||
var pending = await _worker.GetPendingQuestionAsync(taskId);
|
||||
if (pending is not null && monitor.SubscribedTaskId == taskId)
|
||||
monitor.SetPendingQuestion(pending.QuestionId, pending.Question);
|
||||
}
|
||||
catch { /* best-effort hydrate */ }
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ClearFinished()
|
||||
{
|
||||
foreach (var m in Monitors.Where(m => m.IsDone || m.IsFailed || m.IsCancelled || m.IsWaitingForReview).ToList())
|
||||
{
|
||||
Monitors.Remove(m);
|
||||
m.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenSettings() => OpenSettingsRequested?.Invoke();
|
||||
|
||||
[RelayCommand]
|
||||
private void ToggleLayout() => IsFocusMode = !IsFocusMode;
|
||||
|
||||
public void MoveMonitor(TaskMonitorViewModel dragged, TaskMonitorViewModel target)
|
||||
{
|
||||
if (ReferenceEquals(dragged, target)) return;
|
||||
var from = Monitors.IndexOf(dragged);
|
||||
var to = Monitors.IndexOf(target);
|
||||
if (from < 0 || to < 0) return;
|
||||
Monitors.Move(from, to);
|
||||
}
|
||||
|
||||
// Fetches the launch spec for a task's worktree and hosts an embedded ConPTY session as a
|
||||
// Command Center pane (task-based only).
|
||||
public async System.Threading.Tasks.Task OpenConPtySessionAsync(string taskId)
|
||||
@@ -359,42 +264,19 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
|
||||
pane.Dispose();
|
||||
}
|
||||
|
||||
// Mirrors Monitors' add/remove/move into the front (Monitors-prefix) section of Panes.
|
||||
private void OnMonitorsChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
Panes.Insert(e.NewStartingIndex, (TaskMonitorViewModel)e.NewItems![0]!);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
Panes.RemoveAt(e.OldStartingIndex);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
Panes.Move(e.OldStartingIndex, e.NewStartingIndex);
|
||||
break;
|
||||
default: // Reset (Dispose's Monitors.Clear())
|
||||
foreach (var p in Panes.OfType<TaskMonitorViewModel>().ToList())
|
||||
Panes.Remove(p);
|
||||
break;
|
||||
}
|
||||
OnPropertyChanged(nameof(HasMonitors));
|
||||
}
|
||||
|
||||
// Mirrors ConPtySessions' add/remove into the tail (ConPtySessions-suffix) section of Panes.
|
||||
// Mirrors ConPtySessions' add/remove 1:1 into Panes (typed as the pane abstraction).
|
||||
private void OnConPtySessionsChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
Panes.Insert(Monitors.Count + e.NewStartingIndex, (ConPtyPaneViewModel)e.NewItems![0]!);
|
||||
Panes.Insert(e.NewStartingIndex, (ConPtyPaneViewModel)e.NewItems![0]!);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
Panes.RemoveAt(Monitors.Count + e.OldStartingIndex);
|
||||
Panes.RemoveAt(e.OldStartingIndex);
|
||||
break;
|
||||
default: // Reset
|
||||
foreach (var p in Panes.OfType<ConPtyPaneViewModel>().ToList())
|
||||
Panes.Remove(p);
|
||||
Panes.Clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -420,11 +302,8 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
|
||||
_worker.TaskFinishedEvent -= _onTaskFinished;
|
||||
_worker.TaskUpdatedEvent -= _onTaskUpdated;
|
||||
_worker.ConnectionRestoredEvent -= _onConnectionRestored;
|
||||
Monitors.CollectionChanged -= OnMonitorsChanged;
|
||||
ConPtySessions.CollectionChanged -= OnConPtySessionsChanged;
|
||||
Panes.CollectionChanged -= OnPanesChanged;
|
||||
foreach (var m in Monitors) m.Dispose();
|
||||
Monitors.Clear();
|
||||
foreach (var c in ConPtySessions.ToList())
|
||||
{
|
||||
c.ErrorReported -= OnConPtyPaneError;
|
||||
|
||||
@@ -81,8 +81,7 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Question prompt (AskUser): answer the running task's question inline, mirroring
|
||||
the Mission Control banner. State is shared via the same TaskMonitorViewModel. -->
|
||||
<!-- 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"
|
||||
|
||||
@@ -1,24 +1,17 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:ClaudeDo.Ui.ViewModels"
|
||||
xmlns:vmi="using:ClaudeDo.Ui.ViewModels.Islands"
|
||||
xmlns:vmm="using:ClaudeDo.Ui.ViewModels.MissionControl"
|
||||
xmlns:mc="using:ClaudeDo.Ui.Views.MissionControl"
|
||||
xmlns:loc="using:ClaudeDo.Ui.Localization"
|
||||
x:DataType="vm:MissionControlViewModel"
|
||||
x:Class="ClaudeDo.Ui.Views.MissionControl.MissionControlView">
|
||||
<UserControl.DataTemplates>
|
||||
<!-- Polymorphic pane templates: both the grid ItemsControl and the focus-mode TabControl
|
||||
resolve per-item content through these (no explicit ItemTemplate on either). -->
|
||||
<DataTemplate DataType="vmi:TaskMonitorViewModel">
|
||||
<mc:MonitorPaneView Margin="6" />
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="vmm:ConPtyPaneViewModel">
|
||||
<mc:ConPtyPaneView Margin="6" />
|
||||
</DataTemplate>
|
||||
</UserControl.DataTemplates>
|
||||
<DockPanel LastChildFill="True" Background="{DynamicResource VoidBrush}"
|
||||
DragDrop.AllowDrop="True">
|
||||
<DockPanel LastChildFill="True" Background="{DynamicResource VoidBrush}">
|
||||
|
||||
<!-- Header -->
|
||||
<Border DockPanel.Dock="Top"
|
||||
@@ -50,9 +43,6 @@
|
||||
<PathIcon Data="{StaticResource Icon.Settings}" Width="15" Height="15"
|
||||
Foreground="{DynamicResource TextMuteBrush}"/>
|
||||
</Button>
|
||||
<Button Classes="btn"
|
||||
Content="{loc:Tr missionControl.clearFinished}"
|
||||
Command="{Binding ClearFinishedCommand}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
@@ -1,25 +1,15 @@
|
||||
using System.Linq;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.VisualTree;
|
||||
using ClaudeDo.Ui.ViewModels;
|
||||
using ClaudeDo.Ui.ViewModels.Islands;
|
||||
|
||||
namespace ClaudeDo.Ui.Views.MissionControl;
|
||||
|
||||
public partial class MissionControlView : UserControl
|
||||
{
|
||||
// Shared with MonitorPaneView (the drag source).
|
||||
public static readonly DataFormat<string> PaneFormat =
|
||||
DataFormat.CreateStringApplicationFormat("claudedo-monitor-pane");
|
||||
|
||||
public MissionControlView()
|
||||
{
|
||||
InitializeComponent();
|
||||
AddHandler(DragDrop.DragOverEvent, OnPaneDragOver);
|
||||
AddHandler(DragDrop.DropEvent, OnPaneDrop);
|
||||
}
|
||||
|
||||
// Ad-hoc ConPTY session: the view owns the folder picker, the VM only takes the chosen path.
|
||||
@@ -38,31 +28,4 @@ public partial class MissionControlView : UserControl
|
||||
|
||||
await vm.OpenAdHocConPtySessionAsync(folders[0].Path.LocalPath);
|
||||
}
|
||||
|
||||
private void OnPaneDragOver(object? sender, DragEventArgs e)
|
||||
{
|
||||
var dt = e.DataTransfer;
|
||||
e.DragEffects = (dt?.Contains(PaneFormat) ?? false) ? DragDropEffects.Move : DragDropEffects.None;
|
||||
}
|
||||
|
||||
private void OnPaneDrop(object? sender, DragEventArgs e)
|
||||
{
|
||||
if (DataContext is not MissionControlViewModel vm) return;
|
||||
var dt = e.DataTransfer;
|
||||
if (dt is null) return;
|
||||
|
||||
// A pane dragged within the grid → reorder. (Drag-to-queue from the main app now arrives
|
||||
// via the custom ghost drag's screen hit-test, not an OLE drop.)
|
||||
var draggedId = dt.TryGetValue(PaneFormat);
|
||||
if (string.IsNullOrEmpty(draggedId)) return;
|
||||
if (e.Source is not Avalonia.Visual src) return;
|
||||
|
||||
var targetPane = src.FindAncestorOfType<MonitorPaneView>();
|
||||
if (targetPane?.DataContext is not TaskMonitorViewModel target) return;
|
||||
|
||||
var dragged = vm.Monitors.FirstOrDefault(m => m.SubscribedTaskId == draggedId);
|
||||
if (dragged is null) return;
|
||||
|
||||
vm.MoveMonitor(dragged, target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
<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:loc="using:ClaudeDo.Ui.Localization"
|
||||
x:DataType="vm:TaskMonitorViewModel"
|
||||
x:Class="ClaudeDo.Ui.Views.MissionControl.MonitorPaneView">
|
||||
<Border Classes="monitor-pane"
|
||||
DragDrop.AllowDrop="True"
|
||||
Classes.mon-review="{Binding IsWaitingForReview}"
|
||||
Classes.mon-done="{Binding IsDone}"
|
||||
Classes.mon-roadblock="{Binding HasRoadblock}"
|
||||
Classes.mon-failed="{Binding IsFailed}"
|
||||
BorderThickness="1" CornerRadius="10" ClipToBounds="True">
|
||||
<DockPanel LastChildFill="True">
|
||||
|
||||
<!-- Header: per-task action icons -->
|
||||
<Border DockPanel.Dock="Top"
|
||||
Background="{DynamicResource Surface2Brush}"
|
||||
BorderBrush="{DynamicResource LineBrush}"
|
||||
BorderThickness="0,0,0,1" Padding="6,3"
|
||||
PointerPressed="OnHeaderPressed">
|
||||
<StackPanel Orientation="Horizontal" Spacing="2"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||
<Button Classes="title-ctrl"
|
||||
Command="{Binding OpenInAppCommand}"
|
||||
ToolTip.Tip="{loc:Tr missionControl.openInApp}">
|
||||
<PathIcon Data="{StaticResource Icon.ArrowOut}" Width="12" Height="12"/>
|
||||
</Button>
|
||||
<Button Classes="title-ctrl"
|
||||
Command="{Binding DetachCommand}"
|
||||
ToolTip.Tip="{Binding DetachTooltip}">
|
||||
<PathIcon Data="{StaticResource Icon.WinMax}" Width="12" Height="12"/>
|
||||
</Button>
|
||||
<Button Classes="title-ctrl"
|
||||
Command="{Binding CancelTaskCommand}"
|
||||
IsVisible="{Binding IsRunning}"
|
||||
ToolTip.Tip="{loc:Tr missionControl.cancel}">
|
||||
<PathIcon Data="{StaticResource Icon.WinClose}" Width="12" Height="12"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Roadblock banner (amber) -->
|
||||
<Border DockPanel.Dock="Top"
|
||||
IsVisible="{Binding HasRoadblock}"
|
||||
Background="{DynamicResource RoadblockTintBrush}"
|
||||
BorderBrush="{DynamicResource AmberBrush}"
|
||||
BorderThickness="0,0,0,1" Padding="12,6">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<PathIcon Data="{StaticResource Icon.Warning}"
|
||||
Foreground="{DynamicResource AmberBrush}"
|
||||
Width="13" Height="13" VerticalAlignment="Center" />
|
||||
<TextBlock Classes="meta" Text="{Binding Roadblocks}"
|
||||
Foreground="{DynamicResource AmberBrush}"
|
||||
TextWrapping="Wrap" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Failure banner (red) -->
|
||||
<Border DockPanel.Dock="Top"
|
||||
IsVisible="{Binding ShowRoadblock}"
|
||||
Background="{DynamicResource ErrorTintBrush}"
|
||||
BorderBrush="{DynamicResource BloodBrush}"
|
||||
BorderThickness="0,0,0,1" Padding="12,6">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<PathIcon Data="{StaticResource Icon.Warning}"
|
||||
Foreground="{DynamicResource BloodBrush}"
|
||||
Width="13" Height="13" VerticalAlignment="Center" />
|
||||
<TextBlock Classes="meta" Text="{Binding RoadblockMessage}"
|
||||
Foreground="{DynamicResource BloodBrush}"
|
||||
TextWrapping="Wrap" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Question prompt (AskUser): answer a running task's question inline -->
|
||||
<Border DockPanel.Dock="Top"
|
||||
IsVisible="{Binding HasPendingQuestion}"
|
||||
Background="{DynamicResource AccentSoftBrush}"
|
||||
BorderBrush="{DynamicResource AccentBrush}"
|
||||
BorderThickness="0,0,0,1" Padding="12,8">
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock Classes="meta"
|
||||
Text="{loc:Tr missionControl.question.title}"
|
||||
Foreground="{DynamicResource AccentBrush}" FontWeight="SemiBold" />
|
||||
<TextBlock Text="{Binding PendingQuestion}" TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource TextBrush}" />
|
||||
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="6">
|
||||
<TextBox Grid.Column="0"
|
||||
Text="{Binding AnswerDraft, UpdateSourceTrigger=PropertyChanged}"
|
||||
PlaceholderText="{loc:Tr missionControl.question.placeholder}"
|
||||
AcceptsReturn="False">
|
||||
<TextBox.KeyBindings>
|
||||
<KeyBinding Gesture="Enter" Command="{Binding SubmitAnswerCommand}" />
|
||||
</TextBox.KeyBindings>
|
||||
</TextBox>
|
||||
<Button Grid.Column="1"
|
||||
Content="{loc:Tr missionControl.question.send}"
|
||||
Command="{Binding SubmitAnswerCommand}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Console body: reuse SessionTerminalView -->
|
||||
<islands:SessionTerminalView
|
||||
Entries="{Binding Log}"
|
||||
Label="{Binding DisplayTitle}"
|
||||
IsRunning="{Binding IsRunning}"
|
||||
IsDone="{Binding IsDone}"
|
||||
IsFailed="{Binding IsFailed}" />
|
||||
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</UserControl>
|
||||
@@ -1,26 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.VisualTree;
|
||||
using ClaudeDo.Ui.ViewModels.Islands;
|
||||
|
||||
namespace ClaudeDo.Ui.Views.MissionControl;
|
||||
|
||||
public partial class MonitorPaneView : UserControl
|
||||
{
|
||||
public MonitorPaneView() => InitializeComponent();
|
||||
|
||||
private async void OnHeaderPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if (DataContext is not TaskMonitorViewModel m || m.SubscribedTaskId is not { } id) return;
|
||||
if (e.Source is not Avalonia.Visual src) return;
|
||||
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) return;
|
||||
|
||||
// Don't start a drag when the press landed on a header action button.
|
||||
var button = src as Button ?? src.FindAncestorOfType<Button>();
|
||||
if (button is not null) return;
|
||||
|
||||
var data = new DataTransfer();
|
||||
data.Add(DataTransferItem.Create(MissionControlView.PaneFormat, id));
|
||||
await DragDrop.DoDragDropAsync(e, data, DragDropEffects.Move);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:ClaudeDo.Ui.ViewModels.Islands"
|
||||
xmlns:mc="using:ClaudeDo.Ui.Views.MissionControl"
|
||||
x:Class="ClaudeDo.Ui.Views.MissionControl.TaskMonitorWindow"
|
||||
x:DataType="vm:TaskMonitorViewModel"
|
||||
Title="{Binding DisplayTitle}"
|
||||
Width="560" Height="620" MinWidth="360" MinHeight="320"
|
||||
Background="{DynamicResource VoidBrush}"
|
||||
Icon="avares://ClaudeDo.Ui/Assets/ClaudeTask.ico">
|
||||
<mc:MonitorPaneView />
|
||||
</Window>
|
||||
@@ -1,23 +0,0 @@
|
||||
using System;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace ClaudeDo.Ui.Views.MissionControl;
|
||||
|
||||
public partial class TaskMonitorWindow : Window
|
||||
{
|
||||
public TaskMonitorWindow() => InitializeComponent();
|
||||
|
||||
protected override void OnOpened(EventArgs e)
|
||||
{
|
||||
base.OnOpened(e);
|
||||
if (DataContext is ClaudeDo.Ui.ViewModels.Islands.TaskMonitorViewModel vm)
|
||||
vm.CloseWindowRequested = Close;
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
if (DataContext is ClaudeDo.Ui.ViewModels.Islands.TaskMonitorViewModel vm)
|
||||
vm.CloseWindowRequested = null;
|
||||
base.OnClosed(e);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ using Avalonia.Media;
|
||||
using ClaudeDo.Ui.Services;
|
||||
using ClaudeDo.Ui.ViewModels;
|
||||
using ClaudeDo.Ui.ViewModels.Conflicts;
|
||||
using ClaudeDo.Ui.ViewModels.Islands;
|
||||
using ClaudeDo.Ui.ViewModels.Modals;
|
||||
using ClaudeDo.Ui.Views.Conflicts;
|
||||
using ClaudeDo.Ui.Views.MissionControl;
|
||||
@@ -146,13 +145,6 @@ public sealed class WindowDialogService : IDialogService
|
||||
_missionControl.Activate(); // bring to front / focus
|
||||
}
|
||||
|
||||
public void ShowDetachedMonitor(TaskMonitorViewModel monitor, Action onClosed)
|
||||
{
|
||||
var win = new TaskMonitorWindow { DataContext = monitor };
|
||||
win.Closed += (_, _) => onClosed(); // closing re-docks into the grid
|
||||
win.Show(); // modeless, independent
|
||||
}
|
||||
|
||||
public Task<bool> ConfirmAsync(string message)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
|
||||
Reference in New Issue
Block a user