feat(ui): wire avalonia desktop ui to data and worker
App: build a ServiceProvider in Program.cs (AppSettings, SqliteConnectionFactory, all repositories, GitService, WorkerClient, all view-models), apply schema, then hand control to Avalonia. App.OnFrameworkInitializationCompleted resolves MainWindowViewModel from the container. Ui: - AppSettings POCO loaded from ~/.todo-app/ui.config.json (db path, hub url). - WorkerClient wraps HubConnection with auto-reconnect, exposes IsConnected and ActiveTasks plus C# events for TaskStarted/Finished/Message/Updated and WorktreeUpdated; all inbound events are marshalled to the UI thread. - ViewModels: MainWindow (lists CRUD via ListEditor dialog), TaskList (load by list, add/edit/delete, auto WakeQueue on agent+queued create), TaskItem (RunNow gated on connection + status), TaskDetail (description, result, live ndjson rolling buffer of 500 lines, worktree branch/diff with merge/keep/ discard via GitService), StatusBar, ListEditor, TaskEditor. - Views: 3-pane MainWindow (lists | tasks | detail) with GridSplitters, status bar, dialog windows for the editors. Status badges via StatusColorConverter. - Markdown rendering, folder picker, delete-confirmation, settings dialog and scroll-to-bottom on the live log are intentionally TODO -- functional scaffold only. Tests: also debounce the FIFO queue test (poll instead of Task.Delay(200)) so the assertion isn't racy when the suite runs alongside the slower git tests. 38 tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
97
src/ClaudeDo.Ui/ViewModels/TaskEditorViewModel.cs
Normal file
97
src/ClaudeDo.Ui/ViewModels/TaskEditorViewModel.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using ClaudeDo.Data.Models;
|
||||
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";
|
||||
|
||||
private string? _editId;
|
||||
private string _listId = "";
|
||||
private DateTime _createdAt;
|
||||
private TaskCompletionSource<TaskEntity?> _tcs = new();
|
||||
|
||||
public event Action? RequestClose;
|
||||
|
||||
public static string[] CommitTypes { get; } =
|
||||
["chore", "feat", "fix", "refactor", "docs", "test", "perf", "style", "build", "ci"];
|
||||
|
||||
public static string[] StatusChoices { get; } =
|
||||
["manual", "queued"];
|
||||
|
||||
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));
|
||||
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,
|
||||
};
|
||||
_tcs.TrySetResult(entity);
|
||||
RequestClose?.Invoke();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Cancel()
|
||||
{
|
||||
_tcs.TrySetResult(null);
|
||||
RequestClose?.Invoke();
|
||||
}
|
||||
|
||||
public Task<TaskEntity?> ShowAndWaitAsync()
|
||||
{
|
||||
_tcs = new TaskCompletionSource<TaskEntity?>();
|
||||
return _tcs.Task;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user