Files
ClaudeDo/docs/superpowers/plans/2026-06-23-worker-log-footer-overlay.md

3.3 KiB

Plan — Worker log → footer + Log Visualizer overlay

Design: docs/superpowers/specs/2026-06-23-worker-log-footer-overlay-design.md. Build on main, TDD, commit per task (Conventional Commits, explicit paths — shared worktree). Build -c Release.

Task 1 — LogRingBuffer (Worker) + tests

  • src/ClaudeDo.Worker/Logging/WorkerLogRecord.csrecord WorkerLogRecord(string Message, WorkerLogLevel Level, DateTime TimestampUtc).
  • src/ClaudeDo.Worker/Logging/LogRingBuffer.cs — thread-safe, TimeSpan window + int cap; Append(record), Snapshot(). Uses an injected clock func (Func<DateTime>) for testability (default () => DateTime.UtcNow).
  • Tests: age eviction, cap eviction, snapshot order. No DateTime.UtcNow in tests — drive the clock.

Task 2 — BroadcastLogSink (Worker) + tests

  • src/ClaudeDo.Worker/Logging/BroadcastLogSink.cs : ILogEventSink — level map, render (+exception first line), append-all-levels, broadcast Warn/Err via deferred HubBroadcaster (Attach), dedupe window (const 120s), loop-guard (skip SignalR SourceContext for broadcast; swallow broadcast exceptions). Inject clock func.
  • Broadcaster is an abstraction the test can fake: depend on a tiny Func<string,WorkerLogLevel,DateTime,Task>? set by Attach, OR on HubBroadcaster directly (it's a sealed class — prefer a delegate to keep the test pure). Use a delegate.
  • Tests: all levels buffered; only Warn/Err invoke the broadcast delegate; dedupe suppresses 2nd identical within window but still buffers; exception rendering; SignalR-source event buffered but not broadcast.

Task 3 — wire into Program.cs + WorkerHub.GetRecentLogs

  • Program.cs: create LogRingBuffer + BroadcastLogSink locals before build; .WriteTo.Sink(broadcastSink); AddSingleton(logBuffer); after build broadcastSink.Attach((m,l,t) => broadcaster.WorkerLog(m,l,t)) using resolved HubBroadcaster.
  • WorkerHub: inject LogRingBuffer; public IReadOnlyList<WorkerLogRecordDto> GetRecentLogs() → snapshot mapped to DTO. Add WorkerLogRecordDto (Hub or shared). Update WorkerHub ctor → check hub-construction call sites/tests.
  • Build Worker -c Release; run Worker.Tests (filtered to new + hub).

Task 4 — IWorkerClient.GetRecentLogsAsync + WorkerClient + fakes

  • IWorkerClient + WorkerClient impl (_hub.InvokeAsync<List<WorkerLogEntry>>("GetRecentLogs", ct)).
  • Update fakes: tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs, Worker.Tests UiVm fake(s) → return Array.Empty<WorkerLogEntry>().
  • Build Ui + Worker.Tests.

Task 5 — LogVisualizerViewModel + View + dialog wiring + tests

  • VM (Modals/), View (Modals/, ModalShell), IDialogService.ShowLogVisualizerAsync + WindowDialogService impl.
  • IslandsShellViewModel.OpenLogVisualizerCommand (resolves VM, loads, shows). Make footer worker-log line a clickable Button → command.
  • Localization vm.logVisualizer en+de.
  • Tests: VM load/populate/filter. Build App -c Release; Ui.Tests + Localization.Tests.

Task 6 — verify + docs

  • Full relevant test pass. Update src/ClaudeDo.Ui/CLAUDE.md (overlay VM/view, footer click) + src/ClaudeDo.Worker/CLAUDE.md (Logging/ folder, sink, GetRecentLogs, WorkerLog now carries Serilog Warn/Err). Note visual-verification gap (overlay render) for the user.