feat(central-ui): Flow Debugger refreshes pending outbound outcomes after a Live run

After a successful Live run whose graph has Pending outbound steps the page
re-reads the tree through the existing Replay command every 5 s for up to
2 min (page constants), merging by replacing the graph and keeping the
selection by event id; never in Replay mode; cancelled by a new run, a mode
switch or Dispose. Status strip gains a Pending count, a 'Refresh outcomes'
button (both modes) and an 'outcomes still arriving' note while polling. The
run's own timing and the Completed chip are left alone — pending outbound
work is not a failed run. Plan 2026-09-06 D5.

Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
This commit is contained in:
Joseph Doherty
2026-09-06 02:03:16 -04:00
parent 8f6f7ed92b
commit c3b4068de2
4 changed files with 232 additions and 6 deletions
@@ -88,9 +88,9 @@
<label class="form-label form-label-sm">Mode</label>
<div class="btn-group btn-group-sm d-block" role="group" aria-label="Mode">
<button type="button" class="btn @(_mode == FlowMode.Live ? "btn-primary" : "btn-outline-secondary")"
data-test="flow-mode-live" disabled="@_running" @onclick="() => _mode = FlowMode.Live">Live</button>
data-test="flow-mode-live" disabled="@_running" @onclick="() => SetMode(FlowMode.Live)">Live</button>
<button type="button" class="btn @(_mode == FlowMode.Replay ? "btn-primary" : "btn-outline-secondary")"
data-test="flow-mode-replay" disabled="@_running" @onclick="() => _mode = FlowMode.Replay">Replay</button>
data-test="flow-mode-replay" disabled="@_running" @onclick="() => SetMode(FlowMode.Replay)">Replay</button>
</div>
</div>
@@ -144,6 +144,19 @@
<span><span class="fd-label">Steps</span><span class="fd-mono" data-test="flow-step-count">@_graph.Steps.Count</span></span>
<span><span class="fd-label">Boundaries</span><span class="fd-mono">@_graph.Edges.Count(e => e.CrossesBoundary)</span></span>
<span><span class="fd-label">Writes</span><span class="fd-mono">@_graph.Steps.Count(s => s.Kind == FlowStepKind.AttributeWrite)</span></span>
@if (PendingCount > 0)
{
<span><span class="fd-label">Pending</span><span class="fd-mono" data-test="flow-pending-count">@PendingCount</span></span>
}
}
@if (_raw != null)
{
<button type="button" class="btn btn-outline-secondary btn-sm py-0" data-test="flow-refresh"
disabled="@(!CanRefresh)" @onclick="RefreshOutcomesAsync">Refresh outcomes</button>
}
@if (_refreshing)
{
<span class="text-muted small" data-test="flow-pending-note">outcomes still arriving — refreshing</span>
}
@if (_raw != null)
{