Files
ClaudeDo/src/ClaudeDo.Ui/ViewModels/TaskEditorViewModel.cs
Mika Kuns 2a1f26d817 fix(ui): fix live output visibility and editor dialog graying out
- Remove wrapping ScrollViewer from live output TextBox — Avalonia
  TextBox with AcceptsReturn handles its own scrolling; nested
  ScrollViewer caused layout collapse
- Auto-scroll via CaretIndex instead of removed ScrollViewer
- Add OnWindowClosed to both editor ViewModels, ensuring the
  TaskCompletionSource always resolves when the dialog closes
  (including via X button), preventing RelayCommand from staying
  permanently disabled

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 17:01:08 +02:00

127 lines
4.2 KiB
C#

using ClaudeDo.Data.Models;
using ClaudeDo.Ui.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
namespace ClaudeDo.Ui.ViewModels;
public partial class TaskEditorViewModel : ViewModelBase
{
[ObservableProperty] private string _title = "";
[ObservableProperty] private string? _description;
[ObservableProperty] private string _commitType = "chore";
[ObservableProperty] private string _statusChoice = "manual";
[ObservableProperty] private string _tagsInput = "";
[ObservableProperty] private string _windowTitle = "New Task";
[ObservableProperty] private string _modelChoice = "(list default)";
[ObservableProperty] private string? _systemPromptOverride;
[ObservableProperty] private AgentInfo? _selectedAgent;
public List<AgentInfo> AvailableAgents { get; set; } = [];
private string? _editId;
private string _listId = "";
private DateTime _createdAt;
private TaskCompletionSource<TaskEntity?> _tcs = new();
public event Action? RequestClose;
public static string[] ModelChoices { get; } = ["(list default)", "Sonnet", "Opus", "Haiku"];
public static string[] CommitTypes { get; } =
["chore", "feat", "fix", "refactor", "docs", "test", "perf", "style", "build", "ci"];
public static string[] StatusChoices { get; } =
["manual", "queued"];
public async Task LoadAgentsAsync(WorkerClient worker)
{
AvailableAgents = await worker.GetAgentsAsync();
}
public IReadOnlyList<string> SelectedTagNames =>
TagsInput.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Distinct()
.ToList();
public void InitForCreate(string listId, string defaultCommitType = "chore")
{
_editId = null;
_listId = listId;
_createdAt = DateTime.UtcNow;
CommitType = defaultCommitType;
WindowTitle = "New Task";
}
public void InitForEdit(TaskEntity entity, IReadOnlyList<TagEntity> taskTags)
{
_editId = entity.Id;
_listId = entity.ListId;
_createdAt = entity.CreatedAt;
Title = entity.Title;
Description = entity.Description;
CommitType = entity.CommitType;
StatusChoice = entity.Status switch
{
TaskStatus.Manual => "manual",
TaskStatus.Queued => "queued",
_ => entity.Status.ToString().ToLowerInvariant(),
};
TagsInput = string.Join(", ", taskTags.Select(t => t.Name));
ModelChoice = entity.Model is not null
? ListEditorViewModel.ModelIdToDisplay(entity.Model)
: "(list default)";
SystemPromptOverride = entity.SystemPrompt;
SelectedAgent = entity.AgentPath is not null
? AvailableAgents.FirstOrDefault(a => a.Path == entity.AgentPath)
: null;
WindowTitle = $"Edit Task: {entity.Title}";
}
[RelayCommand]
private void Save()
{
if (string.IsNullOrWhiteSpace(Title)) return;
var status = StatusChoice switch
{
"queued" => TaskStatus.Queued,
_ => TaskStatus.Manual,
};
var entity = new TaskEntity
{
Id = _editId ?? Guid.NewGuid().ToString(),
ListId = _listId,
Title = Title.Trim(),
Description = string.IsNullOrWhiteSpace(Description) ? null : Description.Trim(),
Status = status,
CommitType = CommitType,
CreatedAt = _createdAt,
};
entity.Model = ModelChoice != "(list default)"
? ListEditorViewModel.ModelDisplayToId(ModelChoice)
: null;
entity.SystemPrompt = string.IsNullOrWhiteSpace(SystemPromptOverride) ? null : SystemPromptOverride.Trim();
entity.AgentPath = SelectedAgent?.Path;
_tcs.TrySetResult(entity);
RequestClose?.Invoke();
}
[RelayCommand]
private void Cancel()
{
_tcs.TrySetResult(null);
RequestClose?.Invoke();
}
public void OnWindowClosed()
{
_tcs.TrySetResult(null);
}
public Task<TaskEntity?> ShowAndWaitAsync()
{
_tcs = new TaskCompletionSource<TaskEntity?>();
return _tcs.Task;
}
}