feat(ui): structured editors for script schemas and alarm triggers

Replace raw-JSON text inputs with rich UI: script parameter/return types use
a JSON Schema builder (SchemaBuilder + JsonSchemaShapeParser, with a migration
to convert existing definitions); alarm trigger config uses a type-aware
editor with a flattened attribute picker (AlarmTriggerEditor). AlarmActor
gains optional direction (rising/falling/either) on RateOfChange triggers.
This commit is contained in:
Joseph Doherty
2026-05-13 00:33:00 -04:00
parent 57f477fd28
commit 783da8e21a
25 changed files with 3609 additions and 861 deletions
@@ -29,7 +29,8 @@
else
{
<label class="form-label">@state.Body</label>
<input class="form-control form-control-sm"
<input @ref="_promptInputRef"
class="form-control form-control-sm"
placeholder="@state.Placeholder"
value="@_promptValue"
@oninput="OnPromptInput" />
@@ -50,8 +51,10 @@
@code {
private ElementReference _modalRef;
private ElementReference _promptInputRef;
private string _promptValue = string.Empty;
private DialogState? _lastSeenState;
private DialogState? _focusedForState;
protected override void OnInitialized()
{
@@ -85,11 +88,26 @@
{
try { await JS.InvokeVoidAsync("document.body.classList.add", "modal-open"); }
catch { /* prerender: no JS — ignore */ }
try { await _modalRef.FocusAsync(); }
catch { /* element not yet attached: ignore */ }
// Focus once per opened dialog. Without this guard, every input
// keystroke triggers a re-render which would re-focus the modal
// element and yank the caret off the prompt input.
if (!ReferenceEquals(current, _focusedForState))
{
_focusedForState = current;
try
{
if (current.Kind == DialogKind.Prompt)
await _promptInputRef.FocusAsync();
else
await _modalRef.FocusAsync();
}
catch { /* element not yet attached: ignore */ }
}
}
else
{
_focusedForState = null;
try { await JS.InvokeVoidAsync("document.body.classList.remove", "modal-open"); }
catch { /* prerender: no JS — ignore */ }
}