feat(daily-prep): add live prep-output mode to the Details island
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -138,7 +138,8 @@
|
||||
"toggleEditPreviewTip": "Bearbeiten/Vorschau umschalten",
|
||||
"previewBtn": "Vorschau",
|
||||
"editBtn": "Bearbeiten",
|
||||
"descriptionPlaceholder": "Aufgabendetails hinzufügen (Markdown unterstützt)..."
|
||||
"descriptionPlaceholder": "Aufgabendetails hinzufügen (Markdown unterstützt)...",
|
||||
"prepTitle": "Tagesvorbereitung"
|
||||
},
|
||||
"agent": {
|
||||
"stopTip": "Agent stoppen",
|
||||
|
||||
@@ -138,7 +138,8 @@
|
||||
"toggleEditPreviewTip": "Toggle edit/preview",
|
||||
"previewBtn": "Preview",
|
||||
"editBtn": "Edit",
|
||||
"descriptionPlaceholder": "Add task details (markdown supported)..."
|
||||
"descriptionPlaceholder": "Add task details (markdown supported)...",
|
||||
"prepTitle": "Daily prep"
|
||||
},
|
||||
"agent": {
|
||||
"stopTip": "Stop agent",
|
||||
|
||||
@@ -54,6 +54,14 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
||||
private readonly INotesApi _notesApi;
|
||||
|
||||
[ObservableProperty] private bool _isNotesMode;
|
||||
[ObservableProperty] private bool _isPrepMode;
|
||||
[ObservableProperty] private bool _isPrepRunning;
|
||||
public ObservableCollection<LogLineViewModel> PrepLog { get; } = new();
|
||||
public bool IsTaskDetailVisible => !IsNotesMode && !IsPrepMode;
|
||||
|
||||
partial void OnIsNotesModeChanged(bool value) => OnPropertyChanged(nameof(IsTaskDetailVisible));
|
||||
partial void OnIsPrepModeChanged(bool value) => OnPropertyChanged(nameof(IsTaskDetailVisible));
|
||||
|
||||
public NotesEditorViewModel Notes { get; private set; } = null!;
|
||||
|
||||
// Current task row (set by IslandsShellViewModel via Bind)
|
||||
@@ -207,6 +215,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
||||
// Claude CLI stream-json parser + buffer for partial text deltas
|
||||
private readonly StreamLineFormatter _formatter = new();
|
||||
private readonly StringBuilder _claudeBuf = new();
|
||||
private readonly StringBuilder _prepClaudeBuf = new();
|
||||
|
||||
// The task ID we are currently subscribed to for live log messages
|
||||
private string? _subscribedTaskId;
|
||||
@@ -285,6 +294,9 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
||||
|
||||
// Subscribe once; filter by current task id inside the handler
|
||||
_worker.TaskMessageEvent += OnTaskMessage;
|
||||
_worker.PrepStartedEvent += OnPrepStarted;
|
||||
_worker.PrepLineEvent += OnPrepLine;
|
||||
_worker.PrepFinishedEvent += OnPrepFinished;
|
||||
|
||||
// Re-evaluate CanExecute when worker connection flips.
|
||||
_worker.PropertyChanged += (_, e) =>
|
||||
@@ -345,9 +357,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
||||
if (line.StartsWith("[stdout]", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var body = line["[stdout]".Length..].TrimStart();
|
||||
var formatted = _formatter.FormatLine(body);
|
||||
if (formatted is null) return; // filter noise (message_start, etc.)
|
||||
AppendClaudeText(formatted);
|
||||
AppendStdoutLine(Log, body);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -363,20 +373,40 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
||||
Log.Add(new LogLineViewModel { Kind = kind, Text = line });
|
||||
}
|
||||
|
||||
private void AppendClaudeText(string chunk)
|
||||
private void AppendStdoutLine(ObservableCollection<LogLineViewModel> target, string line)
|
||||
{
|
||||
_claudeBuf.Append(chunk);
|
||||
var formatted = _formatter.FormatLine(line);
|
||||
if (formatted is null) return;
|
||||
var buf = ReferenceEquals(target, Log) ? _claudeBuf : _prepClaudeBuf;
|
||||
AppendClaudeText(formatted, target, buf);
|
||||
}
|
||||
|
||||
private void OnPrepStarted()
|
||||
{
|
||||
PrepLog.Clear();
|
||||
IsPrepRunning = true;
|
||||
}
|
||||
|
||||
private void OnPrepLine(string line) => AppendStdoutLine(PrepLog, line);
|
||||
|
||||
private void OnPrepFinished(bool success) => IsPrepRunning = false;
|
||||
|
||||
private void AppendClaudeText(string chunk) => AppendClaudeText(chunk, Log, _claudeBuf);
|
||||
|
||||
private static void AppendClaudeText(string chunk, ObservableCollection<LogLineViewModel> target, StringBuilder buf)
|
||||
{
|
||||
buf.Append(chunk);
|
||||
// Emit a log entry for every completed line; keep the trailing remainder buffered.
|
||||
while (true)
|
||||
{
|
||||
var text = _claudeBuf.ToString();
|
||||
var text = buf.ToString();
|
||||
var nl = text.IndexOf('\n');
|
||||
if (nl < 0) break;
|
||||
var piece = text[..nl].TrimEnd('\r');
|
||||
if (!string.IsNullOrWhiteSpace(piece))
|
||||
Log.Add(new LogLineViewModel { Kind = LogKind.Claude, Text = piece });
|
||||
_claudeBuf.Clear();
|
||||
_claudeBuf.Append(text[(nl + 1)..]);
|
||||
target.Add(new LogLineViewModel { Kind = LogKind.Claude, Text = piece });
|
||||
buf.Clear();
|
||||
buf.Append(text[(nl + 1)..]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,13 +508,22 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
||||
public void ShowNotes()
|
||||
{
|
||||
Bind(null);
|
||||
IsPrepMode = false;
|
||||
IsNotesMode = true;
|
||||
_ = Notes.LoadDayAsync(DateOnly.FromDateTime(DateTime.Today));
|
||||
}
|
||||
|
||||
public void ShowPrep()
|
||||
{
|
||||
Bind(null);
|
||||
IsNotesMode = false;
|
||||
IsPrepMode = true;
|
||||
}
|
||||
|
||||
public void Bind(TaskRowViewModel? row)
|
||||
{
|
||||
IsNotesMode = false;
|
||||
IsPrepMode = false;
|
||||
_loadCts?.Cancel();
|
||||
_loadCts?.Dispose();
|
||||
_loadCts = new CancellationTokenSource();
|
||||
|
||||
@@ -127,10 +127,10 @@
|
||||
<!-- ── Agent status strip (sticky, above metadata footer) ── -->
|
||||
<islands:AgentStripView DockPanel.Dock="Bottom"/>
|
||||
|
||||
<!-- ── Body: task details (normal) or notes editor (notes mode) ── -->
|
||||
<!-- ── Body: task details (normal), notes editor (notes mode), or prep log (prep mode) ── -->
|
||||
<Grid>
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto"
|
||||
IsVisible="{Binding !IsNotesMode}">
|
||||
IsVisible="{Binding IsTaskDetailVisible}">
|
||||
<StackPanel Spacing="0">
|
||||
|
||||
<!-- Planning merge section — visible only for planning parent tasks -->
|
||||
@@ -299,6 +299,29 @@
|
||||
<Panel IsVisible="{Binding IsNotesMode}">
|
||||
<islands:NotesEditorView DataContext="{Binding Notes}"/>
|
||||
</Panel>
|
||||
<Panel IsVisible="{Binding IsPrepMode}">
|
||||
<DockPanel>
|
||||
<TextBlock DockPanel.Dock="Top" Margin="16,12"
|
||||
Text="{loc:Tr details.prepTitle}" Classes="h2"/>
|
||||
<ScrollViewer>
|
||||
<ItemsControl ItemsSource="{Binding PrepLog}">
|
||||
<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>
|
||||
</Panel>
|
||||
</Grid>
|
||||
|
||||
</DockPanel>
|
||||
|
||||
Reference in New Issue
Block a user