Pattern proof for the live-edit forms gated by Phases A–D's read views.
Each entity gets a single edit page handling both create (route param
omitted) and update (route param present) modes, with RowVersion-based
optimistic concurrency checked against EF Core's
DbUpdateConcurrencyException.
Pattern:
- @page "/clusters/{id}/<thing>/new"
- @page "/clusters/{id}/<thing>/{rowId}"
- IsNew computed from rowId presence
- EditForm + DataAnnotations validation
- byte[] RowVersion stashed on FormModel; assigned to
Entry(e).Property(e => e.RowVersion).OriginalValue before SaveChanges
- Delete button (edit mode only) flows through the same RowVersion check
- Concurrency conflict surfaces as an inline error panel; user reloads
This batch:
- NamespaceEdit.razor — small entity, validates the pattern
- DriverEdit.razor — keystone for everything downstream
(Equipment/Tag/VirtualTag/ScriptedAlarm),
JSON config editor per Q1 with reformat
on save and validation pre-flight
- ClusterNamespaces row gains an Edit button + New action
- ClusterDrivers expanded view gains an Edit button + New action
Equipment/UnsArea/UnsLine/Tag/ACL/VirtualTag/ScriptedAlarm/Script forms
follow this same template in subsequent F15.2 batches.
All 9 integration tests still green; no v2 test regressions.
Per Q3 of the rebuild plan, each v1 ClusterDetail tab becomes a separate
route under /clusters/{id}/<tab>. This batch adds read-only table views
for the six core config entity types; live-edit forms with RowVersion
concurrency land in Phase C.2 once the read-view shape is reviewed.
- ClusterEquipment /clusters/{id}/equipment — joins via DriverInstance
so the cluster scope works
- ClusterUns /clusters/{id}/uns — Areas + Lines tables
- ClusterNamespaces /clusters/{id}/namespaces — Kind + URI + Enabled chip
- ClusterDrivers /clusters/{id}/drivers — collapsed list with JSON
config expandable per Q1
(typed editors deferred)
- ClusterTags /clusters/{id}/tags — first 200 by name + filter
- ClusterAcls /clusters/{id}/acls — LDAP group + scope +
NodePermissions bits
Shared ClusterNav.razor extracted; ClusterOverview + ClusterRedundancy
updated to use it. _Imports.razor adds Components.Shared so the shared
nav is in scope across pages.