using System; using System.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using ClaudeDo.Ui.Services; namespace ClaudeDo.Ui.ViewModels.MissionControl; /// /// 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, /// is null — ad-hoc panes are never deduped, unlike task-based ones). Distinct from the streamed-log /// pane; the two coexist until /// the streaming interactive stack is removed. /// public sealed partial class ConPtyPaneViewModel : ViewModelBase, IMissionControlPane, IDisposable { public string? TaskId { get; } [ObservableProperty] private string _displayTitle; public InteractiveTerminalViewModel Terminal { get; } = new(); /// Raised when the terminal failed to start — the host surfaces this via the footer error strip. public event Action? ErrorReported; /// Set by the host (Mission Control) to remove this pane from its collection. public Action? CloseRequested { get; set; } /// Task-based pane — dedup'd by . Pass null for an ad-hoc pane /// (no task, never deduped); prefer at ad-hoc call sites. public ConPtyPaneViewModel(string? taskId, string displayTitle, TerminalLaunchDescriptor descriptor) { TaskId = taskId; _displayTitle = displayTitle; Terminal.PropertyChanged += OnTerminalPropertyChanged; Terminal.Start(descriptor); } /// Ad-hoc pane — no task, no dedup. public static ConPtyPaneViewModel CreateAdHoc(string displayTitle, TerminalLaunchDescriptor descriptor) => new(null, displayTitle, descriptor); private void OnTerminalPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(InteractiveTerminalViewModel.StartError) && Terminal.StartError is { Length: > 0 } error) ErrorReported?.Invoke(error); } [RelayCommand] private void Close() => CloseRequested?.Invoke(this); public void Dispose() { Terminal.PropertyChanged -= OnTerminalPropertyChanged; Terminal.Kill(); Terminal.Dispose(); } }