From 0ef711395834030327d070863b9f609b3235edd1 Mon Sep 17 00:00:00 2001 From: mika kuns Date: Mon, 20 Apr 2026 11:30:47 +0200 Subject: [PATCH] style(ui): task row chip set, selected/done states, live tail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Expose IsOverdue, Tags, StepsCount/StepsCompleted, DiffAdditions/Deletions on TaskRowViewModel; parse DiffStat into numeric add/del. - Rebuild TaskRowView with explicit chip set: status, list-with-dot, branch (GitBranch icon + mono), diff (+N moss / −M blood), per-tag chips. - Selected row shows 2px accent left bar + AccentSoft background. - Done rows dim to 0.55 opacity with faint title plus strikethrough. - Live-tail row: mono 11px ellipsized text + slim 3px moss progress bar, visible only when Status=Running and HasLiveTail. - task-row Border now transitions Background and Margin. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ViewModels/Islands/TaskRowViewModel.cs | 80 +++++++++++--- .../Views/Islands/TaskRowView.axaml | 100 ++++++++++++++---- 2 files changed, 144 insertions(+), 36 deletions(-) diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs index 1b9ddee..5f56782 100644 --- a/src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs @@ -1,4 +1,4 @@ -using System.Collections.ObjectModel; +using System.Collections.Generic; using CommunityToolkit.Mvvm.ComponentModel; using ClaudeDo.Data.Models; using TaskStatus = ClaudeDo.Data.Models.TaskStatus; @@ -18,6 +18,25 @@ public sealed partial class TaskRowViewModel : ViewModelBase [ObservableProperty] private string? _branch; [ObservableProperty] private string? _diffStat; [ObservableProperty] private string? _liveTail; + [ObservableProperty] private DateTime? _scheduledFor; + [ObservableProperty] private int _diffAdditions; + [ObservableProperty] private int _diffDeletions; + + public IReadOnlyList Tags { get; init; } = Array.Empty(); + public int StepsCount { get; init; } + public int StepsCompleted { get; init; } + + public bool HasBranch => !string.IsNullOrWhiteSpace(Branch); + public bool HasDiff => DiffAdditions > 0 || DiffDeletions > 0; + public bool HasTags => Tags.Count > 0; + public bool HasSteps => StepsCount > 0; + public bool IsOverdue => ScheduledFor is { } d && d.Date < DateTime.Today && !Done; + public bool IsRunning => Status == TaskStatus.Running; + public bool HasLiveTail => IsRunning && !string.IsNullOrEmpty(LiveTail); + + public string DiffAdditionsText => $"+{DiffAdditions}"; + public string DiffDeletionsText => $"−{DiffDeletions}"; + public string StepsText => $"{StepsCompleted}/{StepsCount} steps"; public string StatusChipClass => Status switch { @@ -28,18 +47,51 @@ public sealed partial class TaskRowViewModel : ViewModelBase _ => "idle", }; - partial void OnStatusChanged(TaskStatus value) => OnPropertyChanged(nameof(StatusChipClass)); - - public static TaskRowViewModel FromEntity(TaskEntity t) => new() + partial void OnStatusChanged(TaskStatus value) { - Id = t.Id, - Title = t.Title, - ListName = t.List?.Name ?? "", - Done = t.Status == TaskStatus.Done, - IsStarred = t.IsStarred, - IsMyDay = t.IsMyDay, - Status = t.Status, - Branch = t.Worktree?.BranchName, - DiffStat = t.Worktree?.DiffStat, - }; + OnPropertyChanged(nameof(StatusChipClass)); + OnPropertyChanged(nameof(IsRunning)); + OnPropertyChanged(nameof(HasLiveTail)); + } + + partial void OnBranchChanged(string? value) => OnPropertyChanged(nameof(HasBranch)); + partial void OnLiveTailChanged(string? value) => OnPropertyChanged(nameof(HasLiveTail)); + partial void OnDoneChanged(bool value) => OnPropertyChanged(nameof(IsOverdue)); + partial void OnScheduledForChanged(DateTime? value) => OnPropertyChanged(nameof(IsOverdue)); + partial void OnDiffAdditionsChanged(int value) { OnPropertyChanged(nameof(HasDiff)); OnPropertyChanged(nameof(DiffAdditionsText)); } + partial void OnDiffDeletionsChanged(int value) { OnPropertyChanged(nameof(HasDiff)); OnPropertyChanged(nameof(DiffDeletionsText)); } + + public static TaskRowViewModel FromEntity(TaskEntity t) + { + var (add, del) = ParseDiffStat(t.Worktree?.DiffStat); + return new TaskRowViewModel + { + Id = t.Id, + Title = t.Title, + ListName = t.List?.Name ?? "", + Done = t.Status == TaskStatus.Done, + IsStarred = t.IsStarred, + IsMyDay = t.IsMyDay, + Status = t.Status, + Branch = t.Worktree?.BranchName, + DiffStat = t.Worktree?.DiffStat, + ScheduledFor = t.ScheduledFor, + DiffAdditions = add, + DiffDeletions = del, + }; + } + + // Best-effort parse of diff stat strings like "+12 -3" or "12 additions, 3 deletions". + private static (int add, int del) ParseDiffStat(string? s) + { + if (string.IsNullOrWhiteSpace(s)) return (0, 0); + int add = 0, del = 0; + var parts = s.Split(new[] { ' ', ',', '\t' }, StringSplitOptions.RemoveEmptyEntries); + foreach (var p in parts) + { + if (p.Length > 1 && p[0] == '+' && int.TryParse(p.AsSpan(1), out var a)) add = a; + else if (p.Length > 1 && (p[0] == '-' || p[0] == '\u2212') && int.TryParse(p.AsSpan(1), out var d)) del = d; + } + return (add, del); + } } diff --git a/src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml b/src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml index 9f8242d..6f3abe3 100644 --- a/src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml +++ b/src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml @@ -3,10 +3,18 @@ xmlns:vm="using:ClaudeDo.Ui.ViewModels.Islands" x:Class="ClaudeDo.Ui.Views.Islands.TaskRowView" x:DataType="vm:TaskRowViewModel"> - - + + + + + + -