diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs index 3178035..4b5ee19 100644 --- a/src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs @@ -17,7 +17,6 @@ public sealed partial class TaskRowViewModel : ViewModelBase [ObservableProperty] private PlanningPhase _planningPhase; [ObservableProperty] private string? _branch; [ObservableProperty] private string? _diffStat; - [ObservableProperty] private string? _liveTail; [ObservableProperty] private DateTime? _scheduledFor; [ObservableProperty] private int _diffAdditions; [ObservableProperty] private int _diffDeletions; @@ -74,7 +73,6 @@ public sealed partial class TaskRowViewModel : ViewModelBase && PlanningPhase == PlanningPhase.Finalized && !HasQueuedSubtasks; public bool HasSchedule => ScheduledFor.HasValue; - public bool HasLiveTail => IsRunning && !string.IsNullOrEmpty(LiveTail); public string DiffAdditionsText => $"+{DiffAdditions}"; public string DiffDeletionsText => $"−{DiffDeletions}"; @@ -96,7 +94,6 @@ public sealed partial class TaskRowViewModel : ViewModelBase OnPropertyChanged(nameof(IsRunning)); OnPropertyChanged(nameof(IsQueued)); OnPropertyChanged(nameof(IsWaiting)); - OnPropertyChanged(nameof(HasLiveTail)); OnPropertyChanged(nameof(IsDraft)); OnPropertyChanged(nameof(IsPlanned)); OnPropertyChanged(nameof(CanOpenPlanningSession)); @@ -152,7 +149,6 @@ public sealed partial class TaskRowViewModel : ViewModelBase } 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) { diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs index 3369e29..6b85787 100644 --- a/src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs @@ -56,18 +56,11 @@ public sealed partial class TasksIslandViewModel : ViewModelBase { _worker.TaskUpdatedEvent += OnWorkerTaskUpdated; _worker.WorktreeUpdatedEvent += OnWorkerTaskUpdated; - _worker.TaskMessageEvent += OnWorkerTaskMessage; _worker.ListUpdatedEvent += OnWorkerListUpdated; _worker.ConnectionRestoredEvent += () => LoadForList(_currentList); } } - private void OnWorkerTaskMessage(string taskId, string line) - { - var row = Items.FirstOrDefault(r => r.Id == taskId); - if (row is not null) row.LiveTail = line; - } - private async void OnWorkerListUpdated(string listId) { // Mirror the renamed list onto every task row that references it, diff --git a/src/ClaudeDo.Ui/Views/Islands/SessionTerminalView.axaml.cs b/src/ClaudeDo.Ui/Views/Islands/SessionTerminalView.axaml.cs index d2bd36f..d70e299 100644 --- a/src/ClaudeDo.Ui/Views/Islands/SessionTerminalView.axaml.cs +++ b/src/ClaudeDo.Ui/Views/Islands/SessionTerminalView.axaml.cs @@ -1,6 +1,5 @@ using System.Collections.Specialized; using Avalonia.Controls; -using Avalonia.Threading; using ClaudeDo.Ui.ViewModels.Islands; namespace ClaudeDo.Ui.Views.Islands; @@ -9,16 +8,29 @@ public partial class SessionTerminalView : UserControl { public SessionTerminalView() { InitializeComponent(); } + private DetailsIslandViewModel? _boundVm; + protected override void OnDataContextChanged(EventArgs e) { base.OnDataContextChanged(e); - if (DataContext is DetailsIslandViewModel vm) - vm.Log.CollectionChanged += OnLogChanged; + if (_boundVm is not null) + _boundVm.Log.CollectionChanged -= OnLogChanged; + _boundVm = DataContext as DetailsIslandViewModel; + if (_boundVm is not null) + _boundVm.Log.CollectionChanged += OnLogChanged; } private void OnLogChanged(object? sender, NotifyCollectionChangedEventArgs e) { if (e.Action != NotifyCollectionChangedAction.Add) return; - Dispatcher.UIThread.Post(() => LogScroll.ScrollToEnd(), DispatcherPriority.Background); + // Scroll after the next layout pass so the freshly-added (wrapping) line + // is measured first — otherwise ScrollToEnd stops short and clips it. + EventHandler? handler = null; + handler = (_, _) => + { + LogScroll.LayoutUpdated -= handler; + LogScroll.ScrollToEnd(); + }; + LogScroll.LayoutUpdated += handler; } } diff --git a/src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml b/src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml index cd87bd3..b980bfa 100644 --- a/src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml +++ b/src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml @@ -175,20 +175,6 @@ - - - - - - - - - - - diff --git a/src/ClaudeDo.Worker/Runner/TaskRunner.cs b/src/ClaudeDo.Worker/Runner/TaskRunner.cs index 8f9867c..38a2c66 100644 --- a/src/ClaudeDo.Worker/Runner/TaskRunner.cs +++ b/src/ClaudeDo.Worker/Runner/TaskRunner.cs @@ -238,6 +238,11 @@ public sealed class TaskRunner { var runRepo = new TaskRunRepository(context); await runRepo.AddAsync(run, ct); + + // Point the task at this run's log immediately so the UI can replay + // live output when the user navigates away and back mid-run. + var taskRepo = new TaskRepository(context); + await taskRepo.SetLogPathAsync(taskId, logPath, ct); } await _broadcaster.RunCreated(taskId, runNumber, isRetry); @@ -277,9 +282,6 @@ public sealed class TaskRunner { var runRepo = new TaskRunRepository(context); await runRepo.UpdateAsync(run, CancellationToken.None); - - var taskRepo = new TaskRepository(context); - await taskRepo.SetLogPathAsync(taskId, logPath, CancellationToken.None); } return result; @@ -296,9 +298,6 @@ public sealed class TaskRunner using var context = _dbFactory.CreateDbContext(); var runRepo = new TaskRunRepository(context); await runRepo.UpdateAsync(run, CancellationToken.None); - - var taskRepo = new TaskRepository(context); - await taskRepo.SetLogPathAsync(taskId, logPath, CancellationToken.None); } catch (Exception updateEx) {