feat(central-ui): Flow Debugger step detail links to the report page and exposes Retry/Discard on a parked node

Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
This commit is contained in:
Joseph Doherty
2026-09-06 11:01:08 -04:00
parent 95773aa9fd
commit 9c31f70105
2 changed files with 153 additions and 5 deletions
@@ -8,7 +8,10 @@
Attribute writes · Timing plus a "Where this ran" column with drill-ins.
Outbound-calls addendum (plan 2026-09-06 D3, Task 8): a Lifecycle tab for folded
API / DB / notification operations, kind-specific Input labels, and the
System / Connection / List + "Delivered by" facts. *@
System / Connection / List + "Delivered by" facts.
Follow-ups (plan 2026-09-06 D2/D5, Task 6): an "Open in …" deep link to the report page
for a correlated outbound step, and Retry / Discard buttons on a Parked one when the owner
supplies the callbacks. *@
<div class="card" data-test="flow-step-detail">
<div class="card-body">
@@ -108,7 +111,16 @@
</table>
@if (Step.Outcome == FlowStepOutcome.Parked)
{
<div class="text-muted small mt-2" data-test="flow-step-parked-hint">Retry or discard from Site Calls / Notification report.</div>
<div class="text-muted small mt-2" data-test="flow-step-parked-hint">
@if (ReportHref is { } hintHref)
{
<text>Parked — retry or discard here, or <a href="@hintHref">open the report page</a>.</text>
}
else
{
<text>Parked — retry or discard here, or open the Site Calls / Notification report page.</text>
}
</div>
}
}
break;
@@ -150,6 +162,23 @@
<a class="btn btn-outline-secondary btn-sm" data-test="open-audit-rows"
href="@($"/audit/log?executionId={exec}")">Audit rows (@AuditRowCount)</a>
}
@if (ReportHref is { } rh)
{
<a class="btn btn-outline-secondary btn-sm" data-test="open-report" href="@rh">@ReportLabel</a>
}
@if (ShowActions && (OnRetry.HasDelegate || OnDiscard.HasDelegate))
{
@if (OnRetry.HasDelegate)
{
<button type="button" class="btn btn-outline-primary btn-sm" data-test="flow-step-retry"
disabled="@ActionInProgress" @onclick="() => OnRetry.InvokeAsync(Step!)">Retry</button>
}
@if (OnDiscard.HasDelegate)
{
<button type="button" class="btn btn-outline-danger btn-sm" data-test="flow-step-discard"
disabled="@ActionInProgress" @onclick="() => OnDiscard.InvokeAsync(Step!)">Discard</button>
}
}
</div>
</div>
</div>
@@ -173,10 +202,38 @@
/// <summary>Instance database id for the Debug View drill-in (known for value-change entries).</summary>
[Parameter] public int? InstanceDbId { get; set; }
/// <summary>Retry a parked outbound step (follow-ups plan 2026-09-06 D5). No delegate = no Retry button.</summary>
[Parameter] public EventCallback<FlowStep> OnRetry { get; set; }
/// <summary>Discard a parked outbound step (follow-ups plan 2026-09-06 D5). No delegate = no Discard button.</summary>
[Parameter] public EventCallback<FlowStep> OnDiscard { get; set; }
/// <summary>True while the owner is relaying a Retry / Discard — disables both buttons.</summary>
[Parameter] public bool ActionInProgress { get; set; }
private string _tab = "Input";
private bool IsOutbound => Step is { } s && FlowStepProjector.IsOutbound(s.Kind);
/// <summary>
/// Deep link to the report page that owns the operation (D2): the Notification report for a
/// notification, Site Calls for a cached API / DB call. Null for sync one-shots (no correlation id)
/// and for steps without a folded lifecycle.
/// </summary>
private string? ReportHref
=> Step is { CorrelationId: { } c } s && IsOutbound && s.Lifecycle is { Count: > 0 }
? s.Kind == FlowStepKind.Notify
? $"/notifications/report?correlationId={c:D}"
: $"/site-calls/report?correlationId={c:D}"
: null;
private string ReportLabel
=> Step?.Kind == FlowStepKind.Notify ? "Open in Notification report" : "Open in Site Calls";
/// <summary>Retry / Discard apply only to a parked outbound operation the report pages can address by id.</summary>
private bool ShowActions
=> Step is { Outcome: FlowStepOutcome.Parked, CorrelationId: not null } && IsOutbound;
/// <summary>Outbound steps swap the Writes tab (never populated for them) for the Lifecycle tab.</summary>
private IEnumerable<string> VisibleTabs
=> Step?.Lifecycle is { Count: > 0 } ? TabsWithLifecycle : Tabs;