ngx-json-render 0.9.10 → 0.9.12

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
@@ -8,6 +8,8 @@ Built on `@json-render/core` (the same spec format, expressions, state store, ac
8
8
 
9
9
  This is an Angular **adapter over the official core**, not a second implementation of it — `@json-render/core` is a peer dependency, and the spec your model emits is the same one the React, Vue, Solid and Svelte renderers consume. A catalog and a spec written here move to another framework unchanged.
10
10
 
11
+ **Works with:** [AG-UI agents, CopilotKit and LangGraph among them](#from-an-ag-ui-agent) · [MCP Apps in Claude, ChatGPT and VS Code](#inside-an-mcp-app-host) · [the Vercel AI SDK](#reading-an-ai-sdk-message) · [Genkit](#with-genkit) · any endpoint that streams text. The AG-UI and AI SDK paths run against the real clients in CI.
12
+
11
13
  **[Live demo](https://shteynu.github.io/ngx-json-render/)** — opens on a sales dashboard streaming in patch by patch, which you can stop mid-generation; plus a playground, an interactive spec (bindings, repeat, confirm, watch), and a chat where prose and UI patches arrive in one reply. Both streaming tabs replay a recording, or call a real model on your own key ([source](https://github.com/shteynu/ngx-json-render/tree/main/projects/demo)) — or [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/shteynu/ngx-json-render)
12
14
 
13
15
  ![A SpecStream of RFC 6902 patches rendering progressively into an Angular dashboard](https://raw.githubusercontent.com/shteynu/ngx-json-render/main/docs/streaming.gif)
@@ -519,6 +521,9 @@ have not arrived. An activity already on screen stays checked while a later
519
521
  run builds another. CopilotKit mounts the activity mid-run, after the run has
520
522
  fixed its subscribers, so the end of that run is read from the agent's
521
523
  `isRunning` flag.
524
+ [`examples/copilotkit-angular`](https://github.com/shteynu/ngx-json-render/tree/main/examples/copilotkit-angular)
525
+ is the whole path, CopilotKit runtime to rendered activity, and runs without
526
+ a model key.
522
527
 
523
528
  **On the server**, the model's output — prose with ` ```spec ` fenced JSONL,
524
529
  as `catalog.prompt({ mode: 'inline' })` asks for — splits into text-message
@@ -797,7 +802,7 @@ A host loads the view as one HTML document with nowhere to fetch chunks from, so
797
802
  }
798
803
  ```
799
804
 
800
- `ng run mcp-view:mcp` runs the application build, then writes `dist/mcp-app/view.html` with every script and stylesheet inlined, and the fonts and images the stylesheets use as data URLs, and `dist/mcp-app/server.mjs` with its dependencies bundled in, so `node dist/mcp-app/server.mjs` runs anywhere without `node_modules`. Files the page cannot reach, such as an image referenced only from a template, are listed as a warning: point at them from a stylesheet or from an `https:` origin in `csp`. The server bundle is not type-checked; run `tsc --noEmit` on it first if you want that. Options: `server` (leave it out to build the view only), `tsConfig` for the server, and `externalPackages: true` to keep `node_modules` packages out of `server.mjs`.
805
+ `ng run mcp-view:mcp` runs the application build, then writes `dist/mcp-app/view.html` with every script and stylesheet inlined, and the fonts and images the stylesheets use as data URLs, and `dist/mcp-app/server.mjs` with its dependencies bundled in, so `node dist/mcp-app/server.mjs` runs anywhere without `node_modules`. Files the page cannot reach, such as an image referenced only from a template, are listed as a warning: point at them from a stylesheet or from an `https:` origin in `csp`. The server bundle is not type-checked; run `tsc --noEmit` on it first if you want that. Options: `server` (leave it out to build the view only), `tsConfig` for the server, `externalPackages: true` to keep `node_modules` packages out of `server.mjs`, and `keepZodLocales: true` if the view switches zod to another language: by default the builder leaves zod's translations other than English out of `view.html`, about 250 kB that `import { z } from 'zod'` otherwise pulls in.
801
806
 
802
807
  [`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, built with this builder, and this server, the one behind `https://ngx-json-render.vercel.app/mcp`.
803
808
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ngx-json-render",
3
- "version": "0.9.10",
4
- "description": "Generative UI for Angular: a json-render renderer that streams AI-generated JSON specs into real Angular components with signals.",
3
+ "version": "0.9.12",
4
+ "description": "Generative UI for Angular: a json-render renderer that streams AI-generated JSON specs into real Angular components with signals. Works with AG-UI agents and CopilotKit, MCP Apps and the Vercel AI SDK.",
5
5
  "keywords": [
6
6
  "angular",
7
7
  "json-render",
@@ -22,6 +22,12 @@
22
22
  "chat-ui",
23
23
  "mcp",
24
24
  "mcp-apps",
25
+ "ag-ui",
26
+ "copilotkit",
27
+ "langgraph",
28
+ "ai-agents",
29
+ "ai-sdk",
30
+ "genkit",
25
31
  "zoneless",
26
32
  "json-renderer",
27
33
  "dynamic-ui"
@@ -187,7 +187,7 @@ function writeFiles(name, source) {
187
187
  const from = (file, specifier) => source.kind === 'material' ? specifier : relativeImport(file, specifier);
188
188
  const appFile = node_path_1.posix.join(appDir, 'app.ts');
189
189
  tree.create(appFile, viewComponent(name, source.registryExport, from(appFile, registry)));
190
- overwrite(tree, node_path_1.posix.join(src, 'main.ts'), mainFile(material));
190
+ overwrite(tree, node_path_1.posix.join(src, 'main.ts'), mainFile(material, zonelessProvider(readManifest(tree))));
191
191
  overwrite(tree, node_path_1.posix.join(src, 'index.html'), indexFile(name, material));
192
192
  tree.create(node_path_1.posix.join(src, material ? 'styles.scss' : 'styles.css'), material ? MATERIAL_STYLES : PLAIN_STYLES);
193
193
  const serverFile = node_path_1.posix.join(root, 'server.ts');
@@ -262,24 +262,26 @@ ${registry}
262
262
  template: \`
263
263
  @if (mcp.error(); as error) {
264
264
  <p class="status error">Could not connect to the host: {{ error.message }}</p>
265
- } @else if (mcp.spec(); as spec) {
266
- <json-render
267
- [spec]="spec"
268
- [loading]="mcp.loading()"
269
- [registry]="registry"
270
- [handlers]="mcp.handlers"
271
- />
272
- @if (mcp.lastMessage(); as outcome) {
273
- @if (outcome.ok) {
274
- <p class="status" role="status">Message passed to the chat.</p>
275
- } @else {
276
- <p class="status error" role="alert">
277
- Could not send the message: {{ outcome.error.message }}
278
- </p>
265
+ } @else {
266
+ @if (mcp.spec(); as spec) {
267
+ <json-render
268
+ [spec]="spec"
269
+ [loading]="mcp.loading()"
270
+ [registry]="registry"
271
+ [handlers]="mcp.handlers"
272
+ />
273
+ @if (mcp.lastMessage(); as outcome) {
274
+ @if (outcome.ok) {
275
+ <p class="status" role="status">Message passed to the chat.</p>
276
+ } @else {
277
+ <p class="status error" role="alert">
278
+ Could not send the message: {{ outcome.error.message }}
279
+ </p>
280
+ }
279
281
  }
282
+ } @else {
283
+ <p class="status">Waiting for the model's spec…</p>
280
284
  }
281
- } @else {
282
- <p class="status">Waiting for the model's spec…</p>
283
285
  }
284
286
  \`,
285
287
  styles: \`
@@ -319,21 +321,32 @@ export class App {
319
321
  }
320
322
  `;
321
323
  }
322
- function mainFile(material) {
324
+ /**
325
+ * Angular 19 has zoneless change detection only as
326
+ * `provideExperimentalZonelessChangeDetection`; 20 renamed it.
327
+ */
328
+ function zonelessProvider(manifest) {
329
+ const range = manifest.dependencies?.['@angular/core'];
330
+ const major = range ? Number(/\d+/.exec(range)?.[0]) : NaN;
331
+ return major === 19
332
+ ? 'provideExperimentalZonelessChangeDetection'
333
+ : 'provideZonelessChangeDetection';
334
+ }
335
+ function mainFile(material, zoneless) {
323
336
  if (!material) {
324
- return `import { provideZonelessChangeDetection } from '@angular/core';
337
+ return `import { ${zoneless} } from '@angular/core';
325
338
  import { bootstrapApplication } from '@angular/platform-browser';
326
339
  import { App } from './app/app';
327
340
 
328
341
  bootstrapApplication(App, {
329
- providers: [provideZonelessChangeDetection()],
342
+ providers: [${zoneless}()],
330
343
  }).catch((err) => console.error(err));
331
344
  `;
332
345
  }
333
346
  return `import {
334
347
  inject,
335
348
  provideEnvironmentInitializer,
336
- provideZonelessChangeDetection,
349
+ ${zoneless},
337
350
  } from '@angular/core';
338
351
  import { MatIconRegistry } from '@angular/material/icon';
339
352
  import { bootstrapApplication } from '@angular/platform-browser';
@@ -341,7 +354,7 @@ import { App } from './app/app';
341
354
 
342
355
  bootstrapApplication(App, {
343
356
  providers: [
344
- provideZonelessChangeDetection(),
357
+ ${zoneless}(),
345
358
  // The Material catalog names icons as Material Symbols ligatures.
346
359
  provideEnvironmentInitializer(() =>
347
360
  inject(MatIconRegistry).setDefaultFontSetClass('material-symbols-outlined'),
@@ -6,6 +6,11 @@ const node_fs_1 = require("node:fs");
6
6
  const node_module_1 = require("node:module");
7
7
  const node_path_1 = require("node:path");
8
8
  const inline_cjs_1 = require("./inline.cjs");
9
+ /** The application builders whose build this one can run in-process. */
10
+ const APPLICATION_BUILDERS = [
11
+ '@angular/build:application',
12
+ '@angular-devkit/build-angular:application',
13
+ ];
9
14
  /**
10
15
  * `ngx-json-render:mcp-app`: runs the view's application build, folds its
11
16
  * browser output into one `view.html` (an MCP Apps host loads the view as a
@@ -15,9 +20,7 @@ const inline_cjs_1 = require("./inline.cjs");
15
20
  async function buildMcpApp(options, context) {
16
21
  const root = context.workspaceRoot;
17
22
  const target = (0, architect_1.targetFromTargetString)(options.buildTarget, context.target?.project);
18
- const run = await context.scheduleTarget(target);
19
- const result = await run.result;
20
- await run.stop();
23
+ const result = await buildView(options, context, target);
21
24
  if (!result.success)
22
25
  return result;
23
26
  const esbuild = loadEsbuild(root);
@@ -69,6 +72,50 @@ async function buildMcpApp(options, context) {
69
72
  }
70
73
  return { success: true, outputPath: out };
71
74
  }
75
+ /**
76
+ * Runs the view's application build. For an application target in this
77
+ * project it runs in-process, so it can leave zod's translations out (see
78
+ * `zodLocalesPlugin`); any other target is scheduled as it is.
79
+ */
80
+ async function buildView(options, context, target) {
81
+ const builderName = await context.getBuilderNameForTarget(target);
82
+ if (options.keepZodLocales ||
83
+ target.project !== context.target?.project ||
84
+ !APPLICATION_BUILDERS.includes(builderName)) {
85
+ const run = await context.scheduleTarget(target);
86
+ const result = await run.result;
87
+ await run.stop();
88
+ return result;
89
+ }
90
+ const { buildApplication } = (0, node_module_1.createRequire)((0, node_path_1.join)(context.workspaceRoot, 'package.json'))('@angular/build');
91
+ const targetOptions = await context.validateOptions(await context.getTargetOptions(target), builderName);
92
+ for await (const output of buildApplication({ ...targetOptions, watch: false }, context, { codePlugins: [zodLocalesPlugin()] })) {
93
+ return output;
94
+ }
95
+ return { success: false, error: 'The application build produced no result.' };
96
+ }
97
+ /**
98
+ * zod 4 re-exports all of its translations as `z.locales`, and `import { z }`
99
+ * keeps every one of them in the bundle — about 250 kB of a view that only
100
+ * shows English errors. The English one, which zod loads on its own, stays
101
+ * reachable as `z.locales.en`.
102
+ */
103
+ function zodLocalesPlugin() {
104
+ const namespace = 'ngx-json-render-zod-locales';
105
+ return {
106
+ name: namespace,
107
+ setup(build) {
108
+ build.onResolve({ filter: /^\.\.\/locales\/index\.js$/ }, (args) => /[\\/]zod[\\/]v4[\\/](classic|mini|core)$/.test(args.resolveDir)
109
+ ? { path: (0, node_path_1.join)(args.resolveDir, '..', 'locales'), namespace }
110
+ : undefined);
111
+ build.onLoad({ filter: /.*/, namespace }, (args) => ({
112
+ contents: "export { default as en } from './en.js';",
113
+ resolveDir: args.path,
114
+ loader: 'js',
115
+ }));
116
+ },
117
+ };
118
+ }
72
119
  /** Where the application build wrote its browser files. */
73
120
  async function browserOutput(context, target) {
74
121
  const options = await context.getTargetOptions(target);
@@ -24,6 +24,11 @@
24
24
  "type": "boolean",
25
25
  "default": false,
26
26
  "description": "Leave node_modules packages out of server.mjs, to be installed next to it. By default the server is one self-contained file."
27
+ },
28
+ "keepZodLocales": {
29
+ "type": "boolean",
30
+ "default": false,
31
+ "description": "Keep zod's translations (z.locales) in view.html. By default only English stays, which saves about 250 kB; set this if the view calls z.config with another locale."
27
32
  }
28
33
  },
29
34
  "required": ["buildTarget", "outputPath"],
@@ -56,7 +56,7 @@ function declares(manifest, name) {
56
56
  function warnOnOldAngular(manifest, warn) {
57
57
  const range = manifest.dependencies?.['@angular/core'];
58
58
  const major = range ? Number(/\d+/.exec(range)?.[0]) : NaN;
59
- if (major < 20) {
60
- warn(`ngx-json-render needs Angular 20 or newer; this workspace has @angular/core ${range}.`);
59
+ if (major < 19) {
60
+ warn(`ngx-json-render needs Angular 19 or newer; this workspace has @angular/core ${range}.`);
61
61
  }
62
62
  }