ketatlas 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0
4
+
5
+ - Add an opt-in two-port native renderer contract: KetAtlas serves the viewer while the declared React, Vue, KetJS, or other framework server owns screen HTML, assets, and same-origin behavior.
6
+ - Add `atlas.renderer.json`, its public schema, command-array validation, readiness checks, predictable renderer shutdown, and route-aware static audit.
7
+ - Add `screenBaseURL` to the browser API so screen routes can resolve on a different origin while external references continue to resolve beside `atlas.json`.
8
+ - Require the bundled agent skill to use the product's real framework and design-system components, share presenters/styles across atlases, and keep business data outside presentation components.
9
+
3
10
  ## 0.3.0
4
11
 
5
12
  - Standardize discoverable projects as `<name>.ketatlas/atlas.json` bundles, add `discover`, accept bundle directories in Atlas commands, and teach the bundled skill to backfill legacy projects safely.
package/README.md CHANGED
@@ -1,14 +1,17 @@
1
1
  # KetAtlas
2
2
 
3
- **Scaffold, serve, and audit interactive HTML workflow maps.**
3
+ **Scaffold, serve, and audit interactive framework-native workflow maps.**
4
4
 
5
- Turn a JSON file and your existing HTML screens into a canvas you can drag, zoom, and explore. Connect screens with labelled arrows, model decisions and recovery paths, and open the real HTML to try a step. The same tool supports mobile screens, web pages, and processes with no screens at all.
5
+ Turn a JSON file and screens from your existing HTML, React, Vue, KetJS, or other framework into a canvas you can drag, zoom, and explore. Connect screens with labelled arrows, model decisions and recovery paths, and open the real rendered page to try a step. The same tool supports mobile screens, web pages, and processes with no screens at all.
6
6
 
7
- KetAtlas has an English UI, zero runtime npm dependencies, and no build step. Node.js **22+** is needed for the CLI. Viewers use native ES modules and run on an HTTP server.
7
+ KetAtlas has an English UI and zero runtime npm dependencies. Node.js **22+** is needed for the CLI. Static projects need no build step. Framework-native projects reuse their product's own server and dependencies; KetAtlas supervises it on a separate localhost port.
8
8
 
9
9
  ## Demo
10
10
 
