fix(adminui): Monaco text syncs on demand, diagnostics are sequenced, Problems list (D1)

R04:P1 - the editor no longer ships its full text JS->.NET on every keystroke. It reports
at blur, and ScriptEdit / the VirtualTag modal read the authoritative value with
MonacoEditor.GetValueAsync at Save (validation runs after the pull). The Blazor hub's
MaximumReceiveMessageSize is raised from 32 KB to 1 MB. The JS refuses a value that
would not fit instead of sending an oversize frame, and Save surfaces that refusal
rather than persisting stale source.

R04:P9 - diagnostics carry a generation counter and an AbortController. Every edit aborts
the in-flight request, and only the response for the current text is applied.

R04:S2 - the always-empty inlay-hints provider, endpoint, service method and contracts
are removed.

R04:S3 - MarkersChanged is typed (DiagnosticMarker) and feeds a new ScriptProblemsList.
Clicking a problem calls RevealLineAsync. Markers cross to .NET only when a list
subscribes.

Refs #745

Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
Joseph Doherty
2026-10-22 17:38:56 -04:00
parent 8e3bbf91fb
commit da2578833d
14 changed files with 514 additions and 61 deletions
@@ -0,0 +1,57 @@
@namespace ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared
@using ZB.MOM.WW.OtOpcUa.AdminUI.ScriptAnalysis
@* The script editor's Problems list. Fed by MonacoEditor.MarkersChanged; clicking a row calls OnSelect, which the
host wires to MonacoEditor.RevealLineAsync so the caret jumps to the problem. Ordering and wording live in
ScriptProblems (unit-tested). *@
@{
var ordered = ScriptProblems.Order(Markers);
}
<div class="script-problems mt-2 small" role="region" aria-label="Problems">
<div class="d-flex align-items-center gap-2 text-muted mb-1">
<span class="fw-semibold">Problems</span>
<span>@ScriptProblems.Summary(ordered)</span>
</div>
@if (ordered.Count > 0)
{
<ul class="list-group list-group-flush border rounded" style="max-height: 10rem; overflow-y: auto;">
@foreach (var m in ordered.Take(MaxRows))
{
<li class="list-group-item list-group-item-action py-1 px-2 d-flex gap-2 align-items-baseline"
role="button" tabindex="0" style="cursor: pointer;"
title="Go to line @m.StartLineNumber, column @m.StartColumn"
@onclick="() => OnSelect.InvokeAsync(m)"
@onkeydown="e => OnKeyAsync(e, m)">
<span class="badge @BadgeClass(m.Severity)">@ScriptProblems.SeverityLabel(m.Severity)</span>
<span class="mono text-muted">@m.StartLineNumber:@m.StartColumn</span>
<span class="flex-grow-1">@m.Message</span>
<span class="mono text-muted">@m.Code</span>
</li>
}
</ul>
@if (ordered.Count > MaxRows)
{
<div class="text-muted mt-1">… and @(ordered.Count - MaxRows) more</div>
}
}
</div>
@code {
private const int MaxRows = 200;
/// <summary>The markers the editor last reported (null before the first diagnostics pass).</summary>
[Parameter] public IReadOnlyList<DiagnosticMarker>? Markers { get; set; }
/// <summary>Raised when the author picks a problem; wire it to <c>MonacoEditor.RevealLineAsync</c>.</summary>
[Parameter] public EventCallback<DiagnosticMarker> OnSelect { get; set; }
private Task OnKeyAsync(KeyboardEventArgs e, DiagnosticMarker m)
=> e.Key is "Enter" or " " ? OnSelect.InvokeAsync(m) : Task.CompletedTask;
private static string BadgeClass(int severity) => severity switch
{
ScriptProblems.Error => "text-bg-danger",
ScriptProblems.Warning => "text-bg-warning",
_ => "text-bg-secondary",
};
}