The /uns filter was per-level: it matched only a node's direct children by
DisplayName and only under already-expanded nodes, so typing "blender" at the
top matched nothing — the structural ancestors don't contain the text and
weren't expanded.
Rework UnsTree to the standard tree-filter behaviour:
- A node is shown if it self-matches, sits under a matched ancestor, or has a
matching descendant (VisibleUnder).
- The path to a match auto-expands (chevron + child block follow a filter-
derived `childrenShown`, not node.Expanded), and the whole subtree under a
matched node is shown.
- Lazy tag children are only considered once their equipment is loaded, so the
filter never triggers lazy loads; the bounded structural tree keeps the
recursive walk cheap.
Clearing the filter restores the user's manual expand state (node.Expanded is
untouched). Build clean; AdminUI.Tests 216/216.
Low-severity review nits, no behaviour change to the happy path:
- CloseModals() now also resets the leftover _*ModalIsNew / parent-id fields
(area ClusterId, line AreaId, equipment LineId, tag/vtag) for symmetry —
harmless today (always set before a modal opens) but consistent.
- HandleAddChild / HandleAddVirtualTag / HandleEdit gain a _modalBusy guard
(try/finally) so a rapid double-action can't race two service loads into the
same modal state. The switch bodies are re-indented under the try block.
- VirtualTagModal DataType is now an InputSelect over the standard OPC UA type
list (the same set TagModal uses) instead of free-text InputText.
- RefreshEquipmentChildrenAsync documents that callers own StateHasChanged()
and the full-reload fallback is spelled out as a block with a comment.
Build clean; AdminUI.Tests 216/216.