@golemui/gui-mcp 1.0.0-rc.2 → 1.0.0-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,9 @@
1
1
  import Ajv2020 from "ajv/dist/2020.js";
2
2
  import addFormats from "ajv-formats";
3
+ import { existsSync } from "node:fs";
4
+ import { createRequire } from "node:module";
5
+ import { resolve, join, dirname } from "node:path";
6
+ import { fileURLToPath } from "node:url";
3
7
  const $schema$u = "https://json-schema.org/draft/2020-12/schema";
4
8
  const $id$u = "https://golemui.com/schemas/common.schema.json";
5
9
  const title$u = "Golem Common Definitions";
@@ -1081,6 +1085,11 @@ function lintReactiveExpressions(formDefinition) {
1081
1085
  walk$1(formDefinition, "", findings);
1082
1086
  return findings;
1083
1087
  }
1088
+ function checkReactiveExpression(expression, path = "") {
1089
+ const out = [];
1090
+ checkExpression(expression, path, out);
1091
+ return out;
1092
+ }
1084
1093
  function walk$1(node, path, out) {
1085
1094
  if (node === null || typeof node !== "object") return;
1086
1095
  if (Array.isArray(node)) {
@@ -1370,7 +1379,7 @@ function validateFormDefinition(input) {
1370
1379
  errors.push({
1371
1380
  path: "/",
1372
1381
  keyword: "oneOf",
1373
- message: "Form failed schema validation but no specific error could be localized. The form may contain a widget at a position the form schema does not allow, or a structural shape that our targeted validator missed. Verify each widget against its `get_widget_spec` entry."
1382
+ message: "Form failed schema validation but no specific error could be localized. The form may contain a widget at a position the form schema does not allow, or a structural shape that our targeted validator missed. Verify each widget against its `json_get_widget_spec` entry."
1374
1383
  });
1375
1384
  }
1376
1385
  return {
@@ -1381,8 +1390,8 @@ function validateFormDefinition(input) {
1381
1390
  interpolationWarnings
1382
1391
  };
1383
1392
  }
1384
- const VALIDATE_FORM_DEFINITION_TOOL = {
1385
- name: "validate_form_definition",
1393
+ const JSON_VALIDATE_FORM_DEFINITION_TOOL = {
1394
+ name: "json_validate_form_definition",
1386
1395
  description: "Validate a GolemUI form definition against the bundled JSON Schemas. Use this AFTER generating or modifying a form definition to guarantee it is correct before the user pastes it into their codebase. Returns `{ valid, errors, warnings, expressionWarnings, interpolationWarnings }`. Hard mistakes (typos in widget `type`, missing required props, invalid validator shapes) show up in `errors` and flip `valid` to false. Likely-custom widgets (a `type` value that isn't a built-in and isn't close to one) show up in `warnings` instead — they don't affect `valid`. Reactive expressions (`include.when`, `disabled.when`, etc.) are linted separately into `expressionWarnings`. String interpolation templates (`{{$form.x}}`, `{{$meta.y}}`, expressions like `{{$form.count + 1}}`, etc.) in widget props, and bare expressions inside i18n `params` objects, are linted into `interpolationWarnings`.",
1387
1396
  inputSchema: {
1388
1397
  type: "object",
@@ -1765,8 +1774,8 @@ function generateFromJsonSchema(input) {
1765
1774
  const validation = validateFormDefinition({ formDefinition });
1766
1775
  return { formDefinition, unmapped, validation };
1767
1776
  }
1768
- const GENERATE_FROM_JSON_SCHEMA_TOOL = {
1769
- name: "generate_from_json_schema",
1777
+ const JSON_GENERATE_FROM_SCHEMA_TOOL = {
1778
+ name: "json_generate_from_schema",
1770
1779
  description: "Generate a GolemUI form definition from a JSON Schema describing the form data shape (typically an API request body or a Zod-derived schema). The result is validated against the GolemUI JSON Schemas before being returned, so it is guaranteed to be syntactically correct. Anything the mapper cannot handle is reported in `unmapped` rather than silently dropped — use that list to surface remaining work to the user.",
1771
1780
  inputSchema: {
1772
1781
  type: "object",
@@ -1920,8 +1929,8 @@ function resolveLocalRef(ref, doc) {
1920
1929
  }
1921
1930
  return cur ?? null;
1922
1931
  }
1923
- const GENERATE_FROM_OPENAPI_TOOL = {
1924
- name: "generate_from_openapi",
1932
+ const JSON_GENERATE_FROM_OPENAPI_TOOL = {
1933
+ name: "json_generate_from_openapi",
1925
1934
  description: 'Generate a GolemUI form for a specific OpenAPI 3.x operation (e.g. "POST /users"). Resolves the operation\'s JSON request body, dereferences `$ref`s, then maps it to a form definition that is validated against the GolemUI JSON Schemas before being returned, so it is guaranteed syntactically correct. Falls back to operation parameters when no request body is present. Anything the mapper cannot handle is reported in `unmapped` rather than silently dropped — use that list to surface remaining work to the user. Pass either a parsed `document` or a `documentUrl` to fetch.',
1926
1935
  inputSchema: {
1927
1936
  type: "object",
@@ -2257,9 +2266,9 @@ function getWidgetSpec(input) {
2257
2266
  notes: NOTES[input.widgetType] ?? []
2258
2267
  };
2259
2268
  }
2260
- const GET_WIDGET_SPEC_TOOL = {
2261
- name: "get_widget_spec",
2262
- description: "Look up the JSON Schema and a minimal working example for a single GolemUI widget. Use this when you need to know which `props` a widget accepts, what `kind` value it uses, or what shape its `validator` takes. Cheaper than dumping the whole API into context.",
2269
+ const JSON_GET_WIDGET_SPEC_TOOL = {
2270
+ name: "json_get_widget_spec",
2271
+ description: "Look up the JSON Schema and a minimal working example for a single GolemUI widget on the **JSON form-definition** surface. Use this when you need to know which `props` a widget accepts, what `kind` value it uses, or what shape its `validator` takes. **If you are writing `gui.*` DX code (TypeScript), you do NOT need this** — use `dx_list_factories` + `dx_get_spec` instead; fetching both surfaces for the same widget is redundant. Cheaper than dumping the whole API into context.",
2263
2272
  inputSchema: {
2264
2273
  type: "object",
2265
2274
  properties: {
@@ -2271,6 +2280,587 @@ const GET_WIDGET_SPEC_TOOL = {
2271
2280
  required: ["widgetType"]
2272
2281
  }
2273
2282
  };
2283
+ const CONFIG_FIELDS = ["include", "exclude", "disabled", "readonly"];
2284
+ function lintDxSnippet(ts, sourceText, lineOffset) {
2285
+ const sf = ts.createSourceFile("__dx_lint__.ts", sourceText, ts.ScriptTarget.ES2020, true);
2286
+ const diagnostics = [];
2287
+ const expressionWarnings = [];
2288
+ const posOf = (node) => {
2289
+ const p = sf.getLineAndCharacterOfPosition(node.getStart(sf));
2290
+ return { line: p.line + 1 - lineOffset, column: p.character + 1 };
2291
+ };
2292
+ const nameOf = (name) => {
2293
+ if (ts.isIdentifier(name)) return name.text;
2294
+ if (ts.isStringLiteralLike(name)) return name.text;
2295
+ return void 0;
2296
+ };
2297
+ const isGuiCall = (expr) => {
2298
+ if (!ts.isCallExpression(expr)) return false;
2299
+ let e = expr.expression;
2300
+ while (ts.isPropertyAccessExpression(e)) e = e.expression;
2301
+ return ts.isIdentifier(e) && e.text === "gui";
2302
+ };
2303
+ const visit = (node) => {
2304
+ if (ts.isObjectLiteralExpression(node)) {
2305
+ const spreadsGui = node.properties.some(
2306
+ (p) => ts.isSpreadAssignment(p) && isGuiCall(p.expression)
2307
+ );
2308
+ if (spreadsGui) {
2309
+ for (const p of node.properties) {
2310
+ const name = ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p) ? nameOf(p.name) : void 0;
2311
+ if (name && CONFIG_FIELDS.includes(name)) {
2312
+ const { line, column } = posOf(p);
2313
+ diagnostics.push({
2314
+ code: 0,
2315
+ message: `\`${name}\` is attached as a sibling of a \`gui.*\` spread — it is a silent no-op. The field renders/behaves unconditionally because the factory never receives it.`,
2316
+ line,
2317
+ column,
2318
+ hint: `Pass \`${name}\` INSIDE the factory's config argument, not via a spread: \`gui.inputs.x(path, { /* props */, ${name}: ... })\` — never \`{ ...gui.inputs.x(path, { /* props */ }), ${name}: ... }\`.`
2319
+ });
2320
+ }
2321
+ }
2322
+ }
2323
+ }
2324
+ if (ts.isPropertyAssignment(node) && nameOf(node.name) === "when" && ts.isStringLiteralLike(node.initializer)) {
2325
+ const { line, column } = posOf(node.initializer);
2326
+ for (const f of checkReactiveExpression(node.initializer.text, `when@${line}:${column}`)) {
2327
+ expressionWarnings.push(f);
2328
+ }
2329
+ }
2330
+ ts.forEachChild(node, visit);
2331
+ };
2332
+ visit(sf);
2333
+ return { diagnostics, expressionWarnings };
2334
+ }
2335
+ const requireFrom = createRequire(import.meta.url);
2336
+ const GOLEMUI_TYPE_PACKAGES = [
2337
+ { spec: "@golemui/gui-shared", rel: "gui/shared/index.d.ts" },
2338
+ { spec: "@golemui/gui-shared/internals", rel: "gui/shared/internals.d.ts" },
2339
+ { spec: "@golemui/gui-validators", rel: "gui/validators/index.d.ts" },
2340
+ { spec: "@golemui/core", rel: "core/index.d.ts" },
2341
+ { spec: "@golemui/core/internals", rel: "core/internals.d.ts" }
2342
+ ];
2343
+ const DIST_SEARCH_DEPTH = 8;
2344
+ const SNIPPET_FILE = "__dx_snippet__.ts";
2345
+ function resolveStrategy() {
2346
+ try {
2347
+ const pj = requireFrom.resolve("@golemui/gui-shared/package.json");
2348
+ const [root] = pj.split(/[\\/]node_modules[\\/]/);
2349
+ if (root && root !== pj) return { baseUrl: root };
2350
+ } catch {
2351
+ }
2352
+ const typesRoot = findDistTypesRoot();
2353
+ const paths = {};
2354
+ for (const { spec, rel } of GOLEMUI_TYPE_PACKAGES) {
2355
+ const abs = join(typesRoot, rel);
2356
+ if (existsSync(abs)) paths[spec] = [abs];
2357
+ }
2358
+ return { baseUrl: dirname(typesRoot), paths };
2359
+ }
2360
+ function findDistTypesRoot() {
2361
+ let dir = dirname(fileURLToPath(import.meta.url));
2362
+ for (let i = 0; i < DIST_SEARCH_DEPTH; i++) {
2363
+ const c = join(dir, "dist", "libs");
2364
+ if (existsSync(join(c, "gui", "shared", "index.d.ts"))) return c;
2365
+ dir = dirname(dir);
2366
+ }
2367
+ throw new Error(
2368
+ "dx_check_code: could not locate the @golemui type graph — neither an installed `@golemui/gui-shared` in node_modules nor the monorepo's dist/libs. The DX type-check cannot run without the real declarations."
2369
+ );
2370
+ }
2371
+ function resolveTypeCheckOptions(ts) {
2372
+ const strat = resolveStrategy();
2373
+ return {
2374
+ noEmit: true,
2375
+ strict: true,
2376
+ skipLibCheck: true,
2377
+ target: ts.ScriptTarget.ES2020,
2378
+ module: ts.ModuleKind.ESNext,
2379
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
2380
+ jsx: ts.JsxEmit.ReactJSX,
2381
+ lib: ["lib.es2020.d.ts", "lib.dom.d.ts"],
2382
+ // baseUrl roots node_modules resolution (zod, @standard-schema/spec, and — in the
2383
+ // installed case — the @golemui packages themselves).
2384
+ baseUrl: strat.baseUrl,
2385
+ ...strat.paths ? { paths: strat.paths } : {}
2386
+ };
2387
+ }
2388
+ function diagnose(ts, code, options) {
2389
+ const sf = ts.createSourceFile(SNIPPET_FILE, code, ts.ScriptTarget.ES2020, true);
2390
+ const host = ts.createCompilerHost(options, true);
2391
+ const originalGet = host.getSourceFile.bind(host);
2392
+ host.getSourceFile = (fileName, languageVersion, onError, shouldCreate) => fileName === SNIPPET_FILE || resolve(fileName) === resolve(SNIPPET_FILE) ? sf : originalGet(fileName, languageVersion, onError, shouldCreate);
2393
+ const originalExists = host.fileExists.bind(host);
2394
+ host.fileExists = (fileName) => fileName === SNIPPET_FILE || resolve(fileName) === resolve(SNIPPET_FILE) || originalExists(fileName);
2395
+ const originalRead = host.readFile.bind(host);
2396
+ host.readFile = (fileName) => fileName === SNIPPET_FILE ? code : originalRead(fileName);
2397
+ const program = ts.createProgram([SNIPPET_FILE], options, host);
2398
+ return ts.getPreEmitDiagnostics(program).filter((d) => d.file?.fileName === SNIPPET_FILE);
2399
+ }
2400
+ let guardChecked = false;
2401
+ function assertTypesAreLive(ts, options) {
2402
+ if (guardChecked) return;
2403
+ const bogus = `import { gui } from '@golemui/gui-shared';
2404
+ [gui.inputs.__definitely_not_a_real_member__('x', {})];
2405
+ `;
2406
+ if (diagnose(ts, bogus, options).length === 0) {
2407
+ throw new Error(
2408
+ "dx_check_code: internal type-resolution guard failed — a known-invalid snippet type-checked clean, which means the @golemui types resolved to `any`. Refusing to return misleading results."
2409
+ );
2410
+ }
2411
+ const looseEvent = `import { gui } from '@golemui/gui-shared';
2412
+ [gui.inputs.textInput('x', { onChange: 'not-a-function' })];
2413
+ `;
2414
+ if (diagnose(ts, looseEvent, options).length === 0) {
2415
+ throw new Error(
2416
+ "dx_check_code: internal type-resolution guard failed — a bare-string event handler type-checked clean, so the DxEventHandler type resolved to `any` (likely a stale @golemui build). Refusing to return misleading results."
2417
+ );
2418
+ }
2419
+ guardChecked = true;
2420
+ }
2421
+ let cachedTs = null;
2422
+ async function loadTs() {
2423
+ if (!cachedTs) {
2424
+ cachedTs = (await import("typescript")).default ?? await import("typescript");
2425
+ }
2426
+ return cachedTs;
2427
+ }
2428
+ function hintFor(d, flat) {
2429
+ if (d.code === 2339 && /submitButton/i.test(flat)) {
2430
+ return "There is no `gui.actions.submitButton`. Use `gui.actions.button({ label, actionType: 'submit' })`.";
2431
+ }
2432
+ if (d.code === 2769 && /validator/i.test(flat)) {
2433
+ return "Choice widgets (`dropdown`, `radiogroup`, `select`) need a typed validator — e.g. `validator: { type: 'string', required: true }` — unlike `textInput`, which accepts the loose `{ required: true }`. Add the `type`, or omit the validator.";
2434
+ }
2435
+ if (/'items'|'options'/.test(flat)) {
2436
+ return "Choice-widget option lists are not symmetric: `gui.inputs.dropdown` takes **`items`**, while `gui.inputs.radiogroup` and `gui.inputs.select` take **`options`** (both `{ value, label }[]`). Swap the key to the one this factory expects.";
2437
+ }
2438
+ if (/'content'/.test(flat)) {
2439
+ return "`gui.displays.alert` uses **`text`** (not `content`); `gui.displays.markdownText` uses **`md`** (not `content`). Use the factory’s own content key.";
2440
+ }
2441
+ return void 0;
2442
+ }
2443
+ function unfence(code) {
2444
+ const m = code.match(/^\s*```[a-zA-Z]*\n([\s\S]*?)```\s*$/);
2445
+ return m ? m[1] : code;
2446
+ }
2447
+ async function typeCheckDx(code) {
2448
+ const ts = await loadTs();
2449
+ const options = resolveTypeCheckOptions(ts);
2450
+ assertTypesAreLive(ts, options);
2451
+ const body = unfence(code);
2452
+ const full = /@golemui\/gui-shared/.test(body) ? body : `import { gui } from '@golemui/gui-shared';
2453
+ ${body}`;
2454
+ const lineOffset = full === body ? 0 : 1;
2455
+ const tscDiagnostics = diagnose(ts, full, options).map((d) => {
2456
+ const flat = ts.flattenDiagnosticMessageText(d.messageText, " ");
2457
+ const pos = d.file && d.start != null ? d.file.getLineAndCharacterOfPosition(d.start) : null;
2458
+ return {
2459
+ code: d.code,
2460
+ message: flat,
2461
+ line: pos ? pos.line + 1 - lineOffset : 0,
2462
+ column: pos ? pos.character + 1 : 0,
2463
+ hint: hintFor(d, flat)
2464
+ };
2465
+ });
2466
+ const { diagnostics: lintDiagnostics, expressionWarnings } = lintDxSnippet(ts, full, lineOffset);
2467
+ const diagnostics = [...tscDiagnostics, ...lintDiagnostics];
2468
+ return { ok: diagnostics.length === 0, diagnostics, expressionWarnings };
2469
+ }
2470
+ async function checkDxCode(input) {
2471
+ if (typeof input?.code !== "string" || input.code.trim() === "") {
2472
+ throw new Error("dx_check_code requires a non-empty `code` string.");
2473
+ }
2474
+ return typeCheckDx(input.code);
2475
+ }
2476
+ const DX_CHECK_CODE_TOOL = {
2477
+ name: "dx_check_code",
2478
+ description: "Type-check GolemUI **DX code** (the `gui.*` fluent builder, written in TypeScript) against the real `@golemui` type declarations, and return compiler diagnostics. This is for `gui.*` *code* — distinct from `json_validate_form_definition`, which validates a JSON form-definition *object*. GolemUI is not in any model's training data, so generated `gui.*` code is frequently a confident fabrication that does not compile; this is the only trustworthy check (inspection misses it). Beyond type errors it also catches two defects the compiler cannot see: a misplaced `include`/`exclude` attached as a sibling of a `gui.*` spread (`{ ...gui.inputs.x(...), include }` compiles but silently never hides the field — put `include`/`exclude` INSIDE the config argument), and reactive-expression mistakes in `when` strings (linted by the same engine as `json_validate_form_definition`). Pass the `gui.*` snippet as `code` (a bare array of `gui.inputs.*` items is fine — a `@golemui/gui-shared` import is added if missing). Returns `{ ok, diagnostics, expressionWarnings }`; each diagnostic has a TypeScript `code` (0 for the static lints), `message`, `line`/`column`, and — for recognized GolemUI mistakes — a `hint` with the fix. `expressionWarnings` are advisory and do not flip `ok`. Treat `ok: false` as blocking: apply the fixes and re-check until `ok` is true.",
2479
+ inputSchema: {
2480
+ type: "object",
2481
+ properties: {
2482
+ code: {
2483
+ type: "string",
2484
+ description: "The GolemUI `gui.*` DX snippet to type-check (TypeScript)."
2485
+ }
2486
+ },
2487
+ required: ["code"]
2488
+ }
2489
+ };
2490
+ const DX_FRAMEWORKS = ["react", "angular", "vue", "lit", "vanilla"];
2491
+ function resolveDxFramework() {
2492
+ const raw = typeof process !== "undefined" ? process.env?.["GOLEMUI_FRAMEWORK"]?.toLowerCase() : void 0;
2493
+ return DX_FRAMEWORKS.includes(raw ?? "") ? raw : "react";
2494
+ }
2495
+ const FRAMEWORK_SETUP = {
2496
+ react: "RENDER (React) — `import { gui } from '@golemui/gui-shared'; import { GuiForm } from '@golemui/gui-react'; import type { FormSubmitEvent } from '@golemui/core';`, then render `<GuiForm config={{ formDef: form }} formSubmit={(e: FormSubmitEvent) => { /* e.data is the form data */ }} />`. A `gui.displays.display(() => <h2>…</h2>)` returns React JSX.",
2497
+ angular: "RENDER (Angular) — `import { gui } from '@golemui/gui-shared'; import { FormComponent } from '@golemui/gui-angular';`, add `FormComponent` to the standalone component's `imports`, then in the template `<gui-form [config]=\"{ formDef: form }\" (formSubmit)=\"onSubmit($event)\"></gui-form>` — `$event` is a `FormSubmitEvent` (type from `@golemui/core`), `$event.data` is the form data.",
2498
+ vue: "RENDER (Vue) — `import { gui } from '@golemui/gui-shared'; import { GuiForm } from '@golemui/gui-vue';`, then `<GuiForm :config=\"{ formDef: form }\" @form-submit=\"onSubmit\" />` — the handler receives a `FormSubmitEvent` (`.data` is the form data). The event is `form-submit` (kebab-case), not `formSubmit`.",
2499
+ lit: "RENDER (Lit) — `import { gui } from '@golemui/gui-shared'; import '@golemui/gui-lit';` (registers the `<gui-form>` custom element), then `<gui-form .config=${{ formDef: form }} @form-submit=${(e: CustomEvent) => { /* e.detail is the FormSubmitEvent; e.detail.data */ }}></gui-form>`.",
2500
+ vanilla: "RENDER (vanilla JS) — `import { gui } from '@golemui/gui-shared'; import '@golemui/gui-lit';` (registers `<gui-form>`), then `const el = document.querySelector('gui-form'); el.config = { formDef: form }; el.addEventListener('form-submit', (e) => { /* e.detail.data */ });`"
2501
+ };
2502
+ function commonNote(fw = "react") {
2503
+ return "GolemUI builds FORMS — data collection and validation. It is NOT a general-purpose UI toolkit: it never renders documents, page content, or markdown for display. A form is just an array of these items: `export const form = [ /* items */ ];`. " + FRAMEWORK_SETUP[fw] + " Import the component stylesheet ONCE — `@golemui/gui-components/index.css` — or the form renders unstyled. To RECEIVE A SUBMIT: add a `gui.actions.button({ label, actionType: 'submit' })` to the form and listen for the submit on the host component (the RENDER line above shows how for your framework) — the handler gets a `FormSubmitEvent` whose `.data` is the collected form data. To DISABLE submit until the form is valid, add `disabled: { when: '$formIsInvalid' }` to that button (`$formIsInvalid` is a built-in validity flag) — see the conditional-and-state-props pattern. The SAME `formDef` renders in every framework (React/Angular/Vue/Lit/vanilla) — only the host wrapper changes. FORM-LEVEL CONFIG — `formDef` is ALWAYS the bare array. Anything form-wide (named `states`, `validateOn`) goes in a sibling `formConfig` on the config (`config={{ formDef: form, formConfig: { states, validateOn } }}`), NEVER inside `formDef`. Do NOT wrap the array as `{ states, form: [...] }` and pass THAT as `formDef` — `formDef` is typed `Record<string, any>` so it COMPILES, but the `gui.*` items are never resolved and the form renders BLANK with no error. See the form-level-states pattern. Common fields like `include`/`exclude` (conditional visibility) go INSIDE a factory’s config argument — never spread them onto the result (`{ ...gui.inputs.x(...), include }` compiles but silently does nothing). See the conditional-visibility pattern. STATIC CONTENT — a section heading or any non-input text/block is the HOST’s job, not GolemUI’s: use `gui.displays.display(() => <h2>…</h2>)` returning your framework’s own node (React JSX, Vue/Angular/Lit node) — it needs no dependency and always renders. MARKDOWN has exactly ONE use: `gui.inputs.markdown`, an INPUT where the user EDITS markdown (its value is their markdown string). There is NO markdown-for-display widget — never use markdown to render a heading or content; use `display` for that. VALIDATOR `type` — one rule, three cases (so you never have to guess): (1) choice widgets (`dropdown`, `radiogroup`, `select`) REQUIRE an explicit `type`: `validator: { type: 'string', required: true }`. (2) `repeater` (array) validators auto-supply `type: 'array'` — supply only the rules, e.g. `validator: { required: true, minItems: 1 }`, never `type`. (3) everything else (text, number, date) takes the loose validator with NO `type`: `validator: { required: true }`. EVENT HANDLERS — `onChange`/`onLoad`/`onFilter`/`onBlur` (inputs/layouts) and `onClick` (actions) are FUNCTIONS, never bare strings: return a string to dispatch a host event by that name (`onChange: () => 'languageChanged'`), or take the event to push live changes (`onChange: (event) => event.update({ path: 'city', options: [...] })`).";
2504
+ }
2505
+ const PATTERNS = [
2506
+ {
2507
+ name: "conditionalVisibility",
2508
+ title: "Show or hide a field conditionally (hide-when)",
2509
+ example: "gui.inputs.textInput('promoCode', { label: 'Promo code', include: { when: '$form.hasPromoCode === true' } })",
2510
+ notes: [
2511
+ "`include` and `exclude` are common config fields on EVERY `gui.*` item — pass them inside the factory's config argument (the same object as `label`): `gui.inputs.textInput('promoCode', { label, include: { when: '$form.hasPromoCode === true' } })`. `include` shows the field only while the expression is true; `exclude` hides it while true.",
2512
+ "NEVER attach them by spreading the factory result: `{ ...gui.inputs.textInput('promoCode', { label }), include: { when } }` COMPILES (TypeScript does not flag it) but is a silent no-op — the field renders unconditionally. The `include`/`exclude` must be a key of the config object, not a sibling of the spread.",
2513
+ "The `when` value is a `ReactiveExpression` (a plain string) that reads form state via `$form.<path>` and uses strict equality, e.g. `'$form.hasPromoCode === true'`. When the SAME condition gates two or more fields, define a named state and use `include: { in: ['stateName'] }` / `exclude: { from: [...] }` instead of an inline `when`. Named states are declared in `formConfig.states` — see the form-level-states pattern."
2514
+ ]
2515
+ },
2516
+ {
2517
+ name: "formLevelStates",
2518
+ title: "Declare named form-level states (and gate fields by them)",
2519
+ example: "gui.inputs.textInput('spouseName', { label: 'Spouse name', include: { in: ['familyCoverage'] } })",
2520
+ notes: [
2521
+ "Named states are form-level: declare them in `formConfig.states`, a sibling of `formDef` on the `<GuiForm>` config — NOT a wrapper around the array. `formConfig.states` maps each state name to a `ReactiveExpression` string, e.g. `{ familyCoverage: '$form.coverageType === \\'family\\'' }`. Then any item references it by name: `include: { in: ['familyCoverage'] }` (show while true) / `exclude: { from: ['familyCoverage'] }` (hide while true).",
2522
+ "Full shape: `<GuiForm config={{ formDef: form, formConfig: { states: { familyCoverage: '...' }, validateOn: 'blur' } }} />`. The `formDef` stays the bare `gui.*` array.",
2523
+ "NEVER pass `{ states, form: [...] }` as `formDef`. It type-checks (`formDef` is `Record<string, any>`) but that object is not recognized as a `gui.*` bundle, so the items are never resolved — the form renders BLANK with no console error. The `{ states, form }` shape only exists for hand-written core/JSON widgets, not the `gui.*` facade."
2524
+ ]
2525
+ },
2526
+ {
2527
+ name: "conditionalAndStateProps",
2528
+ title: "Conditional & state-driven props (enable/disable, show/hide, readonly)",
2529
+ example: "gui.actions.button({ label: 'Submit', actionType: 'submit', disabled: { when: '$formIsInvalid' } })",
2530
+ notes: [
2531
+ "ENABLE/DISABLE & READONLY: `disabled` and `readonly` are typed `boolean | { when: <expr> }`. To gate the submit button on validity: `gui.actions.button({ label, actionType: 'submit', disabled: { when: '$formIsInvalid' } })`. `$formIsInvalid` is a built-in validity flag — do not declare it as a state.",
2532
+ "SHOW/HIDE: `include` / `exclude` are typed `{ in: ['stateName'] }` / `{ from: ['stateName'] }` (state lists) or `{ when: <expr> }`. Every state name in `in`/`from` MUST be declared in `formConfig.states`; an undeclared name leaves the widget hidden forever (the engine logs an error to the console).",
2533
+ "These are ALL typed config keys — pass them inside the factory’s config argument. NEVER reach a prop by casting the factory result and assigning a key (`(btn as any)['disabled.formValid'] = false`) or by spreading (`{ ...gui.actions.button(...), disabled }`): keys added that way are SILENTLY ignored and the behavior never fires. If a prop is not on the typed config, you are guessing — it is not a real field."
2534
+ ]
2535
+ }
2536
+ ];
2537
+ const INPUTS = [
2538
+ {
2539
+ factory: "textInput",
2540
+ namespace: "inputs",
2541
+ call: "gui.inputs.textInput(path, { label, placeholder?, defaultValue?, validator? })",
2542
+ example: "gui.inputs.textInput('fullName', { label: 'Full name', validator: { required: true, minLength: 2 } })",
2543
+ notes: [
2544
+ "Text fields accept a loose validator: `{ required, minLength, maxLength, pattern, format }` (no `type` needed).",
2545
+ "Email is a text input with `validator: { required: true, format: 'email' }` — use `format`, not a regex. The full `format` enum: `email`, `url`, `uuid`, `hostname`, `ipv4`, `ipv6`, `date`, `time`, `date-time`, `duration`."
2546
+ ]
2547
+ },
2548
+ {
2549
+ factory: "numberInput",
2550
+ namespace: "inputs",
2551
+ call: "gui.inputs.numberInput(path, { label, defaultValue?, validator? })",
2552
+ example: "gui.inputs.numberInput('age', { label: 'Age', validator: { required: true, minimum: 0, maximum: 120 } })",
2553
+ notes: [
2554
+ "Number validator: `{ required, minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf }`."
2555
+ ]
2556
+ },
2557
+ {
2558
+ factory: "booleanInput",
2559
+ namespace: "inputs",
2560
+ call: "gui.inputs.booleanInput(path, { label, defaultValue? })",
2561
+ example: "gui.inputs.booleanInput('newsletter', { label: 'Subscribe to newsletter', defaultValue: false })",
2562
+ notes: ["The on/off toggle for a single boolean. Use `checkbox` for a checkbox presentation."]
2563
+ },
2564
+ {
2565
+ factory: "checkbox",
2566
+ namespace: "inputs",
2567
+ call: "gui.inputs.checkbox(path, { label, defaultValue? })",
2568
+ example: "gui.inputs.checkbox('terms', { label: 'I accept the terms', defaultValue: false })",
2569
+ notes: ["A single boolean rendered as a checkbox."]
2570
+ },
2571
+ {
2572
+ factory: "textarea",
2573
+ namespace: "inputs",
2574
+ call: "gui.inputs.textarea(path, { label, placeholder?, validator? })",
2575
+ example: "gui.inputs.textarea('bio', { label: 'Bio', validator: { maxLength: 500 } })",
2576
+ notes: ["Multi-line text; same loose string validator as `textInput`."]
2577
+ },
2578
+ {
2579
+ factory: "password",
2580
+ namespace: "inputs",
2581
+ call: "gui.inputs.password(path, { label, validator? })",
2582
+ example: "gui.inputs.password('password', { label: 'Password', validator: { required: true, minLength: 8 } })",
2583
+ notes: ["Masked text input; loose string validator."]
2584
+ },
2585
+ {
2586
+ factory: "dropdown",
2587
+ namespace: "inputs",
2588
+ call: "gui.inputs.dropdown(path, { label, items, validator? })",
2589
+ example: "gui.inputs.dropdown('country', { label: 'Country', items: [{ value: 'us', label: 'United States' }, { value: 'ca', label: 'Canada' }], validator: { type: 'string', required: true } })",
2590
+ notes: [
2591
+ "Choice list uses **`items`** (`{ value, label }[]`).",
2592
+ "WART: choice widgets (`dropdown`, `radiogroup`, `select`) need a **typed** validator — `{ type: 'string', required: true }` — unlike text inputs which accept the loose `{ required: true }`. Add the `type`, or omit the validator."
2593
+ ]
2594
+ },
2595
+ {
2596
+ factory: "radiogroup",
2597
+ namespace: "inputs",
2598
+ call: "gui.inputs.radiogroup(path, { label, options, defaultValue?, validator? })",
2599
+ example: "gui.inputs.radiogroup('accountType', { label: 'Account type', defaultValue: 'personal', options: [{ value: 'personal', label: 'Personal' }, { value: 'business', label: 'Business' }] })",
2600
+ notes: [
2601
+ "Radio group uses **`options`** (`{ value, label }[]`) — note the asymmetry: `dropdown` uses `items`, `radiogroup` uses `options`.",
2602
+ "If required, use a typed validator `{ type: 'string', required: true }` (same wart as `dropdown`)."
2603
+ ]
2604
+ },
2605
+ {
2606
+ factory: "datePicker",
2607
+ namespace: "inputs",
2608
+ call: "gui.inputs.datePicker(path, { label, minDate?, maxDate?, validator? })",
2609
+ example: "gui.inputs.datePicker('startDate', { label: 'Coverage start', minDate: '2025-01-01', validator: { required: true } })",
2610
+ notes: [
2611
+ "THE DEFAULT single-date field: a text field with a popover calendar (click to open). Prefer this for most dates. (`calendar` = always-visible inline calendar; `dateInput` = typed entry, no calendar UI.) Accepts the loose `{ required: true }` validator.",
2612
+ 'Bound the selectable range with **`minDate`** / **`maxDate`** — ISO `YYYY-MM-DD` strings. For "today or later" set `minDate` to today’s date; for "not in the future" set `maxDate` to today. Same `minDate`/`maxDate` on `calendar`, `dateInput`, and the range date widgets.'
2613
+ ]
2614
+ },
2615
+ {
2616
+ factory: "currency",
2617
+ namespace: "inputs",
2618
+ call: "gui.inputs.currency(path, { label, validator? })",
2619
+ example: "gui.inputs.currency('price', { label: 'Price', validator: { required: true, minimum: 0 } })",
2620
+ notes: ["Numeric money input; number-style validator."]
2621
+ },
2622
+ {
2623
+ factory: "select",
2624
+ namespace: "inputs",
2625
+ call: "gui.inputs.select(path, { label, options, validator? })",
2626
+ example: "gui.inputs.select('plan', { label: 'Plan', options: [{ value: 'free', label: 'Free' }, { value: 'pro', label: 'Pro' }], validator: { type: 'string', required: true } })",
2627
+ notes: [
2628
+ "Choice widget that uses **`options`** (like `radiogroup`) — NOT `items` (which `dropdown` uses).",
2629
+ "Same validator wart as the other choice widgets: if required, use a typed validator `{ type: 'string', required: true }`."
2630
+ ]
2631
+ },
2632
+ {
2633
+ factory: "dateInput",
2634
+ namespace: "inputs",
2635
+ call: "gui.inputs.dateInput(path, { label, minDate?, maxDate?, validator? })",
2636
+ example: "gui.inputs.dateInput('startDate', { label: 'Start date', validator: { required: true } })",
2637
+ notes: [
2638
+ "Typed date entry, NO calendar UI — use only when keyboard-first entry is wanted. For most dates use `datePicker` (popover calendar) instead. Accepts the loose `{ required: true }`.",
2639
+ "`minDate` / `maxDate` (ISO `YYYY-MM-DD` strings) constrain the accepted range — see `datePicker`."
2640
+ ]
2641
+ },
2642
+ {
2643
+ factory: "calendar",
2644
+ namespace: "inputs",
2645
+ call: "gui.inputs.calendar(path, { label, minDate?, maxDate? })",
2646
+ example: "gui.inputs.calendar('day', { label: 'Pick a day', minDate: '2025-01-01' })",
2647
+ notes: [
2648
+ "An always-visible INLINE calendar (no popover) — use when the calendar should be shown on the page. For a compact single-date field use `datePicker` instead.",
2649
+ "`minDate` / `maxDate` (ISO `YYYY-MM-DD` strings) constrain the selectable range — see `datePicker`."
2650
+ ]
2651
+ },
2652
+ {
2653
+ factory: "markdown",
2654
+ namespace: "inputs",
2655
+ call: "gui.inputs.markdown(path, { label })",
2656
+ example: "gui.inputs.markdown('notes', { label: 'Notes (markdown)' })",
2657
+ notes: [
2658
+ "A markdown *editor input* — the user types markdown and the value IS that markdown string. This is the ONLY use of markdown in GolemUI: there is no markdown-for-display. For a heading or static block, use `gui.displays.display(() => <node>)` (your host renders it), never a markdown widget."
2659
+ ]
2660
+ },
2661
+ {
2662
+ factory: "tags",
2663
+ namespace: "inputs",
2664
+ call: "gui.inputs.tags(path, { label })",
2665
+ example: "gui.inputs.tags('skills', { label: 'Skills' })",
2666
+ notes: ["Free-form multi-value tag input; the value is a string array."]
2667
+ },
2668
+ {
2669
+ factory: "repeater",
2670
+ namespace: "inputs",
2671
+ call: "gui.inputs.repeater(path, { label?, addLabel?, removeLabel?, limit?, template })",
2672
+ example: "gui.inputs.repeater('attendees', { label: 'Attendees', addLabel: 'Add attendee', template: [ gui.inputs.textInput('attendees.items.name', { label: 'Name' }) ] })",
2673
+ notes: [
2674
+ "The variable-length array field: the user adds/removes rows at runtime, each rendering the `template`. This is the idiomatic way to collect an UNKNOWN number of items — do NOT hand-roll N pre-generated copies gated by `include.when` (a common but wrong workaround).",
2675
+ "Child field paths inside the template are **`<path>.items.<field>`** — a repeater on `'attendees'` holds `gui.inputs.textInput('attendees.items.name', …)`. At RUNTIME each `items` token is replaced by the row's array index (`attendees.items.name` → `attendees[0].name`, `attendees[1].name`, …); nesting a repeater inside a template adds another `.items.` segment (`teams.items.members.items.name`).",
2676
+ "`limit` caps the number of rows; `addLabel`/`removeLabel` set the button text."
2677
+ ]
2678
+ },
2679
+ {
2680
+ factory: "list",
2681
+ namespace: "inputs",
2682
+ call: "gui.inputs.list(path, { label, items, height?, itemHeight? })",
2683
+ example: "gui.inputs.list('selection', { label: 'Pick an option', items: ['Option 1', 'Option 2', 'Option 3'], height: 200, itemHeight: 40 })",
2684
+ notes: [
2685
+ "A scrolling selection list. `items` is a string array (or `{ value, label }[]`).",
2686
+ "`height` / `itemHeight` size the scroll viewport (pixels)."
2687
+ ]
2688
+ },
2689
+ {
2690
+ factory: "rangeCalendar",
2691
+ namespace: "inputs",
2692
+ call: "gui.inputs.rangeCalendar(path, { label? })",
2693
+ example: "gui.inputs.rangeCalendar('stayDates', { label: 'Stay dates' })",
2694
+ notes: [
2695
+ "Inline calendar for a start–end date **range** (the value is a date range). For a single date use `calendar`."
2696
+ ]
2697
+ },
2698
+ {
2699
+ factory: "rangeDateInput",
2700
+ namespace: "inputs",
2701
+ call: "gui.inputs.rangeDateInput(path, { label? })",
2702
+ example: "gui.inputs.rangeDateInput('stayDates', { label: 'Stay dates' })",
2703
+ notes: ["Typed start–end date **range** entry (the range sibling of `dateInput`)."]
2704
+ },
2705
+ {
2706
+ factory: "rangeDatePicker",
2707
+ namespace: "inputs",
2708
+ call: "gui.inputs.rangeDatePicker(path, { label? })",
2709
+ example: "gui.inputs.rangeDatePicker('stayDates', { label: 'Stay dates' })",
2710
+ notes: ["Popover calendar for a start–end date **range** (the range sibling of `datePicker`)."]
2711
+ }
2712
+ ];
2713
+ const ACTIONS = [
2714
+ {
2715
+ factory: "button",
2716
+ namespace: "actions",
2717
+ call: "gui.actions.button({ label, actionType?: 'submit', onClick? })",
2718
+ example: "gui.actions.button({ label: 'Sign up', actionType: 'submit' })",
2719
+ notes: [
2720
+ "Submit button: `gui.actions.button({ label, actionType: 'submit' })`.",
2721
+ "There is NO `gui.actions.submitButton` — it was removed. Use `button` with `actionType: 'submit'`.",
2722
+ "For a non-submit action use an `onClick: (event) => { /* event.data is the form data */ }` handler."
2723
+ ]
2724
+ }
2725
+ ];
2726
+ const DISPLAYS = [
2727
+ {
2728
+ factory: "alert",
2729
+ namespace: "displays",
2730
+ call: "gui.displays.alert({ text })",
2731
+ example: "gui.displays.alert({ text: 'Please review your details before submitting.' })",
2732
+ notes: [
2733
+ "Static, non-input callout. Uses **`text`** (not `content`). Displays do not take a `path`."
2734
+ ]
2735
+ },
2736
+ {
2737
+ factory: "display",
2738
+ namespace: "displays",
2739
+ call: "gui.displays.display(render)",
2740
+ example: "gui.displays.display(() => 'Order summary')",
2741
+ notes: [
2742
+ "**The go-to for a static heading or any standalone/formatted block.** Return your framework’s own content from the render function — React: `gui.displays.display(() => <h2>Member enrollment</h2>)`; Vue/Angular/Lit: return that framework’s node. It renders immediately with **zero registration and no parser dependency** — this is how you put a heading or any static block in a form (GolemUI itself never renders content for display).",
2743
+ "Pass the render function **directly** (not wrapped in an object) — `(params) => any`. `params.$form` is the live form data, so content can be dynamic — but a field is **absent until filled**, so guard before indexing: `Array.isArray(params.$form.items) ? params.$form.items : []`, never `params.$form.items.length` raw."
2744
+ ]
2745
+ }
2746
+ ];
2747
+ const LAYOUTS = [
2748
+ {
2749
+ factory: "flex",
2750
+ namespace: "layouts",
2751
+ call: "gui.layouts.flex(children, props?)",
2752
+ example: "gui.layouts.flex([ gui.inputs.textInput('firstName', { label: 'First name' }), gui.inputs.textInput('lastName', { label: 'Last name' }) ])",
2753
+ notes: [
2754
+ "Layouts take the **children array first**, then optional props — unlike inputs (path first).",
2755
+ "Direction-locked variants: `verticalFlex`, `horizontalFlex` (and `grid` / `verticalGrid` / `horizontalGrid`)."
2756
+ ]
2757
+ },
2758
+ {
2759
+ factory: "verticalFlex",
2760
+ namespace: "layouts",
2761
+ call: "gui.layouts.verticalFlex(children, props?)",
2762
+ example: "gui.layouts.verticalFlex([ gui.inputs.textInput('a', { label: 'A' }), gui.inputs.textInput('b', { label: 'B' }) ])",
2763
+ notes: ["A `flex` with direction fixed to vertical."]
2764
+ },
2765
+ {
2766
+ factory: "horizontalFlex",
2767
+ namespace: "layouts",
2768
+ call: "gui.layouts.horizontalFlex(children, props?)",
2769
+ example: "gui.layouts.horizontalFlex([ gui.inputs.textInput('a', { label: 'A' }), gui.inputs.textInput('b', { label: 'B' }) ])",
2770
+ notes: ["A `flex` with direction fixed to horizontal."]
2771
+ },
2772
+ {
2773
+ factory: "grid",
2774
+ namespace: "layouts",
2775
+ call: "gui.layouts.grid(children, props?)",
2776
+ example: "gui.layouts.grid([ gui.inputs.textInput('a', { label: 'A' }), gui.inputs.textInput('b', { label: 'B' }) ])",
2777
+ notes: ["Grid layout; `horizontalGrid` / `verticalGrid` lock the direction."]
2778
+ },
2779
+ {
2780
+ factory: "verticalGrid",
2781
+ namespace: "layouts",
2782
+ call: "gui.layouts.verticalGrid(children, props?)",
2783
+ example: "gui.layouts.verticalGrid([ gui.inputs.textInput('a', { label: 'A' }), gui.inputs.textInput('b', { label: 'B' }) ])",
2784
+ notes: ["A `grid` with direction fixed to vertical."]
2785
+ },
2786
+ {
2787
+ factory: "horizontalGrid",
2788
+ namespace: "layouts",
2789
+ call: "gui.layouts.horizontalGrid(children, props?)",
2790
+ example: "gui.layouts.horizontalGrid([ gui.inputs.textInput('a', { label: 'A' }), gui.inputs.textInput('b', { label: 'B' }) ])",
2791
+ notes: ["A `grid` with direction fixed to horizontal."]
2792
+ },
2793
+ {
2794
+ factory: "tabs",
2795
+ namespace: "layouts",
2796
+ call: "gui.layouts.tabs(sections)",
2797
+ example: "gui.layouts.tabs([ { label: 'Account', children: [ gui.inputs.textInput('email', { label: 'Email' }) ] }, { label: 'Profile', children: [ gui.inputs.textInput('name', { label: 'Name' }) ] } ])",
2798
+ notes: [
2799
+ "Takes `sections: { label, children, uid? }[]` — each section is a tab with its own children."
2800
+ ]
2801
+ },
2802
+ {
2803
+ factory: "accordion",
2804
+ namespace: "layouts",
2805
+ call: "gui.layouts.accordion(sections)",
2806
+ example: "gui.layouts.accordion([ { label: 'Billing', children: [ gui.inputs.textInput('card', { label: 'Card' }) ] } ])",
2807
+ notes: [
2808
+ "Takes `sections: { label, children, uid? }[]` — same shape as `tabs`, rendered as collapsible panels."
2809
+ ]
2810
+ }
2811
+ ];
2812
+ const ALL = [...INPUTS, ...ACTIONS, ...DISPLAYS, ...LAYOUTS];
2813
+ const DX_SPECS = Object.fromEntries(ALL.map((s) => [s.factory, s]));
2814
+ function listDxFactories() {
2815
+ return Object.keys(DX_SPECS).sort();
2816
+ }
2817
+ function dxCatalog(framework = "react") {
2818
+ return {
2819
+ factories: ALL.map(({ factory, namespace, call, example, notes }) => ({
2820
+ factory,
2821
+ namespace,
2822
+ call,
2823
+ example,
2824
+ notes
2825
+ })),
2826
+ patterns: PATTERNS,
2827
+ common: commonNote(framework)
2828
+ };
2829
+ }
2830
+ function getDxSpec(input) {
2831
+ const spec = DX_SPECS[input?.factory];
2832
+ if (!spec) {
2833
+ throw new Error(
2834
+ `Unknown gui.* factory: ${JSON.stringify(input?.factory)}. Known factories: ${listDxFactories().join(", ")}.`
2835
+ );
2836
+ }
2837
+ return { ...spec };
2838
+ }
2839
+ const DX_GET_SPEC_TOOL = {
2840
+ name: "dx_get_spec",
2841
+ description: "Deep-dive lookup for ONE `gui.*` factory — its calling convention, a compile-verified example, and authoring notes. **In most cases you do not need this: call `dx_list_factories` once and write from it** — it already carries every factory's example and gotchas plus the cross-cutting rules. Reach here only when you want to re-confirm a single factory in isolation. Lean by design: it returns just that factory's payload (no repeated common note/patterns — those live in `dx_list_factories`). Distinct from `json_get_widget_spec`, which returns the JSON form-definition shape. GolemUI is not in any model's training data, so do NOT guess the API. After writing, verify with `dx_check_code`. Pass `factory` as the camelCase name (e.g. `textInput`, `dropdown`, `radiogroup`, `button`).",
2842
+ inputSchema: {
2843
+ type: "object",
2844
+ properties: {
2845
+ factory: {
2846
+ type: "string",
2847
+ description: 'The gui.* factory name, e.g. "textInput", "dropdown", "button".'
2848
+ }
2849
+ },
2850
+ required: ["factory"]
2851
+ }
2852
+ };
2853
+ function listDxFactoriesCatalog(framework = resolveDxFramework()) {
2854
+ return dxCatalog(framework);
2855
+ }
2856
+ const DX_LIST_FACTORIES_TOOL = {
2857
+ name: "dx_list_factories",
2858
+ description: "The complete GolemUI `gui.*` DX reference in ONE call: EVERY factory with its namespace, calling convention, a compile-verified example, and its gotchas — plus the cross-cutting patterns (e.g. conditional visibility) and the common authoring rules (incl. the validator `type` rule). **Call this FIRST when authoring `gui.*` DX code, then write from it directly** — it is self-sufficient for most forms, so you rarely need a follow-up lookup. GolemUI is not in any model's training data, so do NOT guess the API; every real name is here. `dx_get_spec(factory)` is only the rare deep-dive for one factory. You do NOT need `json_get_widget_spec` when writing `gui.*` code — that serves the JSON form-definition surface, a different API. Takes no arguments; fetch once and keep it in context.",
2859
+ inputSchema: {
2860
+ type: "object",
2861
+ properties: {}
2862
+ }
2863
+ };
2274
2864
  const STATES_CONCEPT = {
2275
2865
  concept: "states",
2276
2866
  summary: "States are named boolean conditions declared at the form root. Each state name maps to a reactive expression string (using `$form`, `$meta`, or `$formIsInvalid`) that the runtime evaluates continuously as the user interacts with the form. Once declared, state names can gate widget visibility (include / exclude) and swap individual widget properties per-state — a capability unique to named states that has no inline `when` equivalent.",
@@ -2749,7 +3339,7 @@ const ICONS_CONCEPT = {
2749
3339
  "Widgets that support `props.icon`: `button`, `textinput`, `password`, `select`, `currency`, `tags`, `dateInput`, `datePicker`, `rangeCalendar`, `rangedateinput`, `rangedatepicker`.",
2750
3340
  '`button` is the only widget with `props.iconPosition`. Allowed values: `"left"` (default) and `"right"`.',
2751
3341
  'All icon props support state suffixes: `"icon.<stateName>": "check"` swaps the icon when that state is active. Call `get_concept({ concept: "states" })` for the full state-suffix pattern.',
2752
- "Do not set `props.icon` on widgets that do not support it — the schema will reject it and `validate_form_definition` will report an error."
3342
+ "Do not set `props.icon` on widgets that do not support it — the schema will reject it and `json_validate_form_definition` will report an error."
2753
3343
  ]
2754
3344
  };
2755
3345
  const CONCEPTS = {
@@ -2782,14 +3372,25 @@ const GET_CONCEPT_TOOL = {
2782
3372
  }
2783
3373
  };
2784
3374
  export {
2785
- GENERATE_FROM_JSON_SCHEMA_TOOL as G,
2786
- VALIDATE_FORM_DEFINITION_TOOL as V,
2787
- GENERATE_FROM_OPENAPI_TOOL as a,
2788
- GET_CONCEPT_TOOL as b,
2789
- GET_WIDGET_SPEC_TOOL as c,
2790
- generateFromOpenapi as d,
2791
- getConcept as e,
2792
- getWidgetSpec as f,
2793
- generateFromJsonSchema as g,
3375
+ DX_CHECK_CODE_TOOL as D,
3376
+ GET_CONCEPT_TOOL as G,
3377
+ JSON_GENERATE_FROM_OPENAPI_TOOL as J,
3378
+ DX_GET_SPEC_TOOL as a,
3379
+ DX_LIST_FACTORIES_TOOL as b,
3380
+ DX_SPECS as c,
3381
+ JSON_GENERATE_FROM_SCHEMA_TOOL as d,
3382
+ JSON_GET_WIDGET_SPEC_TOOL as e,
3383
+ JSON_VALIDATE_FORM_DEFINITION_TOOL as f,
3384
+ checkDxCode as g,
3385
+ dxCatalog as h,
3386
+ generateFromJsonSchema as i,
3387
+ generateFromOpenapi as j,
3388
+ getConcept as k,
3389
+ getDxSpec as l,
3390
+ getWidgetSpec as m,
3391
+ listDxFactories as n,
3392
+ listDxFactoriesCatalog as o,
3393
+ resolveDxFramework as r,
3394
+ typeCheckDx as t,
2794
3395
  validateFormDefinition as v
2795
3396
  };