Every picker and RawBrowseModal stored the OpenAsync token after the await; if
the host was disposed (or the modal closed) meanwhile, its teardown had already
run with no token, so the plant-side session lived until the reaper's idle TTL.
BrowseOpenGuard.CloseIfOrphaned closes it at once; each host sets _disposed
first thing in DisposeAsync (RawBrowseModal also treats !Visible as gone).
Refs #745
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
Audit (task #134) found the same Razor literal-binding bug class as the UNS
Filter fix (14b4692): a string-typed component parameter assigned without a
leading @ is a LITERAL, not an expression. Confirmed against the generated
.g.cs (literal "_error" vs TypeCheck<String>(_error)).
- DriverFormShell Error="_error" -> "@_error" on all 9 driver edit pages:
Error received the constant "_error", so the error banner rendered
permanently and the real failure message was never shown.
- DriverBrowseTree SelectedNodeId="_tagName"/"_nodeId" -> "@..." in the
Galaxy and OpcUaClient address pickers: the tree's selected-node highlight
compared against a literal that never matched a real node.
Build clean; generated code now binds all 11 as TypeCheck<String>(field);
AdminUI.Tests 216/216 green.
- DriverTagPicker shell: modal chrome + per-driver picker body
rendered as ChildContent.
- 9 picker bodies (Modbus/AbCip/AbLegacy/S7/TwinCat/FOCAS/
OpcUaClient/Galaxy/Historian.Wonderware). 5 have computed
builder logic + unit tests; 4 are free-text passthroughs
(live browse for OPC UA + Galaxy is a documented follow-up).
- Each typed driver page gets a "Pick address" button that opens
the modal with the matching body. Picked address surfaces in
the modal footer for manual copy — no JS interop in v1.