diff --git a/src/ClaudeDo.Data/Git/GitService.cs b/src/ClaudeDo.Data/Git/GitService.cs index 673583f..29c34e6 100644 --- a/src/ClaudeDo.Data/Git/GitService.cs +++ b/src/ClaudeDo.Data/Git/GitService.cs @@ -106,6 +106,15 @@ public sealed class GitService return stdout.Trim(); } + public async Task GetFileDiffAsync(string worktreePath, string? baseCommit, string relativePath, CancellationToken ct = default) + { + string[] args = string.IsNullOrEmpty(baseCommit) + ? ["diff", "--", relativePath] + : ["diff", $"{baseCommit}..HEAD", "--", relativePath]; + var (_, stdout, _) = await RunGitAsync(worktreePath, args, ct); + return stdout; + } + public async Task WorktreeRemoveAsync(string repoDir, string worktreePath, bool force = false, CancellationToken ct = default) { var args = new List { "worktree", "remove" }; diff --git a/src/ClaudeDo.Ui/ViewModels/Modals/WorktreeModalViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Modals/WorktreeModalViewModel.cs index 19d6e90..d0d1492 100644 --- a/src/ClaudeDo.Ui/ViewModels/Modals/WorktreeModalViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Modals/WorktreeModalViewModel.cs @@ -10,6 +10,7 @@ public sealed partial class WorktreeNodeViewModel : ViewModelBase public required string Name { get; init; } public string? Status { get; init; } public bool IsDirectory { get; init; } + public string RelativePath { get; init; } = ""; public ObservableCollection Children { get; } = new(); } @@ -21,6 +22,8 @@ public sealed partial class WorktreeModalViewModel : ViewModelBase [ObservableProperty] private string _worktreePath = ""; [ObservableProperty] private string? _baseCommit; + [ObservableProperty] private WorktreeNodeViewModel? _selectedNode; + [ObservableProperty] private string _selectedFileDiff = ""; // Set by the view (same pattern as DiffModalViewModel.CloseAction) public Action? CloseAction { get; set; } @@ -30,6 +33,29 @@ public sealed partial class WorktreeModalViewModel : ViewModelBase _git = git; } + partial void OnSelectedNodeChanged(WorktreeNodeViewModel? value) + { + _ = LoadFileDiffAsync(value); + } + + private async Task LoadFileDiffAsync(WorktreeNodeViewModel? node) + { + if (node is null || node.IsDirectory || string.IsNullOrEmpty(node.RelativePath)) + { + SelectedFileDiff = ""; + return; + } + + try + { + SelectedFileDiff = await _git.GetFileDiffAsync(WorktreePath, BaseCommit, node.RelativePath); + } + catch + { + SelectedFileDiff = ""; + } + } + [RelayCommand] private void Close() => CloseAction?.Invoke(); @@ -97,7 +123,8 @@ public sealed partial class WorktreeModalViewModel : ViewModelBase { Name = segments[^1], Status = status, - IsDirectory = false + IsDirectory = false, + RelativePath = path }; if (parent == null) Root.Add(leaf); else parent.Children.Add(leaf); diff --git a/src/ClaudeDo.Ui/Views/Modals/WorktreeModalView.axaml b/src/ClaudeDo.Ui/Views/Modals/WorktreeModalView.axaml index 444fcb4..0bbf09a 100644 --- a/src/ClaudeDo.Ui/Views/Modals/WorktreeModalView.axaml +++ b/src/ClaudeDo.Ui/Views/Modals/WorktreeModalView.axaml @@ -4,12 +4,13 @@ x:Class="ClaudeDo.Ui.Views.Modals.WorktreeModalView" x:DataType="vm:WorktreeModalViewModel" Title="Worktree" - Width="640" Height="720" + Width="1100" Height="720" + MinWidth="640" MinHeight="400" WindowStartupLocation="CenterOwner" SystemDecorations="None" ExtendClientAreaToDecorationsHint="True" Background="Transparent" - CanResize="False" + CanResize="True" TransparencyLevelHint="AcrylicBlur"> @@ -39,27 +40,51 @@ TextTrimming="CharacterEllipsis"/> - - - - - - - - + + + + + + + + - - - - - + + + + + + + + + + + + + + + + + diff --git a/src/ClaudeDo.Ui/Views/Modals/WorktreeModalView.axaml.cs b/src/ClaudeDo.Ui/Views/Modals/WorktreeModalView.axaml.cs index bb650ea..da5aa2b 100644 --- a/src/ClaudeDo.Ui/Views/Modals/WorktreeModalView.axaml.cs +++ b/src/ClaudeDo.Ui/Views/Modals/WorktreeModalView.axaml.cs @@ -21,6 +21,18 @@ public partial class WorktreeModalView : Window base.OnDataContextChanged(e); if (DataContext is WorktreeModalViewModel vm) vm.CloseAction = Close; + + // Wire TreeView selection — SelectedItem TwoWay binding may not fire + // reliably in Avalonia 12 for TreeView; use SelectionChanged as backup. + var tree = this.FindControl("FileTree"); + if (tree is not null) + tree.SelectionChanged += OnFileTreeSelectionChanged; + } + + private void OnFileTreeSelectionChanged(object? sender, SelectionChangedEventArgs e) + { + if (DataContext is WorktreeModalViewModel vm && sender is TreeView tree) + vm.SelectedNode = tree.SelectedItem as WorktreeNodeViewModel; } protected override async void OnOpened(EventArgs e) diff --git a/src/ClaudeDo.Ui/Views/Modals/WorktreesOverviewModalView.axaml b/src/ClaudeDo.Ui/Views/Modals/WorktreesOverviewModalView.axaml index 53ec248..cbf552a 100644 --- a/src/ClaudeDo.Ui/Views/Modals/WorktreesOverviewModalView.axaml +++ b/src/ClaudeDo.Ui/Views/Modals/WorktreesOverviewModalView.axaml @@ -8,7 +8,7 @@ Width="900" Height="560" MinWidth="640" MinHeight="360" CanResize="True" WindowStartupLocation="CenterOwner" - Background="{DynamicResource VoidBrush}" + Background="{DynamicResource SurfaceBrush}" SystemDecorations="None" ExtendClientAreaToDecorationsHint="True"> @@ -104,10 +104,7 @@ - - + -