523 lines
21 KiB
Markdown
523 lines
21 KiB
Markdown
# Terminal-style Review Controls Implementation Plan
|
||
|
||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||
|
||
**Goal:** Move review feedback into the Output (terminal) tab as a prompt-style input with `[Retry]`/`[Reset]` actions, and relocate Approve + all merge/worktree controls to a new **Git** tab.
|
||
|
||
**Architecture:** Pure UI-layer change in `ClaudeDo.Ui`. Add an `IsGitTab` computed flag to `DetailsIslandViewModel`, re-home existing XAML blocks across three tabs (Output · Git · Session) in `WorkConsole.axaml`, add a bottom-docked review footer to the Output tab, and intercept Enter in `WorkConsole.axaml.cs`. No worker-side or `IWorkerClient` changes; no ViewModel command renames.
|
||
|
||
**Tech Stack:** .NET 8, Avalonia 12 (Fluent), CommunityToolkit.Mvvm, xUnit (ClaudeDo.Ui.Tests).
|
||
|
||
**Reference spec:** `docs/superpowers/specs/2026-06-05-terminal-review-design.md`
|
||
|
||
**Build/test note (from CLAUDE.md):** A running Worker locks `Debug` output — build UI in `-c Release`:
|
||
`dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj -c Release`
|
||
`dotnet test tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj -c Release`
|
||
|
||
---
|
||
|
||
## File Structure
|
||
|
||
- Modify: `src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs` — add `IsGitTab`, wire notifications.
|
||
- Modify: `src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml` — add Git tab button; split tab bodies; add Output-tab review footer; update Session empty-state text.
|
||
- Modify: `src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml.cs` — Enter-to-Retry key handling.
|
||
- Test: `tests/ClaudeDo.Ui.Tests/ViewModels/DetailsIslandTabsTests.cs` (create) — `IsGitTab` behavior.
|
||
|
||
---
|
||
|
||
### Task 1: Add `IsGitTab` tab flag to the ViewModel
|
||
|
||
**Files:**
|
||
- Modify: `src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs:139-147`
|
||
- Test: `tests/ClaudeDo.Ui.Tests/ViewModels/DetailsIslandTabsTests.cs` (create)
|
||
|
||
- [ ] **Step 1: Write the failing test**
|
||
|
||
Create `tests/ClaudeDo.Ui.Tests/ViewModels/DetailsIslandTabsTests.cs`. Mirror the
|
||
construction pattern from `DetailsIslandPrepModeTests.cs` (temp SQLite db,
|
||
`TestDbFactory`, `StubWorkerClient`, `NullServiceProvider`, `StubNotesApi`).
|
||
|
||
```csharp
|
||
using ClaudeDo.Data;
|
||
using ClaudeDo.Ui.Services;
|
||
using ClaudeDo.Ui.ViewModels.Islands;
|
||
using Microsoft.EntityFrameworkCore;
|
||
|
||
namespace ClaudeDo.Ui.Tests.ViewModels;
|
||
|
||
public class DetailsIslandTabsTests : IDisposable
|
||
{
|
||
private readonly string _dbPath;
|
||
|
||
public DetailsIslandTabsTests()
|
||
{
|
||
_dbPath = Path.Combine(Path.GetTempPath(), $"claudedo_tabs_test_{Guid.NewGuid():N}.db");
|
||
using var ctx = NewContext();
|
||
ctx.Database.EnsureCreated();
|
||
}
|
||
|
||
public void Dispose()
|
||
{
|
||
try { File.Delete(_dbPath); } catch { }
|
||
try { File.Delete(_dbPath + "-wal"); } catch { }
|
||
try { File.Delete(_dbPath + "-shm"); } catch { }
|
||
}
|
||
|
||
private ClaudeDoDbContext NewContext()
|
||
{
|
||
var opts = new DbContextOptionsBuilder<ClaudeDoDbContext>()
|
||
.UseSqlite($"Data Source={_dbPath}")
|
||
.Options;
|
||
return new ClaudeDoDbContext(opts);
|
||
}
|
||
|
||
private sealed class TestDbFactory : IDbContextFactory<ClaudeDoDbContext>
|
||
{
|
||
private readonly Func<ClaudeDoDbContext> _create;
|
||
public TestDbFactory(Func<ClaudeDoDbContext> create) => _create = create;
|
||
public ClaudeDoDbContext CreateDbContext() => _create();
|
||
}
|
||
|
||
private sealed class StubNotesApi : ClaudeDo.Ui.Services.Interfaces.INotesApi
|
||
{
|
||
public Task<List<DailyNoteDto>> ListAsync(DateOnly day) => Task.FromResult(new List<DailyNoteDto>());
|
||
public Task<DailyNoteDto?> AddAsync(DateOnly day, string text) => Task.FromResult<DailyNoteDto?>(null);
|
||
public Task UpdateAsync(string id, string text) => Task.CompletedTask;
|
||
public Task DeleteAsync(string id) => Task.CompletedTask;
|
||
}
|
||
|
||
private sealed class NullServiceProvider : IServiceProvider
|
||
{
|
||
public object? GetService(Type serviceType) => null;
|
||
}
|
||
|
||
// StubWorkerClient is abstract — use a concrete no-op subclass (same pattern as DetailsIslandPrepModeTests).
|
||
private sealed class DefaultStub : StubWorkerClient { }
|
||
|
||
private DetailsIslandViewModel NewVm()
|
||
{
|
||
var factory = new TestDbFactory(NewContext);
|
||
return new DetailsIslandViewModel(factory, new DefaultStub(), new NullServiceProvider(), new StubNotesApi());
|
||
}
|
||
|
||
[Fact]
|
||
public void SelectTab_git_sets_IsGitTab_and_clears_others()
|
||
{
|
||
var vm = NewVm();
|
||
|
||
vm.SelectTabCommand.Execute("git");
|
||
|
||
Assert.True(vm.IsGitTab);
|
||
Assert.False(vm.IsOutputTab);
|
||
Assert.False(vm.IsSessionTab);
|
||
}
|
||
|
||
[Fact]
|
||
public void Default_tab_is_output_not_git()
|
||
{
|
||
var vm = NewVm();
|
||
|
||
Assert.True(vm.IsOutputTab);
|
||
Assert.False(vm.IsGitTab);
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Run test to verify it fails**
|
||
|
||
Run: `dotnet test tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj -c Release --filter DetailsIslandTabsTests`
|
||
Expected: FAIL — compile error, `DetailsIslandViewModel` has no `IsGitTab`.
|
||
|
||
- [ ] **Step 3: Add `IsGitTab` to the ViewModel**
|
||
|
||
In `DetailsIslandViewModel.cs`, find the `SelectedTab` property notifications and the
|
||
tab getters (around lines 139-147). Add the `IsGitTab` notification and getter:
|
||
|
||
```csharp
|
||
[NotifyPropertyChangedFor(nameof(IsOutputTab))]
|
||
[NotifyPropertyChangedFor(nameof(IsSessionTab))]
|
||
[NotifyPropertyChangedFor(nameof(IsGitTab))]
|
||
```
|
||
|
||
```csharp
|
||
public bool IsOutputTab => SelectedTab == "output";
|
||
public bool IsGitTab => SelectedTab == "git";
|
||
public bool IsSessionTab => SelectedTab == "session";
|
||
```
|
||
|
||
(Leave `SelectTab` unchanged — it already accepts any string and defaults to `"output"`.)
|
||
|
||
- [ ] **Step 4: Run test to verify it passes**
|
||
|
||
Run: `dotnet test tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj -c Release --filter DetailsIslandTabsTests`
|
||
Expected: PASS (2 tests).
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
git add src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs tests/ClaudeDo.Ui.Tests/ViewModels/DetailsIslandTabsTests.cs
|
||
git commit -m "feat(ui): add IsGitTab flag to work console view model"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 2: Add the Git tab button and move the merge/worktree block onto it
|
||
|
||
**Files:**
|
||
- Modify: `src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml:124-135` (tab strip)
|
||
- Modify: `src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml:164-273` (tab body)
|
||
|
||
- [ ] **Step 1: Add the Git tab button**
|
||
|
||
In the tab strip `StackPanel` (lines 124-135), insert a Git button between the Output
|
||
and Session buttons:
|
||
|
||
```xml
|
||
<StackPanel Orientation="Horizontal">
|
||
<Button Classes="tab-btn"
|
||
Classes.active="{Binding IsOutputTab}"
|
||
Content="Output"
|
||
Command="{Binding SelectTabCommand}"
|
||
CommandParameter="output" />
|
||
<Button Classes="tab-btn"
|
||
Classes.active="{Binding IsGitTab}"
|
||
Content="Git"
|
||
Command="{Binding SelectTabCommand}"
|
||
CommandParameter="git" />
|
||
<Button Classes="tab-btn"
|
||
Classes.active="{Binding IsSessionTab}"
|
||
Content="Session"
|
||
Command="{Binding SelectTabCommand}"
|
||
CommandParameter="session" />
|
||
</StackPanel>
|
||
```
|
||
|
||
- [ ] **Step 2: Move the "Merge & worktree" block to a new Git-tab ScrollViewer**
|
||
|
||
In the tab body `Grid` (starts line 139), the body currently holds the Output
|
||
`ScrollViewer` (`IsVisible="{Binding IsOutputTab}"`, lines 142-162) and the Session
|
||
`ScrollViewer` (`IsVisible="{Binding IsSessionTab}"`, lines 165-273).
|
||
|
||
Cut the **entire "Merge & worktree management" `StackPanel`** — the block currently at
|
||
lines 195-241, beginning with the comment `<!-- Merge & worktree management -->` and the
|
||
`<StackPanel Spacing="10" IsVisible="{Binding ShowMergeSection}">` and ending at its
|
||
matching `</StackPanel>` after the `MergeAllError` `TextBlock` (line 241).
|
||
|
||
Add a new Git-tab `ScrollViewer` between the Output and Session `ScrollViewer`s, and
|
||
paste the cut block inside it:
|
||
|
||
```xml
|
||
<!-- Git: merge target, approve, diff, worktree -->
|
||
<ScrollViewer IsVisible="{Binding IsGitTab}" Padding="14,10">
|
||
<StackPanel Spacing="14">
|
||
|
||
<!-- Approve (review-gated) -->
|
||
<StackPanel Spacing="8" IsVisible="{Binding IsWaitingForReview}">
|
||
<TextBlock Classes="section-label" Text="REVIEW" />
|
||
<Button Classes="btn accent" Content="Approve"
|
||
Command="{Binding ApproveReviewCommand}" />
|
||
</StackPanel>
|
||
|
||
<!-- Merge & worktree management (moved from Session tab) -->
|
||
<StackPanel Spacing="10" IsVisible="{Binding ShowMergeSection}">
|
||
<TextBlock Classes="section-label" Text="MERGE & WORKTREE" />
|
||
<StackPanel Spacing="4">
|
||
<TextBlock Classes="field-label" Text="Merge target" />
|
||
<ComboBox ItemsSource="{Binding MergeTargetBranches}"
|
||
SelectedItem="{Binding SelectedMergeTarget, Mode=TwoWay}"
|
||
HorizontalAlignment="Stretch" />
|
||
</StackPanel>
|
||
<StackPanel Spacing="0">
|
||
<TextBlock Classes="meta" Text="{Binding MergePreviewText}" TextWrapping="Wrap"
|
||
Foreground="{DynamicResource MossBrush}"
|
||
IsVisible="{Binding MergeIsClean}" />
|
||
<TextBlock Classes="meta" Text="{Binding MergePreviewText}" TextWrapping="Wrap"
|
||
Foreground="{DynamicResource BloodBrush}"
|
||
IsVisible="{Binding MergeIsConflict}" />
|
||
<TextBlock Classes="meta" Text="{Binding MergePreviewText}" TextWrapping="Wrap"
|
||
Foreground="{DynamicResource TextMuteBrush}"
|
||
IsVisible="{Binding ShowMergePreviewMuted}" />
|
||
</StackPanel>
|
||
<WrapPanel Orientation="Horizontal">
|
||
<Button Classes="btn" Content="Open Diff" Margin="0,0,8,8"
|
||
Command="{Binding OpenDiffCommand}" />
|
||
<Button Classes="btn accent" Content="Merge" Margin="0,0,8,8"
|
||
Command="{Binding MergeCommand}"
|
||
IsVisible="{Binding ShowSingleMerge}" />
|
||
<Button Classes="btn" Margin="0,0,8,8"
|
||
Command="{Binding OpenWorktreeCommand}">
|
||
<StackPanel Orientation="Horizontal" Spacing="5">
|
||
<TextBlock Text="Worktree" />
|
||
<PathIcon Data="{StaticResource Icon.ArrowOut}" Width="11" Height="11" />
|
||
</StackPanel>
|
||
</Button>
|
||
<Button Classes="btn" Content="Review Combined Diff" Margin="0,0,8,8"
|
||
Command="{Binding ReviewCombinedDiffCommand}" />
|
||
<Button Classes="btn accent" Content="Merge All Subtasks" Margin="0,0,0,8"
|
||
Command="{Binding MergeAllCommand}"
|
||
IsEnabled="{Binding CanMergeAll}"
|
||
ToolTip.Tip="{Binding MergeAllDisabledReason}" />
|
||
</WrapPanel>
|
||
<TextBlock Text="{Binding MergeAllError}"
|
||
Foreground="{DynamicResource BloodBrush}"
|
||
TextWrapping="Wrap"
|
||
IsVisible="{Binding MergeAllError,
|
||
Converter={x:Static ObjectConverters.IsNotNull}}" />
|
||
</StackPanel>
|
||
|
||
</StackPanel>
|
||
</ScrollViewer>
|
||
```
|
||
|
||
- [ ] **Step 3: Remove the old review block from the Session tab**
|
||
|
||
In the Session `ScrollViewer` (`IsVisible="{Binding IsSessionTab}"`), delete the
|
||
**"Review controls" `StackPanel`** currently at lines 168-193 (the
|
||
`<!-- Review controls -->` comment, the `<StackPanel Spacing="8" IsVisible="{Binding IsWaitingForReview}">`,
|
||
the REVIEW label, Feedback label, the `ReviewFeedback` TextBox, and the four buttons).
|
||
After this and Step 2, the Session tab's `StackPanel` should contain only the Child
|
||
outcomes block (lines 244-263) and the empty-state `TextBlock` (lines 266-270).
|
||
|
||
- [ ] **Step 4: Build and verify it compiles**
|
||
|
||
Run: `dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj -c Release`
|
||
Expected: Build succeeded, 0 errors.
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
git add src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml
|
||
git commit -m "feat(ui): add Git tab and move merge/approve controls onto it"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 3: Add the prompt-style review footer to the Output tab
|
||
|
||
**Files:**
|
||
- Modify: `src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml` (Output-tab area + the `Grid` body)
|
||
|
||
- [ ] **Step 1: Restructure the Output tab body to dock a footer below the log**
|
||
|
||
The body `Grid` (line 139) overlays all three tab `ScrollViewer`s. Replace the Output
|
||
`ScrollViewer` (lines 142-162) with a `DockPanel` that keeps the log filling and docks
|
||
the review footer at the bottom. Keep `Name="LogScroll"` on the `ScrollViewer` (the
|
||
code-behind references it). Use this exact markup:
|
||
|
||
```xml
|
||
<!-- Output: log + review footer, both gated on IsOutputTab -->
|
||
<DockPanel IsVisible="{Binding IsOutputTab}" LastChildFill="True">
|
||
|
||
<!-- Review footer (terminal prompt) — only while awaiting review -->
|
||
<Border DockPanel.Dock="Bottom"
|
||
IsVisible="{Binding IsWaitingForReview}"
|
||
Background="{DynamicResource Surface2Brush}"
|
||
BorderBrush="{DynamicResource LineBrush}"
|
||
BorderThickness="0,1,0,0"
|
||
Padding="10,6">
|
||
<DockPanel LastChildFill="True">
|
||
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" Spacing="8"
|
||
VerticalAlignment="Bottom" Margin="8,0,0,0">
|
||
<Button Classes="btn accent" Content="Retry"
|
||
Command="{Binding RejectReviewCommand}" />
|
||
<Button Classes="btn" Content="Reset"
|
||
Command="{Binding ParkReviewCommand}" />
|
||
</StackPanel>
|
||
<TextBlock DockPanel.Dock="Left" Text="❯"
|
||
FontFamily="{StaticResource MonoFont}"
|
||
Foreground="{DynamicResource TextMuteBrush}"
|
||
VerticalAlignment="Top" Margin="0,4,8,0" />
|
||
<TextBox Name="ReviewInput"
|
||
Text="{Binding ReviewFeedback, Mode=TwoWay}"
|
||
AcceptsReturn="True"
|
||
TextWrapping="Wrap"
|
||
MaxHeight="160"
|
||
PlaceholderText="Feedback for the next run…"
|
||
Background="Transparent"
|
||
BorderThickness="0"
|
||
Padding="0,2"
|
||
FontFamily="{StaticResource MonoFont}"
|
||
FontSize="{StaticResource FontSizeMono}" />
|
||
</DockPanel>
|
||
</Border>
|
||
|
||
<ScrollViewer Name="LogScroll"
|
||
VerticalScrollBarVisibility="Visible"
|
||
AllowAutoHide="False"
|
||
Padding="12,8,12,4">
|
||
<ItemsControl ItemsSource="{Binding Log}">
|
||
<ItemsControl.ItemTemplate>
|
||
<DataTemplate DataType="vm:LogLineViewModel">
|
||
<Grid ColumnDefinitions="60,*" Margin="0,1">
|
||
<TextBlock Grid.Column="0"
|
||
Classes="log-ts"
|
||
Text="{Binding TimestampFormatted}" />
|
||
<SelectableTextBlock Grid.Column="1"
|
||
Text="{Binding Text}" Tag="{Binding ClassName}"
|
||
Foreground="{DynamicResource TextDimBrush}"
|
||
TextWrapping="Wrap" />
|
||
</Grid>
|
||
</DataTemplate>
|
||
</ItemsControl.ItemTemplate>
|
||
</ItemsControl>
|
||
</ScrollViewer>
|
||
|
||
</DockPanel>
|
||
```
|
||
|
||
- [ ] **Step 2: Build and verify it compiles**
|
||
|
||
Run: `dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj -c Release`
|
||
Expected: Build succeeded, 0 errors.
|
||
|
||
- [ ] **Step 3: Commit**
|
||
|
||
```bash
|
||
git add src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml
|
||
git commit -m "feat(ui): add terminal review footer with Retry/Reset to Output tab"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 4: Enter-to-Retry key handling
|
||
|
||
**Files:**
|
||
- Modify: `src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml.cs`
|
||
|
||
- [ ] **Step 1: Add the KeyDown handler**
|
||
|
||
In `WorkConsole.axaml.cs`, add `using Avalonia.Input;` at the top. Add a handler that
|
||
runs `RejectReviewCommand` on Enter (without Shift) and lets Shift+Enter insert a
|
||
newline. Wire it from the `ReviewInput` TextBox. Full file:
|
||
|
||
```csharp
|
||
using System;
|
||
using System.Collections.Specialized;
|
||
using Avalonia.Controls;
|
||
using Avalonia.Input;
|
||
using ClaudeDo.Ui.ViewModels.Islands;
|
||
|
||
namespace ClaudeDo.Ui.Views.Islands.Detail;
|
||
|
||
public partial class WorkConsole : UserControl
|
||
{
|
||
private INotifyCollectionChanged? _log;
|
||
|
||
public WorkConsole()
|
||
{
|
||
InitializeComponent();
|
||
DataContextChanged += OnDataContextChanged;
|
||
}
|
||
|
||
private void OnDataContextChanged(object? sender, EventArgs e)
|
||
{
|
||
if (_log is not null)
|
||
_log.CollectionChanged -= OnLogChanged;
|
||
|
||
_log = (DataContext as DetailsIslandViewModel)?.Log;
|
||
|
||
if (_log is not null)
|
||
_log.CollectionChanged += OnLogChanged;
|
||
}
|
||
|
||
private void OnLogChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||
{
|
||
if (e.Action != NotifyCollectionChangedAction.Add) return;
|
||
EventHandler? handler = null;
|
||
handler = (_, _) =>
|
||
{
|
||
LogScroll.LayoutUpdated -= handler;
|
||
LogScroll.ScrollToEnd();
|
||
};
|
||
LogScroll.LayoutUpdated += handler;
|
||
}
|
||
|
||
private void OnReviewInputKeyDown(object? sender, KeyEventArgs e)
|
||
{
|
||
if (e.Key != Key.Enter || e.KeyModifiers.HasFlag(KeyModifiers.Shift))
|
||
return;
|
||
|
||
if (DataContext is DetailsIslandViewModel vm &&
|
||
vm.RejectReviewCommand.CanExecute(null))
|
||
{
|
||
vm.RejectReviewCommand.Execute(null);
|
||
}
|
||
|
||
e.Handled = true;
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Wire the handler in XAML**
|
||
|
||
On the `ReviewInput` TextBox added in Task 3, add the event hookup attribute:
|
||
|
||
```xml
|
||
<TextBox Name="ReviewInput"
|
||
KeyDown="OnReviewInputKeyDown"
|
||
Text="{Binding ReviewFeedback, Mode=TwoWay}"
|
||
```
|
||
|
||
- [ ] **Step 3: Build and verify it compiles**
|
||
|
||
Run: `dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj -c Release`
|
||
Expected: Build succeeded, 0 errors.
|
||
|
||
- [ ] **Step 4: Commit**
|
||
|
||
```bash
|
||
git add src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml.cs
|
||
git commit -m "feat(ui): send Retry on Enter in the review prompt"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 5: Update the Session empty-state copy
|
||
|
||
**Files:**
|
||
- Modify: `src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml` (empty-state `TextBlock`, was line 266-270)
|
||
|
||
- [ ] **Step 1: Reword the empty-state text**
|
||
|
||
The Session empty-state still says review/merge controls appear there. Replace its
|
||
`Text` so it reflects that those moved:
|
||
|
||
```xml
|
||
<TextBlock IsVisible="{Binding ShowSessionEmpty}"
|
||
Classes="meta"
|
||
Foreground="{DynamicResource TextMuteBrush}"
|
||
TextWrapping="Wrap"
|
||
Text="Nothing to manage yet — subtask outcomes appear here once the run finishes. Review in the Output tab, merge in the Git tab." />
|
||
```
|
||
|
||
- [ ] **Step 2: Build and verify it compiles**
|
||
|
||
Run: `dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj -c Release`
|
||
Expected: Build succeeded, 0 errors.
|
||
|
||
- [ ] **Step 3: Commit**
|
||
|
||
```bash
|
||
git add src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml
|
||
git commit -m "docs(ui): reword Session empty-state for relocated review/merge controls"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 6: Final verification
|
||
|
||
- [ ] **Step 1: Run the full UI test project**
|
||
|
||
Run: `dotnet test tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj -c Release`
|
||
Expected: all tests PASS.
|
||
|
||
- [ ] **Step 2: Manual visual verification (cannot be auto-verified — flag to user)**
|
||
|
||
Launch the app with a task in `WaitingForReview` and confirm:
|
||
- Output tab shows the prompt footer (`❯` + input + `[Retry]` `[Reset]`) only while awaiting review; it is hidden otherwise.
|
||
- Typing + **Enter** sends Retry (requeues with feedback); **Shift+Enter** inserts a newline; **Enter on empty input** does nothing.
|
||
- `[Reset]` parks the task to Idle.
|
||
- Git tab shows **Approve** + merge target + Open Diff / Merge / Worktree / Review Combined Diff / Merge All Subtasks.
|
||
- Session tab shows only subtask outcomes / the reworded empty state.
|
||
- Tab switching highlights the active tab correctly (Output ↔ Git ↔ Session).
|