ketatlas 0.2.0 → 0.2.2

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,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.2
4
+
5
+ - Show screen status, blockers, linked PR states and scoped state checks in preview footers, with direct access to progress details.
6
+ - Keep the footer visible when scrolling, refresh it after edits, and support read-only previews.
7
+
8
+ ## 0.2.1
9
+
10
+ - Show compact numeric progress badges after workflow step counts, with status counts, checklist completion and blockers in tooltips.
11
+ - Place project-wide verified/checklist percentages and blockers next to Screens in the top bar.
12
+ - Deduplicate reused screens and variants, exclude process-only steps, and update the sidebar after saves, refreshes and flow navigation.
13
+
3
14
  ## 0.2.0
4
15
 
5
16
  - Track delivery per screen in a separate progress JSON, with acceptance checks, blockers, ownership, task references and PR/test/release evidence.
package/README.md CHANGED
@@ -17,15 +17,15 @@ 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.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
20
+ npx --yes ketatlas@0.2.2 scaffold my-atlas --template web
21
+ npx --yes ketatlas@0.2.2 serve my-atlas/atlas.json
22
+ npx --yes ketatlas@0.2.2 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.2.0
28
+ npm install --global ketatlas@0.2.2
29
29
  ketatlas scaffold my-atlas
30
30
  ketatlas serve my-atlas/atlas.json
31
31
  ketatlas audit my-atlas/atlas.json --strict
package/docs/progress.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
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
4
 
5
+ Screen and state preview footers show the shared screen status, blockers and linked PRs with their recorded states. **Progress details** opens the same screen record. State-specific acceptance counts appear only when checks explicitly reference the previewed workflow/node; the shared screen status is not a separate claim that a state has been verified. Missing evidence displays **No linked PRs**. The footer stays visible while scrolling and updates after a save.
6
+
7
+ ## Sidebar and project completion
8
+
9
+ Project-wide verified/checklist percentages and blocker counts appear in the top bar beside **Screens**. The sidebar shows one compact numeric badge after each workflow’s step count (`2 steps [30%]`). **Verified %** is the number of Verified screens divided by the total unique screens in that scope. Repeated nodes and error variants count once; note/external nodes do not count. Screenless flows have no badge. Percentages round down so unfinished work cannot appear as 100%.
10
+
11
+ The badge tooltip and accessible label include status counts, blockers and **Checks %**, which counts completed recorded acceptance checks over all recorded checks, with an explicit unscoped-screen count when checklists are missing. This is not an estimate of effort, and 100% of a partial checklist does not make a screen Verified. Saving or refreshing progress updates the sidebar; changing or searching workflows preserves the current progress.
12
+
5
13
  ## Status and scope
6
14
 
7
15
  | Status | Meaning |
@@ -63,11 +71,11 @@ Evidence kinds are `pr`, `test`, `release`, `reference`. Required fields: `id`,
63
71
  ## Editing locally and from agents
64
72
 
65
73
  ```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
74
+ npx --yes ketatlas@0.2.2 serve atlas.json
75
+ npx --yes ketatlas@0.2.2 serve atlas.json --read-only
76
+ npx --yes ketatlas@0.2.2 progress atlas.json --init
77
+ npx --yes ketatlas@0.2.2 progress atlas.json --json
78
+ npx --yes ketatlas@0.2.2 progress atlas.json --set sign-in --record record.json --expect REVISION_FROM_READ
71
79
  ```
72
80
 
