ngx-json-render 0.7.7 → 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 +42 -5
- package/fesm2022/ngx-json-render-mcp.mjs +132 -0
- package/fesm2022/ngx-json-render-mcp.mjs.map +1 -0
- package/fesm2022/ngx-json-render-schema.mjs +107 -0
- package/fesm2022/ngx-json-render-schema.mjs.map +1 -0
- package/fesm2022/ngx-json-render.mjs +128 -124
- package/fesm2022/ngx-json-render.mjs.map +1 -1
- package/package.json +20 -2
- package/types/ngx-json-render-mcp.d.ts +93 -0
- package/types/ngx-json-render-schema.d.ts +74 -0
- package/types/ngx-json-render.d.ts +34 -85
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`,
|
|
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
|
|
@@ -686,14 +715,18 @@ A write reaches only the components whose resolved props actually changed. Writi
|
|
|
686
715
|
- Objects and arrays compare by reference with the built-in store, which copies every path it writes.
|
|
687
716
|
- With an external `store`, objects and arrays always count as changed, because such a store may write into its snapshot in place.
|
|
688
717
|
|
|
689
|
-
Elements with a `$bindState` or `$bindItem` prop
|
|
718
|
+
Elements with a `$bindState` or `$bindItem` prop also re-run on every write that touches their bound path — the path itself, a container above it, or a path inside it — even when the value comes back to what it was. Their DOM can hold the user's input before state does, and the note below depends on them getting the chance to put it right. Writes anywhere else reach them like any other element.
|
|
690
719
|
|
|
691
|
-
Before any of that, a write only makes the elements that can read its path resolve their props at all. The renderer works the paths out from each element's spec — `$state`, `$item`, `$template` placeholders, both branches of a `$cond`, `$computed` arguments
|
|
720
|
+
Before any of that, a write only makes the elements that can read its path resolve their props at all. The renderer works the paths out from each element's spec — `$state`, `$item`, `$template` placeholders, both branches of a `$cond`, `$computed` arguments, `visible` conditions, and the paths `$bindState` and `$bindItem` write back to — so a write to `/user/name` leaves the todo list alone, and its `$computed` functions don't run. A `$computed` function should therefore depend only on its arguments: one that also reads the clock or a variable outside the spec is no longer called again by unrelated writes.
|
|
692
721
|
|
|
693
|
-
Some elements keep resolving on every write, because their reads can't be known from the spec: those using a directive (its `resolve` gets the whole state)
|
|
722
|
+
Some elements keep resolving on every write, because their reads can't be known from the spec: those using a directive (its `resolve` gets the whole state) and any prop with a `$`-key the renderer doesn't recognise.
|
|
694
723
|
|
|
695
724
|
In dev mode each element that skipped a write resolves anyway, against the whole state, and the console warns once if that gives anything other than what it shows. The warning means a `$computed` function reads more than its arguments, or the renderer missed a read, which is a bug worth reporting. The element itself is left as production would leave it. The check calls the element's `$computed` functions again, so in dev mode they still run on every write.
|
|
696
725
|
|
|
726
|
+
### What a new spec re-renders
|
|
727
|
+
|
|
728
|
+
A spec element is compared by content, not by identity. A streamed patch shares every element it did not touch, but a spec can also arrive as all-new objects — a whole-spec part, a host that fetches the spec again, `buildSpecFromParts` replaying a message — and then only the elements whose content changed resolve and re-render. The same goes for `spec.state` in uncontrolled mode: only leaves whose content changed are written to the store, so handing over the same spec again keeps what the user has changed since, including edits inside an array.
|
|
729
|
+
|
|
697
730
|
### A note on inputs
|
|
698
731
|
|
|
699
732
|
If a catalog component renders `<input [value]="ctx.props().value">`, remember that one-way bindings do not re-assert the DOM when the bound value returns to its previously applied value while the user typed in between (e.g. `pushState` + `clearStatePath`). Sync imperatively instead — see `InputComponent` in the demo app for the pattern.
|
|
@@ -870,6 +903,10 @@ is how you tell the two apart. One dialog is open at a time: an action with a
|
|
|
870
903
|
|
|
871
904
|
Registry & schema: `defineRegistry`, `createStoreSetState`, `schema`, and the catalog types `InferComponentProps`, `InferCatalogComponents`, `InferActionParams` (re-exported from core).
|
|
872
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
|
+
|
|
873
910
|
Testing (`ngx-json-render/testing`): `renderSpec`, `renderComponent`, `recordedTransport`, `specStream`, `usageLine`.
|
|
874
911
|
|
|
875
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;;"}
|