@golemui/gui-mcp 1.0.0-rc.4 → 1.0.0
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.
- package/CHANGELOG.md +27 -0
- package/cli.js +2 -1
- package/{get-concept-DMoitPWv.js → get-concept-Cy3zD1BS.js} +8 -603
- package/json.d.ts +7 -0
- package/json.js +13 -0
- package/lib.d.ts +1 -7
- package/lib.js +17 -16
- package/list-dx-factories-CF4ExKyx.js +602 -0
- package/package.json +9 -5
|
@@ -1,9 +1,5 @@
|
|
|
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";
|
|
7
3
|
const $schema$u = "https://json-schema.org/draft/2020-12/schema";
|
|
8
4
|
const $id$u = "https://golemui.com/schemas/common.schema.json";
|
|
9
5
|
const title$u = "Golem Common Definitions";
|
|
@@ -2280,587 +2276,6 @@ const JSON_GET_WIDGET_SPEC_TOOL = {
|
|
|
2280
2276
|
required: ["widgetType"]
|
|
2281
2277
|
}
|
|
2282
2278
|
};
|
|
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
|
-
};
|
|
2864
2279
|
const STATES_CONCEPT = {
|
|
2865
2280
|
concept: "states",
|
|
2866
2281
|
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.",
|
|
@@ -3372,25 +2787,15 @@ const GET_CONCEPT_TOOL = {
|
|
|
3372
2787
|
}
|
|
3373
2788
|
};
|
|
3374
2789
|
export {
|
|
3375
|
-
DX_CHECK_CODE_TOOL as D,
|
|
3376
2790
|
GET_CONCEPT_TOOL as G,
|
|
3377
2791
|
JSON_GENERATE_FROM_OPENAPI_TOOL as J,
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
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,
|
|
2792
|
+
JSON_GENERATE_FROM_SCHEMA_TOOL as a,
|
|
2793
|
+
JSON_GET_WIDGET_SPEC_TOOL as b,
|
|
2794
|
+
JSON_VALIDATE_FORM_DEFINITION_TOOL as c,
|
|
2795
|
+
generateFromOpenapi as d,
|
|
2796
|
+
getConcept as e,
|
|
2797
|
+
getWidgetSpec as f,
|
|
2798
|
+
generateFromJsonSchema as g,
|
|
2799
|
+
checkReactiveExpression as h,
|
|
3395
2800
|
validateFormDefinition as v
|
|
3396
2801
|
};
|
package/json.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { validateFormDefinition, JSON_VALIDATE_FORM_DEFINITION_TOOL, } from './json/validate-form-definition';
|
|
2
|
+
export type { ValidateInput, ValidateResult } from './json/validate-form-definition';
|
|
3
|
+
export { generateFromJsonSchema, JSON_GENERATE_FROM_SCHEMA_TOOL, } from './json/generate-from-json-schema';
|
|
4
|
+
export type { GenerateFromJsonSchemaInput, GenerateFromJsonSchemaResult, } from './json/generate-from-json-schema';
|
|
5
|
+
export { generateFromOpenapi, JSON_GENERATE_FROM_OPENAPI_TOOL } from './json/generate-from-openapi';
|
|
6
|
+
export { getWidgetSpec, JSON_GET_WIDGET_SPEC_TOOL } from './json/get-widget-spec';
|
|
7
|
+
export { getConcept, GET_CONCEPT_TOOL } from './shared/get-concept';
|