73
81
  `--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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ketatlas",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Interactive HTML maps for screens, workflows, and user journeys.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -141,3 +141,5 @@ Update the project's README with flow-to-screen/state coverage, assumptions, dem
141
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
142
 
143
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.
144
+
145
+ Sidebar percentages distinguish Verified screens from completed recorded checks. Do not assign arbitrary weights to intermediate statuses or remove unknown/unscoped screens to inflate completion. Workflow counts deduplicate screen IDs, including variants; process-only flows have no screen percentage.
package/src/index.d.ts CHANGED
@@ -170,3 +170,12 @@ export function loadAtlas(
170
170
  url: string | URL,
171
171
  options?: AtlasOptions,
172
172
  ): Promise<AtlasInstance>;
173
+
174
+ /** Unique-screen delivery and recorded-check completion for one workflow; screenless flows return null percentages. */
175
+ export function summarizeFlowProgress(
176
+ data: AtlasProgress,
177
+ flow: AtlasFlow,
178
+ ): ReturnType<typeof summarizeProgress> & {
179
+ verifiedPercent: number | null;
180
+ checks: { done: number; total: number; unscoped: number; percent: number | null };
181
+ };
package/src/index.js CHANGED
@@ -1,6 +1,11 @@
1
1
  import { mountProgress } from "./progress-ui.js";
2
2
  import { emptyProgress, requireProgress } from "./progress.js";
3
- export { validateProgress, summarizeProgress, progressStatuses } from "./progress.js";
3
+ export {
4
+ validateProgress,
5
+ summarizeProgress,
6
+ summarizeFlowProgress,
7
+ progressStatuses,
8
+ } from "./progress.js";
4
9
  import { normalizeAtlas, safeURL } from "./config.js";
5
10
  import { layoutAtlas, edgePath } from "./layout.js";
6
11
  import { template, escapeHTML as e } from "./template.js";
@@ -117,6 +122,7 @@ export function createAtlas(container, input, options = {}) {
117
122
  world.style.height = height + "px";
118
123
  $("map-total").textContent = `${flows.length} flows · ${screens.size} screens`;
119
124
  const groups = [...new Set(flows.map((f) => f.group))];
125
+ let progressUI;
120
126
  function sidebar() {
121
127
  const q = normalize($("flow-search").value);
122
128
  $("flow-list").innerHTML =
@@ -130,10 +136,11 @@ export function createAtlas(container, input, options = {}) {
130
136
  ).includes(q),
131
137
  );
132
138
  return matches.length
133
- ? `<section><h2 class="flow-group-title">${e(group)}</h2>${matches.map((f) => `<button class="flow-link ${f === current ? "active" : ""}" data-flow="${f.id}" ${f === current ? 'aria-current="true"' : ""}><span class="flow-number">${String(f.index + 1).padStart(2, "0")}</span><span><strong>${e(f.title)}</strong><small>${f.nodes.length} steps${f.edges.some((a) => a.kind === "recovery") ? " · Recovery branch" : ""}</small></span></button>`).join("")}</section>`
139
+ ? `<section><h2 class="flow-group-title">${e(group)}</h2>${matches.map((f) => `<button class="flow-link ${f === current ? "active" : ""}" data-flow="${f.id}" ${f === current ? 'aria-current="true"' : ""}><span class="flow-number">${String(f.index + 1).padStart(2, "0")}</span><span class="flow-link-copy"><strong>${e(f.title)}</strong><small>${f.nodes.length} steps <span class="flow-progress" data-progress-flow="${f.id}"></span></small></span></button>`).join("")}</section>`
134
140
  : "";
135
141
  })
136
142
  .join("") || '<p class="mock-filter-empty">No matching workflows.</p>';
143
+ progressUI?.paintSidebar();
137
144
  }
138
145
  const defs = `<defs>${[
139
146
  ["main", "--kv-accent"],
@@ -372,6 +379,7 @@ export function createAtlas(container, input, options = {}) {
372
379
  return;
373
380
  }
374
381
  previewNode = n;
382
+ progressUI.preview(n.screenId, n.flowId, n.id);
375
383
  $("dialog-screen-id").textContent = n.screenId;
376
384
  $("dialog-title").textContent = n.title;
377
385
  $("dialog-open").href = n.url;
@@ -544,7 +552,7 @@ export function createAtlas(container, input, options = {}) {
544
552
  const n = event.target.closest("[data-node]");
545
553
  if (n && !event.target.closest("button")) openScreen(nodeByKey.get(n.dataset.node));
546
554
  });
547
- const progressUI = mountProgress(root, config, progressData, options, (id) => {
555
+ progressUI = mountProgress(root, config, progressData, options, (id) => {
548
556
  const n = nodes.find((n) => n.screenId === id);
549
557
  if (n) {
550
558
  setCurrent(flowById.get(n.flowId));
@@ -1,5 +1,11 @@
1
1
  import { escapeHTML as e } from "./template.js";
2
- import { progressFor, progressStatuses, summarizeProgress, requireProgress } from "./progress.js";
2
+ import {
3
+ progressFor,
4
+ progressStatuses,
5
+ summarizeProgress,
6
+ summarizeFlowProgress,
7
+ requireProgress,
8
+ } from "./progress.js";
3
9
  import { safeURL } from "./config.js";
4
10
  const optionsHTML = (values, selected) =>
5
11
  Object.entries(values)
@@ -14,7 +20,8 @@ export function mountProgress(root, config, initial, options, selectScreen) {
14
20
  editing,
15
21
  draft,
16
22
  baseline,
17
- saving = false;
23
+ saving = false,
24
+ preview;
18
25
  const $ = (id) => root.getElementById(id);
19
26
  const container = document.createElement("div");
20
27
  container.innerHTML = `
@@ -32,7 +39,61 @@ export function mountProgress(root, config, initial, options, selectScreen) {
32
39
  const badge = (r) =>
33
40
  `<span class="progress-badge" data-status="${e(r.status)}">${e(progressStatuses[r.status])}</span>${r.blocker?.trim() ? '<span class="progress-blocked">Blocked</span>' : ""}`;
34
41
  const record = (id) => progressFor(data, id);
42
+ function paintSidebar() {
43
+ for (const el of root.querySelectorAll("[data-progress-flow], #project-progress")) {
44
+ const project = el.id === "project-progress";
45
+ const flow = project
46
+ ? { nodes: config.screens.map((s) => ({ screen: s.id })) }
47
+ : config.flows.find((f) => f.id === el.dataset.progressFlow);
48
+ const summary = summarizeFlowProgress(data, flow);
49
+ el.hidden = !summary.total;
50
+ if (!summary.total) {
51
+ el.textContent = "";
52
+ el.removeAttribute("title");
53
+ el.removeAttribute("aria-label");
54
+ continue;
55
+ }
56
+ const statuses = Object.entries(progressStatuses)
57
+ .filter(([key]) => summary.counts[key])
58
+ .map(([key, label]) => `${summary.counts[key]} ${label.toLowerCase()}`)
59
+ .join(" · ");
60
+ const checks = summary.checks;
61
+ const details = `${summary.verifiedPercent}% verified · ${summary.counts.verified}/${summary.total} screens. ${statuses}${summary.blocked ? ` · ${summary.blocked} blocked` : ""}. ${checks.total ? `Checks ${checks.percent}% · ${checks.done}/${checks.total}` : "No checks recorded"}${checks.unscoped ? ` · ${checks.unscoped} unscoped` : ""}.`;
62
+ if (project) {
63
+ el.innerHTML = `<span><b>${summary.verifiedPercent}%</b> verified</span><span><b>${checks.percent ?? "—"}${checks.percent === null ? "" : "%"}</b> checks</span>${summary.blocked ? `<span class="project-blocked"><b>${summary.blocked}</b> blocked</span>` : ""}`;
64
+ } else el.textContent = `${summary.verifiedPercent}%`;
65
+ el.title = details;
66
+ el.setAttribute("aria-label", details);
67
+ el.dataset.tone = summary.blocked
68
+ ? "blocked"
69
+ : summary.verifiedPercent === 100
70
+ ? "complete"
71
+ : "pending";
72
+ }
73
+ }
74
+ function paintPreview() {
75
+ if (!preview) return;
76
+ const r = record(preview.screenId);
77
+ const checks = (r.checks || []).filter((c) =>
78
+ c.nodes?.some((n) => n.flowId === preview.flowId && n.nodeId === preview.nodeId),
79
+ );
80
+ const prs = (r.evidence || [])
81
+ .filter((ev) => ev.kind === "pr")
82
+ .map((ev) => {
83
+ const url = safeURL(ev.url, options.baseURL || document.baseURI);
84
+ const parts = new URL(url, document.baseURI).pathname.match(/\/([^/]+)\/pull\/(\d+)\/?$/);
85
+ const title = parts ? `${parts[1]}#${parts[2]}` : ev.title;
86
+ return `<a href="${e(url)}" target="_blank" rel="noopener noreferrer" title="${e(ev.title)}">${e(title)}${ev.state ? ` <span>· ${e(ev.state)}</span>` : ""} ↗</a>`;
87
+ });
88
+ $("dialog-progress").innerHTML =
89
+ `<div class="preview-status"><span>Screen status</span>${badge(r)}${checks.length ? `<span>State checks: ${checks.filter((c) => c.done).length}/${checks.length}</span>` : ""}</div>${r.blocker?.trim() ? `<p class="preview-blocker">${e(r.blocker)}</p>` : ""}<div class="preview-prs" aria-label="Screen pull requests">${prs.length ? prs.join("") : "<span>No linked PRs</span>"}</div>`;
90
+ }
91
+ $("dialog-progress-details").onclick = () => {
92
+ if (preview) edit(preview.screenId);
93
+ };
35
94
  function paint() {
95
+ paintSidebar();
96
+ paintPreview();
36
97
  for (const el of root.querySelectorAll("[data-progress-screen]"))
37
98
  el.innerHTML = badge(record(el.dataset.progressScreen));
38
99
  const summary = summarizeProgress(data, config.screens);
@@ -235,7 +296,7 @@ export function mountProgress(root, config, initial, options, selectScreen) {
235
296
  $("progress-error").textContent = err.message;
236
297
  $("progress-reload-latest").hidden = !options.reloadProgress;
237
298
  } finally {
238
- saving = false;
299
+ ((saving = false), preview);
239
300
  $("progress-save").disabled = false;
240
301
  }
241
302
  };
@@ -243,6 +304,11 @@ export function mountProgress(root, config, initial, options, selectScreen) {
243
304
  return {
244
305
  edit,
245
306
  paint,
307
+ paintSidebar,
308
+ preview(screenId, flowId, nodeId) {
309
+ preview = { screenId, flowId, nodeId };
310
+ paintPreview();
311
+ },
246
312
  open() {
247
313
  paint();
248
314
  $("progress-screens").showModal();
package/src/progress.js CHANGED
@@ -175,3 +175,27 @@ export function summarizeProgress(data, screens) {
175
175
  }
176
176
  return { total: screens.length, counts, blocked };
177
177
  }
178
+
179
+ /** Delivery counts deduplicate reusable screens, including repeated error variants. */
180
+ export function summarizeFlowProgress(data, flow) {
181
+ const ids = [...new Set(flow.nodes.filter((n) => n.screen).map((n) => n.screen))];
182
+ const summary = summarizeProgress(
183
+ data,
184
+ ids.map((id) => ({ id })),
185
+ );
186
+ let done = 0,
187
+ total = 0,
188
+ unscoped = 0;
189
+ for (const id of ids) {
190
+ const checks = progressFor(data, id).checks || [];
191
+ if (!checks.length) unscoped++;
192
+ total += checks.length;
193
+ done += checks.filter((check) => check.done).length;
194
+ }
195
+ return {
196
+ ...summary,
197
+ // Round down so unfinished work can never display as 100%.
198
+ verifiedPercent: ids.length ? Math.floor((summary.counts.verified * 100) / ids.length) : null,
199
+ checks: { done, total, unscoped, percent: total ? Math.floor((done * 100) / total) : null },
200
+ };
201
+ }
package/src/template.js CHANGED
@@ -10,7 +10,7 @@ export function template(config) {
10
10
  <button id="map-menu" class="icon-button" aria-label="Choose a flow" aria-expanded="false" aria-controls="map-sidebar"></button>
11
11
  <div class="map-brand"><span class="brand-mark">K</span><span><strong>${escapeHTML(config.title)}</strong><small>KetAtlas · Workflow atlas</small></span></div>
12
12
  <span id="map-total" class="map-total"></span>
13
- <div class="map-header-actions"><button id="open-progress" data-ui="action" data-variant="secondary">Screens</button><button id="map-help" class="icon-button" aria-label="Help"></button></div>
13
+ <div class="map-header-actions"><div id="project-progress" class="project-progress" role="group" aria-label="Project progress"></div><button id="open-progress" data-ui="action" data-variant="secondary">Screens</button><button id="map-help" class="icon-button" aria-label="Help"></button></div>
14
14
  </header>
15
15
  <div class="map-layout">
16
16
  <aside id="map-sidebar" class="map-sidebar">
@@ -30,7 +30,7 @@ export function template(config) {
30
30
  <div class="map-minimap"><span>Current flow</span><svg id="minimap" viewBox="0 0 168 114" role="img" aria-label="Current viewport position"></svg><button id="minimap-fit">Fit entire flow</button></div>
31
31
  </main>
32
32
  </div>
33
- <dialog id="screen-dialog" class="screen-dialog" aria-labelledby="dialog-title"><header><div><small id="dialog-screen-id"></small><h2 id="dialog-title"></h2></div><button id="close-screen" class="icon-button" aria-label="Close preview"></button></header><div id="dialog-preview"></div><footer><p>Interactive preview</p><a id="dialog-open" data-ui="action" data-variant="secondary" target="_blank" rel="noopener noreferrer">Open in new tab</a></footer></dialog>
33
+ <dialog id="screen-dialog" class="screen-dialog" aria-labelledby="dialog-title"><header><div><small id="dialog-screen-id"></small><h2 id="dialog-title"></h2></div><button id="close-screen" class="icon-button" aria-label="Close preview"></button></header><div id="dialog-preview"></div><footer><div id="dialog-progress" class="preview-progress"></div><div class="preview-actions"><button id="dialog-progress-details" data-ui="action" data-variant="secondary">Progress details</button><a id="dialog-open" data-ui="action" data-variant="secondary" target="_blank" rel="noopener noreferrer">Open in new tab</a></div></footer></dialog>
34
34
  <dialog id="help-dialog" class="help-dialog" aria-labelledby="help-title"><h2 id="help-title">Explore the whole journey</h2><ul>
35
35
  <li>Drag the canvas or a screen card to move around.</li><li>Use + / −, Ctrl/⌘ + scroll, or pinch to zoom.</li><li>Choose a workflow in the sidebar. Fit the flow to see every branch.</li><li>Click a node to follow its outgoing steps. Double-click a screen to interact.</li><li>Use arrow keys to pan, F to fit, 0 to return to the start, and Escape to close.</li>
36
36
  </ul><p>Arrows describe the authored flow. Embedded screens keep their own behavior.</p><button id="close-help" data-ui="action" data-variant="primary">Got it</button></dialog>
@@ -1284,3 +1284,125 @@ a svg {
1284
1284
  color: var(--kv-text-muted);
1285
1285
  font-weight: 400;
1286
1286
  }
1287
+
1288
+ /* Compact verified-screen badges; detailed counts live in the tooltip. */
1289
+ .flow-link-copy {
1290
+ min-width: 0;
1291
+ flex: 1;
1292
+ }
1293
+ .flow-progress {
1294
+ display: inline-flex;
1295
+ align-items: center;
1296
+ justify-content: center;
1297
+ flex: none;
1298
+ min-width: 34px;
1299
+ height: 20px;
1300
+ padding: 0 var(--kv-space-1);
1301
+ border-radius: var(--kv-radius-full);
1302
+ background: var(--kv-surface-hover);
1303
+ color: var(--kv-text-muted);
1304
+ font-size: var(--kv-text-2xs);
1305
+ font-weight: var(--kv-weight-medium);
1306
+ font-variant-numeric: tabular-nums;
1307
+ line-height: 1;
1308
+ letter-spacing: normal;
1309
+ white-space: nowrap;
1310
+ vertical-align: middle;
1311
+ }
1312
+ .flow-progress[hidden] {
1313
+ display: none;
1314
+ }
1315
+ .flow-progress[data-tone="blocked"] {
1316
+ color: var(--kv-warning);
1317
+ }
1318
+ .flow-progress[data-tone="complete"] {
1319
+ color: var(--kv-positive);
1320
+ }
1321
+
1322
+ .project-progress {
1323
+ display: flex;
1324
+ align-items: center;
1325
+ gap: var(--kv-space-3);
1326
+ margin-right: var(--kv-space-2);
1327
+ color: var(--kv-text-muted);
1328
+ font-size: var(--kv-text-xs);
1329
+ font-variant-numeric: tabular-nums;
1330
+ white-space: nowrap;
1331
+ }
1332
+ .project-progress[hidden] {
1333
+ display: none;
1334
+ }
1335
+ .project-progress b {
1336
+ color: var(--kv-text-main);
1337
+ font-weight: var(--kv-weight-semibold);
1338
+ }
1339
+ .project-progress .project-blocked,
1340
+ .project-progress .project-blocked b {
1341
+ color: var(--kv-warning);
1342
+ }
1343
+ @container (max-width: 1000px) {
1344
+ .map-header {
1345
+ height: auto;
1346
+ min-height: 72px;
1347
+ flex-wrap: wrap;
1348
+ gap: var(--kv-space-2);
1349
+ }
1350
+ .map-header-actions {
1351
+ flex-basis: 100%;
1352
+ justify-content: flex-end;
1353
+ }
1354
+ .project-progress {
1355
+ margin-right: auto;
1356
+ gap: var(--kv-space-2);
1357
+ font-size: var(--kv-text-2xs);
1358
+ }
1359
+ }
1360
+
1361
+ /* Keep delivery context visible while scrolling a full-size screen preview. */
1362
+ .screen-dialog footer {
1363
+ position: sticky;
1364
+ bottom: 0;
1365
+ z-index: 1;
1366
+ background: var(--kv-panel-bg);
1367
+ flex-wrap: wrap;
1368
+ max-height: 40dvh;
1369
+ overflow: auto;
1370
+ }
1371
+ .preview-progress {
1372
+ flex: 1;
1373
+ min-width: min(240px, 100%);
1374
+ font-size: var(--kv-text-xs);
1375
+ }
1376
+ .preview-status,
1377
+ .preview-prs,
1378
+ .preview-actions {
1379
+ display: flex;
1380
+ flex-wrap: wrap;
1381
+ align-items: center;
1382
+ gap: var(--kv-space-2);
1383
+ }
1384
+ .preview-status {
1385
+ color: var(--kv-text-muted);
1386
+ }
1387
+ .preview-prs {
1388
+ margin-top: var(--kv-space-2);
1389
+ color: var(--kv-text-muted);
1390
+ }
1391
+ .preview-prs a {
1392
+ color: var(--kv-accent);
1393
+ overflow-wrap: anywhere;
1394
+ }
1395
+ .preview-prs a span {
1396
+ color: var(--kv-text-muted);
1397
+ }
1398
+ .screen-dialog .preview-blocker {
1399
+ color: var(--kv-warning);
1400
+ margin-top: var(--kv-space-2);
1401
+ overflow-wrap: anywhere;
1402
+ }
1403
+ .preview-actions {
1404
+ margin-left: auto;
1405
+ }
1406
+ .preview-actions [data-ui] {
1407
+ font-size: var(--kv-text-xs);
1408
+ }
@@ -7,7 +7,7 @@ npx ketatlas serve atlas.json
7
7
  npx ketatlas audit atlas.json --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@0.2.0 and use ketatlas serve atlas.json.
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@0.2.2 and use ketatlas serve atlas.json.
11
11
 
12
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.
13
13
 
@@ -7,7 +7,7 @@ npx ketatlas serve atlas.json
7
7
  npx ketatlas audit atlas.json --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@0.2.0 and use ketatlas serve atlas.json.
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@0.2.2 and use ketatlas serve atlas.json.
11
11
 
12
12
  This template models a process without HTML screens. Add a screen registry and screen nodes when needed.
13
13
 
@@ -7,7 +7,7 @@ npx ketatlas serve atlas.json
7
7
  npx ketatlas audit atlas.json --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@0.2.0 and use ketatlas serve atlas.json.
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@0.2.2 and use ketatlas serve atlas.json.
11
11
 
12
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.
13
13