feat(central-ui): Debug View picks the instance through InstancePicker

The flat instance <select> on Debug View is replaced by the shared
InstancePicker area-tree combobox (design 2026-09-08 §3): the page now loads
the site's areas alongside its Enabled instances in LoadInstancesForSite and
hands both lists to the picker with ItemLabel = "UniqueName (State)",
Disabled="@_connected" and DataTestPrefix="debug-instance".

Both lists are now REPLACED rather than cleared in place wherever the site
changes (LoadInstancesForSite, StartFresh) — the picker compares Areas and
Instances by reference and a new object is what tells it the site changed.
The ?siteId=/?instanceId= deep link and the localStorage reconnect keep
setting _selectedInstanceId directly; the picker reads it on parameter set,
so both paths are unchanged.

Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
This commit is contained in:
Joseph Doherty
2026-09-08 09:44:26 -04:00
parent 1c665d3acb
commit 1d622f536d
2 changed files with 177 additions and 10 deletions
@@ -82,14 +82,10 @@
</div>
<div class="col-md-4">
<label class="form-label small">Instance</label>
<select class="form-select form-select-sm" data-test="debug-instance-select"
@bind="_selectedInstanceId" @bind:after="OnInstanceSelectionChanged" disabled="@_connected">
<option value="0">Select instance...</option>
@foreach (var inst in _siteInstances)
{
<option value="@inst.Id">@inst.UniqueName (@inst.State)</option>
}
</select>
<InstancePicker Areas="_siteAreas" Instances="_siteInstances"
ItemLabel="@(i => $"{i.UniqueName} ({i.State})")"
SelectedInstanceId="_selectedInstanceId" SelectedInstanceIdChanged="OnInstancePicked"
Disabled="@_connected" DataTestPrefix="debug-instance" />
</div>
<div class="col-md-3 d-flex align-items-end gap-2">
@if (!_connected)
@@ -257,6 +253,11 @@
private List<Site> _sites = new();
private List<Instance> _siteInstances = new();
// The site's areas, backing the InstancePicker's tree. Both lists are REPLACED
// (never mutated in place) on a site change — the picker compares them by
// reference and treats a new object as "the site changed".
private List<Area> _siteAreas = new();
private int _selectedSiteId;
private int _selectedInstanceId;
private bool _loading = true;
@@ -429,7 +430,8 @@
private async Task LoadInstancesForSite()
{
_siteInstances.Clear();
_siteInstances = new();
_siteAreas = new();
_selectedInstanceId = 0;
if (_selectedSiteId == 0) return;
// Site scoping (CentralUI-002): re-check the claim server-side — a query
@@ -445,6 +447,7 @@
_siteInstances = (await TemplateEngineRepository.GetInstancesBySiteIdAsync(_selectedSiteId))
.Where(i => i.State == InstanceState.Enabled)
.ToList();
_siteAreas = (await TemplateEngineRepository.GetAreasBySiteIdAsync(_selectedSiteId)).ToList();
}
catch (Exception ex)
{
@@ -452,6 +455,13 @@
}
}
/// <summary>Handles a pick from the <c>InstancePicker</c> combobox.</summary>
private void OnInstancePicked(int id)
{
_selectedInstanceId = id;
OnInstanceSelectionChanged();
}
private void OnInstanceSelectionChanged()
{
// No-op; selection is tracked via _selectedInstanceId binding
@@ -597,7 +607,8 @@
await Disconnect();
_selectedSiteId = 0;
_selectedInstanceId = 0;
_siteInstances.Clear();
_siteInstances = new();
_siteAreas = new();
_toast.ShowInfo("Cleared previous session — select a site and instance to begin.", autoDismissMs: 5000);
}