fix(adminui): #546 recycle DriverStatusPanel on Enabled flip via @key

DriverStatusPanel decides whether to subscribe exactly once, in
OnInitializedAsync (`if (!Enabled) return;`), and exposes no OnParametersSet
hook. The detail row was keyed only by DriverInstanceId, so a live config edit
that flipped a row's Enabled true->false — surfaced on the page by the 5 s
LoadConfigAsync refresh — reused the same panel instance: the markup correctly
switched to "Disabled - not deployed" while the panel's two store SnapshotChanged
subscriptions and its 5 s staleness timer stayed wired underneath, until the
operator happened to collapse the row.

Including the flag in the detail row's key recycles the component instead. Both
directions verified against the panel's own lifecycle: true->false disposes the
old instance (unsubscribing both stores, draining both timers) and mounts one
that skips subscription setup entirely; false->true disposes an instance that
never subscribed (the unsubscribes are no-ops, both timers null) and mounts one
that subscribes and primes from the stores.

The data row's key is unchanged. No behaviour change when Enabled is stable.

Claude-Session: https://claude.ai/code/session_014WNM4vjoVksyyBraTXSZE1
This commit is contained in:
Joseph Doherty
2026-08-06 17:25:41 -04:00
parent d33e4e20b5
commit e669fb349a
@@ -189,7 +189,7 @@ else
@foreach (var d in g.Drivers)
{
@* Keyed by instance id so the detail row's DriverStatusPanel is never
diffed onto a DIFFERENT driver: the panel resolves DriverInstanceId
diffed onto a DIFFERENT driver: the panel binds DriverInstanceId
once in OnInitializedAsync (store subscription + resilience prime), so
a reused instance would keep reporting the driver it first bound. *@
<tr @key="@($"row-{d.DriverInstanceId}")">
@@ -282,8 +282,17 @@ else
{
@* Full-width detail row beneath its driver. Rendered only while expanded so
the table stays scannable and only the opened panels hold store
subscriptions — collapsing disposes the panel and unsubscribes. *@
<tr @key="@($"detail-{d.DriverInstanceId}")">
subscriptions — collapsing disposes the panel and unsubscribes.
Enabled participates in the key on purpose. The panel decides whether to
subscribe ONCE, in OnInitializedAsync (`if (!Enabled) return;`), and has no
OnParametersSet hook — so a live config edit that flips this row's Enabled
(surfaced by the 5 s LoadConfigAsync refresh) would re-render the panel's
"Disabled — not deployed" markup while its two store subscriptions and its
staleness timer stayed wired underneath. Keying on the flag recycles the
component instead: the old instance is disposed (unsubscribing both stores,
draining both timers) and a fresh one mounts against the new value. *@
<tr @key="@($"detail-{d.DriverInstanceId}-{d.Enabled}")">
<td colspan="9">
<DriverStatusPanel DriverInstanceId="@d.DriverInstanceId"
ClusterId="@g.ClusterId"