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:
177
docs/plans/2026-05-12-script-schema-editor.md
Normal file
177
docs/plans/2026-05-12-script-schema-editor.md
Normal file
@@ -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":<X>}}` |
|
||||
|
||||
`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> }): 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)
|
||||
- `<script>` / `<link>` references to schema-editor in `App.razor`
|
||||
- `<DefaultItemExcludes>Schema.Editor/**` from CentralUI csproj
|
||||
|
||||
**Forms updated:** `TemplateEdit.razor`, `SharedScriptForm.razor`,
|
||||
`ApiMethodForm.razor` now use `<SchemaBuilder>` directly.
|
||||
|
||||
The original `jsonjoy-builder` integration sections above are kept for
|
||||
historical context but no longer reflect what's in the codebase.
|
||||
Reference in New Issue
Block a user