From ce50f9fcceaaf829af3f5fd075718e86f53e1bdc Mon Sep 17 00:00:00 2001 From: mika kuns Date: Thu, 4 Jun 2026 19:28:08 +0200 Subject: [PATCH] feat(ui): add WorkConsole detail component Standalone terminal-styled card with traffic-light title bar, roadblock band, and three tabs (Output / Actions / Session). Renders fully via design-time sample data; does not touch DetailsIslandView. Co-Authored-By: Claude Sonnet 4.6 --- .../Islands/Detail/WorkConsoleViewModel.cs | 145 ++++++++++ .../Views/Islands/Detail/WorkConsole.axaml | 249 ++++++++++++++++++ .../Views/Islands/Detail/WorkConsole.axaml.cs | 8 + 3 files changed, 402 insertions(+) create mode 100644 src/ClaudeDo.Ui/ViewModels/Islands/Detail/WorkConsoleViewModel.cs create mode 100644 src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml create mode 100644 src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml.cs diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/Detail/WorkConsoleViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/Detail/WorkConsoleViewModel.cs new file mode 100644 index 0000000..77e3243 --- /dev/null +++ b/src/ClaudeDo.Ui/ViewModels/Islands/Detail/WorkConsoleViewModel.cs @@ -0,0 +1,145 @@ +using System.Collections.ObjectModel; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using ClaudeDo.Ui.ViewModels.Islands; + +namespace ClaudeDo.Ui.ViewModels.Islands.Detail; + +public sealed class WorkConsoleChildOutcomeRowViewModel +{ + public required string Title { get; init; } + public bool HasRoadblock { get; init; } + public string RoadblockText { get; init; } = ""; + public required string StatusLabel { get; init; } +} + +public sealed partial class WorkConsoleViewModel : ViewModelBase +{ + // ── Tab selection ────────────────────────────────────────────── + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(IsOutputTab))] + [NotifyPropertyChangedFor(nameof(IsActionsTab))] + [NotifyPropertyChangedFor(nameof(IsSessionTab))] + private string _selectedTab = "output"; + + public bool IsOutputTab => SelectedTab == "output"; + public bool IsActionsTab => SelectedTab == "actions"; + public bool IsSessionTab => SelectedTab == "session"; + + [RelayCommand] + private void SelectTab(string tab) => SelectedTab = tab; + + // ── Info header ──────────────────────────────────────────────── + [ObservableProperty] private string _model = ""; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(TurnsText))] + private int _turns; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(DiffAddText))] + private int _diffAdditions; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(DiffDelText))] + private int _diffDeletions; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(ShowRoadblock))] + private bool _isRunning; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(ShowRoadblock))] + private bool _isDone; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(ShowRoadblock))] + private bool _isFailed; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(ShowRoadblock))] + private bool _isCancelled; + + [ObservableProperty] private string _sessionLabel = ""; + + public string TurnsText => $"{Turns} turns"; + public string DiffAddText => $"+{DiffAdditions}"; + public string DiffDelText => $"-{DiffDeletions}"; + + // ── Roadblock ────────────────────────────────────────────────── + public bool ShowRoadblock => IsFailed || IsCancelled; + + [ObservableProperty] private string _roadblockMessage = ""; + [ObservableProperty] private bool _showContinue; + [ObservableProperty] private bool _showResetAndRetry; + + [RelayCommand] private void Continue() { } + [RelayCommand] private void ResetAndRetry() { } + + // ── Actions tab ──────────────────────────────────────────────── + public ObservableCollection MergeTargetBranches { get; } = new(); + + [ObservableProperty] private string? _selectedMergeTarget; + [ObservableProperty] private bool _canMergeAll; + [ObservableProperty] private string _mergeAllDisabledReason = ""; + [ObservableProperty] private string? _mergeAllError; + + [RelayCommand] private void OpenDiff() { } + [RelayCommand] private void OpenWorktree() { } + [RelayCommand] private void ReviewCombinedDiff() { } + [RelayCommand] private void MergeAll() { } + + // ── Session tab ──────────────────────────────────────────────── + [ObservableProperty] private bool _isWaitingForReview; + [ObservableProperty] private string _reviewFeedback = ""; + + public ObservableCollection ChildOutcomes { get; } = new(); + public bool HasChildOutcomes => ChildOutcomes.Count > 0; + + [RelayCommand] private void ApproveReview() { } + [RelayCommand] private void RejectReview() { } + [RelayCommand] private void ParkReview() { } + [RelayCommand] private void CancelReview() { } + + public ObservableCollection Log { get; } = new(); + + public WorkConsoleViewModel() + { + ChildOutcomes.CollectionChanged += (_, _) => OnPropertyChanged(nameof(HasChildOutcomes)); + + // ── Design-time sample data ──────────────────────────────── + _model = "sonnet"; + _turns = 40; + _diffAdditions = 84; + _diffDeletions = 31; + _isRunning = true; + _sessionLabel = "feat/work-console"; + + MergeTargetBranches.Add("main"); + MergeTargetBranches.Add("develop"); + _selectedMergeTarget = "main"; + _canMergeAll = true; + + Log.Add(new LogLineViewModel { Kind = LogKind.Sys, Text = "Starting claude session…" }); + Log.Add(new LogLineViewModel { Kind = LogKind.Claude, Text = "Reading DetailsIslandView.axaml to understand existing layout." }); + Log.Add(new LogLineViewModel { Kind = LogKind.Tool, Text = "Read(src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml)" }); + Log.Add(new LogLineViewModel { Kind = LogKind.Claude, Text = "Building WorkConsole component with three tabs." }); + Log.Add(new LogLineViewModel { Kind = LogKind.Stdout, Text = "dotnet build succeeded — 0 error(s)" }); + + ChildOutcomes.Add(new WorkConsoleChildOutcomeRowViewModel + { + Title = "Add WorkConsole XAML", + StatusLabel = "Done" + }); + ChildOutcomes.Add(new WorkConsoleChildOutcomeRowViewModel + { + Title = "Wire ViewModel bindings", + HasRoadblock = true, + RoadblockText = "Missing token", + StatusLabel = "Failed" + }); + + // To preview roadblock state: _isFailed = true; _roadblockMessage = "Session ended unexpectedly"; _showResetAndRetry = true; _isRunning = false; + // To preview review state: _isWaitingForReview = true; _isDone = false; _isRunning = false; + } +} diff --git a/src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml b/src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml new file mode 100644 index 0000000..3399b01 --- /dev/null +++ b/src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml @@ -0,0 +1,249 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +