ngx-json-render 0.7.8 → 0.8.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/README.md CHANGED
@@ -135,7 +135,7 @@ ready-made Material catalog before you have written a component.
135
135
 
136
136
  ```ts
137
137
  // catalog.ts
138
- import { schema } from 'ngx-json-render';
138
+ import { schema } from 'ngx-json-render/schema';
139
139
  import { z } from 'zod';
140
140
 
141
141
  export const catalog = schema.createCatalog({
@@ -302,7 +302,7 @@ app.post('/api/generate', async (req, res) => {
302
302
  });
303
303
  ```
304
304
 
305
- `catalog.ts` imports `schema` from `ngx-json-render`, and that entry point also loads Angular and the renderer's components, which are published partially compiled. In an Angular SSR app (`ng new --ssr`) the Angular CLI finishes compiling them when it builds the server, so the route above goes into its `server.ts` unchanged and works both under `ng serve` and from the built server. On Angular 20 and 21, add `"prebundle": { "exclude": ["zod"] }` to the `serve` options in `angular.json` first: their Vite 7 dev server can't transform zod for the server (`Cannot split a chunk that has already been edited`) and answers every page and API route with a 500. A Node server the Angular CLI doesn't build, run with `node` or `tsx`, fails on that import with `'@angular/compiler' is not available` — make `import '@angular/compiler';` its first import.
305
+ `catalog.ts` imports `schema` from `ngx-json-render/schema`, an entry point that needs nothing but `@json-render/core`, so any server can load the catalog without Angular: plain Node, `tsx`, an edge function. (`ngx-json-render` exports the same `schema`, but that entry point also loads Angular and the renderer's partially compiled components. A Node server the Angular CLI doesn't build then fails with `'@angular/compiler' is not available`.) In an Angular SSR app (`ng new --ssr`) the route goes into its `server.ts`. On Angular 20 and 21, add `"prebundle": { "exclude": ["zod"] }` to the `serve` options in `angular.json` first: their Vite 7 dev server can't transform zod for the server (`Cannot split a chunk that has already been edited`) and answers every page and API route with a 500.
306
306
 
307
307
  The patches apply to the `spec` signal as each line arrives, so the UI assembles on screen while the model is still generating — exactly what the [demo's Streaming tab](https://shteynu.github.io/ngx-json-render/) replays. Prefer structured output? `catalog.jsonSchema()` exports a JSON Schema for `streamObject`/tool calls, and `checkSpec(spec, 'strict', { catalog })` checks a finished spec against the catalog, props included — see [Checking what the model produced](#checking-what-the-model-produced) for why that and not `catalog.validate(spec)` alone.
308
308
 
@@ -507,6 +507,35 @@ stack proving they exceed it. If you accept specs you did not generate, set
507
507
  `maxDepth` — with no cap there is nothing to stop the check from recursing as
508
508
  deep as the spec asks.
509
509
 
510
+ ## Inside an MCP App host
511
+
512
+ An [MCP App](https://modelcontextprotocol.io/docs/extensions/apps) is a tool whose result a host (Claude, ChatGPT, VS Code) renders as an inline view. `ngx-json-render/mcp` is the Angular side of that view: `injectJsonRenderApp()` connects to the host and keeps the spec the model passed to the tool in a signal. It is the counterpart of `useJsonRenderApp` from `@json-render/mcp/app`. It needs two optional peers:
513
+
514
+ ```bash
515
+ npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk
516
+ ```
517
+
518
+ ```ts
519
+ import { Component } from '@angular/core';
520
+ import { JsonRenderer } from 'ngx-json-render';
521
+ import { injectJsonRenderApp } from 'ngx-json-render/mcp';
522
+ import { materialRegistry } from 'ngx-json-render-material';
523
+
524
+ @Component({
525
+ selector: 'app-root',
526
+ imports: [JsonRenderer],
527
+ template: `<json-render [spec]="mcp.spec()" [loading]="mcp.loading()" [registry]="registry" />`,
528
+ })
529
+ export class App {
530
+ readonly mcp = injectJsonRenderApp({ name: 'my-app', version: '1.0.0' });
531
+ readonly registry = materialRegistry;
532
+ }
533
+ ```
534
+
535
+ The spec renders while the model is still writing the tool call, from the host's `toolinputpartial` notifications; pass `streamPartialInput: false` to wait for the result. `mcp.sendMessage(text, data)` posts a user message to the chat, so a button can continue the conversation, and `mcp.callServerTool(name, args)` replaces the spec with another tool's result. The connection closes with the injector that created it.
536
+
537
+ The server side is upstream's `@json-render/mcp`, which serves the view as a single HTML resource. [`projects/mcp-app`](https://github.com/shteynu/ngx-json-render/tree/main/projects/mcp-app) is a complete example with the Material catalog: the view, the build that inlines it into one page, and a server that works around two `createMcpApp` problems.
538
+
510
539
  ## Testing
511
540
 
512
541
  `ngx-json-render/testing` is a separate entry point, so nothing in it can reach
@@ -874,6 +903,10 @@ is how you tell the two apart. One dialog is open at a time: an action with a
874
903
 
875
904
  Registry & schema: `defineRegistry`, `createStoreSetState`, `schema`, and the catalog types `InferComponentProps`, `InferCatalogComponents`, `InferActionParams` (re-exported from core).
876
905
 
906
+ Schema alone (`ngx-json-render/schema`): `schema`, `AngularSchema`, `AngularSpec`, with no Angular behind them, for a server that defines a catalog.
907
+
908
+ MCP Apps (`ngx-json-render/mcp`): `injectJsonRenderApp`, `parseSpecFromToolResult`, `messageText`, and the types `JsonRenderApp`, `JsonRenderAppOptions`. Needs the optional peers `@modelcontextprotocol/ext-apps` and `@modelcontextprotocol/sdk`.
909
+
877
910
  Testing (`ngx-json-render/testing`): `renderSpec`, `renderComponent`, `recordedTransport`, `specStream`, `usageLine`.
878
911
 
879
912
  Everything from `@json-render/core` (types, `createStateStore`, `nestedToFlat`, prompt builders, spec validators, SpecStream compiler) composes with this package; the most common symbols are re-exported.
@@ -0,0 +1,132 @@
1
+ import { signal, inject, DestroyRef, computed } from '@angular/core';
2
+ import { App } from '@modelcontextprotocol/ext-apps';
3
+
4
+ /** The text of a {@link JsonRenderApp.sendMessage} message. */
5
+ function messageText(text, data) {
6
+ if (!data)
7
+ return text;
8
+ return `${text}\n\n\`\`\`json\n${JSON.stringify(data, null, 2)}\n\`\`\``;
9
+ }
10
+ /**
11
+ * Read a spec out of a tool result. Same rule as `@json-render/mcp/app`: the
12
+ * first text block, parsed as JSON, either a bare spec or `{ spec }`.
13
+ */
14
+ function parseSpecFromToolResult(result) {
15
+ const text = result.content?.find((c) => c.type === 'text')?.text;
16
+ if (!text)
17
+ return null;
18
+ try {
19
+ return specFrom(JSON.parse(text));
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
25
+ /** A bare spec, or the `spec` field of a tool's arguments or result. */
26
+ function specFrom(value) {
27
+ if (!value || typeof value !== 'object')
28
+ return null;
29
+ const spec = 'spec' in value ? value.spec : value;
30
+ if (!spec || typeof spec !== 'object' || !('root' in spec))
31
+ return null;
32
+ return spec;
33
+ }
34
+ /**
35
+ * Connect an Angular app running inside an MCP Apps iframe to its host and
36
+ * keep the json-render spec the model sent in a signal. The Angular
37
+ * counterpart of `useJsonRenderApp` from `@json-render/mcp/app`; the server
38
+ * side is `createMcpApp` from `@json-render/mcp`, unchanged.
39
+ *
40
+ * Call it in an injection context. The connection closes when the injector
41
+ * that created it is destroyed.
42
+ *
43
+ * ```ts
44
+ * readonly mcp = injectJsonRenderApp();
45
+ * // <json-render [spec]="mcp.spec()" [loading]="mcp.loading()" [registry]="registry" />
46
+ * ```
47
+ */
48
+ function injectJsonRenderApp(options = {}) {
49
+ const { name = 'json-render', version = '1.0.0', streamPartialInput = true, transport, autoResize = true, } = options;
50
+ const spec = signal(null, ...(ngDevMode ? [{ debugName: "spec" }] : /* istanbul ignore next */ []));
51
+ const loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
52
+ const connected = signal(false, ...(ngDevMode ? [{ debugName: "connected" }] : /* istanbul ignore next */ []));
53
+ const error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
54
+ const app = new App({ name, version }, {}, { autoResize });
55
+ // Listeners go on before connect(), so a notification the host sends right
56
+ // after the handshake is not lost.
57
+ if (streamPartialInput) {
58
+ app.addEventListener('toolinputpartial', ({ arguments: args }) => {
59
+ const partial = specFrom(args);
60
+ if (partial)
61
+ spec.set(partial);
62
+ });
63
+ }
64
+ app.addEventListener('toolinput', ({ arguments: args }) => {
65
+ const input = specFrom(args);
66
+ if (input)
67
+ spec.set(input);
68
+ });
69
+ app.addEventListener('toolresult', (result) => {
70
+ const parsed = parseSpecFromToolResult(result);
71
+ if (parsed)
72
+ spec.set(parsed);
73
+ loading.set(false);
74
+ });
75
+ app.addEventListener('toolcancelled', () => loading.set(false));
76
+ app
77
+ .connect(transport)
78
+ .then(() => connected.set(true))
79
+ .catch((err) => error.set(err instanceof Error ? err : new Error(String(err))));
80
+ inject(DestroyRef).onDestroy(() => {
81
+ app.close().catch(() => undefined);
82
+ });
83
+ return {
84
+ spec: spec.asReadonly(),
85
+ connecting: computed(() => !connected() && !error()),
86
+ connected: connected.asReadonly(),
87
+ error: error.asReadonly(),
88
+ loading: loading.asReadonly(),
89
+ app,
90
+ async callServerTool(toolName, args = {}) {
91
+ loading.set(true);
92
+ try {
93
+ const result = await app.callServerTool({
94
+ name: toolName,
95
+ arguments: args,
96
+ });
97
+ const parsed = parseSpecFromToolResult(result);
98
+ if (parsed)
99
+ spec.set(parsed);
100
+ }
101
+ finally {
102
+ loading.set(false);
103
+ }
104
+ },
105
+ async sendMessage(text, data) {
106
+ if (!app.getHostCapabilities()?.message) {
107
+ throw new Error('The host does not accept messages from the view.');
108
+ }
109
+ const result = await app.sendMessage({
110
+ role: 'user',
111
+ content: [{ type: 'text', text: messageText(text, data) }],
112
+ });
113
+ if (result.isError)
114
+ throw new Error('The host declined the message.');
115
+ },
116
+ };
117
+ }
118
+
119
+ /*
120
+ * Public API Surface of ngx-json-render/mcp
121
+ *
122
+ * Render json-render specs inside an MCP Apps host (Claude, ChatGPT, VS Code).
123
+ * A separate entry point so `@modelcontextprotocol/ext-apps`, an optional
124
+ * peer, is only needed by apps that import it.
125
+ */
126
+
127
+ /**
128
+ * Generated bundle index. Do not edit.
129
+ */
130
+
131
+ export { injectJsonRenderApp, messageText, parseSpecFromToolResult };
132
+ //# sourceMappingURL=ngx-json-render-mcp.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngx-json-render-mcp.mjs","sources":["../../../projects/ngx-json-render/mcp/src/json-render-app.ts","../../../projects/ngx-json-render/mcp/src/public-api.ts","../../../projects/ngx-json-render/mcp/src/ngx-json-render-mcp.ts"],"sourcesContent":["import {\n DestroyRef,\n type Signal,\n computed,\n inject,\n signal,\n} from '@angular/core';\nimport type { Spec } from '@json-render/core';\nimport { App } from '@modelcontextprotocol/ext-apps';\nimport type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';\n\n/** Options for {@link injectJsonRenderApp}. */\nexport interface JsonRenderAppOptions {\n /** App name sent to the host during initialization. Defaults to `\"json-render\"`. */\n name?: string;\n /** App version sent to the host. Defaults to `\"1.0.0\"`. */\n version?: string;\n /**\n * Render the spec while the model is still writing the tool call, from the\n * host's `toolinputpartial` notifications. The host \"heals\" the truncated\n * JSON, so each partial spec is valid but may end in a half-written\n * element; the renderer skips children that do not exist yet. Defaults to\n * `true`. `useJsonRenderApp` in `@json-render/mcp/app` has no equivalent —\n * it waits for the tool result.\n */\n streamPartialInput?: boolean;\n /**\n * Transport to the host. Omit it inside a real MCP Apps iframe: `App`\n * then talks to `window.parent` over `postMessage`. Tests pass one end of\n * an in-memory pair here and an `AppBridge` on the other.\n */\n transport?: Transport;\n /**\n * Report the page's size to the host as it changes, so the iframe grows\n * with the rendered UI. Defaults to `true`; needs `ResizeObserver`.\n */\n autoResize?: boolean;\n}\n\n/**\n * What {@link injectJsonRenderApp} returns — the fields of\n * `UseJsonRenderAppReturn` from `@json-render/mcp/app`, as signals.\n */\nexport interface JsonRenderApp {\n /** The current spec; `null` until the first tool input or result. */\n readonly spec: Signal<Spec | null>;\n /** Whether the app is still connecting to the host. */\n readonly connecting: Signal<boolean>;\n /** Whether the app is connected to the host. */\n readonly connected: Signal<boolean>;\n /** Connection error, if any. */\n readonly error: Signal<Error | null>;\n /** Whether the spec is still arriving: pass it to `<json-render [loading]>`. */\n readonly loading: Signal<boolean>;\n /** The underlying MCP Apps `App`. */\n readonly app: App;\n /**\n * Call a tool on the MCP server and replace the spec with its result —\n * for refresh and drill-down interactions.\n */\n callServerTool(name: string, args?: Record<string, unknown>): Promise<void>;\n /**\n * Post a user message to the host's chat, so the model answers what the\n * user did in the view. `data`, if given, follows the text as a JSON block.\n * Rejects when the host does not accept messages from views or turns this\n * one down, so an action's `onError` runs.\n */\n sendMessage(text: string, data?: Record<string, unknown>): Promise<void>;\n}\n\n/** The text of a {@link JsonRenderApp.sendMessage} message. */\nexport function messageText(\n text: string,\n data?: Record<string, unknown>,\n): string {\n if (!data) return text;\n return `${text}\\n\\n\\`\\`\\`json\\n${JSON.stringify(data, null, 2)}\\n\\`\\`\\``;\n}\n\ninterface ToolResultLike {\n content?: { type: string; text?: string }[];\n}\n\n/**\n * Read a spec out of a tool result. Same rule as `@json-render/mcp/app`: the\n * first text block, parsed as JSON, either a bare spec or `{ spec }`.\n */\nexport function parseSpecFromToolResult(result: ToolResultLike): Spec | null {\n const text = result.content?.find((c) => c.type === 'text')?.text;\n if (!text) return null;\n try {\n return specFrom(JSON.parse(text));\n } catch {\n return null;\n }\n}\n\n/** A bare spec, or the `spec` field of a tool's arguments or result. */\nfunction specFrom(value: unknown): Spec | null {\n if (!value || typeof value !== 'object') return null;\n const spec = 'spec' in value ? (value as { spec: unknown }).spec : value;\n if (!spec || typeof spec !== 'object' || !('root' in spec)) return null;\n return spec as Spec;\n}\n\n/**\n * Connect an Angular app running inside an MCP Apps iframe to its host and\n * keep the json-render spec the model sent in a signal. The Angular\n * counterpart of `useJsonRenderApp` from `@json-render/mcp/app`; the server\n * side is `createMcpApp` from `@json-render/mcp`, unchanged.\n *\n * Call it in an injection context. The connection closes when the injector\n * that created it is destroyed.\n *\n * ```ts\n * readonly mcp = injectJsonRenderApp();\n * // <json-render [spec]=\"mcp.spec()\" [loading]=\"mcp.loading()\" [registry]=\"registry\" />\n * ```\n */\nexport function injectJsonRenderApp(\n options: JsonRenderAppOptions = {},\n): JsonRenderApp {\n const {\n name = 'json-render',\n version = '1.0.0',\n streamPartialInput = true,\n transport,\n autoResize = true,\n } = options;\n\n const spec = signal<Spec | null>(null);\n const loading = signal(true);\n const connected = signal(false);\n const error = signal<Error | null>(null);\n\n const app = new App({ name, version }, {}, { autoResize });\n\n // Listeners go on before connect(), so a notification the host sends right\n // after the handshake is not lost.\n if (streamPartialInput) {\n app.addEventListener('toolinputpartial', ({ arguments: args }) => {\n const partial = specFrom(args);\n if (partial) spec.set(partial);\n });\n }\n app.addEventListener('toolinput', ({ arguments: args }) => {\n const input = specFrom(args);\n if (input) spec.set(input);\n });\n app.addEventListener('toolresult', (result) => {\n const parsed = parseSpecFromToolResult(result);\n if (parsed) spec.set(parsed);\n loading.set(false);\n });\n app.addEventListener('toolcancelled', () => loading.set(false));\n\n app\n .connect(transport)\n .then(() => connected.set(true))\n .catch((err: unknown) =>\n error.set(err instanceof Error ? err : new Error(String(err))),\n );\n\n inject(DestroyRef).onDestroy(() => {\n app.close().catch(() => undefined);\n });\n\n return {\n spec: spec.asReadonly(),\n connecting: computed(() => !connected() && !error()),\n connected: connected.asReadonly(),\n error: error.asReadonly(),\n loading: loading.asReadonly(),\n app,\n async callServerTool(toolName, args = {}) {\n loading.set(true);\n try {\n const result = await app.callServerTool({\n name: toolName,\n arguments: args,\n });\n const parsed = parseSpecFromToolResult(result);\n if (parsed) spec.set(parsed);\n } finally {\n loading.set(false);\n }\n },\n async sendMessage(text, data) {\n if (!app.getHostCapabilities()?.message) {\n throw new Error('The host does not accept messages from the view.');\n }\n const result = await app.sendMessage({\n role: 'user',\n content: [{ type: 'text', text: messageText(text, data) }],\n });\n if (result.isError) throw new Error('The host declined the message.');\n },\n };\n}\n","/*\n * Public API Surface of ngx-json-render/mcp\n *\n * Render json-render specs inside an MCP Apps host (Claude, ChatGPT, VS Code).\n * A separate entry point so `@modelcontextprotocol/ext-apps`, an optional\n * peer, is only needed by apps that import it.\n */\n\nexport {\n injectJsonRenderApp,\n messageText,\n parseSpecFromToolResult,\n type JsonRenderApp,\n type JsonRenderAppOptions,\n} from './json-render-app';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,107 @@
1
+ import { defineSchema } from '@json-render/core';
2
+
3
+ /**
4
+ * The schema for ngx-json-render.
5
+ *
6
+ * Defines:
7
+ * - Spec: A flat tree of elements with keys, types, props, and children references
8
+ * - Catalog: Components with props schemas, and optional actions
9
+ *
10
+ * This is the same spec grammar the other json-render renderers use, so
11
+ * catalogs and specs are portable across frameworks.
12
+ */
13
+ const schema = defineSchema((s) => ({
14
+ // What the AI-generated SPEC looks like
15
+ spec: s.object({
16
+ /** Root element key */
17
+ root: s.string(),
18
+ /** Flat map of elements by key */
19
+ elements: s.record(s.object({
20
+ /** Component type from catalog */
21
+ type: s.ref('catalog.components'),
22
+ /** Component props */
23
+ props: s.propsOf('catalog.components'),
24
+ /** Child element keys (flat reference) */
25
+ children: s.array(s.string()),
26
+ /** Visibility condition */
27
+ visible: { ...s.any(), ...s.optional() },
28
+ /** Repeat children from a state array */
29
+ repeat: { ...s.any(), ...s.optional() },
30
+ })),
31
+ }),
32
+ // What the CATALOG must provide
33
+ catalog: s.object({
34
+ /** Component definitions */
35
+ components: s.map({
36
+ /** Zod schema for component props */
37
+ props: s.zod(),
38
+ /** Slots for this component. Use ['default'] for children, or named slots like ['header', 'footer'] */
39
+ slots: s.array(s.string()),
40
+ /** Description for AI generation hints */
41
+ description: s.string(),
42
+ /** Example prop values used in prompt examples (auto-generated from Zod schema if omitted) */
43
+ example: s.any(),
44
+ }),
45
+ /** Action definitions (optional) */
46
+ actions: s.map({
47
+ /** Zod schema for action params */
48
+ params: s.zod(),
49
+ /** Description for AI generation hints */
50
+ description: s.string(),
51
+ }),
52
+ }),
53
+ }), {
54
+ builtInActions: [
55
+ {
56
+ name: 'setState',
57
+ description: 'Update a value in the state model at the given statePath. Params: { statePath: string, value: any }',
58
+ },
59
+ {
60
+ name: 'pushState',
61
+ description: 'Append an item to an array in state. Params: { statePath: string, value: any, clearStatePath?: string }. Value can contain {"$state":"/path"} refs and "$id" for auto IDs.',
62
+ },
63
+ {
64
+ name: 'removeState',
65
+ description: 'Remove an item from an array in state by index. Params: { statePath: string, index: number }',
66
+ },
67
+ {
68
+ name: 'validateForm',
69
+ description: 'Validate all registered form fields and write the result to state. Params: { statePath?: string }. Defaults to /formValidation. Result: { valid: boolean, errors: Record<string, string[]> }.',
70
+ },
71
+ {
72
+ name: 'submitForm',
73
+ description: "Validate all registered form fields and, only if every one passes, dispatch another action. Params: { action: string, params?: object, statePath?: string }. Writes the same { valid, errors } result as validateForm. Use this on a form's submit button: it is validateForm and the submit in one binding, and the submit is skipped when the form is invalid.",
74
+ },
75
+ ],
76
+ defaultRules: [
77
+ // Element integrity
78
+ "CRITICAL INTEGRITY CHECK: Before outputting ANY element that references children, you MUST have already output (or will output) each child as its own element. If an element has children: ['a', 'b'], then elements 'a' and 'b' MUST exist. A missing child element causes that entire branch of the UI to be invisible.",
79
+ 'SELF-CHECK: After generating all elements, mentally walk the tree from root. Every key in every children array must resolve to a defined element. If you find a gap, output the missing element immediately.',
80
+ 'REQUIRED FIELDS: Every element MUST include a "children" array. Leaf elements (text, badges, inputs, images) use an empty array: "children": []. Omitting "children" fails validation.',
81
+ // Field placement
82
+ 'CRITICAL: The "visible" field goes on the ELEMENT object, NOT inside "props". Correct: {"type":"<ComponentName>","props":{},"visible":{"$state":"/tab","eq":"home"},"children":[...]}.',
83
+ 'CRITICAL: The "on" field goes on the ELEMENT object, NOT inside "props". Use on.press, on.change, on.submit etc. NEVER put action/actionParams inside props.',
84
+ // State and data
85
+ 'When the user asks for a UI that displays data (e.g. blog posts, products, users), ALWAYS include a state field with realistic sample data. The state field is a top-level field on the spec (sibling of root/elements).',
86
+ 'When building repeating content backed by a state array (e.g. posts, products, items), use the "repeat" field on a container element. Example: { "type": "<ContainerComponent>", "props": {}, "repeat": { "statePath": "/posts", "key": "id" }, "children": ["post-card"] }. For a nested list stored on the enclosing item, use "repeat": { "statePath": { "$item": "comments" }, "key": "id" }. The $item statePath form is valid only inside another repeat. Inside repeated children, use { "$item": "field" } to read a field from the current item, and { "$index": true } for the current array index. For two-way binding to an item field use { "$bindItem": "completed" }. Do NOT hardcode individual elements for each array item.',
87
+ // Design quality
88
+ 'Design with visual hierarchy: use container components to group content, heading components for section titles, proper spacing, and status indicators. ONLY use components from the AVAILABLE COMPONENTS list.',
89
+ 'For data-rich UIs, use multi-column layout components if available. For forms and single-column content, use vertical layout components. ONLY use components from the AVAILABLE COMPONENTS list.',
90
+ 'Always include realistic, professional-looking sample data. For blogs include 3-4 posts with varied titles, authors, dates, categories. For products include names, prices, images. Never leave data empty.',
91
+ ],
92
+ });
93
+
94
+ /*
95
+ * Public API Surface of ngx-json-render/schema
96
+ *
97
+ * The spec grammar alone, with nothing but `@json-render/core` behind it, so
98
+ * a server can define a catalog without loading Angular. The primary entry
99
+ * point re-exports it.
100
+ */
101
+
102
+ /**
103
+ * Generated bundle index. Do not edit.
104
+ */
105
+
106
+ export { schema };
107
+ //# sourceMappingURL=ngx-json-render-schema.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngx-json-render-schema.mjs","sources":["../../../projects/ngx-json-render/schema/src/schema.ts","../../../projects/ngx-json-render/schema/src/public-api.ts","../../../projects/ngx-json-render/schema/src/ngx-json-render-schema.ts"],"sourcesContent":["import { defineSchema } from '@json-render/core';\n\n/**\n * The schema for ngx-json-render.\n *\n * Defines:\n * - Spec: A flat tree of elements with keys, types, props, and children references\n * - Catalog: Components with props schemas, and optional actions\n *\n * This is the same spec grammar the other json-render renderers use, so\n * catalogs and specs are portable across frameworks.\n */\nexport const schema = defineSchema(\n (s) => ({\n // What the AI-generated SPEC looks like\n spec: s.object({\n /** Root element key */\n root: s.string(),\n /** Flat map of elements by key */\n elements: s.record(\n s.object({\n /** Component type from catalog */\n type: s.ref('catalog.components'),\n /** Component props */\n props: s.propsOf('catalog.components'),\n /** Child element keys (flat reference) */\n children: s.array(s.string()),\n /** Visibility condition */\n visible: { ...s.any(), ...s.optional() },\n /** Repeat children from a state array */\n repeat: { ...s.any(), ...s.optional() },\n }),\n ),\n }),\n\n // What the CATALOG must provide\n catalog: s.object({\n /** Component definitions */\n components: s.map({\n /** Zod schema for component props */\n props: s.zod(),\n /** Slots for this component. Use ['default'] for children, or named slots like ['header', 'footer'] */\n slots: s.array(s.string()),\n /** Description for AI generation hints */\n description: s.string(),\n /** Example prop values used in prompt examples (auto-generated from Zod schema if omitted) */\n example: s.any(),\n }),\n /** Action definitions (optional) */\n actions: s.map({\n /** Zod schema for action params */\n params: s.zod(),\n /** Description for AI generation hints */\n description: s.string(),\n }),\n }),\n }),\n {\n builtInActions: [\n {\n name: 'setState',\n description:\n 'Update a value in the state model at the given statePath. Params: { statePath: string, value: any }',\n },\n {\n name: 'pushState',\n description:\n 'Append an item to an array in state. Params: { statePath: string, value: any, clearStatePath?: string }. Value can contain {\"$state\":\"/path\"} refs and \"$id\" for auto IDs.',\n },\n {\n name: 'removeState',\n description:\n 'Remove an item from an array in state by index. Params: { statePath: string, index: number }',\n },\n {\n name: 'validateForm',\n description:\n 'Validate all registered form fields and write the result to state. Params: { statePath?: string }. Defaults to /formValidation. Result: { valid: boolean, errors: Record<string, string[]> }.',\n },\n {\n name: 'submitForm',\n description:\n \"Validate all registered form fields and, only if every one passes, dispatch another action. Params: { action: string, params?: object, statePath?: string }. Writes the same { valid, errors } result as validateForm. Use this on a form's submit button: it is validateForm and the submit in one binding, and the submit is skipped when the form is invalid.\",\n },\n ],\n defaultRules: [\n // Element integrity\n \"CRITICAL INTEGRITY CHECK: Before outputting ANY element that references children, you MUST have already output (or will output) each child as its own element. If an element has children: ['a', 'b'], then elements 'a' and 'b' MUST exist. A missing child element causes that entire branch of the UI to be invisible.\",\n 'SELF-CHECK: After generating all elements, mentally walk the tree from root. Every key in every children array must resolve to a defined element. If you find a gap, output the missing element immediately.',\n 'REQUIRED FIELDS: Every element MUST include a \"children\" array. Leaf elements (text, badges, inputs, images) use an empty array: \"children\": []. Omitting \"children\" fails validation.',\n\n // Field placement\n 'CRITICAL: The \"visible\" field goes on the ELEMENT object, NOT inside \"props\". Correct: {\"type\":\"<ComponentName>\",\"props\":{},\"visible\":{\"$state\":\"/tab\",\"eq\":\"home\"},\"children\":[...]}.',\n 'CRITICAL: The \"on\" field goes on the ELEMENT object, NOT inside \"props\". Use on.press, on.change, on.submit etc. NEVER put action/actionParams inside props.',\n\n // State and data\n 'When the user asks for a UI that displays data (e.g. blog posts, products, users), ALWAYS include a state field with realistic sample data. The state field is a top-level field on the spec (sibling of root/elements).',\n 'When building repeating content backed by a state array (e.g. posts, products, items), use the \"repeat\" field on a container element. Example: { \"type\": \"<ContainerComponent>\", \"props\": {}, \"repeat\": { \"statePath\": \"/posts\", \"key\": \"id\" }, \"children\": [\"post-card\"] }. For a nested list stored on the enclosing item, use \"repeat\": { \"statePath\": { \"$item\": \"comments\" }, \"key\": \"id\" }. The $item statePath form is valid only inside another repeat. Inside repeated children, use { \"$item\": \"field\" } to read a field from the current item, and { \"$index\": true } for the current array index. For two-way binding to an item field use { \"$bindItem\": \"completed\" }. Do NOT hardcode individual elements for each array item.',\n\n // Design quality\n 'Design with visual hierarchy: use container components to group content, heading components for section titles, proper spacing, and status indicators. ONLY use components from the AVAILABLE COMPONENTS list.',\n 'For data-rich UIs, use multi-column layout components if available. For forms and single-column content, use vertical layout components. ONLY use components from the AVAILABLE COMPONENTS list.',\n 'Always include realistic, professional-looking sample data. For blogs include 3-4 posts with varied titles, authors, dates, categories. For products include names, prices, images. Never leave data empty.',\n ],\n },\n);\n\n/**\n * Type for the Angular schema\n */\nexport type AngularSchema = typeof schema;\n\n/**\n * Infer the spec type from a catalog\n */\nexport type AngularSpec<TCatalog> = typeof schema extends {\n createCatalog: (catalog: TCatalog) => { _specType: infer S };\n}\n ? S\n : never;\n","/*\n * Public API Surface of ngx-json-render/schema\n *\n * The spec grammar alone, with nothing but `@json-render/core` behind it, so\n * a server can define a catalog without loading Angular. The primary entry\n * point re-exports it.\n */\n\nexport { schema, type AngularSchema, type AngularSpec } from './schema';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;AAEA;;;;;;;;;AASG;AACI,MAAM,MAAM,GAAG,YAAY,CAChC,CAAC,CAAC,MAAM;;AAEN,IAAA,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;;AAEb,QAAA,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;;QAEhB,QAAQ,EAAE,CAAC,CAAC,MAAM,CAChB,CAAC,CAAC,MAAM,CAAC;;AAEP,YAAA,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,oBAAoB,CAAC;;AAEjC,YAAA,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,oBAAoB,CAAC;;YAEtC,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;;AAE7B,YAAA,OAAO,EAAE,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE;;AAExC,YAAA,MAAM,EAAE,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE;AACxC,SAAA,CAAC,CACH;KACF,CAAC;;AAGF,IAAA,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC;;AAEhB,QAAA,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC;;AAEhB,YAAA,KAAK,EAAE,CAAC,CAAC,GAAG,EAAE;;YAEd,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;;AAE1B,YAAA,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;;AAEvB,YAAA,OAAO,EAAE,CAAC,CAAC,GAAG,EAAE;SACjB,CAAC;;AAEF,QAAA,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC;;AAEb,YAAA,MAAM,EAAE,CAAC,CAAC,GAAG,EAAE;;AAEf,YAAA,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;SACxB,CAAC;KACH,CAAC;AACH,CAAA,CAAC,EACF;AACE,IAAA,cAAc,EAAE;AACd,QAAA;AACE,YAAA,IAAI,EAAE,UAAU;AAChB,YAAA,WAAW,EACT,qGAAqG;AACxG,SAAA;AACD,QAAA;AACE,YAAA,IAAI,EAAE,WAAW;AACjB,YAAA,WAAW,EACT,4KAA4K;AAC/K,SAAA;AACD,QAAA;AACE,YAAA,IAAI,EAAE,aAAa;AACnB,YAAA,WAAW,EACT,8FAA8F;AACjG,SAAA;AACD,QAAA;AACE,YAAA,IAAI,EAAE,cAAc;AACpB,YAAA,WAAW,EACT,+LAA+L;AAClM,SAAA;AACD,QAAA;AACE,YAAA,IAAI,EAAE,YAAY;AAClB,YAAA,WAAW,EACT,kWAAkW;AACrW,SAAA;AACF,KAAA;AACD,IAAA,YAAY,EAAE;;QAEZ,2TAA2T;QAC3T,8MAA8M;QAC9M,wLAAwL;;QAGxL,wLAAwL;QACxL,8JAA8J;;QAG9J,0NAA0N;QAC1N,+sBAA+sB;;QAG/sB,gNAAgN;QAChN,kMAAkM;QAClM,6MAA6M;AAC9M,KAAA;AACF,CAAA;;ACxGH;;;;;;AAMG;;ACNH;;AAEG;;"}
@@ -1,8 +1,9 @@
1
1
  import { NgComponentOutlet } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
3
  import { signal, Injectable, isDevMode, inject, computed, untracked, effect, DestroyRef, InjectionToken, input, output, viewChild, afterNextRender, ChangeDetectionStrategy, Component, Injector, forwardRef } from '@angular/core';
4
- import { createStateStore, getByPath, runValidation, resolveAction, nextActionDispatchId, notifyActionDispatch, notifyActionSettle, executeAction, isDevtoolsActive, subscribeDevtoolsActive, resolveRepeatStatePath, evaluateVisibility, resolveElementProps, resolveBindings, resolveActionParam, autoFixSpec, validateSpec, createDirectiveRegistry, resolveRepeatItemStatePath, removeByPath, addByPath, setByPath, SPEC_DATA_PART_TYPE, nestedToFlat, createMixedStreamParser, defineSchema } from '@json-render/core';
4
+ import { createStateStore, getByPath, runValidation, resolveAction, nextActionDispatchId, notifyActionDispatch, notifyActionSettle, executeAction, isDevtoolsActive, subscribeDevtoolsActive, resolveRepeatStatePath, evaluateVisibility, resolveElementProps, resolveBindings, resolveActionParam, autoFixSpec, validateSpec, createDirectiveRegistry, resolveRepeatItemStatePath, removeByPath, addByPath, setByPath, SPEC_DATA_PART_TYPE, nestedToFlat, createMixedStreamParser } from '@json-render/core';
5
5
  export { autoFixSpec, createStateStore, formatSpecIssues, nestedToFlat, validateSpec } from '@json-render/core';
6
+ export { schema } from 'ngx-json-render/schema';
6
7
 
7
8
  const EMPTY = signal(undefined, ...(ngDevMode ? [{ debugName: "EMPTY" }] : /* istanbul ignore next */ []));
8
9
  /**
@@ -3716,97 +3717,6 @@ function injectChatUI(options) {
3716
3717
  * - `ui-stream` / `chat-ui` — the two hooks, composed from the above.
3717
3718
  */
3718
3719
 
3719
- /**
3720
- * The schema for ngx-json-render.
3721
- *
3722
- * Defines:
3723
- * - Spec: A flat tree of elements with keys, types, props, and children references
3724
- * - Catalog: Components with props schemas, and optional actions
3725
- *
3726
- * This is the same spec grammar the other json-render renderers use, so
3727
- * catalogs and specs are portable across frameworks.
3728
- */
3729
- const schema = defineSchema((s) => ({
3730
- // What the AI-generated SPEC looks like
3731
- spec: s.object({
3732
- /** Root element key */
3733
- root: s.string(),
3734
- /** Flat map of elements by key */
3735
- elements: s.record(s.object({
3736
- /** Component type from catalog */
3737
- type: s.ref('catalog.components'),
3738
- /** Component props */
3739
- props: s.propsOf('catalog.components'),
3740
- /** Child element keys (flat reference) */
3741
- children: s.array(s.string()),
3742
- /** Visibility condition */
3743
- visible: { ...s.any(), ...s.optional() },
3744
- /** Repeat children from a state array */
3745
- repeat: { ...s.any(), ...s.optional() },
3746
- })),
3747
- }),
3748
- // What the CATALOG must provide
3749
- catalog: s.object({
3750
- /** Component definitions */
3751
- components: s.map({
3752
- /** Zod schema for component props */
3753
- props: s.zod(),
3754
- /** Slots for this component. Use ['default'] for children, or named slots like ['header', 'footer'] */
3755
- slots: s.array(s.string()),
3756
- /** Description for AI generation hints */
3757
- description: s.string(),
3758
- /** Example prop values used in prompt examples (auto-generated from Zod schema if omitted) */
3759
- example: s.any(),
3760
- }),
3761
- /** Action definitions (optional) */
3762
- actions: s.map({
3763
- /** Zod schema for action params */
3764
- params: s.zod(),
3765
- /** Description for AI generation hints */
3766
- description: s.string(),
3767
- }),
3768
- }),
3769
- }), {
3770
- builtInActions: [
3771
- {
3772
- name: 'setState',
3773
- description: 'Update a value in the state model at the given statePath. Params: { statePath: string, value: any }',
3774
- },
3775
- {
3776
- name: 'pushState',
3777
- description: 'Append an item to an array in state. Params: { statePath: string, value: any, clearStatePath?: string }. Value can contain {"$state":"/path"} refs and "$id" for auto IDs.',
3778
- },
3779
- {
3780
- name: 'removeState',
3781
- description: 'Remove an item from an array in state by index. Params: { statePath: string, index: number }',
3782
- },
3783
- {
3784
- name: 'validateForm',
3785
- description: 'Validate all registered form fields and write the result to state. Params: { statePath?: string }. Defaults to /formValidation. Result: { valid: boolean, errors: Record<string, string[]> }.',
3786
- },
3787
- {
3788
- name: 'submitForm',
3789
- description: "Validate all registered form fields and, only if every one passes, dispatch another action. Params: { action: string, params?: object, statePath?: string }. Writes the same { valid, errors } result as validateForm. Use this on a form's submit button: it is validateForm and the submit in one binding, and the submit is skipped when the form is invalid.",
3790
- },
3791
- ],
3792
- defaultRules: [
3793
- // Element integrity
3794
- "CRITICAL INTEGRITY CHECK: Before outputting ANY element that references children, you MUST have already output (or will output) each child as its own element. If an element has children: ['a', 'b'], then elements 'a' and 'b' MUST exist. A missing child element causes that entire branch of the UI to be invisible.",
3795
- 'SELF-CHECK: After generating all elements, mentally walk the tree from root. Every key in every children array must resolve to a defined element. If you find a gap, output the missing element immediately.',
3796
- 'REQUIRED FIELDS: Every element MUST include a "children" array. Leaf elements (text, badges, inputs, images) use an empty array: "children": []. Omitting "children" fails validation.',
3797
- // Field placement
3798
- 'CRITICAL: The "visible" field goes on the ELEMENT object, NOT inside "props". Correct: {"type":"<ComponentName>","props":{},"visible":{"$state":"/tab","eq":"home"},"children":[...]}.',
3799
- 'CRITICAL: The "on" field goes on the ELEMENT object, NOT inside "props". Use on.press, on.change, on.submit etc. NEVER put action/actionParams inside props.',
3800
- // State and data
3801
- 'When the user asks for a UI that displays data (e.g. blog posts, products, users), ALWAYS include a state field with realistic sample data. The state field is a top-level field on the spec (sibling of root/elements).',
3802
- 'When building repeating content backed by a state array (e.g. posts, products, items), use the "repeat" field on a container element. Example: { "type": "<ContainerComponent>", "props": {}, "repeat": { "statePath": "/posts", "key": "id" }, "children": ["post-card"] }. For a nested list stored on the enclosing item, use "repeat": { "statePath": { "$item": "comments" }, "key": "id" }. The $item statePath form is valid only inside another repeat. Inside repeated children, use { "$item": "field" } to read a field from the current item, and { "$index": true } for the current array index. For two-way binding to an item field use { "$bindItem": "completed" }. Do NOT hardcode individual elements for each array item.',
3803
- // Design quality
3804
- 'Design with visual hierarchy: use container components to group content, heading components for section titles, proper spacing, and status indicators. ONLY use components from the AVAILABLE COMPONENTS list.',
3805
- 'For data-rich UIs, use multi-column layout components if available. For forms and single-column content, use vertical layout components. ONLY use components from the AVAILABLE COMPONENTS list.',
3806
- 'Always include realistic, professional-looking sample data. For blogs include 3-4 posts with varied titles, authors, dates, categories. For products include names, prices, images. Never leave data empty.',
3807
- ],
3808
- });
3809
-
3810
3720
  /*
3811
3721
  * Public API Surface of ngx-json-render
3812
3722
  */
@@ -3816,5 +3726,5 @@ const schema = defineSchema((s) => ({
3816
3726
  * Generated bundle index. Do not edit.
3817
3727
  */
3818
3728
 
3819
- export { CONFIRM_CONTEXT, ELEMENT_KEY, JR_CONFIRM_DIALOG, JR_CONFIRM_LABELS, JrChildren, JrConfirmDialog, JrElement, JrRepeatScope, JsonRenderActionsService, JsonRenderRootContext, JsonRenderStateService, JsonRenderValidationService, JsonRenderer, RENDER_CONTEXT, REPEAT_SCOPE, applyPatch, buildSpecFromParts, checkSpec, createStoreSetState, defineRegistry, flatToTree, formatSpecCheckIssues, getTextFromParts, injectAction, injectActions, injectBoundProp, injectChatUI, injectConfirmContext, injectDevtoolsActive, injectElementKey, injectFieldValidation, injectRenderContext, injectRepeatScope, injectStateBinding, injectStateStore, injectStateValue, injectUIStream, injectValidation, isActionCancelled, jsonRenderMessage, schema };
3729
+ export { CONFIRM_CONTEXT, ELEMENT_KEY, JR_CONFIRM_DIALOG, JR_CONFIRM_LABELS, JrChildren, JrConfirmDialog, JrElement, JrRepeatScope, JsonRenderActionsService, JsonRenderRootContext, JsonRenderStateService, JsonRenderValidationService, JsonRenderer, RENDER_CONTEXT, REPEAT_SCOPE, applyPatch, buildSpecFromParts, checkSpec, createStoreSetState, defineRegistry, flatToTree, formatSpecCheckIssues, getTextFromParts, injectAction, injectActions, injectBoundProp, injectChatUI, injectConfirmContext, injectDevtoolsActive, injectElementKey, injectFieldValidation, injectRenderContext, injectRepeatScope, injectStateBinding, injectStateStore, injectStateValue, injectUIStream, injectValidation, isActionCancelled, jsonRenderMessage };
3820
3730
  //# sourceMappingURL=ngx-json-render.mjs.map