Extract the 8 typed driver pages into embeddable <Driver>DriverForm bodies
(channel/protocol config) and add per-driver <Driver>DeviceForm bodies
(connection endpoint -> Device.DeviceConfig, v3 endpoint split). New
DriverConfigModal + DeviceModal dispatch by DriverType (DriverTypeNames) for
the /raw tree; Test-connect inside DeviceModal builds the merged Driver+Device
config transiently via DriverDeviceConfigMerger. Routed pages now host the
form bodies and keep working. Round-trip + enum-serialization guard tests for
the Modbus driver form + Modbus device model; retargeted the existing
page-form serialization tests + the _jsonOpts converter guard to the forms.
Claude-Session: https://claude.ai/code/session_01LVneM3eh1UtJxEisFXgmox
Namespace entity + NamespaceKind retired; DriverInstance dropped NamespaceId;
Tag is raw-only (no EquipmentId/DriverInstanceId/FullName/FolderPath); driver
options replaced pre-declared Tags with RawTags. Stub the retired surfaces so
the AdminUI + Host build green; real Raw/UNS authoring arrives in v3 Batch 2/3.
- ClusterNamespaces/NamespaceEdit: bodies replaced with 'Namespaces retired' banner
- DriverIdentitySection: dropped Namespace dropdown + NamespaceId model field
- 8 driver pages: dropped Namespace binding; retired the pre-declared Tags editor
(replaced with a '/raw Batch 2' note); multi-device pages keep their Devices editor
- UnsTreeService: equipment-tag counts/list -> empty; Create/UpdateTag -> failure
result ('Tag authoring moved to the Raw tree (/raw) in v3 Batch 2'); Namespace/
driver-binding queries removed
- IScriptTagCatalog: project surviving Tag columns only (path from TagConfig FullName, else Name)
- OpcUaClientTagConfigEditor: rebind stale FullName -> model's NodeId
- ClusterDrivers: drop retired NamespaceId column
AdminUI + Host build 0/0. AdminUI.Tests / Host.IntegrationTests remain red (WP6).
AbCip/TwinCAT/FOCAS driver-config pages host the same *AddressPickerBody as the /uns TagModal but
did not pass GetConfigJson, so their universal Browse button would capture against an empty {} (no
device). Thread SerializeCurrentConfig through. DriverType is left to the picker-body default
(canonical registered type) rather than the page's DriverTypeKey const, whose TwinCAT/FOCAS values
("TwinCat"/"Focas") are non-canonical casing that could miss the factory TryCreate lookup.
AdminUI driver-instance pages serialized enum config fields (S7 CpuType,
Modbus DataType/Region, AbCip PlcFamily, ...) as JSON *numbers* because each
page's _jsonOpts lacked a JsonStringEnumConverter. The driver factories,
however, deserialize into string-typed DTOs (+ lenient ParseEnum) and throw
when binding a JSON number to a string? — so an AdminUI-authored config
containing any enum field produced a blob the driver could not parse,
faulting the driver on deploy. Proven end-to-end for S7 and Modbus; latent
for AbCip/AbLegacy/TwinCAT/FOCAS/Galaxy/Historian. Only OpcUaClient was safe
(its factory + probe already carried the converter).
Add JsonStringEnumConverter to all 9 driver-instance pages' _jsonOpts and the
8 missing driver probes' _opts (factories unchanged — already string-via-
ParseEnum; strictly more permissive, also lets pages load hand-seeded
string-enum configs back into the form).
Also fix DriverProbeHandshakeE2eTests.AbCip_Green_AgainstSim to probe a real
sim tag (TestDINT) — the no-tags @raw_cpu_type fallback is rejected by the
ab_server sim with ErrorBadParam (a real ControlLogix returns ErrorNotFound,
which the probe treats as reachable; hardware-gated follow-up).
Tests: reflection guard over all driver pages' _jsonOpts (AdminUI.Tests);
factory round-trip + numeric-form-throws guards for S7 and Modbus.
Found by running the never-before-run FB-9/FB-10 live verifies.
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.
- AdminProbeService routes TestDriverConnect through
IAdminOperationsClient with a 65s outer guard (actor side already
clamps to [1,60]).
- Added generic AskAsync<T> to IAdminOperationsClient interface and
AdminOperationsClient impl, delegating straight to the Akka proxy.
- DriverTestConnectButton renders the button + inline result chip,
auto-clears after 30s, disables during in-flight.
- Wired into all 9 typed driver pages directly under the
identity section. Sources timeout from the form's
ProbeTimeoutSeconds; sources config JSON from the form's
current Options (operator can test BEFORE saving).
Live panel subscribed to the /hubs/driverstatus SignalR feed —
renders state chip, last-success age, 5-min error count, last
error message. Auto-reconnect; dimmed when no push arrives for 30s.
Hidden for new instances (nothing deployed yet); shown read-only
on every edit-mode page. Reconnect/Restart buttons land in Phase 8.