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:
Mika Kuns
2026-04-13 14:01:03 +02:00
parent 01235d986f
commit 48e4aabeb1
28 changed files with 1527 additions and 26 deletions

View File

@@ -0,0 +1,41 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:ClaudeDo.Ui.ViewModels"
x:Class="ClaudeDo.Ui.Views.TaskEditorView"
x:DataType="vm:TaskEditorViewModel"
Title="{Binding WindowTitle}"
Width="500" Height="420"
WindowStartupLocation="CenterOwner"
CanResize="False">
<StackPanel Margin="16" Spacing="10">
<TextBlock Text="Title" FontWeight="SemiBold"/>
<TextBox Text="{Binding Title}" PlaceholderText="Task title..."/>
<TextBlock Text="Description" FontWeight="SemiBold"/>
<TextBox Text="{Binding Description}" PlaceholderText="(optional)" AcceptsReturn="True"
TextWrapping="Wrap" MinHeight="80"/>
<Grid ColumnDefinitions="*,16,*">
<StackPanel Grid.Column="0" Spacing="4">
<TextBlock Text="Status" FontWeight="SemiBold"/>
<ComboBox ItemsSource="{Binding StatusChoices}"
SelectedItem="{Binding StatusChoice}"
MinWidth="120"/>
</StackPanel>
<StackPanel Grid.Column="2" Spacing="4">
<TextBlock Text="Commit Type" FontWeight="SemiBold"/>
<ComboBox ItemsSource="{Binding CommitTypes}"
SelectedItem="{Binding CommitType}"
MinWidth="120"/>
</StackPanel>
</Grid>
<TextBlock Text="Tags (comma-separated)" FontWeight="SemiBold"/>
<TextBox Text="{Binding TagsInput}" PlaceholderText="agent, manual, code, ..."/>
<StackPanel Orientation="Horizontal" Spacing="8" HorizontalAlignment="Right" Margin="0,10,0,0">
<Button Content="Save" Command="{Binding SaveCommand}" IsDefault="True" MinWidth="80"/>
<Button Content="Cancel" Command="{Binding CancelCommand}" IsCancel="True" MinWidth="80"/>
</StackPanel>
</StackPanel>
</Window>