ketatlas 0.1.1 → 0.2.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 +7 -0
- package/README.md +9 -5
- package/bin/audit.js +8 -0
- package/bin/ketatlas.js +56 -2
- package/bin/progress.js +120 -0
- package/bin/server.js +2 -1
- package/bin/viewer.js +10 -2
- package/docs/architecture.md +5 -1
- package/docs/cli.md +4 -0
- package/docs/configuration.md +4 -0
- package/docs/integration.md +4 -0
- package/docs/progress.md +83 -0
- package/package.json +7 -5
- package/progress.schema.json +130 -0
- package/skills/ketatlas/SKILL.md +6 -0
- package/src/index.d.ts +66 -0
- package/src/index.js +55 -2
- package/src/layout.js +1 -1
- package/src/progress-ui.js +251 -0
- package/src/progress.js +177 -0
- package/src/template.js +1 -1
- package/styles/ketatlas.css +294 -0
- package/templates/basic/README.md +2 -2
- package/templates/basic/progress.schema.json +130 -0
- package/templates/process/README.md +2 -2
- package/templates/process/progress.schema.json +130 -0
- package/templates/web/README.md +2 -2
- package/templates/web/progress.schema.json +130 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
- Track delivery per screen in a separate progress JSON, with acceptance checks, blockers, ownership, task references and PR/test/release evidence.
|
|
6
|
+
- Review all screens with filters and shared map badges; edit progress using the local viewer or revision-checked CLI.
|
|
7
|
+
- Refuse stale writes and unsupported verification claims; keep static hosting read-only and existing version 1 workflows compatible.
|
|
8
|
+
- Audit progress references, provide a public schema/API, and test browser editing, concurrency and packaged installation.
|
|
9
|
+
|
|
3
10
|
## 0.1.1 — 2026-09-04
|
|
4
11
|
|
|
5
12
|
- Publish the tested archive through an absolute file path; support guarded retries after a CI workflow fix.
|
package/README.md
CHANGED
|
@@ -17,21 +17,21 @@ https://github.com/user-attachments/assets/13f24fc8-7b8e-4bda-9e02-364c120ee163
|
|
|
17
17
|
[KetAtlas is available on npm](https://www.npmjs.com/package/ketatlas). Use Node.js 22+ and run it from any directory:
|
|
18
18
|
|
|
19
19
|
```sh
|
|
20
|
-
npx --yes ketatlas@0.
|
|
21
|
-
npx --yes ketatlas@0.
|
|
22
|
-
npx --yes ketatlas@0.
|
|
20
|
+
npx --yes ketatlas@0.2.0 scaffold my-atlas --template web
|
|
21
|
+
npx --yes ketatlas@0.2.0 serve my-atlas/atlas.json
|
|
22
|
+
npx --yes ketatlas@0.2.0 audit my-atlas/atlas.json --strict
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
Or install the CLI once for your user account:
|
|
26
26
|
|
|
27
27
|
```sh
|
|
28
|
-
npm install --global ketatlas@0.
|
|
28
|
+
npm install --global ketatlas@0.2.0
|
|
29
29
|
ketatlas scaffold my-atlas
|
|
30
30
|
ketatlas serve my-atlas/atlas.json
|
|
31
31
|
ketatlas audit my-atlas/atlas.json --strict
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
The consumer folder contains `atlas.json`, its schema, product HTML/CSS/JavaScript, local assets, and documentation. It needs no `package.json`, lockfile, `node_modules`, build step, or copy of the viewer. The CLI supplies scaffold, serving, and static audit from its own installation. `
|
|
34
|
+
The consumer folder contains `atlas.json`, its schema, product HTML/CSS/JavaScript, local assets, and documentation. It needs no `package.json`, lockfile, `node_modules`, build step, or copy of the viewer. The CLI supplies scaffold, 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.
|
|
35
35
|
|
|
36
36
|
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.
|
|
37
37
|
|
|
@@ -75,6 +75,10 @@ See [Agent mockups](docs/agent-mockups.md) for installation options, a ready-to-
|
|
|
75
75
|
|
|
76
76
|
URLs resolve relative to the JSON file. Nodes default to a left-to-right row. Set `column` and `row` for branches; set each screen's `viewport` for mobile or desktop dimensions. Reuse a screen in many flows without duplicating its HTML.
|
|
77
77
|
|
|
78
|
+
## Track delivery
|
|
79
|
+
|
|
80
|
+
Open **Screens** to filter screen progress, review blockers and edit acceptance checks with PR/test evidence. Records live in a sibling `atlas.progress.json` tracked in Git; repeated nodes share one screen record. A merged PR is not a verified screen. Local saves detect concurrent edits; static hosting is read-only. See [Screen progress](docs/progress.md) for the contract, CLI and embedding API.
|
|
81
|
+
|
|
78
82
|
## Commands
|
|
79
83
|
|
|
80
84
|
| Command | Purpose |
|
package/bin/audit.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readProgress, progressPath } from "./progress.js";
|
|
1
2
|
import { readFile, realpath, stat } from "node:fs/promises";
|
|
2
3
|
import { resolve, dirname, relative, sep, extname } from "node:path";
|
|
3
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -93,6 +94,13 @@ export async function auditAtlas(file, { root: rootOption } = {}) {
|
|
|
93
94
|
for (const f of config.flows)
|
|
94
95
|
for (const n of f.nodes) if (n.url) await inspect(n.url, absolute, `node:${f.id}/${n.id}`);
|
|
95
96
|
}
|
|
97
|
+
if (result.valid) {
|
|
98
|
+
try {
|
|
99
|
+
await readProgress(progressPath(absolute), config);
|
|
100
|
+
} catch (e) {
|
|
101
|
+
errors.push({ path: "progress", message: e.message });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
96
104
|
for (const url of remote)
|
|
97
105
|
warnings.push({ path: "remote", message: `Remote URL not fetched: ${url}` });
|
|
98
106
|
const flows = Array.isArray(config?.flows) ? config.flows : [];
|
package/bin/ketatlas.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { progressPath, readProgress, saveProgress } from "./progress.js";
|
|
3
|
+
import { summarizeProgress } from "../src/progress.js";
|
|
2
4
|
import { readFile, readdir, mkdir, cp, writeFile, stat } from "node:fs/promises";
|
|
3
5
|
import { resolve, dirname, join } from "node:path";
|
|
4
6
|
import { fileURLToPath } from "node:url";
|
|
@@ -13,6 +15,8 @@ const help = `KetAtlas — scaffold, serve, and audit HTML workflow maps
|
|
|
13
15
|
ketatlas serve <atlas.json> [--port 4178] [--root directory]
|
|
14
16
|
ketatlas audit <atlas.json> [--root directory] [--json] [--strict]
|
|
15
17
|
ketatlas validate <atlas.json>
|
|
18
|
+
ketatlas progress <atlas.json> [--json] [--init]
|
|
19
|
+
ketatlas progress <atlas.json> --set <screen-id> --record <record.json> --expect <revision>
|
|
16
20
|
ketatlas --version
|
|
17
21
|
|
|
18
22
|
Examples:
|
|
@@ -118,8 +122,58 @@ async function main(args) {
|
|
|
118
122
|
);
|
|
119
123
|
return;
|
|
120
124
|
}
|
|
125
|
+
if (command === "progress") {
|
|
126
|
+
const { target, options } = parse(rest, {
|
|
127
|
+
json: "boolean",
|
|
128
|
+
init: "boolean",
|
|
129
|
+
set: "string",
|
|
130
|
+
record: "string",
|
|
131
|
+
expect: "string",
|
|
132
|
+
});
|
|
133
|
+
const file = resolve(target),
|
|
134
|
+
atlas = JSON.parse(await readFile(file, "utf8"));
|
|
135
|
+
const valid = validateAtlas(atlas);
|
|
136
|
+
if (!valid.valid) throw new Error(valid.errors.map((e) => e.message).join("; "));
|
|
137
|
+
const path = progressPath(file);
|
|
138
|
+
let result = await readProgress(path, atlas);
|
|
139
|
+
if (options.init) {
|
|
140
|
+
if (options.set || options.record || options.expect)
|
|
141
|
+
throw new Error("Use --init separately from an update.");
|
|
142
|
+
if (result.revision !== "missing") throw new Error("Progress already exists.");
|
|
143
|
+
result = await saveProgress(path, file, "missing", (data) => {
|
|
144
|
+
for (const s of atlas.screens || [])
|
|
145
|
+
Object.defineProperty(data.screens, s.id, {
|
|
146
|
+
value: { status: "unassessed" },
|
|
147
|
+
enumerable: true,
|
|
148
|
+
});
|
|
149
|
+
return data;
|
|
150
|
+
});
|
|
151
|
+
} else if (options.set || options.record || options.expect) {
|
|
152
|
+
if (!options.set || !options.record || !options.expect)
|
|
153
|
+
throw new Error("Updates need --set, --record and --expect from progress --json.");
|
|
154
|
+
const record = JSON.parse(await readFile(resolve(options.record), "utf8"));
|
|
155
|
+
result = await saveProgress(path, file, options.expect, (data) => {
|
|
156
|
+
Object.defineProperty(data.screens, options.set, {
|
|
157
|
+
value: { ...record, updatedAt: new Date().toISOString() },
|
|
158
|
+
enumerable: true,
|
|
159
|
+
});
|
|
160
|
+
return data;
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
const summary = summarizeProgress(result.data, atlas.screens || []);
|
|
164
|
+
console.log(
|
|
165
|
+
options.json
|
|
166
|
+
? JSON.stringify({ ...result, summary }, null, 2)
|
|
167
|
+
: `${summary.total} screens · ${summary.counts.verified} verified · ${summary.blocked} blocked\nRevision: ${result.revision}\n${path}`,
|
|
168
|
+
);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
121
171
|
if (command === "serve") {
|
|
122
|
-
const { target, options } = parse(rest, {
|
|
172
|
+
const { target, options } = parse(rest, {
|
|
173
|
+
port: "string",
|
|
174
|
+
root: "string",
|
|
175
|
+
"read-only": "boolean",
|
|
176
|
+
}),
|
|
123
177
|
port = Number(options.port || 4178);
|
|
124
178
|
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
125
179
|
throw new Error("Port must be 1–65535.");
|
|
@@ -128,7 +182,7 @@ async function main(args) {
|
|
|
128
182
|
throw new Error("--root is only needed when serving an atlas JSON file.");
|
|
129
183
|
const server = directory
|
|
130
184
|
? await serve(target, { port })
|
|
131
|
-
: await serveAtlas(target, { port, root: options.root });
|
|
185
|
+
: await serveAtlas(target, { port, root: options.root, readOnly: options["read-only"] });
|
|
132
186
|
console.log(
|
|
133
187
|
`KetAtlas: http://127.0.0.1:${server.address().port}\nServing ${resolve(target)}\nPress Ctrl+C to stop.`,
|
|
134
188
|
);
|
package/bin/progress.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { readFile, writeFile, rename, unlink, open, lstat } from "node:fs/promises";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { emptyProgress, requireProgress } from "../src/progress.js";
|
|
5
|
+
export const progressPath = (file) => file.replace(/\.json$/i, "") + ".progress.json";
|
|
6
|
+
const hash = (text) => createHash("sha256").update(text).digest("hex");
|
|
7
|
+
export async function readProgress(file, atlas) {
|
|
8
|
+
try {
|
|
9
|
+
if ((await lstat(file)).isSymbolicLink())
|
|
10
|
+
throw new Error("Progress file must not be a symlink.");
|
|
11
|
+
const text = await readFile(file, "utf8");
|
|
12
|
+
return { data: requireProgress(JSON.parse(text), atlas), revision: hash(text) };
|
|
13
|
+
} catch (e) {
|
|
14
|
+
if (e.code !== "ENOENT") throw e;
|
|
15
|
+
return { data: emptyProgress(), revision: "missing" };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** Shared by the CLI and local editor. One lock and content revision prevent stale writes. */
|
|
19
|
+
export async function saveProgress(file, atlasFile, expected, change) {
|
|
20
|
+
file = resolve(file);
|
|
21
|
+
let lock;
|
|
22
|
+
try {
|
|
23
|
+
lock = await open(file + ".lock", "wx");
|
|
24
|
+
} catch (e) {
|
|
25
|
+
if (e.code === "EEXIST")
|
|
26
|
+
throw Object.assign(new Error("Another writer is saving. Reload and retry."), {
|
|
27
|
+
status: 409,
|
|
28
|
+
});
|
|
29
|
+
throw e;
|
|
30
|
+
}
|
|
31
|
+
const temp = file + "." + randomUUID() + ".tmp";
|
|
32
|
+
try {
|
|
33
|
+
const atlas = JSON.parse(await readFile(atlasFile, "utf8"));
|
|
34
|
+
const current = await readProgress(file, atlas);
|
|
35
|
+
if (!expected || expected !== current.revision)
|
|
36
|
+
throw Object.assign(
|
|
37
|
+
new Error(
|
|
38
|
+
"Progress changed since it was loaded. Reload before saving; your draft is still visible.",
|
|
39
|
+
),
|
|
40
|
+
{ status: 409 },
|
|
41
|
+
);
|
|
42
|
+
const data = requireProgress(change(structuredClone(current.data)), atlas);
|
|
43
|
+
const text = JSON.stringify(data, null, 2) + "\n";
|
|
44
|
+
await writeFile(temp, text, { flag: "wx" });
|
|
45
|
+
if ((await readProgress(file, atlas)).revision !== current.revision)
|
|
46
|
+
throw Object.assign(new Error("Progress changed during saving. Reload and retry."), {
|
|
47
|
+
status: 409,
|
|
48
|
+
});
|
|
49
|
+
await rename(temp, file);
|
|
50
|
+
return { data, revision: hash(text) };
|
|
51
|
+
} finally {
|
|
52
|
+
await unlink(temp).catch(() => {});
|
|
53
|
+
await lock.close();
|
|
54
|
+
await unlink(file + ".lock");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export function progressHandler(file, atlasFile, token, editable) {
|
|
58
|
+
const path = progressPath(file);
|
|
59
|
+
return async (req, res) => {
|
|
60
|
+
if (new URL(req.url, "http://localhost").pathname !== "/__ketatlas__/progress") return false;
|
|
61
|
+
const reply = (status, data) => {
|
|
62
|
+
res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
63
|
+
res.end(JSON.stringify(data));
|
|
64
|
+
};
|
|
65
|
+
try {
|
|
66
|
+
if (req.method === "GET") {
|
|
67
|
+
reply(200, {
|
|
68
|
+
...(await readProgress(path, JSON.parse(await readFile(atlasFile, "utf8")))),
|
|
69
|
+
editable,
|
|
70
|
+
});
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
if (req.method !== "PUT") {
|
|
74
|
+
reply(405, { error: "Method not allowed" });
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
const origin = `http://${req.headers.host}`;
|
|
78
|
+
if (
|
|
79
|
+
!editable ||
|
|
80
|
+
req.headers.origin !== origin ||
|
|
81
|
+
req.headers["x-ketatlas-token"] !== token ||
|
|
82
|
+
!req.headers["content-type"]?.startsWith("application/json")
|
|
83
|
+
) {
|
|
84
|
+
reply(403, { error: "Progress writes require the local editor." });
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
let text = "",
|
|
88
|
+
size = 0;
|
|
89
|
+
for await (const chunk of req) {
|
|
90
|
+
size += chunk.length;
|
|
91
|
+
if (size > 1024 * 1024) {
|
|
92
|
+
reply(413, { error: "Progress update is too large." });
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
text += chunk;
|
|
96
|
+
}
|
|
97
|
+
const body = JSON.parse(text);
|
|
98
|
+
if (
|
|
99
|
+
typeof body.screenId !== "string" ||
|
|
100
|
+
!body.record ||
|
|
101
|
+
typeof body.record !== "object" ||
|
|
102
|
+
Array.isArray(body.record)
|
|
103
|
+
)
|
|
104
|
+
throw new Error("Expected a screen ID and record.");
|
|
105
|
+
const result = await saveProgress(path, atlasFile, req.headers["if-match"], (data) => {
|
|
106
|
+
Object.defineProperty(data.screens, body.screenId, {
|
|
107
|
+
value: { ...body.record, updatedAt: new Date().toISOString() },
|
|
108
|
+
enumerable: true,
|
|
109
|
+
writable: true,
|
|
110
|
+
configurable: true,
|
|
111
|
+
});
|
|
112
|
+
return data;
|
|
113
|
+
});
|
|
114
|
+
reply(200, { ...result, editable });
|
|
115
|
+
} catch (e) {
|
|
116
|
+
reply(e.status || 400, { error: e.message });
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
};
|
|
120
|
+
}
|
package/bin/server.js
CHANGED
|
@@ -21,7 +21,7 @@ const within = (root, path) => {
|
|
|
21
21
|
/** Local static preview only. Resolves symlinks before checking the root boundary. */
|
|
22
22
|
export async function serve(
|
|
23
23
|
directory,
|
|
24
|
-
{ port = 4178, host = "127.0.0.1", viewer, packageRoot } = {},
|
|
24
|
+
{ port = 4178, host = "127.0.0.1", viewer, packageRoot, handler } = {},
|
|
25
25
|
) {
|
|
26
26
|
const root = await realpath(resolve(directory));
|
|
27
27
|
if (!(await stat(root)).isDirectory()) throw new Error("Serve root must be a directory.");
|
|
@@ -30,6 +30,7 @@ export async function serve(
|
|
|
30
30
|
res.writeHead(status, { "Content-Type": "text/plain; charset=utf-8" });
|
|
31
31
|
res.end(message);
|
|
32
32
|
};
|
|
33
|
+
if (handler && (await handler(req, res))) return;
|
|
33
34
|
if (!["GET", "HEAD"].includes(req.method)) return fail(405, "Method not allowed");
|
|
34
35
|
try {
|
|
35
36
|
const pathname = decodeURIComponent(new URL(req.url, "http://localhost").pathname);
|
package/bin/viewer.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { progressHandler } from "./progress.js";
|
|
1
3
|
import { readFile, realpath, stat } from "node:fs/promises";
|
|
2
4
|
import { resolve, dirname, relative, sep } from "node:path";
|
|
3
5
|
import { validateAtlas, AtlasValidationError } from "../src/config.js";
|
|
@@ -15,6 +17,12 @@ export async function serveAtlas(file, options = {}) {
|
|
|
15
17
|
if (rel === ".." || rel.startsWith(".." + sep) || rel.startsWith(sep))
|
|
16
18
|
throw new Error("The JSON file must be inside --root.");
|
|
17
19
|
const configURL = "/" + rel.split(sep).map(encodeURIComponent).join("/");
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
+
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
|
+
});
|
|
20
28
|
}
|
package/docs/architecture.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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
|
|
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.
|
|
4
4
|
|
|
5
5
|
```text
|
|
6
6
|
atlas.json + screen HTML
|
|
@@ -61,3 +61,7 @@ The default theme uses Inter and KetJS's canonical tokens and primitives. `prepa
|
|
|
61
61
|
## Version 1 boundaries
|
|
62
62
|
|
|
63
63
|
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.
|
|
64
|
+
|
|
65
|
+
## Delivery tracking
|
|
66
|
+
|
|
67
|
+
`src/progress.js` owns progress validation and unique-screen counts. `src/progress-ui.js` owns the screen register and progress editor. `bin/progress.js` shares a locked, revision-checked atomic writer between CLI and local HTTP updates. Progress does not alter graph layout or infer completion from PR state. See [Screen progress](progress.md).
|
package/docs/cli.md
CHANGED
|
@@ -70,3 +70,7 @@ ketatlas --help
|
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
`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.
|
|
73
|
+
|
|
74
|
+
## Screen progress
|
|
75
|
+
|
|
76
|
+
`progress <atlas.json> --json` reads the progress record, content revision and unique-screen summary. `--init` creates a missing sidecar. `--set <screen-id> --record <record.json> --expect <revision>` replaces one record with conflict detection. `serve <atlas.json> --read-only` disables local progress writes. See [Screen progress](progress.md) for examples and persistence rules.
|
package/docs/configuration.md
CHANGED
|
@@ -63,3 +63,7 @@ A node without `screen` defaults to `note` and requires a title. Use `type: "ext
|
|
|
63
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
64
|
|
|
65
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.
|
|
66
|
+
|
|
67
|
+
## Delivery progress
|
|
68
|
+
|
|
69
|
+
Delivery records use the optional sibling `<atlas-name>.progress.json`, with `progress.schema.json` and semantic validation, rather than extra properties in the version 1 workflow. See [Screen progress](progress.md).
|
package/docs/integration.md
CHANGED
|
@@ -106,3 +106,7 @@ Iframe previews are sandboxed. Scripts and forms work by default, but the page h
|
|
|
106
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
107
|
|
|
108
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.
|
|
109
|
+
|
|
110
|
+
## Progress adapters
|
|
111
|
+
|
|
112
|
+
`createAtlas` accepts `options.progress`. `loadAtlas` discovers a sibling progress JSON or accepts `progressURL`. Both support `progressRevision`, `saveProgress(screenId, record, revision)` and `reloadProgress()`; adapters return `{data, revision}`. No save adapter means read-only. See [Screen progress](progress.md).
|
package/docs/progress.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Screen progress
|
|
2
|
+
|
|
3
|
+
Open **Screens** to review delivery across the project. Counts use unique screen IDs, not repeated workflow nodes. Filter by status, workflow, blocker, or search names, IDs, tasks and owners. **View in map** focuses a screen; **Screen progress** opens its record from the node inspector. Mockup badges remain separate from implementation status.
|
|
4
|
+
|
|
5
|
+
## Status and scope
|
|
6
|
+
|
|
7
|
+
| Status | Meaning |
|
|
8
|
+
| ----------- | --------------------------------------------------------------------------------------------- |
|
|
9
|
+
| Unassessed | Implementation has not been compared with the intended screen scope. |
|
|
10
|
+
| Planned | Scope is known; implementation has not started. |
|
|
11
|
+
| In progress | Some work exists, but required implementation is incomplete. |
|
|
12
|
+
| In review | Required changes are in review. |
|
|
13
|
+
| Implemented | Required code is merged; full acceptance verification is outstanding. |
|
|
14
|
+
| Verified | Every recorded acceptance check has passed test evidence at a named revision and environment. |
|
|
15
|
+
|
|
16
|
+
A blocker is independent of status: record the reason and next action. A merged PR does not automatically change the screen's status. A release or pin is separate evidence; neither proves that an environment was deployed or a user journey passed. Counts do not invent a completion percentage.
|
|
17
|
+
|
|
18
|
+
Acceptance checks have stable IDs, a title, a done flag and evidence references. Completed checks require evidence. Verified additionally requires **each** check to reference a `test` with `state: "passed"`, a nonempty `revision` and `environment`, and no blocker. This is a validation rule for authored claims, not an independent test execution service. Scope the checklist honestly; omitted requirements cannot be verified by the viewer.
|
|
19
|
+
|
|
20
|
+
A check may identify particular states using `nodes: [{ "flowId": "sign-in", "nodeId": "invalid" }]`. Every referenced node must belong to that screen. The editor displays these state references and preserves them; authors and agents manage the references in JSON. Screens reused across flows share one record.
|
|
21
|
+
|
|
22
|
+
## Files and persistence
|
|
23
|
+
|
|
24
|
+
For `atlas.json`, progress lives in **atlas.progress.json** next to it. For `journey.json`, use `journey.progress.json`. Format version 1 of the workflow remains unchanged. Existing viewers still open the workflow JSON. Missing progress means Unassessed and does not create a file merely by viewing the project.
|
|
25
|
+
|
|
26
|
+
`progress.schema.json` ships with the package and templates. `validateProgress` adds semantic checks for references, safe evidence URLs, timestamps and verification requirements. `audit` validates a present sidecar without fetching evidence URLs or executing tests. Git records history; keep progress alongside the atlas in the same project repository.
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"version": 1,
|
|
31
|
+
"screens": {
|
|
32
|
+
"sign-in": {
|
|
33
|
+
"status": "in_progress",
|
|
34
|
+
"owner": "Frontend team",
|
|
35
|
+
"tasks": ["AUTH-12"],
|
|
36
|
+
"summary": "Sign-in is implemented; expired-link recovery still needs work.",
|
|
37
|
+
"checks": [
|
|
38
|
+
{
|
|
39
|
+
"id": "submit",
|
|
40
|
+
"title": "Sign in with valid credentials",
|
|
41
|
+
"done": true,
|
|
42
|
+
"evidenceIds": ["pr-12"]
|
|
43
|
+
},
|
|
44
|
+
{ "id": "recovery", "title": "Recover from an expired link", "done": false }
|
|
45
|
+
],
|
|
46
|
+
"evidence": [
|
|
47
|
+
{
|
|
48
|
+
"id": "pr-12",
|
|
49
|
+
"kind": "pr",
|
|
50
|
+
"title": "Sign-in implementation",
|
|
51
|
+
"url": "https://example.test/pull/12",
|
|
52
|
+
"state": "merged",
|
|
53
|
+
"revision": "abc123"
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Evidence kinds are `pr`, `test`, `release`, `reference`. Required fields: `id`, `kind`, `title`, `url`. Optional fields: `state`, `revision`, `environment`, `observedAt`. Screen records also support `updatedAt`, `blocker`, `owner`, `summary`, `tasks`, `checks`, `evidence`; only `status` is required.
|
|
62
|
+
|
|
63
|
+
## Editing locally and from agents
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
npx --yes ketatlas@0.2.0 serve atlas.json
|
|
67
|
+
npx --yes ketatlas@0.2.0 serve atlas.json --read-only
|
|
68
|
+
npx --yes ketatlas@0.2.0 progress atlas.json --init
|
|
69
|
+
npx --yes ketatlas@0.2.0 progress atlas.json --json
|
|
70
|
+
npx --yes ketatlas@0.2.0 progress atlas.json --set sign-in --record record.json --expect REVISION_FROM_READ
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`--init` explicitly creates Unassessed records and refuses an existing progress file. `--set` replaces one complete screen record; preserve existing checklist IDs, evidence and state references. Read the revision with `--json` first. Do not retry a stale revision by blindly substituting a new one: reload and reconcile changes. `updatedAt` is stamped on successful record writes.
|
|
74
|
+
|
|
75
|
+
The UI and CLI use the same validated, atomic file writer and exclusive lock. Two writers cannot silently overwrite each other. A stale browser save keeps its draft visible; reload the latest record before reconciling. If a process is forcibly killed while holding the lock, inspect that no writer is running before removing the sibling `.lock` file. Direct file edits are supported, but agents should prefer the CLI for coordinated writes.
|
|
76
|
+
|
|
77
|
+
Only the local CLI editor offers save controls. It uses a per-server write token, same-origin JSON requests, bounded request bodies, and a fixed sidecar path. Screen iframes retain their sandbox and cannot use the write capability. `--read-only` refuses writes. Viewing or auditing alone never writes. Static hosting discovers the sidecar and displays it read-only; no database or runtime dependency is needed.
|
|
78
|
+
|
|
79
|
+
## Embedding
|
|
80
|
+
|
|
81
|
+
`createAtlas(container, config, { progress })` accepts in-memory progress. `loadAtlas` discovers the sibling sidecar unless `progress` or `progressURL` is supplied. Missing (404) is optional; invalid JSON, failed requests and malformed records surface errors rather than masquerading as unassessed data.
|
|
82
|
+
|
|
83
|
+
Hosts may provide `progressRevision`, `saveProgress(screenId, record, revision)` and `reloadProgress()` adapters. Both return `{ data, revision }`. The local CLI supplies these through its protected endpoint. Public helpers `validateProgress`, `summarizeProgress` and `progressStatuses` have no DOM dependency.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ketatlas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Interactive HTML maps for screens, workflows, and user journeys.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"types": "./src/index.d.ts",
|
|
19
19
|
"import": "./src/index.js"
|
|
20
20
|
},
|
|
21
|
-
"./schema.json": "./schema.json"
|
|
21
|
+
"./schema.json": "./schema.json",
|
|
22
|
+
"./progress.schema.json": "./progress.schema.json"
|
|
22
23
|
},
|
|
23
24
|
"bin": {
|
|
24
25
|
"ketatlas": "bin/ketatlas.js"
|
|
@@ -35,19 +36,20 @@
|
|
|
35
36
|
"templates",
|
|
36
37
|
"skills",
|
|
37
38
|
"docs",
|
|
38
|
-
"CHANGELOG.md"
|
|
39
|
+
"CHANGELOG.md",
|
|
40
|
+
"progress.schema.json"
|
|
39
41
|
],
|
|
40
42
|
"scripts": {
|
|
41
43
|
"dev": "node bin/ketatlas.js serve examples/atlas.json --root . --port 4178",
|
|
42
44
|
"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",
|
|
43
45
|
"test": "node --test tests/*.test.js",
|
|
44
|
-
"test:e2e": "node tests/browser.mjs",
|
|
46
|
+
"test:e2e": "node tests/browser.mjs && node tests/progress-browser.mjs",
|
|
45
47
|
"check": "npm run validate && npm test && npm run test:e2e",
|
|
46
48
|
"format": "prettier --write src styles/ketatlas.css bin scripts docs examples starter templates skills tests '*.md' '*.json' .github/workflows",
|
|
47
49
|
"format:check": "prettier --check src styles/ketatlas.css bin scripts docs examples starter templates skills tests '*.md' '*.json' .github/workflows",
|
|
48
50
|
"prepare:assets": "node scripts/prepare-assets.mjs",
|
|
49
51
|
"prepack": "node scripts/verify-assets.mjs",
|
|
50
|
-
"prepare:templates": "node scripts/schema.mjs && node scripts/prepare-templates.mjs",
|
|
52
|
+
"prepare:templates": "node scripts/schema.mjs && node scripts/progress-schema.mjs && node scripts/prepare-templates.mjs",
|
|
51
53
|
"test:package": "node tests/package.mjs"
|
|
52
54
|
},
|
|
53
55
|
"devDependencies": {
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "KetAtlas screen progress",
|
|
4
|
+
"description": "Semantic validation also checks screen/node IDs, evidence references and verification requirements.",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"version": {
|
|
8
|
+
"const": 1
|
|
9
|
+
},
|
|
10
|
+
"screens": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"additionalProperties": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"properties": {
|
|
15
|
+
"status": {
|
|
16
|
+
"enum": ["unassessed", "planned", "in_progress", "in_review", "implemented", "verified"]
|
|
17
|
+
},
|
|
18
|
+
"owner": {
|
|
19
|
+
"type": "string"
|
|
20
|
+
},
|
|
21
|
+
"summary": {
|
|
22
|
+
"type": "string"
|
|
23
|
+
},
|
|
24
|
+
"blocker": {
|
|
25
|
+
"type": "string"
|
|
26
|
+
},
|
|
27
|
+
"updatedAt": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"pattern": "\\S"
|
|
30
|
+
},
|
|
31
|
+
"tasks": {
|
|
32
|
+
"type": "array",
|
|
33
|
+
"items": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"pattern": "\\S"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"checks": {
|
|
39
|
+
"type": "array",
|
|
40
|
+
"items": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"properties": {
|
|
43
|
+
"id": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"pattern": "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"
|
|
46
|
+
},
|
|
47
|
+
"title": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"pattern": "\\S"
|
|
50
|
+
},
|
|
51
|
+
"done": {
|
|
52
|
+
"type": "boolean"
|
|
53
|
+
},
|
|
54
|
+
"evidenceIds": {
|
|
55
|
+
"type": "array",
|
|
56
|
+
"items": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"pattern": "\\S"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"nodes": {
|
|
62
|
+
"type": "array",
|
|
63
|
+
"items": {
|
|
64
|
+
"type": "object",
|
|
65
|
+
"properties": {
|
|
66
|
+
"flowId": {
|
|
67
|
+
"type": "string",
|
|
68
|
+
"pattern": "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"
|
|
69
|
+
},
|
|
70
|
+
"nodeId": {
|
|
71
|
+
"type": "string",
|
|
72
|
+
"pattern": "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"required": ["flowId", "nodeId"],
|
|
76
|
+
"additionalProperties": false
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"required": ["id", "title", "done"],
|
|
81
|
+
"additionalProperties": false
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"evidence": {
|
|
85
|
+
"type": "array",
|
|
86
|
+
"items": {
|
|
87
|
+
"type": "object",
|
|
88
|
+
"properties": {
|
|
89
|
+
"id": {
|
|
90
|
+
"type": "string",
|
|
91
|
+
"pattern": "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"
|
|
92
|
+
},
|
|
93
|
+
"kind": {
|
|
94
|
+
"enum": ["pr", "test", "release", "reference"]
|
|
95
|
+
},
|
|
96
|
+
"title": {
|
|
97
|
+
"type": "string",
|
|
98
|
+
"pattern": "\\S"
|
|
99
|
+
},
|
|
100
|
+
"url": {
|
|
101
|
+
"type": "string",
|
|
102
|
+
"pattern": "\\S"
|
|
103
|
+
},
|
|
104
|
+
"state": {
|
|
105
|
+
"type": "string"
|
|
106
|
+
},
|
|
107
|
+
"revision": {
|
|
108
|
+
"type": "string"
|
|
109
|
+
},
|
|
110
|
+
"environment": {
|
|
111
|
+
"type": "string"
|
|
112
|
+
},
|
|
113
|
+
"observedAt": {
|
|
114
|
+
"type": "string",
|
|
115
|
+
"pattern": "\\S"
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
"required": ["id", "kind", "title", "url"],
|
|
119
|
+
"additionalProperties": false
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"required": ["status"],
|
|
124
|
+
"additionalProperties": false
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
"required": ["version", "screens"],
|
|
129
|
+
"additionalProperties": false
|
|
130
|
+
}
|
package/skills/ketatlas/SKILL.md
CHANGED
|
@@ -135,3 +135,9 @@ For local projects, fix structural errors, missing assets, and unreachable nodes
|
|
|
135
135
|
When browser automation is available, inspect the map and actual HTML at the declared viewport. Exercise each requested flow's primary route and relevant recovery route, and check for console errors and accidental clipping. Audit alone does not prove interaction, appearance, or complete coverage. If browser verification is unavailable, state that limit and still deliver the auditable files.
|
|
136
136
|
|
|
137
137
|
Update the project's README with flow-to-screen/state coverage, assumptions, demo inputs, and run commands. Return the output location, the serve command or live preview URL, audit results, and any remaining limitations. Do not claim that a mockup implements production APIs or native application behavior.
|
|
138
|
+
|
|
139
|
+
## Maintain screen delivery progress
|
|
140
|
+
|
|
141
|
+
When implementing or reviewing screens, track progress in the sibling `<atlas-name>.progress.json`. Use KetAtlas 0.2.0 or newer. Keep version 1 workflow JSON unchanged. Read `ketatlas progress atlas.json --json`, preserve existing evidence/check IDs and state references, then update one record with `--set <screen-id> --record <record.json> --expect <revision>`. Re-read and reconcile conflicts instead of overwriting another writer. Run `audit` after updates.
|
|
142
|
+
|
|
143
|
+
Map task scope to screen IDs before assigning progress. Start unknown coverage at `unassessed`; a mockup is not implementation evidence. Use `planned`, `in_progress`, `in_review`, `implemented`, `verified` with a separate blocker reason/next action. Link PRs and exact merge commits, pin/release evidence, and test evidence at the revision/environment actually checked. Do not infer deployment, full screen coverage or verification from a PR merge or green aggregate CI. Completed acceptance checks need evidence; `verified` requires passed test evidence with revision and environment for every recorded check and no blocker. Track error/recovery states using check `nodes` references. Leave unrelated or unreviewed product surfaces unassessed and say why. See `docs/progress.md` in the package for the complete contract.
|