Editors now set a _normalized flag when ParseFromJson coalesces a
legacy type name (lowercase "string", "Int32", "Double", etc.) to the
canonical set. When flagged, render a small alert-info inline:
"Some parameter types were normalized... Save to persist the
canonical form." The flag clears on any user edit so the notice
doesn't linger after Emit overwrites the JSON.
31 new bUnit tests in tests/.../Shared/:
- ParameterListEditorTests: null/empty rendering, row count per
JSON entry, legacy type normalization across .NET names +
lowercase, the normalized notice trigger, add/remove emission,
List/non-List item-type column visibility, required-flag round
trip, invalid JSON + non-array error paths.
- ReturnTypeEditorTests: null vs simple vs List shape, legacy type
normalization, change-type / clear-type emission, invalid JSON
+ non-object error paths.
Total CentralUI test count: 82 -> 113.
Eliminates the per-page <ConfirmDialog @ref="_confirmDialog"
ConfirmButtonClass="btn-danger" /> boilerplate. Pages now inject
IDialogService and call ConfirmAsync(title, message, danger: true)
programmatically.
New scoped service holds a single active dialog (throws on nested
calls), with a global DialogHost mounted once in MainLayout that
renders the modal markup, owns body scroll-lock via Bootstrap's
modal-open class, traps focus on the modal element, and handles
Escape-to-cancel.
Same service also exposes PromptAsync, used to replace the bespoke
NewFolderDialog. Both ConfirmDialog and NewFolderDialog components
are deleted — their callers (~13 pages across Admin/Design/Deployment
/Monitoring) now go through the service.
DiffDialog stays as-is — different use case (before/after content).
bUnit tests in TopologyPageTests, DataConnectionsPageTests, and
TemplatesPageTests register IDialogService in their service
collection.
Also: a top-of-file Razor comment on Sites.razor pointing future
implementers at it as the reference list-page pattern.
LdapMappings: flex header, search filter, per-row Edit + kebab Delete,
@key, dropped Site-Scope-Rules cell in favor of a {n rule(s)} badge.
LdapMappingForm: two stacked cards (Mapping then Site Scope Rules);
scope rules render as removable chips with an inline "Add scope rule"
form; create-mode disables the scope card with an explainer; role
select gets form-text help.
DataConnections: <h4> in flex header, Bulk actions dropdown holding
Expand/Collapse, hover-visible kebab on tree nodes mirroring the
right-click context menu, aria-labels, "No connections match the
filter." inline empty state.
DataConnectionForm: Site rendered as readonly plaintext + lock-after-
creation note in edit mode; parallel Primary endpoint / Backup endpoint
headings; "Optional" badge on Backup when null; form-text on
FailoverRetryCount.
ApiKeys: search filter, Status column dropped (state now lives in the
kebab menu label "Disable"/"Enable"), Edit + kebab actions, @key,
aria-labels.
ApiKeyForm: nested card removed; fixed-text Back header; real
clipboard copy via IJSRuntime + toast confirmation.
Test selector fix in DataConnectionFormTests for the new Site
readonly-plaintext rendering.
Three new sections inserted into <OpcUaEndpointEditor>:
1. Authentication (between the existing Connection row and Timing)
- 'Enable Authentication' button when Config.UserIdentity is null
- TokenType select (Anonymous / UsernamePassword / X509Certificate)
- Conditional Username + Password inputs for UsernamePassword
- Conditional Certificate path + Certificate password for X509Certificate
- 'Remove Authentication' button
2. Advanced subscription (after the existing Subscription row)
- Subscription display name (text)
- Subscription priority (number 0-255)
- Timestamps to return (Source / Server / Both select)
- Discard oldest (checkbox)
3. Deadband filter (after Advanced subscription)
- 'Enable Deadband' button when Config.Deadband is null
- Type select (Absolute / Percent), Value number input
- 'Remove Deadband' button
EnableAuthentication and EnableDeadband helpers complement EnableHeartbeat.
All new fields use the existing RenderFieldError helper for validator errors.
82/82 CentralUI tests pass (the 10 new editor tests drove the design).
Brings the Data Connections admin page up to the same UX standard as the
Topology page:
- Search box with dim non-matches (opacity 0.4, shape preserved)
- Toolbar: + Connection (disabled until a site is selected), Refresh,
Expand, Collapse
- Site context menu gains "Add Connection here" that navigates with
?siteId= so the form preselects + locks the Site field
- Form gains "Primary Endpoint" / "Backup Endpoint" h6 subsection
headers matching the SiteForm convention; Failover Retry Count moved
inside the Backup subsection
- URL renamed: /admin/connections (primary) + /admin/data-connections
(legacy secondary @page). Same dual-route treatment on the form
- Nav label: "Data Connections" -> "Connections"
- Adds DataConnectionsPageTests bUnit suite (6 tests)
Single /deployment/topology page replaces /deployment/instances (legacy URL
preserved as a secondary @page directive) and the /admin/areas* CRUD pages.
TreeView with Site → Area → Instance, V1–V7 visual guide (bi-building /
bi-diagram-3 / bi-box), always-visible empty containers, search dim, F2
inline area rename, and right-click context menus per node kind (Add Area,
Move to Area…, lifecycle actions, etc.).
Adds AreaService.MoveAreaAsync with cycle prevention, same-site enforcement,
and name-collision check at the new parent. Instance rename intentionally
out of scope — UniqueName is the site-side actor identity, requires its own
design pass.
Right-click a template now offers "New Derived Template" — opens
TemplateCreate with the parent pre-selected via a new ?parentId query
parameter. Composition rows in the tree drop the trailing
"→ TargetName" muted text; the kind glyph plus the instance name carry
enough meaning, and the composed template is one click away from the
row's right-click menu.
Templates page is now a tree-only browser; editing happens on a dedicated
TemplateEdit page. Drag-drop is replaced by context-menu Move-to-Folder.
TreeView gains Bootstrap Icons (chevron + per-kind glyphs), ancestor guide
lines, defined hover/selected/focus tokens, and Escape-dismisses-menu per
the new Visual Design Guide (V1-V7) in Component-TreeView.md.
- Add JoeAppEngine folder to OPC UA nodes.json (BTCS, AlarmCntsBySeverity, Scheduler/ScanTime)
- Fix DataConnectionActor: capture Self in PreStart for use from non-actor threads,
preventing Self.Tell failure in Disconnected event handler
- Implement InstanceActor.HandleConnectionQualityChanged to mark attributes Bad on disconnect
- Fix LmxFakeProxy TagMapper to serialize arrays as JSON instead of "System.Int32[]"
- Allow DataType and DataSourceReference updates in TemplateService.UpdateAttributeAsync
- Update test_infra_opcua.md with JoeAppEngine documentation
17 source projects (Commons + Host + 15 components) and 17 xUnit test projects.
SLNX format, net10.0, nullable enabled, warnings as errors. All components
reference Commons; Host references all components. Builds and tests clean.