From 783da8e21aa64de7c6222cf661e622d299cbe936 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Wed, 13 May 2026 00:33:00 -0400 Subject: [PATCH] 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. --- docs/plans/2026-05-12-script-schema-editor.md | 177 +++ .../Pages/Design/ApiMethodForm.razor | 8 +- .../Pages/Design/SharedScriptForm.razor | 8 +- .../Pages/Design/TemplateEdit.razor | 559 +++++-- .../Components/Shared/AlarmAttributeChoice.cs | 8 + .../Shared/AlarmTriggerEditor.razor | 572 ++++++++ .../Components/Shared/DialogHost.razor | 24 +- .../Components/Shared/MonacoEditor.razor | 6 +- .../Shared/ParameterListEditor.razor | 218 --- .../Components/Shared/ReturnTypeEditor.razor | 132 -- .../Components/Shared/SchemaBuilder.razor | 207 +++ .../Components/Shared/SchemaBuilderModel.cs | 204 +++ .../Components/Shared/ScriptParameterNames.cs | 47 +- .../ScadaLink.CentralUI.csproj | 4 + .../ScriptAnalysis/JsonSchemaShapeParser.cs | 177 +++ .../ScriptAnalysis/ScriptAnalysisService.cs | 7 +- .../ScriptAnalysis/ScriptShapeParser.cs | 54 +- ..._MigrateParametersToJsonSchema.Designer.cs | 1300 +++++++++++++++++ ...512211204_MigrateParametersToJsonSchema.cs | 196 +++ .../Actors/AlarmActor.cs | 29 +- .../Validation/SemanticValidator.cs | 12 +- .../JsonSchemaShapeParserTests.cs | 106 ++ .../Shared/ParameterListEditorTests.cs | 150 -- .../Shared/ReturnTypeEditorTests.cs | 124 -- .../Shared/SchemaBuilderModelTests.cs | 141 ++ 25 files changed, 3609 insertions(+), 861 deletions(-) create mode 100644 docs/plans/2026-05-12-script-schema-editor.md create mode 100644 src/ScadaLink.CentralUI/Components/Shared/AlarmAttributeChoice.cs create mode 100644 src/ScadaLink.CentralUI/Components/Shared/AlarmTriggerEditor.razor delete mode 100644 src/ScadaLink.CentralUI/Components/Shared/ParameterListEditor.razor delete mode 100644 src/ScadaLink.CentralUI/Components/Shared/ReturnTypeEditor.razor create mode 100644 src/ScadaLink.CentralUI/Components/Shared/SchemaBuilder.razor create mode 100644 src/ScadaLink.CentralUI/Components/Shared/SchemaBuilderModel.cs create mode 100644 src/ScadaLink.CentralUI/ScriptAnalysis/JsonSchemaShapeParser.cs create mode 100644 src/ScadaLink.ConfigurationDatabase/Migrations/20260512211204_MigrateParametersToJsonSchema.Designer.cs create mode 100644 src/ScadaLink.ConfigurationDatabase/Migrations/20260512211204_MigrateParametersToJsonSchema.cs create mode 100644 tests/ScadaLink.CentralUI.Tests/ScriptAnalysis/JsonSchemaShapeParserTests.cs delete mode 100644 tests/ScadaLink.CentralUI.Tests/Shared/ParameterListEditorTests.cs delete mode 100644 tests/ScadaLink.CentralUI.Tests/Shared/ReturnTypeEditorTests.cs create mode 100644 tests/ScadaLink.CentralUI.Tests/Shared/SchemaBuilderModelTests.cs diff --git a/docs/plans/2026-05-12-script-schema-editor.md b/docs/plans/2026-05-12-script-schema-editor.md new file mode 100644 index 0000000..dbc97af --- /dev/null +++ b/docs/plans/2026-05-12-script-schema-editor.md @@ -0,0 +1,177 @@ +# Script parameter / return: JSON Schema + JSONJoy editor + +**Date:** 2026-05-12 +**Status:** Superseded — see "Reversal: native Blazor SchemaBuilder" below. + +## Decision + +Replace the custom `ParameterListEditor` / `ReturnTypeEditor` Blazor components +with [`jsonjoy-builder`](https://github.com/lovasoa/jsonjoy-builder) (`SchemaVisualEditor`), +embedded as a React island. The on-disk format for `TemplateScript.ParameterDefinitions` +and `TemplateScript.ReturnDefinition` changes from the project-local flat shape +(`[{name,type,required,itemType?}]` / `{type,itemType?}`) to standard JSON Schema. + +## Rationale + +The existing flat shape lacked descriptions, defaults, enums, nested objects, +and arrays of structured items. JSON Schema covers all of that, is the +industry vocabulary other tooling already speaks (OpenAPI 3.1, function-calling +APIs, validators), and `jsonjoy-builder` is a polished pre-built visual editor +for it. + +## Trade-offs + +- **Breaks the no-UI-framework rule for this feature.** `jsonjoy-builder` is + React 19 + Radix UI + Tailwind. Accepted: the island is isolated to one + modal panel, Tailwind is shipped pre-built (no toolchain shared with the + Blazor side), and the visual delta is contained. +- **New build pipeline.** A small Vite project under `src/ScadaLink.CentralUI/Schema.Editor/` + builds a single IIFE bundle into `wwwroot/lib/schema-editor/`. Output is + committed so `dotnet build` doesn't require Node. +- **Monaco overlap.** `jsonjoy-builder` depends on `@monaco-editor/react`, + which depends on `monaco-editor`. We already load Monaco globally for the + script code editor. The island calls `@monaco-editor/react`'s `loader.config({ monaco: window.monaco })` + at boot to reuse the same instance — no duplicate Monaco download. + +## Storage format change + +| Field | Before | After | +| ---------------------- | --------------------------------- | ----------------------------------------------------------- | +| `ParameterDefinitions` | `[{name,type,required,itemType?}]` | `{"type":"object","properties":{...},"required":[...]}` | +| `ReturnDefinition` | `{type,itemType?}` | Any JSON Schema (root `type` describes the returned value) | + +Per the chosen rollout: **one-shot migration** rewrites all existing rows on +deploy. After the migration, the analysis pipeline reads JSON Schema only — +no dual-format support code. + +Type mapping (flat → JSON Schema): + +| Flat type | JSON Schema | +| --------- | ----------- | +| `Boolean` | `{"type":"boolean"}` | +| `Integer` | `{"type":"integer"}` | +| `Float` | `{"type":"number"}` | +| `String` | `{"type":"string"}` | +| `Object` | `{"type":"object"}` | +| `List` of X | `{"type":"array","items":{"type":}}` | + +`required: false` ⇒ name omitted from the `required` array. +`required: true` (default) ⇒ name added to `required`. + +## Component layout + +``` +src/ScadaLink.CentralUI/Schema.Editor/ ← new Vite project (committed) + package.json + vite.config.ts + tsconfig.json + src/main.tsx ← exposes window.ScadaSchemaEditor + src/SchemaEditorApp.tsx + src/index.css + .gitignore ← node_modules only + dist/ ← (Vite outputs to wwwroot, not here) + +src/ScadaLink.CentralUI/wwwroot/lib/schema-editor/ + schema-editor.js ← built IIFE, committed + schema-editor.css + +src/ScadaLink.CentralUI/Components/Shared/ + SchemaEditor.razor ← Blazor wrapper; mirrors MonacoEditor.razor + +src/ScadaLink.CentralUI/ScriptAnalysis/ + ScriptShapeParser.cs ← rewrite to read JSON Schema +src/ScadaLink.CentralUI/Components/Shared/ + ScriptParameterNames.cs ← rewrite to read JSON Schema +``` + +Removed after rollout: `ParameterListEditor.razor`, `ReturnTypeEditor.razor`. + +## JS interop contract + +```ts +window.ScadaSchemaEditor = { + mount(id: string, host: HTMLElement, options: { + value: string; // current schema JSON (may be empty) + mode: 'parameters' | 'return'; + readOnly?: boolean; + }, dotNetRef: { invokeMethodAsync(name: 'OnValueChanged', json: string): Promise }): void; + setValue(id: string, value: string): void; + dispose(id: string): void; +} +``` + +## Migration + +EF Core migration in `ScadaLink.ConfigurationDatabase` reads +`TemplateScripts.ParameterDefinitions` and `ReturnDefinition` from every row, +sniffs format (array vs object), translates if legacy, writes back. Idempotent: +re-running a row already in JSON Schema is a no-op. Runs once at deploy via +the existing auto-apply path. + +## Out of scope (deferred) + +- Schema-driven value-entry forms (e.g. Inbound API tester) — would also use + `jsonjoy-builder`'s value-editor mode, but no caller surface needs it today. +- Hover/completion enhancements derived from JSON Schema descriptions or + defaults. Today's pipeline only needs name + type + required. +- Reuse of JSON Schema `$ref` across templates — could be a future template-level + schema library. + +--- + +## Reversal: native Blazor SchemaBuilder (2026-05-12, same day) + +JSONJoy worked but felt heavy for the actual data we author here. Specifically: + +- The "Add Field" modal flow is two clicks per parameter where the legacy + inline-row editor was zero. For the common 1-3 scalar-param case, a visible + modal dialog every time is friction. +- JSONJoy's value-mode UX is awkward — it always renders an "Add Field" button + even when the schema's root type is `string` / `integer` / etc., so the + Return-type tab is mismatched to the underlying single-value model. +- React 19 + Radix + Tailwind for one form field is a lot of build pipeline + surface to maintain. + +**Decision:** replace JSONJoy with a Bootstrap-only Blazor component +(`SchemaBuilder.razor`) that recurses through its own render methods. +Storage format unchanged — still JSON Schema. The migration, parser, and +downstream analysis code are untouched. + +**Scope decisions (from refinement session):** + +- Type set: only the six JSON Schema primitives + (`string · integer · number · boolean · object · array`). No `date-time` / + `format`, no `enum` / `pattern` / `min/max`, no `$ref` / `oneOf` / + `anyOf` / `allOf`, no `additionalProperties`. Power-user expansion can + come later behind a per-row "more options" toggle. +- No description support per property. The row stays a single horizontal + line: name + type + (items: type if array) + required + remove. +- Nested objects and arrays-of-objects recurse — same editor renders at any + depth. + +**Files added:** + +- `src/ScadaLink.CentralUI/Components/Shared/SchemaBuilderModel.cs` — + in-memory `SchemaNode` / `SchemaProperty` tree plus pure-static + parse / serialize. Round-trips through the canonical JSON Schema text and + tolerates legacy flat-array shape as a parse fallback. +- `src/ScadaLink.CentralUI/Components/Shared/SchemaBuilder.razor` — + recursive renderer driven by `Mode="object"` (parameter list) or + `Mode="value"` (single value, with object/array falling back to the + property editor). +- `tests/ScadaLink.CentralUI.Tests/Shared/SchemaBuilderModelTests.cs` — + parse / serialize / round-trip / legacy-array coverage. + +**Files removed:** + +- `src/ScadaLink.CentralUI/Schema.Editor/` (Vite project, node_modules, etc.) +- `src/ScadaLink.CentralUI/wwwroot/lib/schema-editor/` (built bundle) +- `src/ScadaLink.CentralUI/Components/Shared/SchemaEditor.razor` (Blazor wrapper) +- `