feat(daily-prep): trigger planning from inside the prep-log window with an empty-state hint

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
mika kuns
2026-06-04 10:01:27 +02:00
parent 26758b6e8a
commit 7d743f17c6
9 changed files with 55 additions and 36 deletions

View File

@@ -66,7 +66,6 @@
"addPlaceholder": "Aufgabe hinzufügen…", "addPlaceholder": "Aufgabe hinzufügen…",
"enterKey": "ENTER", "enterKey": "ENTER",
"notesPinnedRow": "Notizen (Tagesnotizen)", "notesPinnedRow": "Notizen (Tagesnotizen)",
"prepareDay": "Tag vorbereiten",
"clearDayTip": "Tag leeren", "clearDayTip": "Tag leeren",
"prepLogTip": "Vorbereitungs-Log", "prepLogTip": "Vorbereitungs-Log",
"overdue": "ÜBERFÄLLIG", "overdue": "ÜBERFÄLLIG",
@@ -140,7 +139,9 @@
"previewBtn": "Vorschau", "previewBtn": "Vorschau",
"editBtn": "Bearbeiten", "editBtn": "Bearbeiten",
"descriptionPlaceholder": "Aufgabendetails hinzufügen (Markdown unterstützt)...", "descriptionPlaceholder": "Aufgabendetails hinzufügen (Markdown unterstützt)...",
"prepTitle": "Tagesvorbereitung" "prepTitle": "Tagesvorbereitung",
"planDay": "Tag planen",
"prepEmpty": "Heute noch keine Vorbereitung — klick Tag planen"
}, },
"agent": { "agent": {
"stopTip": "Agent stoppen", "stopTip": "Agent stoppen",

View File

@@ -66,7 +66,6 @@
"addPlaceholder": "Add a task…", "addPlaceholder": "Add a task…",
"enterKey": "ENTER", "enterKey": "ENTER",
"notesPinnedRow": "Notes (daily notes)", "notesPinnedRow": "Notes (daily notes)",
"prepareDay": "Prepare day",
"clearDayTip": "Clear day", "clearDayTip": "Clear day",
"prepLogTip": "Prep log", "prepLogTip": "Prep log",
"overdue": "OVERDUE", "overdue": "OVERDUE",
@@ -140,7 +139,9 @@
"previewBtn": "Preview", "previewBtn": "Preview",
"editBtn": "Edit", "editBtn": "Edit",
"descriptionPlaceholder": "Add task details (markdown supported)...", "descriptionPlaceholder": "Add task details (markdown supported)...",
"prepTitle": "Daily prep" "prepTitle": "Daily prep",
"planDay": "Plan day",
"prepEmpty": "No prep run today yet — click Plan day"
}, },
"agent": { "agent": {
"stopTip": "Stop agent", "stopTip": "Stop agent",

View File

@@ -346,6 +346,8 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
RecomputeCanMergeAll(); RecomputeCanMergeAll();
ReviewCombinedDiffCommand.NotifyCanExecuteChanged(); ReviewCombinedDiffCommand.NotifyCanExecuteChanged();
}; };
PrepLog.CollectionChanged += (_, _) => OnPropertyChanged(nameof(ShowPrepEmptyState));
} }
private void OnTaskMessage(string taskId, string line) private void OnTaskMessage(string taskId, string line)
@@ -381,6 +383,18 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
AppendClaudeText(formatted, target, buf); AppendClaudeText(formatted, target, buf);
} }
[RelayCommand]
private async Task PlanDayAsync()
{
if (_worker is null) return;
try { await _worker.RunDailyPrepNowAsync(); }
catch { /* worker offline; PrepStarted/PrepLine will reconcile */ }
}
public bool ShowPrepEmptyState => !IsPrepRunning && PrepLog.Count == 0;
partial void OnIsPrepRunningChanged(bool value) => OnPropertyChanged(nameof(ShowPrepEmptyState));
private void OnPrepStarted() private void OnPrepStarted()
{ {
PrepLog.Clear(); PrepLog.Clear();

View File

@@ -37,15 +37,6 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
NotesRequested?.Invoke(); NotesRequested?.Invoke();
} }
[RelayCommand]
private async Task PrepareDayAsync()
{
if (_worker is null) return;
PrepRequested?.Invoke();
try { await _worker.RunDailyPrepNowAsync(); }
catch { /* worker offline; broadcast will reconcile on return */ }
}
[RelayCommand] [RelayCommand]
private void ShowPrepLog() => PrepRequested?.Invoke(); private void ShowPrepLog() => PrepRequested?.Invoke();

View File

