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:
@@ -1,18 +1,31 @@
|
||||
using Avalonia;
|
||||
using Avalonia;
|
||||
using ClaudeDo.Data;
|
||||
using ClaudeDo.Data.Git;
|
||||
using ClaudeDo.Data.Repositories;
|
||||
using ClaudeDo.Ui;
|
||||
using ClaudeDo.Ui.Services;
|
||||
using ClaudeDo.Ui.ViewModels;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
|
||||
namespace ClaudeDo.App;
|
||||
|
||||
sealed class Program
|
||||
{
|
||||
// Initialization code. Don't use any Avalonia, third-party APIs or any
|
||||
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
||||
// yet and stuff might break.
|
||||
[STAThread]
|
||||
public static void Main(string[] args) => BuildAvaloniaApp()
|
||||
.StartWithClassicDesktopLifetime(args);
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var services = BuildServices();
|
||||
App.Services = services;
|
||||
|
||||
// Ensure DB schema exists
|
||||
var factory = services.GetRequiredService<SqliteConnectionFactory>();
|
||||
SchemaInitializer.Apply(factory);
|
||||
|
||||
BuildAvaloniaApp()
|
||||
.StartWithClassicDesktopLifetime(args);
|
||||
}
|
||||
|
||||
// Avalonia configuration, don't remove; also used by visual designer.
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
=> AppBuilder.Configure<App>()
|
||||
.UsePlatformDetect()
|
||||
@@ -21,4 +34,56 @@ sealed class Program
|
||||
#endif
|
||||
.WithInterFont()
|
||||
.LogToTrace();
|
||||
|
||||
private static ServiceProvider BuildServices()
|
||||
{
|
||||
var settings = AppSettings.Load();
|
||||
var dbPath = Paths.Expand(settings.DbPath);
|
||||
|
||||
var sc = new ServiceCollection();
|
||||
|
||||
// Infrastructure
|
||||
sc.AddSingleton(settings);
|
||||
sc.AddSingleton(new SqliteConnectionFactory(dbPath));
|
||||
|
||||
// Repositories
|
||||
sc.AddSingleton<ListRepository>();
|
||||
sc.AddSingleton<TaskRepository>();
|
||||
sc.AddSingleton<TagRepository>();
|
||||
sc.AddSingleton<WorktreeRepository>();
|
||||
|
||||
// Services
|
||||
sc.AddSingleton<GitService>();
|
||||
sc.AddSingleton(sp => new WorkerClient(sp.GetRequiredService<AppSettings>().SignalRUrl));
|
||||
|
||||
// ViewModels
|
||||
sc.AddTransient<ListEditorViewModel>();
|
||||
sc.AddTransient<TaskEditorViewModel>();
|
||||
sc.AddSingleton<StatusBarViewModel>();
|
||||
sc.AddSingleton<TaskDetailViewModel>();
|
||||
sc.AddSingleton<TaskListViewModel>(sp =>
|
||||
{
|
||||
var taskRepo = sp.GetRequiredService<TaskRepository>();
|
||||
var tagRepo = sp.GetRequiredService<TagRepository>();
|
||||
var listRepo = sp.GetRequiredService<ListRepository>();
|
||||
var worker = sp.GetRequiredService<WorkerClient>();
|
||||
var statusBar = sp.GetRequiredService<StatusBarViewModel>();
|
||||
return new TaskListViewModel(
|
||||
taskRepo, tagRepo, listRepo, worker,
|
||||
() => sp.GetRequiredService<TaskEditorViewModel>(),
|
||||
msg => statusBar.ShowMessage(msg));
|
||||
});
|
||||
sc.AddSingleton<MainWindowViewModel>(sp =>
|
||||
{
|
||||
return new MainWindowViewModel(
|
||||
sp.GetRequiredService<ListRepository>(),
|
||||
sp.GetRequiredService<WorkerClient>(),
|
||||
sp.GetRequiredService<TaskListViewModel>(),
|
||||
sp.GetRequiredService<TaskDetailViewModel>(),
|
||||
sp.GetRequiredService<StatusBarViewModel>(),
|
||||
() => sp.GetRequiredService<ListEditorViewModel>());
|
||||
});
|
||||
|
||||
return sc.BuildServiceProvider();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user