# 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) - `