Merge master 4fb04a9f (lane D-a) into remed/lane-D-b

ScriptEdit.razor conflict resolved keeping both D-a's on-demand Monaco sync +
Problems list and D-b's delete confirm.

Refs #745
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
Joseph Doherty
2026-10-22 18:14:53 -04:00
85 changed files with 4618 additions and 812 deletions
@@ -12,6 +12,7 @@
@using ZB.MOM.WW.OtOpcUa.Configuration
@using ZB.MOM.WW.OtOpcUa.Configuration.Entities
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Raw
@using ZB.MOM.WW.OtOpcUa.AdminUI.ScriptAnalysis
@inject IDbContextFactory<OtOpcUaConfigDbContext> DbFactory
@inject NavigationManager Nav
@@ -31,7 +32,9 @@ else if (!IsNew && _existing is null)
}
else
{
<EditForm Model="_form" OnValidSubmit="SubmitAsync" FormName="scriptEdit">
@* OnSubmit (not OnValidSubmit): the editor text is pulled from Monaco FIRST, then validated — the bound
SourceCode is only refreshed at blur, so validating before the pull could judge stale text. *@
<EditForm Model="_form" OnSubmit="SubmitAsync" FormName="scriptEdit">
<DataAnnotationsValidator />
<ValidationSummary class="alert alert-danger py-2 px-3" />
<section class="panel rise">
@@ -58,7 +61,9 @@ else
<section class="panel rise mt-3">
<div class="panel-head">Source</div>
<div style="padding:1rem">
<MonacoEditor @bind-Value="_form.SourceCode" Height="420px" />
<MonacoEditor @ref="_editor" @bind-Value="_form.SourceCode" Height="420px"
MarkersChanged="OnMarkersChanged" />
<ScriptProblemsList Report="_markers" OnSelect="RevealProblemAsync" />
<div class="form-text">SHA-256 hash is computed automatically on save.</div>
</div>
</section>
@@ -91,6 +96,13 @@ else
private bool _busy;
private string? _error;
private bool _confirmDeleteVisible;
private MonacoEditor? _editor;
private ScriptProblemReport _markers = ScriptProblemReport.Empty;
private void OnMarkersChanged(ScriptProblemReport report) => _markers = report;
private Task RevealProblemAsync(DiagnosticMarker m)
=> _editor is null ? Task.CompletedTask : _editor.RevealLineAsync(m.StartLineNumber, m.StartColumn);
protected override async Task OnInitializedAsync()
{
@@ -112,11 +124,21 @@ else
_loaded = true;
}
private async Task SubmitAsync()
private async Task SubmitAsync(EditContext editContext)
{
_busy = true; _error = null;
try
{
// The editor reports its text only at blur, so read the authoritative current value now. A read
// failure (e.g. the script is too large to send) aborts the save rather than persisting stale source.
if (_editor is not null)
{
var read = await _editor.GetValueAsync();
if (!read.Ok) { _error = $"Not saved: {read.Error}"; return; }
_form.SourceCode = read.Value!;
}
if (!editContext.Validate()) return;
var sourceHash = HashSource(_form.SourceCode);
await using var db = await DbFactory.CreateDbContextAsync();
if (IsNew)