feat(ui): replay persisted task log when selecting a task
Read the task's LogPath on selection and feed each line through the live-stream parser so Claude output stays visible across app restarts. Tail-caps at 2000 lines to avoid flooding the UI.
This commit is contained in:
@@ -378,6 +378,9 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
||||
// Subscribe only after DB load confirms the task exists
|
||||
_subscribedTaskId = row.Id;
|
||||
|
||||
// Replay the latest run's persisted log so output is visible across app restarts.
|
||||
await ReplayLogFileAsync(entity.LogPath, ct);
|
||||
|
||||
var subs = await subtaskRepo.GetByTaskIdAsync(row.Id);
|
||||
ct.ThrowIfCancellationRequested();
|
||||
foreach (var s in subs)
|
||||
@@ -386,6 +389,59 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
|
||||
catch (OperationCanceledException) { }
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task ReplayLogFileAsync(string? logPath, CancellationToken ct)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(logPath)) return;
|
||||
var expanded = ExpandUserPath(logPath);
|
||||
if (!System.IO.File.Exists(expanded)) return;
|
||||
|
||||
try
|
||||
{
|
||||
const int maxLines = 2000;
|
||||
string[] all;
|
||||
await using (var fs = new System.IO.FileStream(
|
||||
expanded,
|
||||
System.IO.FileMode.Open,
|
||||
System.IO.FileAccess.Read,
|
||||
System.IO.FileShare.ReadWrite | System.IO.FileShare.Delete))
|
||||
using (var reader = new System.IO.StreamReader(fs))
|
||||
{
|
||||
var list = new List<string>();
|
||||
while (await reader.ReadLineAsync(ct) is { } line)
|
||||
list.Add(line);
|
||||
all = list.ToArray();
|
||||
}
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
var start = Math.Max(0, all.Length - maxLines);
|
||||
for (int i = start; i < all.Length; i++)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
if (_subscribedTaskId is null) return;
|
||||
// Worker writes raw Claude CLI stdout to disk (no prefix) but broadcasts
|
||||
// it with a "[stdout] " prefix. Match the live-stream format so the same
|
||||
// stream-json parser handles both.
|
||||
var line = all[i];
|
||||
var normalized = line.StartsWith("[", StringComparison.Ordinal) ? line : "[stdout] " + line;
|
||||
OnTaskMessage(_subscribedTaskId, normalized);
|
||||
}
|
||||
FlushClaudeBuffer();
|
||||
}
|
||||
catch (OperationCanceledException) { throw; }
|
||||
catch { /* best-effort replay */ }
|
||||
}
|
||||
|
||||
private static string ExpandUserPath(string path)
|
||||
{
|
||||
if (path.StartsWith("~/", StringComparison.Ordinal) || path.StartsWith("~\\", StringComparison.Ordinal))
|
||||
return System.IO.Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
path[2..]);
|
||||
if (path == "~")
|
||||
return Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
return path;
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task RefreshWorktreeAsync(string taskId)
|
||||
{
|
||||
try
|
||||
|
||||
Reference in New Issue
Block a user