diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs index 604ed891..314ca732 100644 --- a/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs @@ -258,6 +258,9 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable private bool _suppressDescSave; private CancellationTokenSource? _descSaveCts; + private bool _suppressTitleSave; + private CancellationTokenSource? _titleSaveCts; + // Set by shell so CloseDetailCommand can clear SelectedTask public Action? CloseDetail { get; set; } @@ -430,6 +433,39 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable } } + partial void OnEditableTitleChanged(string value) + { + if (_suppressTitleSave || Task is null) return; + _titleSaveCts?.Cancel(); + _titleSaveCts = new CancellationTokenSource(); + // Capture the row so a task switch mid-debounce cannot retitle the new task. + _ = SaveTitleAsync(Task, value, _titleSaveCts.Token); + } + + private async System.Threading.Tasks.Task SaveTitleAsync( + TaskRowViewModel row, string value, CancellationToken ct) + { + try + { + await System.Threading.Tasks.Task.Delay(400, ct); + var title = value?.Trim(); + // An empty title would make the row unidentifiable — keep the stored one. + if (string.IsNullOrEmpty(title)) return; + await using var ctx = _dbFactory.CreateDbContext(); + var repo = new TaskRepository(ctx); + var entity = await repo.GetByIdAsync(row.Id); + if (entity is null || entity.Title == title) return; + entity.Title = title; + await repo.UpdateAsync(entity); + ct.ThrowIfCancellationRequested(); + row.Title = title; + if (ReferenceEquals(Task, row)) + Merge.SyncTaskContext(row.Id, title, row.IsPlanningParent); + } + catch (OperationCanceledException) { } + catch { } + } + partial void OnEditableDescriptionChanged(string value) { if (_suppressDescSave || Task is null) return; @@ -525,7 +561,9 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable ct.ThrowIfCancellationRequested(); if (entity == null) return; - EditableTitle = entity.Title; + _suppressTitleSave = true; + try { EditableTitle = entity.Title; } + finally { _suppressTitleSave = false; } _suppressDescSave = true; try { EditableDescription = entity.Description ?? ""; } finally { _suppressDescSave = false; }