# Instance Picker Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:executing-plans to implement this plan task-by-task. **Goal:** Replace the flat instance ` @if (_open) {
@node.Label @(_hasFilter ? "No instances match." : "No instances at this site.")
} ``` Code-behind essentials: - Parameters per design §2 table (`Areas`, `Instances`, `SelectedInstanceId`, `SelectedInstanceIdChanged`, `Disabled`, `ItemLabel`, `DataTestPrefix` (default `"instance"`), `Placeholder` (default `"Search or browse instances…"`)). - State: `_open`, `_text`, `_filter`, `_roots`, `_tree` (`TreeView?`), `_inputId`/`_panelId` from a `Guid` suffix. - `OnParametersSet`: if `Areas`/`Instances` references changed → rebuild `_roots` with the current filter and (if the selection is no longer in `Instances`) clear text; if `SelectedInstanceId` changed and the panel is closed → `_text = LabelOf(selected)`. - `Rebuild()` → `InstanceTreeBuilder.Build(Areas, Instances, _filter)` — the label used for matching is `UniqueName` (the builder's contract), rows display `ItemLabel`. - `OnRowClickAsync(node)`: area → `_tree.ToggleExpand`? (`TreeView` has no public toggle — expose one: add `public void ToggleExpanded(object key)` to `TreeView.razor` next to `IsExpanded`, a one-line wrapper around the existing private `ToggleExpand`); instance → `SelectAsync(node.Instance)`. - `SelectAsync(inst)`: `_text = LabelOf(inst)`, `_filter = ""`, close, `await SelectedInstanceIdChanged.InvokeAsync(inst.Id)`. - `CloseAsync`: `_open = false`, `_filter = ""`, `_text = LabelOf(current)`. - Guard: when `Disabled`, `OpenAsync`/`ToggleAsync` no-op. CSS (`InstancePicker.razor.css`): `.ip-backdrop { position: fixed; inset: 0; z-index: 1040; background: transparent; }` `.ip-panel { position: absolute; left: 0; right: 0; top: 100%; z-index: 1041; max-height: 40vh; overflow-y: auto; min-width: 18rem; padding: .25rem; }` `.ip-instance { cursor: pointer; }` `.ip-area { cursor: pointer; }`. Rows render inside `TreeView`, whose `tv-*` classes are already global to that component — check `TreeView.razor.css` whether `tv-label`/`tv-glyph` are `::deep`-reachable from a consumer (TemplatePickerDialog uses them the same way, so follow that). **Step 4: tests → PASS. Step 5: Commit** `feat(central-ui): InstancePicker — combobox over the site's area tree`. --- ### Task 3: `InstancePicker` — search filter + keyboard **Classification:** standard **Estimated implement time:** ~4 min **Parallelizable with:** none **Files:** - Modify: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Shared/InstancePicker.razor` / `.razor.cs` - Test: `tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Shared/InstancePickerTests.cs` **Step 1: Failing tests** - `Typing_Filters_AndExpandsToTheMatches`: open, `Input("p1")` on the input → `[data-instance-id='7']` is rendered (ancestors expanded), Zulu row absent, Line1 row present. - `Clearing_RestoresTheFullTree_Collapsed`: after the above, `Input("")` → Zulu back, P1 hidden again (Line1 collapsed). - `Typing_OpensTheClosedPanel`. - `Enter_PicksTheFirstVisibleMatch`: type "u" (matches Zulu only) + `KeyDown(Key="Enter")` → callback 8, closed, text "Zulu"; with no match Enter does nothing (still open). - `Escape_ClosesAndRestoresTheSelectedText`: select P1, open, type "zzz", Escape → closed, text "P1". - `ArrowDown_MovesFocusIntoTheTree`: after ArrowDown the first visible `[role='treeitem']` has `tabindex="0"` (the tree's roving tabindex target) — assert via the `TreeView` markup; focus itself is JS (Loose mode), so assert the call happened through `JSInterop` if `TreeView` uses `FocusAsync` (an `ElementReference.FocusAsync` shows up as a `Blazor._internal.domWrapper.focus` invocation in bUnit — assert `JSInterop.Invocations` contains it, or expose a `public Task FocusFirstVisibleAsync()` on `TreeView` and assert it doesn't throw). **Step 2: run → fail. Step 3: Implement** - `OnInput(ChangeEventArgs e)`: `_text = e.Value?.ToString() ?? ""`, `_filter = _text`, `_open = true`, `Rebuild()`; after render, if `_hasFilter` → `_tree?.ExpandAll()` else `_tree?.CollapseAll()` (do it in `OnAfterRender` guarded by a `_pendingExpand` flag so the tree instance exists). - `OnInputKeyDown(KeyboardEventArgs e)`: `Enter` → first instance node in document order of the current (filtered) `_roots` → `SelectAsync`; `Escape` → `CloseAsync`; `ArrowDown` → `_open = true` and focus the tree's first visible item (`TreeView`: add `public async Task FocusFirstVisibleAsync()` that sets `_focusedKey` to the first visible node key and applies focus via the existing `_focusNeedsApply` mechanism). - Typing on a closed picker opens it. **Step 4: PASS. Step 5: Commit** `feat(central-ui): InstancePicker search filter + keyboard (Enter/Escape/ArrowDown)`. --- ### Task 4: Wire Flow Debugger to the picker **Classification:** standard **Estimated implement time:** ~4 min **Parallelizable with:** Task 5, Task 6 **Files:** - Modify: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/FlowDebugger.razor` (~lines 62–71, the instance `` ~lines 83–92; `_siteInstances` ~259; `LoadInstancesForSite` ~430–452) - Test: create `tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Deployment/DebugViewInstancePickerTests.cs` (copy the service registration from `DebugViewDisposalTests.cs`) **Steps:** (1) Test: substitute repo returns one site, instances [Enabled "Sim01" id 7 in area 2] + areas [Line1 (1) → Cell A (2)]; render `DebugView`, `Change` the site select to the site, open `[data-test='debug-instance-toggle']` → only the Line1 row; expand twice → row `[data-instance-id='7']` text `"Sim01 (Enabled)"`; click it → *Connect* button enabled. (2) fails. (3) Page: `_siteAreas` list loaded in `LoadInstancesForSite` (same try/catch), picker markup with `ItemLabel="@(i => $"{i.UniqueName} ({i.State})")"`, `SelectedInstanceId="_selectedInstanceId" SelectedInstanceIdChanged="id => { _selectedInstanceId = id; OnInstanceSelectionChanged(); }"`, `Disabled="@_connected"`, `DataTestPrefix="debug-instance"`. (4) `DebugView*` tests pass. (5) Commit `feat(central-ui): Debug View picks the instance through InstancePicker`. --- ### Task 6: Playwright tests drive the picker **Classification:** small **Estimated implement time:** ~3 min **Parallelizable with:** Task 4, Task 5 **Files:** - Modify: `tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/FlowDebuggerTests.cs` (~lines 209–213) - Modify: `tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/DebugViewTests.cs` (~68, ~93–97) - Modify: `tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/DebugViewTreeTests.cs` (~79–83) Replace `