ketatlas 0.1.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 +13 -0
- package/LICENSE +21 -0
- package/NOTICE.md +10 -0
- package/README.md +130 -0
- package/assets/INTER-LICENSE +93 -0
- package/assets/KETJS-LICENSE +21 -0
- package/assets/LUCIDE-LICENSE +43 -0
- package/assets/design-system.lock.json +56 -0
- package/assets/inter-latin-wght-normal.woff2 +0 -0
- package/assets/inter-vietnamese-wght-normal.woff2 +0 -0
- package/bin/audit.js +116 -0
- package/bin/ketatlas.js +148 -0
- package/bin/server.js +93 -0
- package/bin/viewer.js +20 -0
- package/docs/architecture.md +57 -0
- package/docs/authoring.md +49 -0
- package/docs/cli.md +70 -0
- package/docs/configuration.md +65 -0
- package/docs/integration.md +108 -0
- package/docs/migration.md +35 -0
- package/docs/releasing.md +40 -0
- package/package.json +59 -0
- package/schema.json +187 -0
- package/src/config.js +196 -0
- package/src/icons.js +23 -0
- package/src/index.d.ts +106 -0
- package/src/index.js +679 -0
- package/src/layout.js +103 -0
- package/src/template.js +38 -0
- package/styles/design-system.css +2838 -0
- package/styles/design-system.document.css +2838 -0
- package/styles/fonts.css +17 -0
- package/styles/ketatlas.css +992 -0
- package/templates/basic/README.md +14 -0
- package/templates/basic/atlas.json +45 -0
- package/templates/basic/ketatlas.schema.json +187 -0
- package/templates/basic/screens/done.html +18 -0
- package/templates/basic/screens/screen.css +48 -0
- package/templates/basic/screens/welcome.html +21 -0
- package/templates/basic/styles/LICENSE +21 -0
- package/templates/basic/styles/design-system.css +2838 -0
- package/templates/process/README.md +14 -0
- package/templates/process/atlas.json +83 -0
- package/templates/process/ketatlas.schema.json +187 -0
- package/templates/web/README.md +14 -0
- package/templates/web/atlas.json +77 -0
- package/templates/web/ketatlas.schema.json +187 -0
- package/templates/web/screens/demo.css +255 -0
- package/templates/web/screens/portal.html +41 -0
- package/templates/web/styles/LICENSE +21 -0
- package/templates/web/styles/design-system.css +2838 -0
package/bin/server.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { realpath, stat } from "node:fs/promises";
|
|
3
|
+
import { createReadStream } from "node:fs";
|
|
4
|
+
import { resolve, relative, extname, sep } from "node:path";
|
|
5
|
+
const types = {
|
|
6
|
+
".html": "text/html; charset=utf-8",
|
|
7
|
+
".js": "text/javascript; charset=utf-8",
|
|
8
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
9
|
+
".css": "text/css; charset=utf-8",
|
|
10
|
+
".json": "application/json; charset=utf-8",
|
|
11
|
+
".svg": "image/svg+xml",
|
|
12
|
+
".png": "image/png",
|
|
13
|
+
".jpg": "image/jpeg",
|
|
14
|
+
".woff2": "font/woff2",
|
|
15
|
+
".md": "text/plain; charset=utf-8",
|
|
16
|
+
};
|
|
17
|
+
const within = (root, path) => {
|
|
18
|
+
const r = relative(root, path);
|
|
19
|
+
return r === "" || (!r.startsWith(".." + sep) && r !== ".." && !r.startsWith(sep));
|
|
20
|
+
};
|
|
21
|
+
/** Local static preview only. Resolves symlinks before checking the root boundary. */
|
|
22
|
+
export async function serve(
|
|
23
|
+
directory,
|
|
24
|
+
{ port = 4178, host = "127.0.0.1", viewer, packageRoot } = {},
|
|
25
|
+
) {
|
|
26
|
+
const root = await realpath(resolve(directory));
|
|
27
|
+
if (!(await stat(root)).isDirectory()) throw new Error("Serve root must be a directory.");
|
|
28
|
+
const server = createServer(async (req, res) => {
|
|
29
|
+
const fail = (status, message) => {
|
|
30
|
+
res.writeHead(status, { "Content-Type": "text/plain; charset=utf-8" });
|
|
31
|
+
res.end(message);
|
|
32
|
+
};
|
|
33
|
+
if (!["GET", "HEAD"].includes(req.method)) return fail(405, "Method not allowed");
|
|
34
|
+
try {
|
|
35
|
+
const pathname = decodeURIComponent(new URL(req.url, "http://localhost").pathname);
|
|
36
|
+
if (
|
|
37
|
+
pathname.includes("\0") ||
|
|
38
|
+
pathname.includes("\\") ||
|
|
39
|
+
pathname.split("/").some((part) => part.startsWith("."))
|
|
40
|
+
)
|
|
41
|
+
return fail(404, "Not found");
|
|
42
|
+
if (viewer && pathname === "/") {
|
|
43
|
+
res.writeHead(200, {
|
|
44
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
45
|
+
"Cache-Control": "no-cache",
|
|
46
|
+
});
|
|
47
|
+
res.end(req.method === "HEAD" ? undefined : viewer);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
let requestRoot = root,
|
|
51
|
+
requestPath = pathname;
|
|
52
|
+
if (viewer && pathname.startsWith("/__ketatlas__/")) {
|
|
53
|
+
requestPath = pathname.slice("/__ketatlas__".length);
|
|
54
|
+
if (!["src", "styles", "assets"].includes(requestPath.split("/")[1]))
|
|
55
|
+
return fail(404, "Not found");
|
|
56
|
+
requestRoot = packageRoot;
|
|
57
|
+
}
|
|
58
|
+
let path = resolve(requestRoot, "." + requestPath);
|
|
59
|
+
if (!within(requestRoot, path)) return fail(404, "Not found");
|
|
60
|
+
path = await realpath(path);
|
|
61
|
+
if (!within(requestRoot, path)) return fail(404, "Not found");
|
|
62
|
+
let info = await stat(path);
|
|
63
|
+
if (info.isDirectory()) {
|
|
64
|
+
if (!pathname.endsWith("/")) {
|
|
65
|
+
res.writeHead(301, { Location: encodeURI(pathname) + "/" });
|
|
66
|
+
res.end();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
path = await realpath(resolve(path, "index.html"));
|
|
70
|
+
if (!within(requestRoot, path)) return fail(404, "Not found");
|
|
71
|
+
info = await stat(path);
|
|
72
|
+
}
|
|
73
|
+
if (!info.isFile()) return fail(404, "Not found");
|
|
74
|
+
res.writeHead(200, {
|
|
75
|
+
"Content-Type": types[extname(path)] || "application/octet-stream",
|
|
76
|
+
"Content-Length": info.size,
|
|
77
|
+
"Cache-Control": "no-cache",
|
|
78
|
+
"X-Content-Type-Options": "nosniff",
|
|
79
|
+
});
|
|
80
|
+
if (req.method === "HEAD") return res.end();
|
|
81
|
+
const stream = createReadStream(path);
|
|
82
|
+
stream.on("error", () => res.destroy());
|
|
83
|
+
stream.pipe(res);
|
|
84
|
+
} catch {
|
|
85
|
+
fail(404, "Not found");
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
await new Promise((resolve, reject) => {
|
|
89
|
+
server.once("error", reject);
|
|
90
|
+
server.listen(port, host, resolve);
|
|
91
|
+
});
|
|
92
|
+
return server;
|
|
93
|
+
}
|
package/bin/viewer.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { readFile, realpath, stat } from "node:fs/promises";
|
|
2
|
+
import { resolve, dirname, relative, sep } from "node:path";
|
|
3
|
+
import { validateAtlas, AtlasValidationError } from "../src/config.js";
|
|
4
|
+
import { serve } from "./server.js";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
|
+
export async function serveAtlas(file, options = {}) {
|
|
8
|
+
const absolute = await realpath(resolve(file));
|
|
9
|
+
if (!(await stat(absolute)).isFile()) throw new Error("Expected an atlas JSON file.");
|
|
10
|
+
const config = JSON.parse(await readFile(absolute, "utf8"));
|
|
11
|
+
const result = validateAtlas(config);
|
|
12
|
+
if (!result.valid) throw new AtlasValidationError(result);
|
|
13
|
+
const root = await realpath(resolve(options.root || dirname(absolute))),
|
|
14
|
+
rel = relative(root, absolute);
|
|
15
|
+
if (rel === ".." || rel.startsWith(".." + sep) || rel.startsWith(sep))
|
|
16
|
+
throw new Error("The JSON file must be inside --root.");
|
|
17
|
+
const configURL = "/" + rel.split(sep).map(encodeURIComponent).join("/");
|
|
18
|
+
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});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>`;
|
|
19
|
+
return serve(root, { ...options, viewer: html, packageRoot });
|
|
20
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
KetAtlas is a static browser viewer plus a Node.js command-line toolkit. There is no database, account service, application backend, or runtime npm dependency.
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
atlas.json + screen HTML
|
|
7
|
+
│
|
|
8
|
+
├── validate / audit → diagnostics and CI exit code
|
|
9
|
+
│
|
|
10
|
+
└── serve → built-in viewer page
|
|
11
|
+
│
|
|
12
|
+
├── config validation + URL resolution
|
|
13
|
+
├── deterministic grid layout
|
|
14
|
+
├── Shadow DOM shell + SVG edges
|
|
15
|
+
└── lazy HTML iframes + interactive inspector
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Ownership
|
|
19
|
+
|
|
20
|
+
| Directory | Responsibility |
|
|
21
|
+
| ----------------- | ------------------------------------------------------------- |
|
|
22
|
+
| `src/config.js` | DOM-free validation and normalization. |
|
|
23
|
+
| `src/layout.js` | Pure grid sizing and edge routes. |
|
|
24
|
+
| `src/index.js` | Mounting, camera, input, iframe lifecycle and public API. |
|
|
25
|
+
| `src/template.js` | English viewer shell. |
|
|
26
|
+
| `src/index.d.ts` | Public TypeScript declarations. |
|
|
27
|
+
| `styles/` | Viewer layout and generated canonical design-system bundles. |
|
|
28
|
+
| `assets/` | Local fonts, licenses and provenance manifest. |
|
|
29
|
+
| `bin/` | Scaffold, JSON serving, validation and static audit. |
|
|
30
|
+
| `templates/` | Self-contained starting projects included in the npm package. |
|
|
31
|
+
| `examples/` | Maintainer playground with mobile, web and process flows. |
|
|
32
|
+
| `scripts/` | Reproducible assets, templates, schema and package checks. |
|
|
33
|
+
| `tests/` | Pure logic, CLI/server, browser and installation tests. |
|
|
34
|
+
|
|
35
|
+
## Layout and performance
|
|
36
|
+
|
|
37
|
+
Flows are stacked on one canvas. A row and column grow to fit their largest node, so mobile and desktop aspect ratios can coexist. The layout respects authored grid positions. Arrows use orthogonal routes and a small lane offset; this is not an obstacle-avoiding graph-layout engine. Use nearby grid cells and concise labels for readable large diagrams.
|
|
38
|
+
|
|
39
|
+
Camera changes use an animation frame and a CSS transform. 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.
|
|
40
|
+
|
|
41
|
+
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.
|
|
42
|
+
|
|
43
|
+
## Isolation and lifecycle
|
|
44
|
+
|
|
45
|
+
Each mount owns a Shadow DOM subtree, event handlers, a camera, an observer, and an iframe pool. IDs and SVG markers are scoped to that subtree. `destroy()` removes the subtree, disconnects the observer, cancels scheduled painting and closes dialogs. Host CSS and unrelated content are not rewritten.
|
|
46
|
+
|
|
47
|
+
URL synchronization is opt-in. The CLI enables it; an embedded map does not take ownership of the host URL by default.
|
|
48
|
+
|
|
49
|
+
## Design provenance
|
|
50
|
+
|
|
51
|
+
The default theme uses Inter and KetJS's canonical tokens and primitives. `prepare-assets` reads exact source files from a pinned KetJS revision, flattens imports, and emits two bundles: a regular document bundle and a Shadow DOM bundle. The only selector adaptation is `:root` → `:host`; values are unchanged.
|
|
52
|
+
|
|
53
|
+
`assets/design-system.lock.json` records upstream hashes and generated output hashes. `npm run prepack` verifies them and checks that viewer CSS consumes existing tokens without redefining `--kv-*` values. Upstream capability changes belong in KetJS before changing this pin.
|
|
54
|
+
|
|
55
|
+
## Version 1 boundaries
|
|
56
|
+
|
|
57
|
+
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.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Authoring a useful atlas
|
|
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.
|
|
4
|
+
|
|
5
|
+
## 1. Create a starting point
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
ketatlas scaffold ./customer-journeys --template basic
|
|
9
|
+
ketatlas serve ./customer-journeys/atlas.json
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Open a card with a double-click. The preview is actual HTML; links and JavaScript inside it can work independently of the map. Edit those files before adding more nodes.
|
|
13
|
+
|
|
14
|
+
## 2. Register your screens
|
|
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.
|
|
17
|
+
|
|
18
|
+
For a desktop page:
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"id": "dashboard",
|
|
23
|
+
"title": "Team dashboard",
|
|
24
|
+
"url": "./screens/dashboard.html",
|
|
25
|
+
"viewport": { "width": 1440, "height": 900 }
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
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
|
+
|
|
31
|
+
## 3. Connect actions and decisions
|
|
32
|
+
|
|
33
|
+
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"`.
|
|
34
|
+
|
|
35
|
+
A repeated screen gets a different node ID but the same `screen` reference. Add a node URL override when it represents a different state. Use process notes for actions outside a UI, so a step does not need a fake screenshot.
|
|
36
|
+
|
|
37
|
+
## 4. Review from start to outcome
|
|
38
|
+
|
|
39
|
+
Click a node to highlight incoming/outgoing arrows and follow a next-step button. Drag the canvas, use the minimap, or press F to fit the flow. Press 0 to return to its start. Opening the actual HTML tests the prototype's own behavior; the arrow graph is not an automated user-action script.
|
|
40
|
+
|
|
41
|
+
## 5. Audit before sharing
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
ketatlas audit ./customer-journeys/atlas.json --strict
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Keep the JSON and screens in your project's repository. Review changes in pull requests. Share the directory or run the same serve command in the recipient's checkout. No hosted account or service state is required.
|
|
48
|
+
|
|
49
|
+
The built-in audit finds structural and local-file issues. Add browser E2E tests for your interactive screens, especially forms, navigation, and errors. A design reference is not evidence of an implemented production flow.
|
package/docs/cli.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# CLI reference
|
|
2
|
+
|
|
3
|
+
Use `ketatlas` after installing the package, `node /path/to/ketatlas/bin/ketatlas.js` from a checkout, or the npx forms in the [README](../README.md).
|
|
4
|
+
|
|
5
|
+
## Scaffold
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
ketatlas scaffold ./journeys
|
|
9
|
+
ketatlas scaffold ./approvals --template web
|
|
10
|
+
ketatlas scaffold ./operations --template process
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`basic` is the default: two interactive mobile pages and one flow. `web` contains desktop purchase approval pages. `process` contains notes and an external handoff, without HTML screens. Every template includes a local JSON Schema for editor completion and a README. Product HTML and its styles belong to the new project.
|
|
14
|
+
|
|
15
|
+
`init` is an alias for `scaffold`. The destination must be missing or empty. Existing files are never overwritten; there is no `--force` flag.
|
|
16
|
+
|
|
17
|
+
## Serve a JSON file
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
ketatlas serve ./tasks/onboarding/atlas.json
|
|
21
|
+
ketatlas serve ./tasks/onboarding/atlas.json --port 4180
|
|
22
|
+
ketatlas serve ./tasks/onboarding/atlas.json --root .
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The tool supplies the viewer page. It validates the JSON at startup, serves your files, and mounts the atlas at `/`. The default file root is the JSON file's directory. Choose `--root` when a relative URL points to a sibling directory outside that default root. The JSON must be inside the selected root.
|
|
26
|
+
|
|
27
|
+
Open the printed localhost URL. Changes appear after refreshing; there is no hot reload or authoring server state. Use `?flow=your-flow-id` or `?screen=your-screen-id` to open a specific part of the map. An unknown ID falls back to the first flow.
|
|
28
|
+
|
|
29
|
+
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.
|
|
30
|
+
|
|
31
|
+
`serve <directory>` also works as a plain static preview for an existing HTML integration. JSON mode is recommended for project planning.
|
|
32
|
+
|
|
33
|
+
## Audit
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
ketatlas audit ./atlas.json
|
|
37
|
+
ketatlas audit ./atlas.json --root .. --strict
|
|
38
|
+
ketatlas audit ./atlas.json --json
|
|
39
|
+
ketatlas audit ./atlas.json --json --output audit-report.json
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Audit checks:
|
|
43
|
+
|
|
44
|
+
- Required fields, types, version, valid IDs and unknown properties.
|
|
45
|
+
- Duplicate IDs, missing screen references, invalid edge endpoints, start/end references, and grid collisions.
|
|
46
|
+
- Nodes unreachable from the chosen start, reported as warnings. Cycles and recovery paths are allowed.
|
|
47
|
+
- Local screen files and node URL overrides, using the same root boundary as the server.
|
|
48
|
+
- Literal resource references in HTML (`src`/`href`), links between HTML pages, and CSS imports/URLs.
|
|
49
|
+
- A viewport meta tag in HTML previews.
|
|
50
|
+
|
|
51
|
+
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.
|
|
52
|
+
|
|
53
|
+
Exit codes:
|
|
54
|
+
|
|
55
|
+
| Code | Meaning |
|
|
56
|
+
| ---- | ------------------------------------------------------------------------------- |
|
|
57
|
+
| `0` | Passed; warnings are allowed unless `--strict` is present. |
|
|
58
|
+
| `1` | Invalid input, missing resources, command failure, or warnings with `--strict`. |
|
|
59
|
+
|
|
60
|
+
`--json` writes a machine-readable report to stdout. `--output` additionally writes the report to a new file and refuses to overwrite an existing one. The report has `valid`, `summary`, `errors`, `warnings`, `file`, `root`, and `scope`. `valid` reflects errors; with `--strict`, also check the exit code or warnings array.
|
|
61
|
+
|
|
62
|
+
## Validate and version
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
ketatlas validate ./atlas.json
|
|
66
|
+
ketatlas --version
|
|
67
|
+
ketatlas --help
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`validate` checks configuration and graph references only. It is also available as `validateAtlas(data)` in JavaScript. JSON Schema supports editor completion; graph-reference and URL safety checks belong to the runtime validator and audit.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Configuration reference
|
|
2
|
+
|
|
3
|
+
The [JSON Schema](../schema.json) defines version 1. A generated project contains `ketatlas.schema.json` and references it using `$schema`. The CLI rejects unknown fields so misspellings do not silently change the layout.
|
|
4
|
+
|
|
5
|
+
## Atlas
|
|
6
|
+
|
|
7
|
+
| Field | Required | Default / meaning |
|
|
8
|
+
| ------------- | -------- | ------------------------------------------------------------------ |
|
|
9
|
+
| `version` | Yes | Must be `1`. |
|
|
10
|
+
| `title` | Yes | Project name in the viewer header. |
|
|
11
|
+
| `description` | No | Project description for tooling; not rendered as a separate panel. |
|
|
12
|
+
| `viewport` | No | `{ "width": 390, "height": 844 }`. |
|
|
13
|
+
| `screens` | No | Reusable screen definitions; `[]` supports screenless processes. |
|
|
14
|
+
| `flows` | Yes | A nonempty array of workflows. |
|
|
15
|
+
| `$schema` | No | Schema URL/path for your editor. |
|
|
16
|
+
|
|
17
|
+
All viewer UI is English. Project titles, descriptions and labels can use any language. Search ignores case and combining accents.
|
|
18
|
+
|
|
19
|
+
## Screen
|
|
20
|
+
|
|
21
|
+
| Field | Required | Meaning |
|
|
22
|
+
| ------------- | -------- | --------------------------------------------------------------------- |
|
|
23
|
+
| `id` | Yes | Unique across the atlas. |
|
|
24
|
+
| `title` | Yes | Human-readable screen name. |
|
|
25
|
+
| `url` | Yes | Relative URL or HTTP(S) URL for actual HTML. |
|
|
26
|
+
| `viewport` | No | Overrides the atlas viewport. Both dimensions are integers, 160–4096. |
|
|
27
|
+
| `description` | No | Context inherited by its nodes. |
|
|
28
|
+
| `badge` | No | Small card footer label; defaults to `Preview`. |
|
|
29
|
+
|
|
30
|
+
The declared viewport determines the iframe's actual layout size. Canvas thumbnails preserve its aspect ratio. Opening a screen uses that same layout size and scales the full frame to the available width without side padding.
|
|
31
|
+
|
|
32
|
+
## Flow
|
|
33
|
+
|
|
34
|
+
`id` and `title` are required. `group` and `description` are optional. `nodes` must be nonempty. `edges` is required; use `[]` for a single step. `start` defaults to the first node. `ends` defaults to nodes without outgoing edges; set it explicitly when an outcome also has a return path.
|
|
35
|
+
|
|
36
|
+
Flow IDs are unique across the atlas. Node IDs are local to a flow. IDs use letters, digits, dots, underscores and hyphens, beginning with a letter or digit.
|
|
37
|
+
|
|
38
|
+
## Node
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"id": "retry",
|
|
43
|
+
"screen": "sign-in",
|
|
44
|
+
"title": "Session expired",
|
|
45
|
+
"url": "./screens/sign-in.html?state=expired",
|
|
46
|
+
"column": 1,
|
|
47
|
+
"row": 1
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`id` is required. A node with `screen` references the registry and defaults to type `screen`. Its optional `title`, `description` and `url` override that screen for this occurrence only.
|
|
52
|
+
|
|
53
|
+
A node without `screen` defaults to `note` and requires a title. Use `type: "external"` to mark a handoff. These nodes show text rather than an iframe; an optional URL becomes an **Open reference** link in the selected-node panel.
|
|
54
|
+
|
|
55
|
+
`column` defaults to the node's index in the array and `row` to `0`. Both accept integers from 0 to 100. Two nodes cannot share a cell within one flow. Set columns explicitly on branch nodes; this is a deterministic grid, not automatic graph layout.
|
|
56
|
+
|
|
57
|
+
## Edge
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{ "from": "verify", "to": "retry", "label": "Code expired", "kind": "conditional" }
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`from`, `to` and a nonempty `label` are required. Endpoints reference node IDs in the same flow. `kind` is `primary` (default), `conditional`, or `recovery`. Arrows are orthogonal and labels describe the action or condition.
|
|
64
|
+
|
|
65
|
+
Cross-flow edges are not part of version 1. Reuse the destination screen in another flow, or add an external/note handoff to explain the boundary. Start and outcome markers express authored intent; clicking them does not invoke a backend operation.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Integration and API
|
|
2
|
+
|
|
3
|
+
Use the CLI for a standalone map. Use the JavaScript API when a map belongs inside a documentation portal or another application.
|
|
4
|
+
|
|
5
|
+
## Plain HTML
|
|
6
|
+
|
|
7
|
+
Copy the package's `src/`, `styles/`, and `assets/` directories together into a public `vendor/ketatlas/` directory. No build output is required.
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<div id="map" style="height: 800px"></div>
|
|
11
|
+
<script type="module">
|
|
12
|
+
import { loadAtlas } from "./vendor/ketatlas/src/index.js";
|
|
13
|
+
const atlas = await loadAtlas(document.querySelector("#map"), "./atlas.json");
|
|
14
|
+
</script>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Serve over HTTP. ES modules and JSON fetch do not support opening the page directly through `file://`. The viewer requires a container with an explicit height (minimum 480px).
|
|
18
|
+
|
|
19
|
+
## Bundled applications
|
|
20
|
+
|
|
21
|
+
Install the package and copy `styles/` and `assets/` to the same public directory. For example:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npm install ketatlas
|
|
25
|
+
mkdir -p public/ketatlas
|
|
26
|
+
cp -R node_modules/ketatlas/styles node_modules/ketatlas/assets public/ketatlas/
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Before an npm release, install from the repository or an archive produced by `npm pack`. When JavaScript is bundled, provide `assetBaseURL` because dynamic stylesheet URLs cannot be inferred by every bundler.
|
|
30
|
+
|
|
31
|
+
React lifecycle example:
|
|
32
|
+
|
|
33
|
+
```jsx
|
|
34
|
+
import { useEffect, useRef } from "react";
|
|
35
|
+
import { createAtlas } from "ketatlas";
|
|
36
|
+
|
|
37
|
+
export function WorkflowMap({ config }) {
|
|
38
|
+
const container = useRef(null);
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const atlas = createAtlas(container.current, config, {
|
|
41
|
+
baseURL: new URL("/planning/atlas.json", location.href).href,
|
|
42
|
+
assetBaseURL: "/ketatlas/",
|
|
43
|
+
});
|
|
44
|
+
atlas.ready.catch(console.error);
|
|
45
|
+
return () => atlas.destroy();
|
|
46
|
+
}, [config]);
|
|
47
|
+
return <div ref={container} style={{ height: 800 }} />;
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The browser implementation has no React dependency. Instantiate it after mount; do not call `createAtlas` during server-side rendering. Replace a config by destroying and recreating the instance. Input data is cloned before layout and is never mutated.
|
|
52
|
+
|
|
53
|
+
## Functions
|
|
54
|
+
|
|
55
|
+
- `createAtlas(container, config, options?)` returns an instance immediately. `await instance.ready` before measuring or navigating it.
|
|
56
|
+
- `loadAtlas(container, url, options?)` fetches JSON and resolves to a ready instance. Relative screen URLs resolve against the JSON response URL, including redirects.
|
|
57
|
+
- `validateAtlas(unknown)` returns `{ valid, errors, warnings }` without requiring a DOM.
|
|
58
|
+
- Invalid configuration throws `AtlasValidationError` during mounting, with an `errors` array containing `{ path, message }` entries.
|
|
59
|
+
|
|
60
|
+
## Options
|
|
61
|
+
|
|
62
|
+
| Option | Default | Purpose |
|
|
63
|
+
| ------------------ | ------------------------------------------ | ------------------------------------------------------------------------------- |
|
|
64
|
+
| `baseURL` | Page URL; JSON response URL in `loadAtlas` | Resolve screen and node URLs. |
|
|
65
|
+
| `assetBaseURL` | Package directory | Public directory containing `styles/` and `assets/`; must end in `/`. |
|
|
66
|
+
| `initialFlow` | First flow | Initial flow ID. |
|
|
67
|
+
| `theme` | `light` | `light` or `dark`. |
|
|
68
|
+
| `syncUrl` | `false` | Read/write `flow`/`screen` URL parameters. Enable for only one viewer per page. |
|
|
69
|
+
| `maxPreviews` | `24` | Maximum mounted canvas iframes, from 1 to 64. The open inspector may add one. |
|
|
70
|
+
| `previewThreshold` | `0.3` | Below this zoom, canvas cards use placeholders. |
|
|
71
|
+
| `sandbox` | `allow-scripts allow-forms` | Space-separated iframe sandbox permissions. |
|
|
72
|
+
| `signal` | None | AbortSignal for the `loadAtlas` JSON request. |
|
|
73
|
+
|
|
74
|
+
All UI is English. Labels in project data are rendered as text. Shadow DOM keeps viewer CSS and click handling isolated from the host document. Fonts are registered with one document stylesheet per asset URL; the stylesheet is retained for reuse after teardown.
|
|
75
|
+
|
|
76
|
+
## Instance methods
|
|
77
|
+
|
|
78
|
+
| Method | Behavior |
|
|
79
|
+
| --------------------------- | ---------------------------------------------------------------------------------- |
|
|
80
|
+
| `goToFlow(id)` | Select a flow and return to its start. Clears the search. |
|
|
81
|
+
| `focusNode(flowId, nodeId)` | Select and focus a node. |
|
|
82
|
+
| `openNode(flowId, nodeId)` | Open a screen's interactive preview; note nodes open their details. |
|
|
83
|
+
| `fit()` | Fit the current flow. |
|
|
84
|
+
| `zoomTo(number)` | Zoom, clamped to 0.18–2.2. |
|
|
85
|
+
| `getState()` | Snapshot of flow ID, selected node ID, zoom, pan and counts. |
|
|
86
|
+
| `destroy()` | Remove the owned viewer, frames, observers and animation work. Safe to call twice. |
|
|
87
|
+
|
|
88
|
+
Unknown IDs passed to navigation methods throw `RangeError`. Methods after teardown throw, except `getState()` and `destroy()`.
|
|
89
|
+
|
|
90
|
+
## Events
|
|
91
|
+
|
|
92
|
+
Listen on the instance's `element` or its containing element. Events bubble and include a `detail` object.
|
|
93
|
+
|
|
94
|
+
- `ketatlas:flowchange`: `{ flowId }`.
|
|
95
|
+
- `ketatlas:select`: `{ flowId, nodeId }`.
|
|
96
|
+
- `ketatlas:previewopen`: `{ flowId, nodeId, screenId }`.
|
|
97
|
+
- `ketatlas:viewportchange`: `{ x, y, z }`, emitted during canvas painting.
|
|
98
|
+
- `ketatlas:previewerror`: `{ nodeId, url }`, when an iframe reports a load error. Browsers do not reliably report blocked framing or HTTP errors through this event.
|
|
99
|
+
|
|
100
|
+
These are viewer events. KetAtlas does not inspect or synchronize navigation inside the iframe. Its title remains the screen that was opened; use your own prototype's navigation to continue.
|
|
101
|
+
|
|
102
|
+
## Embedding behavior
|
|
103
|
+
|
|
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
|
+
|
|
106
|
+
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
|
+
|
|
108
|
+
The viewer uses native dialog, Shadow DOM, ResizeObserver, container queries and modern CSS. Chromium is covered by the browser suite. Other modern engines should be qualified against your host application before promising support.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Migrating the KétSuite mobile map
|
|
2
|
+
|
|
3
|
+
The original mobile map remains in its project. KetAtlas does not copy its business data or change the mobile repository. The extraction generalizes the viewer; project migration is a separate configuration change.
|
|
4
|
+
|
|
5
|
+
## Field mapping
|
|
6
|
+
|
|
7
|
+
| Original mobile reference | KetAtlas |
|
|
8
|
+
| ------------------------------------------ | ------------------------------------------------ |
|
|
9
|
+
| `window.MOBILE_SCREENS` | `screens` array in `atlas.json`. |
|
|
10
|
+
| `screen.label` | `screen.title`. |
|
|
11
|
+
| `index.html?screen=ID&state=STATE&embed=1` | Explicit `screen.url` or node `url` override. |
|
|
12
|
+
| `screen.sourceStatus` | Optional `screen.badge`, e.g. `DOC` or `TARGET`. |
|
|
13
|
+
| `window.MOBILE_FLOWS` | `flows` array. |
|
|
14
|
+
| `node.key` | `node.id` (string). |
|
|
15
|
+
| `node.screenId` | `node.screen`. |
|
|
16
|
+
| `node.col` / `node.row` | `node.column` / `node.row`. |
|
|
17
|
+
| `node.caption` | `node.title`. |
|
|
18
|
+
| `node.kind: "external"` | `node.type: "external"`. |
|
|
19
|
+
| `flow.end` | `flow.ends`. |
|
|
20
|
+
| `edge.tone: "main"` | `edge.kind: "primary"`. |
|
|
21
|
+
| `edge.tone: "branch"` | `edge.kind: "conditional"`. |
|
|
22
|
+
| `edge.tone: "recovery"` | `edge.kind: "recovery"`. |
|
|
23
|
+
|
|
24
|
+
Keep the existing screen IDs. Give repeated states unique node IDs, but reuse the screen reference. Materialize the old `state` field into the preview URL rather than teaching the framework about mobile-specific states.
|
|
25
|
+
|
|
26
|
+
## Migration sequence
|
|
27
|
+
|
|
28
|
+
1. Export the screen and flow registries into the version 1 JSON contract above.
|
|
29
|
+
2. Keep existing HTML screens in the mobile project. Set relative URLs against the new JSON location and use the clean `embed=1` route.
|
|
30
|
+
3. Set the default viewport to 390 × 844. Keep DOC/TARGET source labels if useful to reviewers.
|
|
31
|
+
4. Run `ketatlas audit atlas.json --root <project-root>` and resolve diagnostics.
|
|
32
|
+
5. Run `ketatlas serve atlas.json --root <project-root>` and compare the existing 22 journeys, 108 screen IDs, and 162 edges against the exported graph.
|
|
33
|
+
6. Re-run the project's browser interactions under the iframe sandbox. Only enable additional sandbox permissions for trusted HTML when its behavior requires them.
|
|
34
|
+
|
|
35
|
+
KetAtlas UI is English. Vietnamese business labels inside the project's configuration and HTML can remain Vietnamese.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Releasing KetAtlas
|
|
2
|
+
|
|
3
|
+
The npm package name and executable are both **ketatlas**. The package is ESM, includes TypeScript declarations, and ships the CLI, viewer modules, styles, fonts, licenses, schema and templates. Development dependencies, examples, test results and the sibling mobile project are not required at runtime.
|
|
4
|
+
|
|
5
|
+
## Verify the exact package
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm ci
|
|
9
|
+
npx playwright install chromium
|
|
10
|
+
npm run check
|
|
11
|
+
npm run test:package
|
|
12
|
+
npm run format:check
|
|
13
|
+
npm pack --dry-run
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The package test creates a real archive, invokes its CLI through `npm exec` without a registry lookup, scaffolds a project outside the repository, audits it, and starts its JSON viewer. This catches missing files and accidental reliance on a maintainer checkout.
|
|
17
|
+
|
|
18
|
+
## Publish
|
|
19
|
+
|
|
20
|
+
An npm release is an explicit maintainer action. Confirm npm ownership of the unscoped `ketatlas` name and authenticate with an account allowed to publish it. Do not put credentials in repository files.
|
|
21
|
+
|
|
22
|
+
For interactive publishing, [configure two-factor authentication on your npm account](https://docs.npmjs.com/configuring-two-factor-authentication/) and complete the verification requested during publication. A successful `npm whoami` confirms login but does not guarantee that publishing authentication requirements are met. Keep recovery codes and credentials private.
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
npm whoami
|
|
26
|
+
npm publish --access public
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Bump `package.json` and `package-lock.json` together before subsequent releases, record the change in `CHANGELOG.md`, and tag the released commit. A Git push alone does not publish npm. The CI workflow verifies pushes and pull requests; it does not publish automatically.
|
|
30
|
+
|
|
31
|
+
After publishing, verify the registry distribution outside the checkout:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
npx ketatlas@0.1.0 --version
|
|
35
|
+
npx ketatlas@0.1.0 scaffold /tmp/ketatlas-release-check
|
|
36
|
+
npx ketatlas@0.1.0 audit /tmp/ketatlas-release-check/atlas.json --strict
|
|
37
|
+
npx ketatlas@0.1.0 serve /tmp/ketatlas-release-check/atlas.json
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Before publication, use `npx --yes --package=github:ketvietlab/ketatlas ketatlas ...` or a local `.tgz` archive. Pin a commit or released version for reproducible team workflows.
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ketatlas",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Interactive HTML maps for screens, workflows, and user journeys.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/ketvietlab/ketatlas.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/ketvietlab/ketatlas#readme",
|
|
12
|
+
"bugs": "https://github.com/ketvietlab/ketatlas/issues",
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=22"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./src/index.d.ts",
|
|
19
|
+
"import": "./src/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./schema.json": "./schema.json"
|
|
22
|
+
},
|
|
23
|
+
"bin": {
|
|
24
|
+
"ketatlas": "bin/ketatlas.js"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"src",
|
|
28
|
+
"styles",
|
|
29
|
+
"assets",
|
|
30
|
+
"bin",
|
|
31
|
+
"schema.json",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE",
|
|
34
|
+
"NOTICE.md",
|
|
35
|
+
"templates",
|
|
36
|
+
"docs",
|
|
37
|
+
"CHANGELOG.md"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"dev": "node bin/ketatlas.js serve examples/atlas.json --root . --port 4178",
|
|
41
|
+
"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",
|
|
42
|
+
"test": "node --test tests/*.test.js",
|
|
43
|
+
"test:e2e": "node tests/browser.mjs",
|
|
44
|
+
"check": "npm run validate && npm test && npm run test:e2e",
|
|
45
|
+
"format": "prettier --write src styles/ketatlas.css bin scripts docs examples starter templates tests '*.md' '*.json' .github/workflows",
|
|
46
|
+
"format:check": "prettier --check src styles/ketatlas.css bin scripts docs examples starter templates tests '*.md' '*.json' .github/workflows",
|
|
47
|
+
"prepare:assets": "node scripts/prepare-assets.mjs",
|
|
48
|
+
"prepack": "node scripts/verify-assets.mjs",
|
|
49
|
+
"prepare:templates": "node scripts/schema.mjs && node scripts/prepare-templates.mjs",
|
|
50
|
+
"test:package": "node tests/package.mjs"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@fontsource-variable/inter": "5.3.0",
|
|
54
|
+
"ajv": "8.18.0",
|
|
55
|
+
"lucide-static": "1.40.0",
|
|
56
|
+
"playwright": "1.62.1",
|
|
57
|
+
"prettier": "3.6.2"
|
|
58
|
+
}
|
|
59
|
+
}
|