diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/Detail/DescriptionStepsCardViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/Detail/DescriptionStepsCardViewModel.cs deleted file mode 100644 index dab0050..0000000 --- a/src/ClaudeDo.Ui/ViewModels/Islands/Detail/DescriptionStepsCardViewModel.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Text; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; - -namespace ClaudeDo.Ui.ViewModels.Islands.Detail; - -public partial class SubtaskRowSampleViewModel : ObservableObject -{ - [ObservableProperty] string _title = ""; - [ObservableProperty] bool _done; - [ObservableProperty] bool _isEditing; - - [RelayCommand] void ToggleDone() => Done = !Done; - [RelayCommand] void BeginEdit() => IsEditing = true; - [RelayCommand] void CommitEdit() => IsEditing = false; -} - -public partial class DescriptionStepsCardViewModel : ClaudeDo.Ui.ViewModels.ViewModelBase -{ - [ObservableProperty] - [NotifyPropertyChangedFor(nameof(IsDescriptionView))] - bool _isStepsView; - - public bool IsDescriptionView => !IsStepsView; - - [ObservableProperty] - [NotifyPropertyChangedFor(nameof(ComposedPreview))] - string _title = ""; - - [ObservableProperty] - [NotifyPropertyChangedFor(nameof(ComposedPreview))] - string _editableDescription = ""; - - [ObservableProperty] bool _isEditingDescription; - [ObservableProperty] string _newSubtaskTitle = ""; - - public ObservableCollection Subtasks { get; } = new(); - - public string ComposedPreview => BuildComposedPreview(); - - [RelayCommand] void ToggleCardView() => IsStepsView = !IsStepsView; - [RelayCommand] void ToggleEditDescription() => IsEditingDescription = !IsEditingDescription; - - [RelayCommand] - void AddSubtask() - { - if (string.IsNullOrWhiteSpace(NewSubtaskTitle)) return; - var row = new SubtaskRowSampleViewModel { Title = NewSubtaskTitle.Trim() }; - row.PropertyChanged += OnRowPropertyChanged; - Subtasks.Add(row); - NewSubtaskTitle = ""; - OnPropertyChanged(nameof(ComposedPreview)); - } - - [RelayCommand] - void ToggleSubtaskDone(SubtaskRowSampleViewModel row) - { - row.Done = !row.Done; - } - - [RelayCommand] - void CommitSubtaskEdit(SubtaskRowSampleViewModel row) - { - row.IsEditing = false; - } - - private void OnRowPropertyChanged(object? sender, PropertyChangedEventArgs e) - { - if (e.PropertyName is nameof(SubtaskRowSampleViewModel.Done) - or nameof(SubtaskRowSampleViewModel.Title)) - OnPropertyChanged(nameof(ComposedPreview)); - } - - private void OnSubtasksChanged(object? sender, NotifyCollectionChangedEventArgs e) - { - if (e.NewItems is not null) - foreach (SubtaskRowSampleViewModel row in e.NewItems) - row.PropertyChanged += OnRowPropertyChanged; - if (e.OldItems is not null) - foreach (SubtaskRowSampleViewModel row in e.OldItems) - row.PropertyChanged -= OnRowPropertyChanged; - OnPropertyChanged(nameof(ComposedPreview)); - } - - private string BuildComposedPreview() - { - var sb = new StringBuilder(); - sb.AppendLine(Title); - - if (!string.IsNullOrWhiteSpace(EditableDescription)) - { - sb.AppendLine(); - sb.AppendLine(EditableDescription.TrimEnd()); - } - - var openSteps = Subtasks.Where(s => !s.Done).ToList(); - if (openSteps.Count > 0) - { - sb.AppendLine(); - sb.AppendLine("## Sub-Tasks"); - foreach (var step in openSteps) - sb.AppendLine($"- [ ] {step.Title}"); - } - - return sb.ToString().TrimEnd(); - } - - public DescriptionStepsCardViewModel() - { - Subtasks.CollectionChanged += OnSubtasksChanged; - - _title = "Refactor diff viewer"; - _editableDescription = - "Split the current monolithic diff renderer into smaller, testable components.\n\n" + - "The goal is to improve readability and allow unit testing of each parsing stage independently."; - - var samples = new[] - { - new SubtaskRowSampleViewModel { Title = "Extract DiffParser into its own class", Done = true }, - new SubtaskRowSampleViewModel { Title = "Add unit tests for DiffParser", Done = true }, - new SubtaskRowSampleViewModel { Title = "Wire new parser into DiffViewerViewModel" }, - new SubtaskRowSampleViewModel { Title = "Update snapshot tests" }, - }; - foreach (var row in samples) - { - row.PropertyChanged += OnRowPropertyChanged; - Subtasks.Add(row); - } - } -} diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/Detail/TaskHeaderBarViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/Detail/TaskHeaderBarViewModel.cs deleted file mode 100644 index 5e9eb94..0000000 --- a/src/ClaudeDo.Ui/ViewModels/Islands/Detail/TaskHeaderBarViewModel.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Collections.ObjectModel; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; - -namespace ClaudeDo.Ui.ViewModels.Islands.Detail; - -public record AgentOption(string Name); - -public partial class TaskHeaderBarViewModel : ViewModelBase -{ - [ObservableProperty] private string _taskIdBadge = "#T42"; - [ObservableProperty] private string _editableTitle = "Refactor diff viewer"; - - // Change to true to preview skull icon - [ObservableProperty] private bool _isRunning = false; - [ObservableProperty] private bool _isAgentSectionEnabled = true; - - [ObservableProperty] private ObservableCollection _taskModelOptions = - new() { "claude-opus-4-5", "claude-sonnet-4-5", "claude-haiku-4-5" }; - [ObservableProperty] private string? _taskModelSelection; - [ObservableProperty] private string? _modelBadge = "inherited · Global"; - [ObservableProperty] private string? _modelInheritedHint = "claude-opus-4-5"; - - [ObservableProperty] private decimal? _taskMaxTurns; - [ObservableProperty] private string? _turnsBadge = "inherited · List"; - [ObservableProperty] private string? _turnsInheritedHint = "40"; - - [ObservableProperty] private string? _taskSystemPrompt; - [ObservableProperty] private string? _effectiveSystemPromptHint = "You are a senior .NET developer…"; - - [ObservableProperty] private ObservableCollection _taskAgentOptions = - new() { new("default"), new("code-reviewer"), new("test-writer") }; - [ObservableProperty] private AgentOption? _taskSelectedAgent; - [ObservableProperty] private string? _agentBadge; - - [RelayCommand] private void DeleteTask() { } - [RelayCommand] private void KillSession() { } - [RelayCommand] private void ResetTaskModel() { } - [RelayCommand] private void ResetTaskTurns() { } - [RelayCommand] private void ResetTaskAgent() { } -} diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/Detail/WorkConsoleViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/Detail/WorkConsoleViewModel.cs deleted file mode 100644 index 77e3243..0000000 --- a/src/ClaudeDo.Ui/ViewModels/Islands/Detail/WorkConsoleViewModel.cs +++ /dev/null @@ -1,145 +0,0 @@ -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/ViewModels/Islands/DetailsIslandViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs index 2ed2ae9..2be5bd2 100644 --- a/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs @@ -74,8 +74,12 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase private TaskRowViewModel? _task; // Editable fields - [ObservableProperty] private string _editableTitle = ""; - [ObservableProperty] private string _editableDescription = ""; + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(ComposedPreview))] + private string _editableTitle = ""; + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(ComposedPreview))] + private string _editableDescription = ""; [ObservableProperty] private bool _isEditingDescription; [ObservableProperty] private bool _isDescriptionExpanded = true; @@ -100,6 +104,46 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase [RelayCommand] private void ToggleDescriptionExpanded() => IsDescriptionExpanded = !IsDescriptionExpanded; + // ── Description/Steps card (redesign) ────────────────────────────── + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(IsDescriptionView))] + private bool _isStepsView; + + public bool IsDescriptionView => !IsStepsView; + + [RelayCommand] + private void ToggleCardView() => IsStepsView = !IsStepsView; + + // The exact text handed to Claude: title + description + open steps only. + public string ComposedPreview => + ClaudeDo.Data.TaskPromptComposer.Compose( + EditableTitle, EditableDescription, Subtasks.Select(s => (s.Title, s.Done))); + + // ── Work console (redesign) ──────────────────────────────────────── + [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 ?? "output"; + + public string TurnsText => $"{Turns} turns"; + public string DiffAddText => $"+{DiffAdditions}"; + public string DiffDelText => $"-{DiffDeletions}"; + + public bool ShowRoadblock => IsFailed || IsCancelled; + public string RoadblockMessage => + IsFailed ? "The session ended with an error." : + IsCancelled ? "The session was cancelled." : ""; + + public string SessionLabel => "claude-session"; + // Short task-id badge, e.g. "#T1A" public string TaskIdBadge => Task != null ? $"#T{Task.Id[..Math.Min(3, Task.Id.Length)].ToUpperInvariant()}" : ""; @@ -142,6 +186,8 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase OnPropertyChanged(nameof(ShowContinue)); OnPropertyChanged(nameof(ShowResetAndRetry)); OnPropertyChanged(nameof(IsAgentSectionEnabled)); + OnPropertyChanged(nameof(ShowRoadblock)); + OnPropertyChanged(nameof(RoadblockMessage)); } [ObservableProperty] private string? _model; @@ -221,8 +267,9 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase public string ElapsedFormatted => ""; // placeholder — no start-time stored yet partial void OnTokensChanged(int value) => OnPropertyChanged(nameof(TokensFormatted)); - partial void OnDiffAdditionsChanged(int value) => OnPropertyChanged(nameof(DiffMeterRatio)); - partial void OnDiffDeletionsChanged(int value) => OnPropertyChanged(nameof(DiffMeterRatio)); + partial void OnTurnsChanged(int value) => OnPropertyChanged(nameof(TurnsText)); + partial void OnDiffAdditionsChanged(int value) { OnPropertyChanged(nameof(DiffMeterRatio)); OnPropertyChanged(nameof(DiffAddText)); } + partial void OnDiffDeletionsChanged(int value) { OnPropertyChanged(nameof(DiffMeterRatio)); OnPropertyChanged(nameof(DiffDelText)); } // 0.0–1.0 additions share for the diff meter public double DiffMeterRatio @@ -328,6 +375,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase _services = services; _notesApi = notesApi; Notes = new NotesEditorViewModel(_notesApi); + Subtasks.CollectionChanged += (_, _) => OnPropertyChanged(nameof(ComposedPreview)); Loc.LanguageChanged += (_, _) => { OnPropertyChanged(nameof(AgentStatusLabel)); @@ -1092,6 +1140,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase { if (row is null) return; row.Done = !row.Done; + OnPropertyChanged(nameof(ComposedPreview)); await using var ctx = _dbFactory.CreateDbContext(); var repo = new SubtaskRepository(ctx); var subs = await repo.GetByTaskIdAsync(Task?.Id ?? ""); @@ -1157,6 +1206,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase await repo.UpdateAsync(entity); } row.Title = title; + OnPropertyChanged(nameof(ComposedPreview)); } [RelayCommand] diff --git a/src/ClaudeDo.Ui/Views/Islands/Detail/DescriptionStepsCard.axaml b/src/ClaudeDo.Ui/Views/Islands/Detail/DescriptionStepsCard.axaml index be69475..a8459fd 100644 --- a/src/ClaudeDo.Ui/Views/Islands/Detail/DescriptionStepsCard.axaml +++ b/src/ClaudeDo.Ui/Views/Islands/Detail/DescriptionStepsCard.axaml @@ -1,13 +1,9 @@ - - - - + x:DataType="vm:DetailsIslandViewModel"> @@ -109,7 +105,7 @@ - + @@ -119,7 +115,7 @@ Padding="0" Margin="0,0,8,0" VerticalAlignment="Center" - Command="{Binding $parent[ItemsControl].((vm:DescriptionStepsCardViewModel)DataContext).ToggleSubtaskDoneCommand}" + Command="{Binding $parent[ItemsControl].((vm:DetailsIslandViewModel)DataContext).ToggleSubtaskDoneCommand}" CommandParameter="{Binding}"> diff --git a/src/ClaudeDo.Ui/Views/Islands/Detail/DescriptionStepsCard.axaml.cs b/src/ClaudeDo.Ui/Views/Islands/Detail/DescriptionStepsCard.axaml.cs index db581db..0e4d729 100644 --- a/src/ClaudeDo.Ui/Views/Islands/Detail/DescriptionStepsCard.axaml.cs +++ b/src/ClaudeDo.Ui/Views/Islands/Detail/DescriptionStepsCard.axaml.cs @@ -2,7 +2,7 @@ using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; -using ClaudeDo.Ui.ViewModels.Islands.Detail; +using ClaudeDo.Ui.ViewModels.Islands; namespace ClaudeDo.Ui.Views.Islands.Detail; @@ -15,7 +15,7 @@ public partial class DescriptionStepsCard : UserControl private async void OnCopyClick(object? sender, RoutedEventArgs e) { - if (DataContext is not DescriptionStepsCardViewModel vm) return; + if (DataContext is not DetailsIslandViewModel vm) return; var clipboard = TopLevel.GetTopLevel(this)?.Clipboard; if (clipboard is null) return; await clipboard.SetTextAsync(vm.ComposedPreview); @@ -23,13 +23,13 @@ public partial class DescriptionStepsCard : UserControl private void OnSubtaskTitleTapped(object? sender, TappedEventArgs e) { - if (sender is TextBlock { DataContext: SubtaskRowSampleViewModel row }) + if (sender is TextBlock { DataContext: SubtaskRowViewModel row }) row.IsEditing = true; } private void OnSubtaskEditLostFocus(object? sender, RoutedEventArgs e) { - if (sender is TextBox { DataContext: SubtaskRowSampleViewModel row }) + if (sender is TextBox { DataContext: SubtaskRowViewModel row }) row.IsEditing = false; } } diff --git a/src/ClaudeDo.Ui/Views/Islands/Detail/TaskHeaderBar.axaml b/src/ClaudeDo.Ui/Views/Islands/Detail/TaskHeaderBar.axaml index 769ad8a..09efa48 100644 --- a/src/ClaudeDo.Ui/Views/Islands/Detail/TaskHeaderBar.axaml +++ b/src/ClaudeDo.Ui/Views/Islands/Detail/TaskHeaderBar.axaml @@ -1,14 +1,10 @@ - - - - + x:DataType="vm:DetailsIslandViewModel"> @@ -17,7 +13,9 @@ + Cursor="Hand" + ToolTip.Tip="{loc:Tr details.copyTaskIdTip}" + Tapped="OnTaskIdTapped"/>