11
- See a mobile workflow map in action: explore connected screens and try the HTML prototype.
11
+ Open the live map at **[atlas.ketsuite.com](https://atlas.ketsuite.com)**: drag, zoom, follow the
12
+ labelled arrows, and open a real HTML prototype from any screen. Nothing to install.
13
+
14
+ The same map recorded as a video, for a quick look:
12
15
 
13
16
  https://github.com/user-attachments/assets/13f24fc8-7b8e-4bda-9e02-364c120ee163
14
17
 
@@ -32,11 +35,11 @@ ketatlas serve my-atlas.ketatlas
32
35
  ketatlas audit my-atlas.ketatlas --strict
33
36
  ```
34
37
 
35
- The consumer bundle is named `<name>.ketatlas/` and contains `atlas.json`, its schema, product HTML/CSS/JavaScript, local assets, and documentation. The stable directory suffix lets desktop tools discover atlases without parsing unrelated JSON. A bundle needs no `package.json`, lockfile, `node_modules`, build step, or copy of the viewer. The CLI supplies scaffold, discovery, serving, and static audit from its own installation. `audit` leaves project files unchanged unless an output file is explicitly requested. Viewing with `serve` does not write; explicit **Save progress** writes the sibling progress file. Use `--read-only` to disable editing.
38
+ The consumer bundle is named `<name>.ketatlas/` and contains `atlas.json`, its schema, documentation, and either static screen assets or an `atlas.renderer.json` sidecar. The stable directory suffix lets desktop tools discover atlases without parsing unrelated JSON. A bundle needs no KetAtlas package manifest, lockfile, `node_modules`, build step, or copy of the viewer. Framework dependencies and reusable UI stay in the product workspace, where multiple atlases can share one set of presenters, components, fixtures, and styles. The CLI supplies discovery, serving, validation, and audit. `audit` leaves project files unchanged unless an output file is explicitly requested. Viewing with `serve` does not write; explicit **Save progress** writes the sibling progress file. Use `--read-only` to disable editing.
36
39
 
37
- Pin the version in run commands or the global installation for reproducible team workflows. Product scripts implement mock screen interactions; they are authored content, not a local installation of KetAtlas. Framework tooling and tests stay in the KetAtlas repository.
40
+ Pin the version in run commands or the global installation for reproducible team workflows. Product scripts implement mock screen interactions; they are authored content, not a local installation of KetAtlas. A native renderer reuses the product's framework tooling and shared UI source rather than copying markup into each atlas.
38
41
 
39
- Edit the JSON and screen files, then refresh the browser. The default viewer address is **http://127.0.0.1:4178**.
42
+ Edit the JSON and product screen source, then refresh the browser. The default viewer address is **http://127.0.0.1:4178**.
40
43
 
41
44
  ## Create mockups with an agent
42
45
 
@@ -46,7 +49,31 @@ Install the KetAtlas skill in your product project:
46
49
  npx skills add ketvietlab/ketatlas --skill ketatlas
47
50
  ```
48
51
 
49
- Ask your agent to use the skill with a product brief: requested flows, target platforms, design references, and output directory. The agent creates actual HTML screens, a version 1 `atlas.json`, and run instructions, then audits the result.
52
+ Ask your agent to use the skill with a product brief: requested flows, target platforms, design references, and output directory. The agent creates native framework routes (or static HTML for a static product), a version 1 `atlas.json`, and run instructions, then audits the result.
53
+
54
+ ## Render with the product framework
55
+
56
+ KetAtlas 0.4.0 keeps the viewer and product renderer separate. Put `atlas.renderer.json` beside `atlas.json` when screens come from a framework:
57
+
58
+ ```json
59
+ {
60
+ "$schema": "https://unpkg.com/ketatlas@0.4.0/renderer.schema.json",
61
+ "version": 1,
62
+ "framework": "vue",
63
+ "command": ["npm", "run", "atlas:serve", "--", "--host", "{host}", "--port", "{port}"],
64
+ "cwd": "../..",
65
+ "readyPath": "/__atlas/ready",
66
+ "screenBasePath": "/__atlas/orders/"
67
+ }
68
+ ```
69
+
70
+ Then run:
71
+
72
+ ```sh
73
+ npx --yes ketatlas@0.4.0 serve ./tasks/orders.ketatlas --renderer --port 60550 --html-port 60551
74
+ ```
75
+
76
+ The first port serves the map and progress API. The second is owned by the declared React/Vue/KetJS/etc. server and serves screen routes. `--renderer` is explicit because it executes the local command array. Static projects continue to use the one-port command without this flag. See [Native renderers](docs/authoring.md#framework-native-screens).
50
77
 
51
78
  See [Agent mockups](docs/agent-mockups.md) for installation options, a ready-to-use request, and a reusable brief template. Agents without skill support can read the [single skill file](skills/ketatlas/SKILL.md) directly.
52
79
 
@@ -86,7 +113,7 @@ Open **Screens** to filter screen progress, review blockers and edit acceptance
86
113
  | -------------------------- | -------------------------------------------------------------------------------- |
87
114
  | `scaffold <name.ketatlas>` | Create a bundle from `basic`, `web`, or `process`. Refuses existing content. |
88
115
  | `discover <directory>` | Find valid `*.ketatlas/atlas.json` bundles and report invalid bundles. |
89
- | `serve <bundle\|json>` | Start the viewer and serve local screens. Defaults to port 4178 on localhost. |
116
+ | `serve <bundle\|json>` | Start the viewer; optionally supervise a native renderer on a second port. |
90
117
  | `audit <bundle\|json>` | Check configuration, reachability, local files, and literal HTML/CSS references. |
91
118
  | `validate <bundle\|json>` | Validate configuration only, without reading screen files. |
92
119
 
package/bin/audit.js CHANGED
@@ -3,6 +3,7 @@ import { readFile, realpath, stat } from "node:fs/promises";
3
3
  import { resolve, dirname, relative, sep, extname } from "node:path";
4
4
  import { fileURLToPath, pathToFileURL } from "node:url";
5
5
  import { validateAtlas } from "../src/config.js";
6
+ import { readRendererConfig, rendererFileName } from "./renderer.js";
6
7
  const within = (root, path) => {
7
8
  const r = relative(root, path);
8
9
  return r === "" || (!r.startsWith(".." + sep) && r !== ".." && !r.startsWith(sep));
@@ -29,6 +30,12 @@ export async function auditAtlas(file, { root: rootOption } = {}) {
29
30
  warnings = [...result.warnings],
30
31
  checked = new Set(),
31
32
  remote = new Set();
33
+ let renderer;
34
+ try {
35
+ renderer = await readRendererConfig(resolve(dirname(absolute), rendererFileName));
36
+ } catch (error) {
37
+ if (error.code !== "ENOENT") errors.push({ path: rendererFileName, message: error.message });
38
+ }
32
39
  if (!within(root, absolute))
33
40
  errors.push({ path: "file", message: "The atlas JSON must be inside the serve root." });
34
41
  async function inspect(value, from, label) {
@@ -90,9 +97,12 @@ export async function auditAtlas(file, { root: rootOption } = {}) {
90
97
  }
91
98
  }
92
99
  if (result.valid) {
93
- for (const s of config.screens || []) await inspect(s.url, absolute, `screen:${s.id}`);
100
+ if (!renderer)
101
+ for (const s of config.screens || []) await inspect(s.url, absolute, `screen:${s.id}`);
94
102
  for (const f of config.flows)
95
- for (const n of f.nodes) if (n.url) await inspect(n.url, absolute, `node:${f.id}/${n.id}`);
103
+ for (const n of f.nodes)
104
+ if (n.url && (!renderer || (n.type || (n.screen ? "screen" : "note")) !== "screen"))
105
+ await inspect(n.url, absolute, `node:${f.id}/${n.id}`);
96
106
  }
97
107
  if (result.valid) {
98
108
  try {
@@ -118,7 +128,8 @@ export async function auditAtlas(file, { root: rootOption } = {}) {
118
128
  },
119
129
  errors,
120
130
  warnings,
121
- scope:
122
- "Static configuration and literal local HTML/CSS references. JavaScript behavior and remote embedding policies are not executed.",
131
+ scope: renderer
132
+ ? `Configuration and ${renderer.data.framework} renderer contract. The renderer command, routes, JavaScript behavior and embedding are not executed.`
133
+ : "Static configuration and literal local HTML/CSS references. JavaScript behavior and remote embedding policies are not executed.",
123
134
  };
124
135
  }
package/bin/ketatlas.js CHANGED
@@ -15,11 +15,11 @@ import {
15
15
  resolveAtlasFile,
16
16
  } from "./discovery.js";
17
17
  const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
18
- const help = `KetAtlas — scaffold, serve, and audit HTML workflow maps
18
+ const help = `KetAtlas — scaffold, serve, and audit framework-native workflow maps
19
19
 
20
20
  ketatlas scaffold <name.ketatlas> [--template basic|web|process]
21
21
  ketatlas discover <directory> [--json]
22
- ketatlas serve <name.ketatlas|atlas.json> [--port 4178] [--root directory]
22
+ ketatlas serve <name.ketatlas|atlas.json> [--port 4178] [--root directory] [--renderer] [--html-port 4179]
23
23
  ketatlas audit <name.ketatlas|atlas.json> [--root directory] [--json] [--strict]
24
24
  ketatlas validate <name.ketatlas|atlas.json>
25
25
  ketatlas progress <name.ketatlas|atlas.json> [--json] [--init]
@@ -32,7 +32,7 @@ Examples:
32
32
  npx ketatlas serve my-atlas.ketatlas
33
33
  npx ketatlas audit my-atlas.ketatlas --strict
34
34
 
35
- Serve binds to 127.0.0.1. No HTML wrapper, build, account, or backend required.
35
+ Serve binds to 127.0.0.1. Static screens need no build; --renderer starts the project's declared framework server.
36
36
  `;
37
37
  function parse(args, allowed) {
38
38
  const options = {},
@@ -196,10 +196,17 @@ async function main(args) {
196
196
  port: "string",
197
197
  root: "string",
198
198
  "read-only": "boolean",
199
+ renderer: "boolean",
200
+ "html-port": "string",
199
201
  }),
200
- port = Number(options.port || 4178);
202
+ port = Number(options.port || 4178),
203
+ htmlPort = Number(options["html-port"] || (port < 65535 ? port + 1 : 0));
201
204
  if (!Number.isInteger(port) || port < 1 || port > 65535)
202
205
  throw new Error("Port must be 1–65535.");
206
+ if (options.renderer && (!Number.isInteger(htmlPort) || htmlPort < 1 || htmlPort > 65535))
207
+ throw new Error("HTML port must be 1–65535.");
208
+ if (options.renderer && htmlPort === port)
209
+ throw new Error("Viewer and HTML ports must be different.");
203
210
  const directory = (await stat(resolve(target))).isDirectory();
204
211
  if (directory && options.root)
205
212
  throw new Error("--root is only needed when serving an atlas JSON file.");
@@ -210,13 +217,16 @@ async function main(args) {
210
217
  port,
211
218
  root: options.root,
212
219
  readOnly: options["read-only"],
220
+ renderer: options.renderer,
221
+ htmlPort,
213
222
  });
214
223
  console.log(
215
- `KetAtlas: http://127.0.0.1:${server.address().port}\nServing ${resolve(target)}\nPress Ctrl+C to stop.`,
224
+ `KetAtlas: http://127.0.0.1:${server.address().port}${server.htmlOrigin ? `\nHTML: ${server.htmlOrigin}` : ""}\nServing ${resolve(target)}\nPress Ctrl+C to stop.`,
216
225
  );
217
- const close = () => {
226
+ const close = async () => {
218
227
  server.close();
219
228
  server.closeAllConnections();
229
+ await server.closeRenderer?.();
220
230
  };
221
231
  process.once("SIGINT", close);
222
232
  process.once("SIGTERM", close);
@@ -0,0 +1,189 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createServer } from "node:net";
3
+ import { readFile, realpath, stat } from "node:fs/promises";
4
+ import { dirname, resolve } from "node:path";
5
+
6
+ const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
7
+ const text = (value) => typeof value === "string" && value.trim().length > 0;
8
+
9
+ export const rendererFileName = "atlas.renderer.json";
10
+
11
+ export function validateRendererConfig(input) {
12
+ const errors = [];
13
+ const error = (path, message) => errors.push({ path, message });
14
+ if (!object(input))
15
+ return {
16
+ valid: false,
17
+ errors: [{ path: "$", message: "Expected a renderer object." }],
18
+ };
19
+ for (const key of Object.keys(input))
20
+ if (
21
+ ![
22
+ "$schema",
23
+ "version",
24
+ "framework",
25
+ "command",
26
+ "cwd",
27
+ "readyPath",
28
+ "screenBasePath",
29
+ "readyTimeoutMs",
30
+ ].includes(key)
31
+ )
32
+ error(key, "Unknown property.");
33
+ if (input.version !== 1) error("version", "Expected version: 1.");
34
+ if (!text(input.framework)) error("framework", "Expected a non-empty framework name.");
35
+ if (!Array.isArray(input.command) || input.command.length === 0)
36
+ error("command", "Expected a non-empty command array.");
37
+ else
38
+ for (const [index, value] of input.command.entries())
39
+ if (!text(value)) error(`command[${index}]`, "Expected a non-empty argument.");
40
+ if (input.$schema !== undefined && typeof input.$schema !== "string")
41
+ error("$schema", "Expected text.");
42
+ if (input.cwd !== undefined && !text(input.cwd)) error("cwd", "Expected a directory path.");
43
+ for (const key of ["readyPath", "screenBasePath"])
44
+ if (input[key] !== undefined && (!text(input[key]) || !input[key].startsWith("/")))
45
+ error(key, "Expected an origin-relative path beginning with /.");
46
+ if (input.screenBasePath !== undefined && !input.screenBasePath.endsWith("/"))
47
+ error("screenBasePath", "Expected a path ending with /.");
48
+ if (
49
+ input.readyTimeoutMs !== undefined &&
50
+ (!Number.isInteger(input.readyTimeoutMs) ||
51
+ input.readyTimeoutMs < 100 ||
52
+ input.readyTimeoutMs > 120000)
53
+ )
54
+ error("readyTimeoutMs", "Expected an integer from 100 to 120000.");
55
+ return { valid: errors.length === 0, errors };
56
+ }
57
+
58
+ export class RendererValidationError extends Error {
59
+ constructor(result) {
60
+ super(result.errors.map((issue) => `${issue.path}: ${issue.message}`).join("\n"));
61
+ this.name = "RendererValidationError";
62
+ this.errors = result.errors;
63
+ }
64
+ }
65
+
66
+ export async function readRendererConfig(file) {
67
+ const absolute = await realpath(resolve(file));
68
+ if (!(await stat(absolute)).isFile()) throw new Error("Expected a renderer JSON file.");
69
+ const data = JSON.parse(await readFile(absolute, "utf8"));
70
+ const result = validateRendererConfig(data);
71
+ if (!result.valid) throw new RendererValidationError(result);
72
+ const directory = dirname(absolute);
73
+ const cwd = await realpath(resolve(directory, data.cwd || "."));
74
+ if (!(await stat(cwd)).isDirectory()) throw new Error("Renderer cwd must be a directory.");
75
+ return {
76
+ file: absolute,
77
+ directory,
78
+ cwd,
79
+ data: {
80
+ ...data,
81
+ cwd: data.cwd || ".",
82
+ readyPath: data.readyPath || "/",
83
+ screenBasePath: data.screenBasePath || "/",
84
+ readyTimeoutMs: data.readyTimeoutMs || 15000,
85
+ },
86
+ };
87
+ }
88
+
89
+ const reservePort = (host) =>
90
+ new Promise((resolvePort, reject) => {
91
+ const probe = createServer();
92
+ probe.once("error", reject);
93
+ probe.listen(0, host, () => {
94
+ const port = probe.address().port;
95
+ probe.close((error) => (error ? reject(error) : resolvePort(port)));
96
+ });
97
+ });
98
+
99
+ function originPath(origin, value, name) {
100
+ const url = new URL(value, origin);
101
+ if (url.origin !== origin) throw new Error(`${name} must stay on the renderer origin.`);
102
+ return url.href;
103
+ }
104
+
105
+ function terminate(child) {
106
+ if (child.exitCode !== null || child.signalCode !== null) return;
107
+ try {
108
+ if (process.platform === "win32") child.kill("SIGTERM");
109
+ else process.kill(-child.pid, "SIGTERM");
110
+ } catch {}
111
+ }
112
+
113
+ async function stop(child) {
114
+ if (child.exitCode !== null || child.signalCode !== null) return;
115
+ terminate(child);
116
+ await Promise.race([
117
+ new Promise((done) => child.once("exit", done)),
118
+ new Promise((done) => setTimeout(done, 2000)),
119
+ ]);
120
+ if (child.exitCode === null && child.signalCode === null) {
121
+ try {
122
+ if (process.platform === "win32") child.kill("SIGKILL");
123
+ else process.kill(-child.pid, "SIGKILL");
124
+ } catch {}
125
+ }
126
+ }
127
+
128
+ export async function startRenderer(file, options = {}) {
129
+ const config = await readRendererConfig(file);
130
+ const host = options.host || "127.0.0.1";
131
+ const port = options.port || (await reservePort(host));
132
+ if (!Number.isInteger(port) || port < 1 || port > 65535)
133
+ throw new Error("HTML port must be 1–65535.");
134
+ const values = {
135
+ host,
136
+ port: String(port),
137
+ atlasDirectory: config.directory,
138
+ };
139
+ const replace = (value) =>
140
+ value.replace(/\{(host|port|atlasDirectory)\}/g, (_, key) => values[key]);
141
+ const command = config.data.command.map(replace);
142
+ const origin = `http://${host}:${port}`;
143
+ const readyURL = originPath(origin, config.data.readyPath, "readyPath");
144
+ const screenBaseURL = originPath(origin, config.data.screenBasePath, "screenBasePath");
145
+ const child = spawn(command[0], command.slice(1), {
146
+ cwd: config.cwd,
147
+ detached: process.platform !== "win32",
148
+ env: {
149
+ ...process.env,
150
+ KETATLAS_HOST: host,
151
+ KETATLAS_HTML_PORT: String(port),
152
+ KETATLAS_PROJECT_DIR: config.directory,
153
+ },
154
+ stdio: options.stdio || "inherit",
155
+ });
156
+ let spawnError;
157
+ child.once("error", (error) => {
158
+ spawnError = error;
159
+ });
160
+ const started = Date.now();
161
+ while (Date.now() - started < config.data.readyTimeoutMs) {
162
+ if (spawnError) throw spawnError;
163
+ if (child.exitCode !== null || child.signalCode !== null)
164
+ throw new Error(`Renderer exited before ${readyURL} became ready.`);
165
+ try {
166
+ const response = await fetch(readyURL, { signal: AbortSignal.timeout(1000) });
167
+ if (response.ok) {
168
+ let closed = false;
169
+ return {
170
+ child,
171
+ config,
172
+ origin,
173
+ readyURL,
174
+ screenBaseURL,
175
+ async close() {
176
+ if (closed) return;
177
+ closed = true;
178
+ await stop(child);
179
+ },
180
+ };
181
+ }
182
+ } catch {}
183
+ await new Promise((done) => setTimeout(done, 100));
184
+ }
185
+ await stop(child);
186
+ throw new Error(
187
+ `Renderer did not become ready at ${readyURL} within ${config.data.readyTimeoutMs}ms.`,
188
+ );
189
+ }
package/bin/viewer.js CHANGED
@@ -5,6 +5,7 @@ import { resolve, dirname, relative, sep } from "node:path";
5
5
  import { validateAtlas, AtlasValidationError } from "../src/config.js";
6
6
  import { serve } from "./server.js";
7
7
  import { fileURLToPath } from "node:url";
8
+ import { rendererFileName, startRenderer } from "./renderer.js";
8
9
  const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
9
10
  export async function serveAtlas(file, options = {}) {
10
11
  const absolute = await realpath(resolve(file));
@@ -18,11 +19,42 @@ export async function serveAtlas(file, options = {}) {
18
19
  throw new Error("The JSON file must be inside --root.");
19
20
  const configURL = "/" + rel.split(sep).map(encodeURIComponent).join("/");
20
21
  const token = randomBytes(24).toString("hex");
21
- const html = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>KetAtlas</title><style>html,body{margin:0;height:100%}#atlas{height:100dvh}#error{font:16px system-ui;padding:24px;white-space:pre-wrap}</style></head><body><main id="atlas"></main><pre id="error" hidden></pre><script type="module">import {loadAtlas} from '/__ketatlas__/src/index.js';try{window.atlas=await loadAtlas(document.querySelector('#atlas'),${JSON.stringify(configURL).replaceAll("<", "\\u003c")},{syncUrl:true,progressEndpoint:"/__ketatlas__/progress",progressToken:${JSON.stringify(token)}});document.title=${JSON.stringify(config.title).replaceAll("<", "\\u003c")}+' · KetAtlas';}catch(error){const el=document.querySelector('#error');el.hidden=false;el.textContent=error.message;}</script></body></html>`;
22
- return serve(root, {
23
- ...options,
24
- viewer: html,
25
- packageRoot,
26
- handler: progressHandler(absolute, absolute, token, !options.readOnly),
27
- });
22
+ let renderer;
23
+ if (options.renderer) {
24
+ const rendererFile = resolve(options.rendererFile || dirname(absolute), rendererFileName);
25
+ const htmlPort =
26
+ options.htmlPort ?? (options.port && options.port < 65535 ? options.port + 1 : 0);
27
+ renderer = await startRenderer(rendererFile, {
28
+ host: options.host,
29
+ port: htmlPort,
30
+ stdio: options.rendererStdio,
31
+ });
32
+ }
33
+ const loadOptions = {
34
+ syncUrl: true,
35
+ progressEndpoint: "/__ketatlas__/progress",
36
+ progressToken: token,
37
+ ...(renderer
38
+ ? {
39
+ screenBaseURL: renderer.screenBaseURL,
40
+ sandbox: "allow-scripts allow-forms allow-same-origin",
41
+ }
42
+ : {}),
43
+ };
44
+ const html = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>KetAtlas</title><style>html,body{margin:0;height:100%}#atlas{height:100dvh}#error{font:16px system-ui;padding:24px;white-space:pre-wrap}</style></head><body><main id="atlas"></main><pre id="error" hidden></pre><script type="module">import {loadAtlas} from '/__ketatlas__/src/index.js';try{window.atlas=await loadAtlas(document.querySelector('#atlas'),${JSON.stringify(configURL).replaceAll("<", "\\u003c")},${JSON.stringify(loadOptions).replaceAll("<", "\\u003c")});document.title=${JSON.stringify(config.title).replaceAll("<", "\\u003c")}+' · KetAtlas';}catch(error){const el=document.querySelector('#error');el.hidden=false;el.textContent=error.message;}</script></body></html>`;
45
+ try {
46
+ const server = await serve(root, {
47
+ ...options,
48
+ viewer: html,
49
+ packageRoot,
50
+ handler: progressHandler(absolute, absolute, token, !options.readOnly),
51
+ });
52
+ server.renderer = renderer;
53
+ server.htmlOrigin = renderer?.origin;
54
+ server.closeRenderer = () => renderer?.close();
55
+ return server;
56
+ } catch (error) {
57
+ await renderer?.close();
58
+ throw error;
59
+ }
28
60
  }
@@ -1,6 +1,6 @@
1
1
  # Create mockups with an agent
2
2
 
3
- Give an agent the KetAtlas skill and a product brief. The agent produces real HTML screens plus `atlas.json`; KetAtlas opens those files as an interactive workflow map. The brief describes the product, while the generated JSON schema and CLI audit check the output format.
3
+ Give an agent the KetAtlas skill and a product brief. The agent produces native framework screen routes (or static HTML for a static product) plus `atlas.json`; KetAtlas opens them as an interactive workflow map. The brief describes the product, while the schemas and CLI audit check the output contracts.
4
4
 
5
5
  ## Install the skill once
6
6
 
@@ -26,7 +26,7 @@ If the agent does not support skill installation, attach that file or ask it to
26
26
  For a short request, invoke the skill and include the product, target, design reference, and flows:
27
27
 
28
28
  ```text
29
- Use $ketatlas to create one shared HTML mobile prototype for Northstar Tasks
29
+ Use $ketatlas to create one shared React mobile prototype for Northstar Tasks
30
30
  in ./tasks/mobile. Use 390 × 844 screens, English product copy, and the design
31
31
  system in ./design-system. Include sign-in, password recovery, task list,
32
32
  task details, and task completion. Map entry points, actions, outcomes, and
@@ -36,11 +36,13 @@ and preview the result. Return the serve command and verification results.
36
36
 
37
37
  `$ketatlas` is the explicit skill invocation in Codex. In other agents, use that agent's skill selector or explicitly ask it to use the installed KetAtlas skill. Keep the same product brief.
38
38
 
39
- The deliverable is a `<name>.ketatlas/` bundle containing data and product screen assets: JSON/schema, HTML/CSS/JavaScript, local assets, and documentation. Do not add a package manifest, lockfile, node_modules, or copied viewer/test tooling to make the atlas runnable. Use a global KetAtlas installation or a version-pinned npx command. Browser checks can use the agent's existing tooling outside the atlas folder.
39
+ The deliverable is a `<name>.ketatlas/` bundle containing JSON/schema, documentation, and either static screen assets or `atlas.renderer.json`. Do not add a package manifest, lockfile, node_modules, or copied viewer/test tooling inside the bundle. Native React/Vue/KetJS/etc. routes reuse the product's existing framework package and shared UI source outside the atlas folder. Use `ketatlas@0.4.0` for this contract. Browser checks can use the agent's existing tooling outside the atlas folder.
40
40
 
41
41
  The agent should infer routine details and record assumptions. Provide an exact list when “all screens” means a defined inventory, so missing coverage can be checked against a source.
42
42
 
43
- Before it creates or edits visual screens, the skill asks for one design-system choice: Auto (Két Design System), Két Design System, Carbon, GitHub Primer, Microsoft Fluent 2, no design system, or a repository URL/local path. A design system already named in the request counts as the answer. Auto uses [Két Design System](https://github.com/ketvietlab/ketjs/tree/develop/packages/design-system). The agent inspects and uses the selected system's actual tokens, components, assets, and patterns, then records the source and integration strategy in the atlas README.
43
+ Before it creates or edits visual screens, the skill asks for one design-system choice: Auto (Két Design System), Két Design System, Carbon, GitHub Primer, Microsoft Fluent 2, no design system, or a repository URL/local path. A design system already named in the request counts as the answer. Auto uses [Két Design System](https://github.com/ketvietlab/ketjs/tree/develop/packages/design-system). The agent inspects its actual tokens, components, assets, patterns, and framework, then renders with that framework instead of duplicating markup. An incorrect canonical component is fixed at its source before Atlas work continues.
44
+
45
+ For several atlases in one workspace, the agent creates or reuses one shared framework module for screen presenters, design-system composition, fixture factories, route helpers, and styles. Individual bundles keep flow/state declarations and namespaced screen paths; they do not copy UI code from one another. Production routes and Atlas fixture routes call the same presenter so the mock and built product do not become two visual sources of truth.
44
46
 
45
47
  ## Use a saved brief for larger projects
46
48
 
@@ -55,7 +57,7 @@ Save this template as `mockup-brief.md`, fill in the relevant fields, and ask: *
55
57
  - Target platforms and viewport sizes:
56
58
  - Product content language:
57
59
  - Design system choice (Auto/Két/Carbon/Primer/Fluent 2/none/custom source):
58
- - Existing HTML and reference files/URLs:
60
+ - Existing application/framework and reference files/URLs:
59
61
  - Requested flows and screen inventory:
60
62
  - Relevant loading, empty, validation, error, and recovery states:
61
63
  - Interactions to demonstrate and synthetic demo inputs:
@@ -63,15 +65,17 @@ Save this template as `mockup-brief.md`, fill in the relevant fields, and ask: *
63
65
 
64
66
  ## Expected delivery
65
67
 
66
- Create actual HTML screens and one KetAtlas version 1 atlas.json. Reuse
67
- screens and styles across flows. Set explicit flow starts, outcomes, and
68
- labelled transitions. Each named screen state must open directly.
68
+ Create native framework routes, or static HTML only for a static product,
69
+ and one KetAtlas version 1 atlas.json. Reuse shared presenters and styles
70
+ across flows and atlases. Set explicit flow starts, outcomes, and labelled
71
+ transitions. Each named screen state must open directly.
69
72
 
70
73
  Use the generated ketatlas.schema.json. Run KetAtlas audit, test the
71
74
  important interactions in the viewer when browser automation is available,
72
75
  and report results or limitations. Include a README with run commands,
73
- flow coverage, demo inputs, and assumptions. Keep the output free of package
74
- manifests, lockfiles, local dependencies, and copied viewer/test tooling.
76
+ flow coverage, demo inputs, and assumptions. Keep the atlas bundle free of
77
+ package manifests, lockfiles, local dependencies, duplicated component/style
78
+ implementations, and copied viewer/test tooling.
75
79
  ```
76
80
 
77
81
  For an existing prototype, ask the agent to reuse its HTML and add or update the atlas instead of rebuilding it. For later changes, name the flow or screen IDs to preserve, for example: **“Add an expired-code recovery branch to sign-in; keep existing screen IDs and audit the updated project.”**
@@ -79,11 +83,13 @@ For an existing prototype, ask the agent to reuse its HTML and add or update the
79
83
  ## Review the result
80
84
 
81
85
  ```sh
82
- npx ketatlas discover . --json
83
- npx ketatlas serve ./tasks/mobile.ketatlas
84
- npx ketatlas audit ./tasks/mobile.ketatlas --strict
86
+ npx --yes ketatlas@0.4.0 discover . --json
87
+ npx --yes ketatlas@0.4.0 serve ./tasks/mobile.ketatlas --renderer --port 60550 --html-port 60551
88
+ npx --yes ketatlas@0.4.0 audit ./tasks/mobile.ketatlas --strict
85
89
  ```
86
90
 
91
+ For a genuinely static project, omit `atlas.renderer.json`, `--renderer`, and `--html-port`.
92
+
87
93
  Check the delivered flow coverage against the brief. Open **Try this screen** to test the actual HTML. A successful audit confirms structural and local-file checks, not visual quality, full product coverage, or working production integrations. Intentional remote URLs require separate verification and produce strict-audit warnings.
88
94
 
89
95
  The skill lives in this repository so teams can review and evolve it alongside the schema and CLI. It also backfills legacy atlas directories into the discoverable bundle pattern before extending them.
@@ -1,26 +1,34 @@
1
1
  # Architecture
2
2
 
3
- KetAtlas is a static browser viewer plus a Node.js command-line toolkit. There is no database, account service or runtime npm dependency. The localhost CLI has a narrow, protected API to read and save the progress sidecar; static hosting remains read-only.
3
+ KetAtlas is a browser viewer plus a Node.js command-line orchestrator. There is no database, account service or runtime npm dependency. Static screens can still use the built-in file server. Framework-native screens run through the product's own server on a second loopback origin, preserving the component implementation as the single source of truth. The localhost CLI has a narrow, protected API to read and save the progress sidecar; static hosting remains read-only.
4
4
 
5
5
  ```text
6
- <name>.ketatlas/atlas.json + screen HTML
6
+ <name>.ketatlas/atlas.json
7
7
  │
8
8
  ├── discover → workspace bundle catalog
9
9
  ├── validate / audit → diagnostics and CI exit code
10
- │
11
- └── serve → built-in viewer page
12
- │
13
- ├── config validation + URL resolution
14
- ├── deterministic grid layout
15
- ├── Shadow DOM shell + SVG edges
16
- └── lazy HTML iframes + interactive inspector
10
+ └── serve → viewer/control origin
11
+ ├── config validation + deterministic map
12
+ ├── progress API + Shadow DOM viewer
13
+ └── lazy iframes
14
+ ├── static files on the viewer origin, or
15
+ └── native framework renderer origin
16
+ └── shared presenters + Atlas fixtures
17
17
  ```
18
18
 
19
19
  ## Consumer boundary
20
20
 
21
- KetAtlas is installed globally or executed through npx. Consumers provide a `<name>.ketatlas/` bundle containing JSON, a schema, HTML/CSS/JavaScript mock screens, assets, and documentation. The suffix is the discovery boundary: tools inspect only its direct `atlas.json`, not arbitrary workspace JSON. Consumers do not need a Node package, lockfile, development dependencies, viewer implementation, or build/test scripts to scaffold, discover, serve, or audit a map.
21
+ KetAtlas is installed globally or executed through npx. Consumers provide a `<name>.ketatlas/` bundle containing JSON, a schema, documentation, and either static mock screens or `atlas.renderer.json`. The suffix is the discovery boundary: tools inspect only its direct `atlas.json`, not arbitrary workspace JSON. Consumers do not need a KetAtlas package, lockfile, development dependencies, viewer implementation, or build/test scripts. A native renderer deliberately reuses the product's existing framework package and lockfile outside the bundle.
22
+
23
+ The package owns the CLI, viewer, validation, renderer supervision, and contract verification. The product owns screen markup, components, styles, fixtures, and framework tests. Static audit does not execute renderer commands or simulate product interactions. Agent browser checks can run through external tooling without adding a test harness to the delivered atlas folder.
24
+
25
+ ## Native renderer boundary
26
+
27
+ `atlas.renderer.json` declares a framework label, an argv array, a working directory, readiness route, and screen base path. It is separate from `atlas.json`: workflow format version 1 remains framework-neutral. `serve --renderer` is required to execute the command, making local code execution explicit. KetAtlas substitutes host/port tokens, waits for a 2xx readiness response, and terminates the renderer with the viewer.
28
+
29
+ The viewer and renderer use different loopback origins. Relative screen and screen-node URLs resolve against `screenBasePath` on the renderer origin. Relative external/note references still resolve beside `atlas.json`. Renderer iframes receive `allow-same-origin` because the separate origin prevents access to the viewer while enabling native module loading, assets, storage, and same-origin requests.
22
30
 
23
- The package owns the CLI, viewer, validation, and framework verification. A product may have its own application tests elsewhere; those are independent of the map format. Static audit does not simulate product interactions. Agent browser checks can run through external tooling without adding a test harness to the delivered atlas folder.
31
+ Multiple atlases should share one framework renderer source. Shared design-system compositions, presenters, fixture factories, and styles live in one product module; each atlas selects namespaced routes through `screenBasePath` and owns only its flow/state declarations. The core does not define a component DSL or translate component trees.
24
32
 
25
33
  ## Ownership
26
34
 
@@ -45,7 +53,7 @@ Flows are stacked on one canvas. A row and column grow to fit their largest node
45
53
 
46
54
  Camera changes use an animation frame and a CSS transform. Drag, wheel, keyboard and pinch input use shared accelerated motion constants while zoom remains anchored under the pointer. Only the nearest visible screen cards receive iframes, up to the configured cap. Below the preview threshold, all cards show placeholders. This limits embedded-page cost; every node and edge still has a DOM element. There is no claim of unlimited graph size.
47
55
 
48
- The inspector reuses the same screen URL at its declared viewport dimensions, with no extra side padding. Its iframe is removed when closed. Reopening a screen loads a fresh instance; editing a prototype is not persistent business state.
56
+ The inspector reuses the same screen URL at its declared viewport dimensions, with no extra side padding. Its iframe is removed when closed. Reopening a screen loads a fresh instance; editing a prototype is not persistent business state. Framework rendering and business simulation remain inside the renderer, never the viewer.
49
57
 
50
58
  ## Isolation and lifecycle
51
59
 
@@ -61,7 +69,7 @@ The default theme uses Inter and KetJS's canonical tokens and primitives. `prepa
61
69
 
62
70
  ## Version 1 boundaries
63
71
 
64
- KetAtlas is a viewer and authoring toolkit, not a visual graph editor. Users edit JSON and HTML in their normal tools. It does not record a graph by watching clicks, synthesize screens, calculate backend permissions, or execute the arrow graph as an automated test. Cross-flow edges, collaborative editing and browser-based project audits are outside the current contract.
72
+ KetAtlas is a viewer and authoring toolkit, not a visual graph editor. Users edit JSON and product framework source in their normal tools. It does not record a graph by watching clicks, synthesize screens, translate React/Vue/KetJS components, calculate backend permissions, or execute the arrow graph as an automated test. Cross-flow edges, collaborative editing and browser-based project audits are outside the current contract.
65
73
 
66
74
  ## Delivery tracking
67
75
 
package/docs/authoring.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Authoring a useful atlas
2
2
 
3
- Start with a user goal: sign in, approve a purchase, or deliver an order. Give each flow one clear start and a small number of outcomes. Screen files can come from an existing prototype; KetAtlas does not require a particular frontend framework.
3
+ Start with a user goal: sign in, approve a purchase, or deliver an order. Give each flow one clear start and a small number of outcomes. Screen routes come from the product's actual framework; static HTML remains supported for products that are truly static.
4
4
 
5
5
  ## 1. Create a starting point
6
6
 
@@ -13,7 +13,7 @@ Open a card with a double-click. The preview is actual HTML; links and JavaScrip
13
13
 
14
14
  ## 2. Register your screens
15
15
 
16
- Add each reusable screen once in `screens`. Use paths relative to `atlas.json`, including query parameters for prototype states. A React/Vite prototype can expose its own embedded routes through an HTTP URL, provided it permits iframe embedding. Audit reports remote URLs for separate verification.
16
+ Add each reusable screen once in `screens`. Use relative paths, including query parameters for prototype states. Static paths resolve beside `atlas.json`; framework paths resolve beneath the native renderer's `screenBasePath`.
17
17
 
18
18
  For a desktop page:
19
19
 
@@ -28,6 +28,44 @@ For a desktop page:
28
28
 
29
29
  HTML should include a viewport meta tag. Avoid adding a second device frame, reviewer sidebar or fixed outer margin inside the page being embedded; point to its clean preview route instead.
30
30
 
31
+ ## Framework-native screens
32
+
33
+ Use the same implementation framework as the product and design system. React screens stay React, Vue screens stay Vue, and KetJS server components render through KetJS. Do not reproduce component output with copied HTML, string templates, DOM post-processing, or a parallel Atlas component format.
34
+
35
+ Keep one presentation path:
36
+
37
+ ```text
38
+ business loader ─┐
39
+ ├─ shared screen presenter ─ design-system components/styles
40
+ Atlas fixture ───┘
41
+ ```
42
+
43
+ The business loader supplies real data, permissions, and actions. The Atlas route supplies a deterministic fixture selected by its route/query state. Both call the same presenter. If several atlases need the same shell, field, table, or layout, move that composition and its styles into one shared product module rather than copying it between bundles.
44
+
45
+ Place this sidecar beside `atlas.json`:
46
+
47
+ ```json
48
+ {
49
+ "$schema": "https://unpkg.com/ketatlas@0.4.0/renderer.schema.json",
50
+ "version": 1,
51
+ "framework": "ketjs",
52
+ "command": ["npm", "run", "atlas:serve", "--", "--host", "{host}", "--port", "{port}"],
53
+ "cwd": "../..",
54
+ "readyPath": "/__atlas/ready",
55
+ "screenBasePath": "/__atlas/customer-care/"
56
+ }
57
+ ```
58
+
59
+ Commands are argv arrays and do not run through a shell. `cwd` is relative to the bundle. The command may use `{host}`, `{port}`, and `{atlasDirectory}`; the same values are also available through `KETATLAS_HOST`, `KETATLAS_HTML_PORT`, and `KETATLAS_PROJECT_DIR`. `readyPath` must return 2xx. `screenBasePath` must start and end with `/`.
60
+
61
+ Run the two origins explicitly:
62
+
63
+ ```sh
64
+ npx --yes ketatlas@0.4.0 serve ./customer-journeys.ketatlas --renderer --port 60550 --html-port 60551
65
+ ```
66
+
67
+ The first origin owns only the map and progress API. The second origin owns HTML, framework modules, styles, assets, and screen-side requests. Audit validates the renderer contract and graph without executing the command; browser verification must exercise the combined result.
68
+
31
69
  ## 3. Connect actions and decisions
32
70
 
33
71
  Use short labels such as **Submit request**, **Permission missing**, and **Try again**. Put the main journey on row 0, with explicit columns. Place recovery states on row 1 and give their arrows `kind: "recovery"`.
package/docs/cli.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Install once with `npm install --global ketatlas`, or prefix commands with `npx --yes ketatlas`. No dependency installation is required in the consumer directory. `node /path/to/ketatlas/bin/ketatlas.js` is also available to framework maintainers.
4
4
 
5
- A consumer keeps only its JSON/schema, product HTML/CSS/JavaScript, assets, and documentation. The package manifest, lockfile, development scripts, and browser test dependencies belong to the tool. Normal `serve`, `validate`, and `audit` calls do not create files in the consumer; `audit --output` is an explicit exception.
5
+ A consumer keeps its JSON/schema, documentation, and either static product assets or a native renderer sidecar. KetAtlas package files and browser test dependencies belong to the tool; framework package files remain in the product workspace. Normal `serve`, `validate`, and `audit` calls do not create files in the consumer; `audit --output` is an explicit exception.
6
6
 
7
7
  ## Scaffold
8
8
 
@@ -32,6 +32,7 @@ Legacy manifest paths remain valid command arguments, but discovery intentionall
32
32
  ```sh
33
33
  ketatlas serve ./tasks/onboarding.ketatlas
34
34
  ketatlas serve ./tasks/onboarding.ketatlas --port 4180
35
+ ketatlas serve ./tasks/onboarding.ketatlas --renderer --port 60550 --html-port 60551
35
36
  ketatlas serve ./legacy/atlas.json --root .
36
37
  ```
37
38
 
@@ -41,6 +42,14 @@ Open the printed localhost URL. Changes appear after refreshing; there is no hot
41
42
 
42
43
  The `/__ketatlas__/` route is reserved for viewer assets. Dotfiles and paths resolving outside the file root, including symlink escapes, are not served. Only GET/HEAD are supported. The server binds to `127.0.0.1`; it is a local preview server, not a production application server.
43
44
 
45
+ ### Native renderer mode
46
+
47
+ `--renderer` reads `atlas.renderer.json` beside the manifest and executes its command array without a shell. This is opt-in because the project controls the command. The renderer gets `KETATLAS_HOST`, `KETATLAS_HTML_PORT`, and `KETATLAS_PROJECT_DIR`; command arguments can also use `{host}`, `{port}`, and `{atlasDirectory}` placeholders.
48
+
49
+ `--port` selects the viewer/control origin. `--html-port` selects the separate framework renderer origin and defaults to the following port. The ports must differ. KetAtlas waits for the configured `readyPath`, resolves relative screen URLs beneath `screenBasePath`, prints both origins, and terminates the renderer when the viewer receives SIGINT or SIGTERM.
50
+
51
+ The second origin lets trusted React/Vue/KetJS routes load their own modules, styles, assets, and same-origin requests while remaining isolated from the viewer origin. A shared product renderer can serve namespaced routes for several atlas bundles so they reuse the same presenters and styles.
52
+
44
53
  `serve <directory>` also works as a plain static preview for an existing HTML integration. JSON mode is recommended for project planning.
45
54
 
46
55
  ## Audit
@@ -60,8 +69,9 @@ Audit checks:
60
69
  - Local screen files and node URL overrides, using the same root boundary as the server.
61
70
  - Literal resource references in HTML (`src`/`href`), links between HTML pages, and CSS imports/URLs.
62
71
  - A viewport meta tag in HTML previews.
72
+ - A colocated native renderer contract when present. Its screen paths are treated as framework routes rather than local files.
63
73
 
64
- Remote URLs are reported but not fetched. Audit does not execute JavaScript, discover dynamic imports/URLs, validate remote CSP or `X-Frame-Options`, simulate user actions, or verify backend/native behavior. Use your project's browser tests for those checks.
74
+ Remote URLs are reported but not fetched. Audit does not execute renderer commands or JavaScript, discover dynamic imports/URLs, validate remote CSP or `X-Frame-Options`, simulate user actions, or verify backend/native behavior. Use the two-port preview and your project's browser tests for those checks.
65
75
 
66
76
  Exit codes:
67
77
 
@@ -14,7 +14,26 @@ The [JSON Schema](../schema.json) defines version 1. A generated project contain
14
14
  | `flows` | Yes | A nonempty array of workflows. |
15
15
  | `$schema` | No | Schema URL/path for your editor. |
16
16
 
17
- All viewer UI is English. Project titles, descriptions and labels can use any language. Search ignores case and combining accents.
17
+ All viewer UI is English. Project titles, descriptions and labels can use any language. Search ignores case and combining accents. Workflow format version 1 remains framework-neutral; native rendering is declared in a separate sidecar.
18
+
19
+ ## Native renderer sidecar
20
+
21
+ `atlas.renderer.json` is optional and sits beside `atlas.json`. Its [schema](../renderer.schema.json) has these fields:
22
+
23
+ | Field | Required | Default / meaning |
24
+ | ---------------- | -------- | ------------------------------------------------------------------- |
25
+ | `version` | Yes | Renderer contract version; must be `1`. |
26
+ | `framework` | Yes | Human/tooling label such as `react`, `vue`, or `ketjs`. |
27
+ | `command` | Yes | Nonempty argv array executed directly, without a shell. |
28
+ | `cwd` | No | Working directory relative to the bundle; defaults to `.`. |
29
+ | `readyPath` | No | Origin-relative 2xx readiness route; defaults to `/`. |
30
+ | `screenBasePath` | No | Origin-relative screen route prefix ending in `/`; defaults to `/`. |
31
+ | `readyTimeoutMs` | No | Startup timeout from 100–120000 milliseconds; defaults to 15000. |
32
+ | `$schema` | No | Schema URL/path for editor completion. |
33
+
34
+ The command may interpolate `{host}`, `{port}`, and `{atlasDirectory}`. The corresponding environment values are `KETATLAS_HOST`, `KETATLAS_HTML_PORT`, and `KETATLAS_PROJECT_DIR`. The sidecar changes relative resolution for registered screens and screen-node URL overrides only; note/external references remain relative to `atlas.json`.
35
+
36
+ `audit` validates a discovered sidecar without running it. `serve --renderer` explicitly authorizes command execution and starts the framework on `--html-port`.
18
37
 
19
38
  ## Screen
20
39
 
@@ -22,7 +41,7 @@ All viewer UI is English. Project titles, descriptions and labels can use any la
22
41
  | ------------- | -------- | --------------------------------------------------------------------- |
23
42
  | `id` | Yes | Unique across the atlas. |
24
43
  | `title` | Yes | Human-readable screen name. |
25
- | `url` | Yes | Relative URL or HTTP(S) URL for actual HTML. |
44
+ | `url` | Yes | Relative or HTTP(S) URL for the actual rendered page. |
26
45
  | `viewport` | No | Overrides the atlas viewport. Both dimensions are integers, 160–4096. |
27
46
  | `description` | No | Context inherited by its nodes. |
28
47
  | `badge` | No | Small card footer label; defaults to `Preview`. |
@@ -62,6 +62,7 @@ The browser implementation has no React dependency. Instantiate it after mount;
62
62
  | Option | Default | Purpose |
63
63
  | ------------------ | ------------------------------------------ | ------------------------------------------------------------------------------- |
64
64
  | `baseURL` | Page URL; JSON response URL in `loadAtlas` | Resolve screen and node URLs. |
65
+ | `screenBaseURL` | `baseURL` | Resolve registered screens and screen-node URL overrides on another origin. |
65
66
  | `assetBaseURL` | Package directory | Public directory containing `styles/` and `assets/`; must end in `/`. |
66
67
  | `initialFlow` | First flow | Initial flow ID. |
67
68
  | `theme` | `light` | `light` or `dark`. |
@@ -101,7 +102,9 @@ These are viewer events. KetAtlas does not inspect or synchronize navigation ins
101
102
 
102
103
  ## Embedding behavior
103
104
 
104
- Iframe previews are sandboxed. Scripts and forms work by default, but the page has an opaque origin: authenticated fetch, storage, some module imports, and other same-origin features can require additional permissions. Only add `allow-same-origin` for trusted prototypes. Combining it with scripts on a same-origin page weakens sandbox isolation.
105
+ Iframe previews are sandboxed. Scripts and forms work by default, but a static page has an opaque origin: authenticated fetch, storage, some module imports, and other same-origin features can require additional permissions. Only add `allow-same-origin` for trusted prototypes. Combining it with scripts on a same-origin page weakens sandbox isolation.
106
+
107
+ The CLI's native renderer mode uses `screenBaseURL` and adds `allow-same-origin` automatically. Its HTML server is a separate loopback origin from the viewer, so framework routes can use native modules and same-origin resources without gaining same-origin access to the viewer/control page.
105
108
 
106
109
  Remote pages can reject framing using CSP or `X-Frame-Options`. KetAtlas cannot override that. Use a permitted embedded route or the **Open in new tab** action. Test links/forms with the default sandbox before sharing a project.
107
110
 
package/docs/migration.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Migrating the KétSuite mobile map
2
2
 
3
+ ## From copied HTML to a 0.4 native renderer
4
+
5
+ Version 0.4 does not require static projects to change. When an atlas copied or reconstructed markup from a React, Vue, KetJS, or other framework, remove that duplicate implementation instead:
6
+
7
+ 1. Extract one shared screen presenter in the product's framework and compose canonical design-system components there.
8
+ 2. Keep business loaders and Atlas fixtures separate; pass both into the same presenter shape.
9
+ 3. Reuse that presenter and its styles across all atlases in the workspace.
10
+ 4. Add namespaced Atlas routes and `atlas.renderer.json` beside each manifest.
11
+ 5. Change screen URLs to paths relative to the declared `screenBasePath`.
12
+ 6. Run `ketatlas@0.4.0 audit`, then use `serve --renderer` and verify both origins in a browser.
13
+
14
+ Do not migrate to a JSON component tree or another generated HTML layer. The framework component remains the screen source of truth.
15
+
3
16
  ## Bundle layout backfill
4
17
 
5
18
  Current projects use `<name>.ketatlas/atlas.json`. For a legacy self-contained Atlas directory, rename the whole directory so relative screen, style and asset URLs remain unchanged. When `atlas.json` shares a directory with unrelated application code, create a sibling `<name>.ketatlas` bundle, move only Atlas-owned files, and update relative URLs for resources that intentionally remain outside it. Keep one canonical manifest, then run `ketatlas discover <workspace> --json`, `ketatlas validate <name>.ketatlas`, and `ketatlas audit <name>.ketatlas --strict`.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ketatlas",
3
- "version": "0.3.0",
4
- "description": "Interactive HTML maps for screens, workflows, and user journeys.",
3
+ "version": "0.4.0",
4
+ "description": "Interactive framework-native maps for screens, workflows, and user journeys.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -19,7 +19,8 @@
19
19
  "import": "./src/index.js"
20
20
  },
21
21
  "./schema.json": "./schema.json",
22
- "./progress.schema.json": "./progress.schema.json"
22
+ "./progress.schema.json": "./progress.schema.json",
23
+ "./renderer.schema.json": "./renderer.schema.json"
23
24
  },
24
25
  "bin": {
25
26
  "ketatlas": "bin/ketatlas.js"
@@ -37,19 +38,21 @@
37
38
  "skills",
38
39
  "docs",
39
40
  "CHANGELOG.md",
40
- "progress.schema.json"
41
+ "progress.schema.json",
42
+ "renderer.schema.json"
41
43
  ],
42
44
  "scripts": {
43
45
  "dev": "node bin/ketatlas.js serve examples/atlas.json --root . --port 4178",
46
+ "build:demo": "node scripts/build-demo.mjs",
44
47
  "validate": "node bin/ketatlas.js audit examples/atlas.json --root . && node bin/ketatlas.js audit templates/basic/atlas.json && node bin/ketatlas.js audit templates/web/atlas.json && node bin/ketatlas.js audit templates/process/atlas.json",
45
48
  "test": "node --test tests/*.test.js",
46
- "test:e2e": "node tests/browser.mjs && node tests/progress-browser.mjs",
49
+ "test:e2e": "node tests/browser.mjs && node tests/progress-browser.mjs && node tests/renderer-browser.mjs",
47
50
  "check": "npm run validate && npm test && npm run test:e2e",
48
51
  "format": "prettier --write src styles/ketatlas.css bin scripts docs examples starter templates skills tests '*.md' '*.json' .github/workflows",
49
52
  "format:check": "prettier --check src styles/ketatlas.css bin scripts docs examples starter templates skills tests '*.md' '*.json' .github/workflows",
50
53
  "prepare:assets": "node scripts/prepare-assets.mjs",
51
54
  "prepack": "node scripts/verify-assets.mjs",
52
- "prepare:templates": "node scripts/schema.mjs && node scripts/progress-schema.mjs && node scripts/prepare-templates.mjs",
55
+ "prepare:templates": "node scripts/schema.mjs && node scripts/progress-schema.mjs && node scripts/renderer-schema.mjs && node scripts/prepare-templates.mjs",
53
56
  "test:package": "node tests/package.mjs"
54
57
  },
55
58
  "devDependencies": {
@@ -0,0 +1,44 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "KetAtlas native renderer configuration",
4
+ "type": "object",
5
+ "properties": {
6
+ "$schema": {
7
+ "type": "string"
8
+ },
9
+ "version": {
10
+ "const": 1
11
+ },
12
+ "framework": {
13
+ "type": "string",
14
+ "pattern": "\\S"
15
+ },
16
+ "command": {
17
+ "type": "array",
18
+ "minItems": 1,
19
+ "items": {
20
+ "type": "string",
21
+ "pattern": "\\S"
22
+ }
23
+ },
24
+ "cwd": {
25
+ "type": "string",
26
+ "pattern": "\\S"
27
+ },
28
+ "readyPath": {
29
+ "type": "string",
30
+ "pattern": "^/"
31
+ },
32
+ "screenBasePath": {
33
+ "type": "string",
34
+ "pattern": "^/.*/$|^/$"
35
+ },
36
+ "readyTimeoutMs": {
37
+ "type": "integer",
38
+ "minimum": 100,
39
+ "maximum": 120000
40
+ }
41
+ },
42
+ "required": ["version", "framework", "command"],
43
+ "additionalProperties": false
44
+ }
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: ketatlas
3
- description: Create or update interactive HTML mockups and workflow maps in KetAtlas format. Use when a user requests KetAtlas output, an atlas.json project, or draggable flows connecting real HTML screens; also use to scaffold, serve, or audit an existing atlas.
3
+ description: Create or update interactive framework-native mockups and workflow maps in KetAtlas format. Use when a user requests KetAtlas output, an atlas.json project, or draggable flows connecting screens rendered by HTML, React, Vue, KetJS, or another product framework; also use to scaffold, serve, or audit an existing atlas.
4
4
  ---
5
5
 
6
6
  # KetAtlas
7
7
 
8
- Deliver an editable `<name>.ketatlas/` bundle containing actual HTML screens and a version 1 `atlas.json` that `npx ketatlas serve` can open. KetAtlas supplies the draggable viewer; the agent authors the product mockups and their flow graph. A screenshot gallery or a Mermaid diagram alone is not this deliverable.
8
+ Deliver an editable `<name>.ketatlas/` bundle with a version 1 `atlas.json` that `npx ketatlas@0.4.0 serve` can open. KetAtlas supplies the draggable viewer; the selected product framework owns screen rendering. A screenshot gallery or a Mermaid diagram alone is not this deliverable.
9
9
 
10
10
  ## Read the brief and choose the scope
11
11
 
@@ -32,25 +32,68 @@ Before scaffolding or editing visual screens, ask the user to choose a design sy
32
32
 
33
33
  Do not start visual screen implementation until the user answers. If the user explicitly delegates the choice to the agent, treat that as **Auto**. This selection prompt is still required for an existing atlas; its documented/current system may be offered as the likely choice. A request that already names a system or supplies a design-system source counts as an answer and must not be asked again.
34
34
 
35
- Inspect the selected source, its usage documentation, tokens, components, icons, and relevant product patterns before mocking. Reuse its actual public assets and composition patterns when available; do not merely imitate its color palette. Replace incompatible starter styling rather than layering multiple design systems. For one shared iOS/Android prototype, create one HTML implementation with reusable styles and states unless separate variants were requested.
35
+ Inspect the selected source, its usage documentation, tokens, components, icons, relevant product patterns, and implementation framework before mocking. Reuse its actual public components, assets, and composition patterns when available; do not merely imitate its color palette. Replace incompatible starter styling rather than layering multiple design systems. For one shared iOS/Android prototype, create one framework implementation with reusable presenters and states unless separate variants were requested.
36
36
 
37
- Prefer a local adapter descriptor with `schemaVersion: "ketatlas.design-system-adapter.v1"` when the selected design system provides one. Discover it from the user-supplied path, a repository `design-system.atlas.json`, or the package's documented `./atlas/profile.json` export. Read the descriptor before using its assets or commands; do not assume capabilities it does not declare. If there is no adapter, use the design system through its documented HTML/CSS interface and record that no reproducible adapter lock is available.
37
+ Prefer a local adapter descriptor with `schemaVersion: "ketatlas.design-system-adapter.v1"` when the selected design system provides one. Discover it from the user-supplied path, a repository `design-system.atlas.json`, or the package's documented `./atlas/profile.json` export. Read the descriptor before using its assets or commands; do not assume capabilities it does not declare. If there is no adapter, use the design system through its documented native framework interface and record that no reproducible adapter lock is available.
38
38
 
39
39
  For Két Design System, prefer the descriptor exported at `@ketvietlab/design-system/atlas/profile.json` and its declared `ket-design-system-atlas` materializer. Read that descriptor for asset names, root attributes, slots, hooks, and state ownership instead of hard-coding them.
40
40
 
41
- Keep the atlas self-contained and dependency-free. Do not add a consumer package installation or custom bundler just to use a design system. Materialize or copy permitted static assets only when the selected source supports it, preserve required notices, and do not rely on remote runtime assets when the requested mockup must work offline. Record the selected system, source URL/path, pinned version or commit when known, adapter/asset strategy, and any license or fidelity limitation in the bundle README.
41
+ ## Use the product's native framework
42
+
43
+ Inspect the product and selected design-system source before choosing how screens render. Match their implementation framework exactly:
44
+
45
+ - React components render through the product's React runtime and routes.
46
+ - Vue components render through the product's Vue runtime and routes.
47
+ - KetJS server components render on a KetJS server. Do not reconstruct their output with client-side DOM builders or handwritten HTML strings.
48
+ - Apply the equivalent rule to any other declared framework. Use static HTML only when the product is actually static HTML, no framework exists, or the user explicitly requests static HTML.
49
+
50
+ Choose the existing product framework first. If there is no product implementation yet, use the selected design system's canonical framework. If neither declares a framework, static HTML is the fallback. Preserve the rendering model as well as the library name: server components must render on their server, while client components remain native client components. If the product and design system require incompatible frameworks and no documented adapter exists, pause for a design-system/framework decision instead of translating either implementation.
51
+
52
+ Do not create a second component representation for Atlas. In particular, do not translate framework components into a KetAtlas-specific component JSON DSL, duplicate their markup in `screens/`, or imitate them with local CSS. Fix an incorrect canonical design-system component or adapter at its source before continuing; do not hide the mismatch in an Atlas-only override.
53
+
54
+ Separate screen presentation from business behavior. A screen presenter receives a deterministic view model and composes the real design-system components. Atlas routes call that presenter with fixtures; production routes call the same presenter with real data, permissions, and actions. Atlas may select fixture states through routes or query parameters, but it does not own product markup.
55
+
56
+ When a workspace contains multiple atlases, put reusable presenters, component compositions, fixture factories, tokens, and Atlas route helpers in one shared framework module outside the individual `.ketatlas` bundles. Each bundle owns only its flow graph, per-atlas fixture/state declarations, renderer sidecar, and documentation. Never copy shared component or style files from one atlas to another. Prefer one native renderer application that exposes namespaced `screenBasePath` routes for all atlases.
57
+
58
+ Keep static atlas bundles self-contained and dependency-free. For framework-native atlases, reuse the product's existing package manifest, lockfile, framework server, and shared UI source outside the atlas bundle; do not add another package installation or bundler inside `.ketatlas`. Materialize or copy permitted assets only for a genuinely static product. Record the selected system, implementation framework, shared module location, source URL/path, pinned version or commit when known, adapter strategy, and any license or fidelity limitation in the bundle README.
59
+
60
+ ### Declare the native renderer
61
+
62
+ Place `atlas.renderer.json` beside `atlas.json`. This sidecar is the executable renderer contract and does not change the version 1 workflow schema. Use an argument array, never a shell command string:
63
+
64
+ ```json
65
+ {
66
+ "$schema": "https://unpkg.com/ketatlas@0.4.0/renderer.schema.json",
67
+ "version": 1,
68
+ "framework": "react",
69
+ "command": ["npm", "run", "atlas:serve", "--", "--host", "{host}", "--port", "{port}"],
70
+ "cwd": "../..",
71
+ "readyPath": "/__atlas/ready",
72
+ "screenBasePath": "/__atlas/customer-care/"
73
+ }
74
+ ```
75
+
76
+ `cwd` is relative to the bundle. KetAtlas substitutes `{host}`, `{port}`, and `{atlasDirectory}` and also provides `KETATLAS_HOST`, `KETATLAS_HTML_PORT`, and `KETATLAS_PROJECT_DIR`. `readyPath` must return HTTP 2xx when the framework server is ready. `screenBasePath` is origin-relative and ends in `/`; screen URLs in `atlas.json` resolve beneath it. Multiple atlases may use the same command and shared renderer source while choosing different namespaced base paths.
77
+
78
+ Start the viewer and HTML renderer on separate loopback ports:
79
+
80
+ ```sh
81
+ npx --yes ketatlas@0.4.0 serve ./tasks/mockups.ketatlas --renderer --port 60550 --html-port 60551
82
+ ```
83
+
84
+ `--renderer` is an explicit trust boundary because it executes the declared local command. The viewer remains on the first port; framework HTML, scripts, styles, assets, and same-origin requests stay on the second. Do not proxy or rebuild framework output in the viewer process.
42
85
 
43
86
  ## Scaffold or extend
44
87
 
45
88
  Node.js 22 or newer is required. For a new, empty destination:
46
89
 
47
90
  ```sh
48
- npx --yes ketatlas scaffold ./tasks/mockups.ketatlas --template basic
91
+ npx --yes ketatlas@0.4.0 scaffold ./tasks/mockups.ketatlas --template basic
49
92
  ```
50
93
 
51
94
  Choose `basic` for mobile, `web` for desktop, or `process` for steps without UI. These are starting examples, not required product flows. Replace their sample content with the requested product.
52
95
 
53
- For an existing atlas, read and edit its JSON and screen files directly. Preserve useful IDs and URLs; scaffold refuses a nonempty directory and has no `--force` option. Pin the CLI version in the README commands or use a global installation. A consumer atlas does not need a local package installation.
96
+ For an existing atlas, read its JSON, renderer contract, and referenced framework source or static screen files. Preserve useful IDs and URLs; scaffold refuses a nonempty directory and has no `--force` option. Pin the CLI version in the README commands or use a global installation. A consumer atlas does not need a local KetAtlas installation.
54
97
 
55
98
  ### Backfill legacy projects
56
99
 
@@ -75,7 +118,9 @@ tasks/mockups.ketatlas/
75
118
  README.md Run commands, flow coverage, assumptions, verification
76
119
  ```
77
120
 
78
- Keep product assets inside the served directory when practical. Deliver JSON/schema, screen HTML/CSS/JavaScript, assets, and documentation. Do not scaffold a `package.json`, lockfile, `node_modules`, asset build scripts, or a copied viewer/test harness in the atlas folder just to use KetAtlas. The installed CLI supplies scaffold, serve, validate, and audit. Browser verification can use the agent's external tooling. Preserve unrelated application tooling when extending an existing repository.
121
+ A framework-native bundle replaces `screens/` and copied styles with `atlas.renderer.json`; its actual routes, shared presenters, fixtures, and styles stay in the product's framework source. Multiple bundles should point to the same shared renderer module instead of growing parallel component implementations.
122
+
123
+ For static mode, keep product assets inside the served directory when practical. For native mode, deliver JSON/schema, `atlas.renderer.json`, per-atlas fixtures or declarations, and documentation while changing shared framework source in its owning product module. Do not scaffold a `package.json`, lockfile, `node_modules`, asset build scripts, or a copied viewer/test harness in the atlas folder just to use KetAtlas. The installed CLI supplies scaffold, serve, validate, and audit. Browser verification can use the agent's external tooling. Preserve unrelated application tooling when extending an existing repository.
79
124
 
80
125
  Do not create a wrapper viewer or a custom canvas: `serve ./tasks/mockups.ketatlas` provides it.
81
126
 
@@ -97,7 +142,7 @@ Core rules:
97
142
  - Set `column` and `row` explicitly for branches. Both are integers from 0 to 100; no two nodes in a flow share a cell. This is a grid, not an automatic graph layout engine.
98
143
  - Set the atlas `viewport` or a screen override to the intended layout size. The mobile default is 390 × 844; choose desktop dimensions from the brief. Width and height are integers from 160 to 4096.
99
144
 
100
- Example structure, to adapt to the actual product and HTML files:
145
+ Example static structure; for native routes, replace the `.html` paths with paths relative to `screenBasePath`:
101
146
 
102
147
  ```json
103
148
  {
@@ -144,23 +189,25 @@ Example structure, to adapt to the actual product and HTML files:
144
189
 
145
190
  ## Build the actual screens
146
191
 
147
- Make each screen a complete HTML document with a viewport meta tag and `body { margin: 0; }`. Keep product content padding, but do not wrap the page in a second phone bezel, presentation frame, reviewer sidebar, or outer mockup margin. The iframe is the screen boundary.
192
+ For static HTML mode, make each screen a complete HTML document with a viewport meta tag and `body { margin: 0; }`. For framework-native mode, make every declared screen URL a directly addressable framework route whose response is a complete document at the selected state. Keep product content padding, but do not wrap the page in a second phone bezel, presentation frame, reviewer sidebar, or outer mockup margin. The iframe is the screen boundary.
148
193
 
149
- Use shared CSS/components and deterministic mock data. States referenced by node URLs must render directly when opened or refreshed, without requiring a previous login or click. Implement the relevant buttons, forms, validation feedback, and navigation in HTML/JavaScript: graph arrows do not wire screen interactions automatically.
194
+ Use the shared framework presenters/styles and deterministic fixtures. States referenced by node URLs must render directly when opened or refreshed, without requiring a previous login or click. Implement the relevant buttons, forms, validation feedback, and navigation in the native framework: graph arrows do not wire screen interactions automatically.
150
195
 
151
- The default iframe sandbox allows scripts and forms but gives the page an opaque origin. Prefer ordinary links, classic scripts, inline mock data, URL parameters, and in-memory state for portable prototypes. Storage, authenticated fetch, and some module imports can fail in this sandbox. Test in **Try this screen**, not just a standalone tab; do not solve a prototype issue by weakening the viewer's sandbox.
196
+ Static screens use an opaque sandbox by default. Native renderer mode places screens on a distinct origin and enables `allow-same-origin` there so framework modules and same-origin assets work without granting access to the viewer origin. Keep framework routes trusted and loopback-only. Test in **Try this screen**, not just a standalone tab.
152
197
 
153
198
  ## Validate and hand off
154
199
 
155
200
  Run from the user's project with the same root for audit and serve:
156
201
 
157
202
  ```sh
158
- npx --yes ketatlas discover . --json
159
- npx --yes ketatlas validate ./tasks/mockups.ketatlas
160
- npx --yes ketatlas audit ./tasks/mockups.ketatlas --strict
161
- npx --yes ketatlas serve ./tasks/mockups.ketatlas
203
+ npx --yes ketatlas@0.4.0 discover . --json
204
+ npx --yes ketatlas@0.4.0 validate ./tasks/mockups.ketatlas
205
+ npx --yes ketatlas@0.4.0 audit ./tasks/mockups.ketatlas --strict
206
+ npx --yes ketatlas@0.4.0 serve ./tasks/mockups.ketatlas --renderer --port 60550 --html-port 60551
162
207
  ```
163
208
 
209
+ Omit `--renderer` and `--html-port` only for a genuinely static atlas. Audit validates a discovered `atlas.renderer.json` and treats screen URLs as framework routes, but deliberately does not execute the command. The browser check must therefore prove the ready route, every requested screen state, framework scripts/styles, and important interactions through the two-port viewer.
210
+
164
211
  Use `--root .` on both audit and serve if screens or assets intentionally live outside the atlas directory but inside the project. Use `--port 4180` or another free port when necessary. Refresh after file edits; there is no hot reload.
165
212
 
166
213
  For local projects, fix structural errors, missing assets, and unreachable nodes until strict audit passes. Remote screen/assets URLs produce warnings and are not fetched by audit: when those are intentional, run the normal audit, report the warnings, and verify embedding separately rather than claiming a strict pass. With `audit --json --strict`, check the exit status and warnings as well as `valid`.
package/src/config.js CHANGED
@@ -166,7 +166,7 @@ export class AtlasValidationError extends Error {
166
166
  this.errors = result.errors;
167
167
  }
168
168
  }
169
- export function normalizeAtlas(input, baseURL) {
169
+ export function normalizeAtlas(input, baseURL, screenBaseURL = baseURL) {
170
170
  const result = validateAtlas(input);
171
171
  if (!result.valid) throw new AtlasValidationError(result);
172
172
  const data = structuredClone(input);
@@ -174,7 +174,7 @@ export function normalizeAtlas(input, baseURL) {
174
174
  data.viewport ??= { width: 390, height: 844 };
175
175
  data.screens ??= [];
176
176
  for (const s of data.screens) {
177
- s.url = safeURL(s.url, baseURL);
177
+ s.url = safeURL(s.url, screenBaseURL);
178
178
  s.viewport ??= { ...data.viewport };
179
179
  }
180
180
  for (const f of data.flows) {
@@ -186,7 +186,7 @@ export function normalizeAtlas(input, baseURL) {
186
186
  n.column ??= i;
187
187
  n.row ??= 0;
188
188
  n.type ??= n.screen ? "screen" : "note";
189
- if (n.url) n.url = safeURL(n.url, baseURL);
189
+ if (n.url) n.url = safeURL(n.url, n.type === "screen" ? screenBaseURL : baseURL);
190
190
  });
191
191
  f.edges.forEach((edge) => {
192
192
  edge.kind ??= "primary";
package/src/index.d.ts CHANGED
@@ -114,6 +114,8 @@ export interface AtlasOptions {
114
114
  progressToken?: string;
115
115
  /** Relative screen URLs resolve against this URL. loadAtlas uses the JSON URL by default. */
116
116
  baseURL?: string;
117
+ /** Override only relative screen and screen-node URLs, for a separate framework renderer. */
118
+ screenBaseURL?: string;
117
119
  /** Public directory containing styles/ and assets/, ending in /. Needed when bundling the JS. */
118
120
  assetBaseURL?: string;
119
121
  initialFlow?: string;
package/src/index.js CHANGED
@@ -26,7 +26,11 @@ const cameraMotion = Object.freeze({
26
26
  export function createAtlas(container, input, options = {}) {
27
27
  if (!(container instanceof HTMLElement))
28
28
  throw new TypeError("createAtlas requires an HTML element.");
29
- const config = normalizeAtlas(input, options.baseURL || document.baseURI);
29
+ const config = normalizeAtlas(
30
+ input,
31
+ options.baseURL || document.baseURI,
32
+ options.screenBaseURL || options.baseURL || document.baseURI,
33
+ );
30
34
  const progressData = requireProgress(options.progress || emptyProgress(), config);
31
35
  const maxPreviews = options.maxPreviews ?? 24,
32
36
  previewThreshold = options.previewThreshold ?? 0.3;
@@ -3,12 +3,12 @@
3
3
  This directory must keep its `.ketatlas` suffix. Run with Node.js 22 or newer:
4
4
 
5
5
  ```sh
6
- npx ketatlas serve .
7
- npx ketatlas audit . --strict
6
+ npx --yes ketatlas@0.4.0 serve .
7
+ npx --yes ketatlas@0.4.0 audit . --strict
8
8
  ```
9
9
 
10
- Edit `atlas.json` to change nodes, edges, and screen URLs. Screen URLs are relative to that file. Refresh the browser after editing. No wrapper HTML, package manifest, lockfile, node_modules, or build step is needed. Alternatively, install the CLI once with npm install --global ketatlas and use ketatlas serve .
10
+ This starter is intentionally static. Edit `atlas.json` to change nodes, edges, and screen URLs. Screen URLs are relative to that file. Refresh the browser after editing. No wrapper HTML, package manifest, lockfile, node_modules, or build step is needed. For React, Vue, KetJS, or another framework, use atlas.renderer.json and shared framework presenters instead of copying this static implementation.
11
11
 
12
- Edit the HTML files in screens/ to replace the sample product. styles/design-system.css is generated from the pinned KetJS design system; do not manually fork its tokens.
12
+ Edit the HTML files in screens/ to replace the sample static product. styles/design-system.css is generated from the pinned KetJS design system; do not manually fork its tokens.
13
13
 
14
- Open **Screens** in the viewer to review or edit delivery progress. Saving writes atlas.progress.json beside the workflow; use --read-only to disable editing. You can initialize records with ketatlas progress . --init. Keep this directory in your own project repository. The schema provides editor completion. See https://github.com/ketvietlab/ketatlas for the full API, CLI, and configuration reference.
14
+ Open **Screens** in the viewer to review or edit delivery progress. Saving writes atlas.progress.json beside the workflow; use --read-only to disable editing. You can initialize records with npx --yes ketatlas@0.4.0 progress . --init. Keep this directory in your own project repository. The schema provides editor completion. See https://github.com/ketvietlab/ketatlas for the full API, CLI, and configuration reference.
@@ -3,12 +3,12 @@
3
3
  This directory must keep its `.ketatlas` suffix. Run with Node.js 22 or newer:
4
4
 
5
5
  ```sh
6
- npx ketatlas serve .
7
- npx ketatlas audit . --strict
6
+ npx --yes ketatlas@0.4.0 serve .
7
+ npx --yes ketatlas@0.4.0 audit . --strict
8
8
  ```
9
9
 
10
- Edit `atlas.json` to change nodes, edges, and screen URLs. Screen URLs are relative to that file. Refresh the browser after editing. No wrapper HTML, package manifest, lockfile, node_modules, or build step is needed. Alternatively, install the CLI once with npm install --global ketatlas and use ketatlas serve .
10
+ This starter is intentionally static. Edit `atlas.json` to change nodes, edges, and screen URLs. Screen URLs are relative to that file. Refresh the browser after editing. No wrapper HTML, package manifest, lockfile, node_modules, or build step is needed. For React, Vue, KetJS, or another framework, use atlas.renderer.json and shared framework presenters instead of copying this static implementation.
11
11
 
12
12
  This template models a process without HTML screens. Add a screen registry and screen nodes when needed.
13
13
 
14
- Open **Screens** in the viewer to review or edit delivery progress. Saving writes atlas.progress.json beside the workflow; use --read-only to disable editing. You can initialize records with ketatlas progress . --init. Keep this directory in your own project repository. The schema provides editor completion. See https://github.com/ketvietlab/ketatlas for the full API, CLI, and configuration reference.
14
+ Open **Screens** in the viewer to review or edit delivery progress. Saving writes atlas.progress.json beside the workflow; use --read-only to disable editing. You can initialize records with npx --yes ketatlas@0.4.0 progress . --init. Keep this directory in your own project repository. The schema provides editor completion. See https://github.com/ketvietlab/ketatlas for the full API, CLI, and configuration reference.
@@ -3,12 +3,12 @@
3
3
  This directory must keep its `.ketatlas` suffix. Run with Node.js 22 or newer:
4
4
 
5
5
  ```sh
6
- npx ketatlas serve .
7
- npx ketatlas audit . --strict
6
+ npx --yes ketatlas@0.4.0 serve .
7
+ npx --yes ketatlas@0.4.0 audit . --strict
8
8
  ```
9
9
 
10
- Edit `atlas.json` to change nodes, edges, and screen URLs. Screen URLs are relative to that file. Refresh the browser after editing. No wrapper HTML, package manifest, lockfile, node_modules, or build step is needed. Alternatively, install the CLI once with npm install --global ketatlas and use ketatlas serve .
10
+ This starter is intentionally static. Edit `atlas.json` to change nodes, edges, and screen URLs. Screen URLs are relative to that file. Refresh the browser after editing. No wrapper HTML, package manifest, lockfile, node_modules, or build step is needed. For React, Vue, KetJS, or another framework, use atlas.renderer.json and shared framework presenters instead of copying this static implementation.
11
11
 
12
- Edit the HTML files in screens/ to replace the sample product. styles/design-system.css is generated from the pinned KetJS design system; do not manually fork its tokens.
12
+ Edit the HTML files in screens/ to replace the sample static product. styles/design-system.css is generated from the pinned KetJS design system; do not manually fork its tokens.
13
13
 
14
- Open **Screens** in the viewer to review or edit delivery progress. Saving writes atlas.progress.json beside the workflow; use --read-only to disable editing. You can initialize records with ketatlas progress . --init. Keep this directory in your own project repository. The schema provides editor completion. See https://github.com/ketvietlab/ketatlas for the full API, CLI, and configuration reference.
14
+ Open **Screens** in the viewer to review or edit delivery progress. Saving writes atlas.progress.json beside the workflow; use --read-only to disable editing. You can initialize records with npx --yes ketatlas@0.4.0 progress . --init. Keep this directory in your own project repository. The schema provides editor completion. See https://github.com/ketvietlab/ketatlas for the full API, CLI, and configuration reference.