@@ -303,9 +303,23 @@
<islands:NotesEditorView DataContext="{Binding Notes}"/> <islands:NotesEditorView DataContext="{Binding Notes}"/>
</Panel> </Panel>
<Panel IsVisible="{Binding IsPrepMode}"> <Panel IsVisible="{Binding IsPrepMode}">
<islands:SessionTerminalView <DockPanel>
Entries="{Binding PrepLog}" Label="daily-prep" <Border DockPanel.Dock="Top" Padding="12,8">
IsRunning="{Binding IsPrepRunning}"/> <Button Classes="btn primary"
Command="{Binding PlanDayCommand}"
IsEnabled="{Binding !IsPrepRunning}"
Content="{loc:Tr details.planDay}"/>
</Border>
<Panel>
<islands:SessionTerminalView
Entries="{Binding PrepLog}" Label="daily-prep"
IsRunning="{Binding IsPrepRunning}"/>
<TextBlock IsVisible="{Binding ShowPrepEmptyState}"
HorizontalAlignment="Center" VerticalAlignment="Center"
Foreground="{DynamicResource TextMuteBrush}"
Text="{loc:Tr details.prepEmpty}"/>
</Panel>
</DockPanel>
</Panel> </Panel>
</Grid> </Grid>

View File

@@ -79,14 +79,6 @@
Command="{Binding OpenNotesCommand}" Command="{Binding OpenNotesCommand}"
Content="{loc:Tr tasks.notesPinnedRow}"/> Content="{loc:Tr tasks.notesPinnedRow}"/>
<!-- Prepare Day button (My Day only) -->
<Button DockPanel.Dock="Top"
Classes="btn" HorizontalAlignment="Stretch" HorizontalContentAlignment="Left"
Margin="16,0,16,8"
IsVisible="{Binding IsMyDayList}"
Command="{Binding PrepareDayCommand}"
Content="{loc:Tr tasks.prepareDay}"/>
<!-- Task list --> <!-- Task list -->
<ScrollViewer> <ScrollViewer>
<StackPanel Margin="10,4"> <StackPanel Margin="10,4">

View File

@@ -33,6 +33,7 @@ public abstract class StubWorkerClient : IWorkerClient
#pragma warning restore CS0067 #pragma warning restore CS0067
public int ClearMyDayCalls { get; private set; } public int ClearMyDayCalls { get; private set; }
public int RunDailyPrepNowCalls { get; private set; }
public void RaisePrepStarted() => PrepStartedEvent?.Invoke(); public void RaisePrepStarted() => PrepStartedEvent?.Invoke();
public void RaisePrepLine(string line) => PrepLineEvent?.Invoke(line); public void RaisePrepLine(string line) => PrepLineEvent?.Invoke(line);
@@ -71,7 +72,7 @@ public abstract class StubWorkerClient : IWorkerClient
public virtual Task QueuePlanningSubtasksAsync(string parentTaskId, CancellationToken ct = default) => Task.CompletedTask; public virtual Task QueuePlanningSubtasksAsync(string parentTaskId, CancellationToken ct = default) => Task.CompletedTask;
public virtual Task<string?> GetWeekReportAsync(DateOnly start, DateOnly end) => Task.FromResult<string?>(null); public virtual Task<string?> GetWeekReportAsync(DateOnly start, DateOnly end) => Task.FromResult<string?>(null);
public virtual Task<string> GenerateWeekReportAsync(DateOnly start, DateOnly end) => Task.FromResult(""); public virtual Task<string> GenerateWeekReportAsync(DateOnly start, DateOnly end) => Task.FromResult("");
public virtual Task<bool> RunDailyPrepNowAsync() => Task.FromResult(false); public virtual Task<bool> RunDailyPrepNowAsync() { RunDailyPrepNowCalls++; return Task.FromResult(false); }
public virtual Task ClearMyDayAsync() { ClearMyDayCalls++; return Task.CompletedTask; } public virtual Task ClearMyDayAsync() { ClearMyDayCalls++; return Task.CompletedTask; }
public virtual Task<AppSettingsDto?> GetAppSettingsAsync() => Task.FromResult<AppSettingsDto?>(null); public virtual Task<AppSettingsDto?> GetAppSettingsAsync() => Task.FromResult<AppSettingsDto?>(null);
public virtual Task<List<DailyNoteDto>> GetDailyNotesAsync(DateOnly day) => Task.FromResult(new List<DailyNoteDto>()); public virtual Task<List<DailyNoteDto>> GetDailyNotesAsync(DateOnly day) => Task.FromResult(new List<DailyNoteDto>());

View File

@@ -92,4 +92,20 @@ public class DetailsIslandPrepModeTests : IDisposable
Assert.NotEmpty(vm.PrepLog); Assert.NotEmpty(vm.PrepLog);
} }
[Fact]
public async Task PlanDayCommand_calls_worker()
{
var stub = new DefaultStub();
var vm = NewDetailsVm(stub);
await vm.PlanDayCommand.ExecuteAsync(null);
Assert.Equal(1, stub.RunDailyPrepNowCalls);
}
[Fact]
public void ShowPrepEmptyState_true_when_empty_and_not_running()
{
var vm = NewDetailsVm(new DefaultStub());
Assert.True(vm.ShowPrepEmptyState);
}
} }

View File

@@ -53,15 +53,4 @@ public class TasksIslandDailyPrepTests : IDisposable
Assert.Equal(1, stub.ClearMyDayCalls); Assert.Equal(1, stub.ClearMyDayCalls);
} }
[Fact]
public async Task PrepareDayCommand_raises_PrepRequested()
{
var vm = NewTasksVm(new DefaultStub());
var raised = false;
vm.PrepRequested += () => raised = true;
await vm.PrepareDayCommand.ExecuteAsync(null);
Assert.True(raised);
}
} }