@caddis/cli 0.4.48 → 0.4.49

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.
Files changed (21) hide show
  1. package/bundles/antigravity-plugin/plugin.json +1 -1
  2. package/bundles/antigravity-plugin/skills/flow-studio/SKILL.md +1 -1
  3. package/bundles/antigravity-plugin/skills/flow-studio/references/design-language.md +5 -5
  4. package/bundles/antigravity-plugin/skills/flow-studio/scripts/lib/styles.test.mjs +13 -4
  5. package/bundles/antigravity-plugin/skills/flow-studio/src/playbackui.ts +1 -1
  6. package/bundles/antigravity-plugin/skills/flow-studio/src/styles.css +1 -1
  7. package/bundles/antigravity-plugin/skills/flow-studio/templates/engine.html +3 -3
  8. package/bundles/antigravity-plugin/skills/flow-studio/templates/flow-studio.css +1 -1
  9. package/bundles/antigravity-plugin/skills/flow-studio/templates/flow-studio.lib.mjs +2 -2
  10. package/bundles/antigravity-plugin/skills/flow-studio/templates/shell-body.html +1 -1
  11. package/bundles/codex/.codex/skills/frontend/flow-studio/SKILL.md +1 -1
  12. package/bundles/codex/.codex/skills/frontend/flow-studio/references/design-language.md +5 -5
  13. package/bundles/codex/.codex/skills/frontend/flow-studio/scripts/lib/styles.test.mjs +13 -4
  14. package/bundles/codex/.codex/skills/frontend/flow-studio/src/playbackui.ts +1 -1
  15. package/bundles/codex/.codex/skills/frontend/flow-studio/src/styles.css +1 -1
  16. package/bundles/codex/.codex/skills/frontend/flow-studio/templates/engine.html +3 -3
  17. package/bundles/codex/.codex/skills/frontend/flow-studio/templates/flow-studio.css +1 -1
  18. package/bundles/codex/.codex/skills/frontend/flow-studio/templates/flow-studio.lib.mjs +2 -2
  19. package/bundles/codex/.codex/skills/frontend/flow-studio/templates/shell-body.html +1 -1
  20. package/bundles/manifest.json +2 -2
  21. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "caddis",
3
- "version": "1.3.134",
3
+ "version": "1.3.135",
4
4
  "description": "caddis — agent-agnostic dev harness as an Antigravity (agy) plugin: TDD skills + commands, single-sourced with the Claude Code plugin."
5
5
  }
@@ -70,7 +70,7 @@ In either route, derive node and link IDs from immutable business keys. Preserve
70
70
  8. **Short titles**: titles fit one line at column width. Long descriptions belong in subtitles or member lists.
71
71
  9. **Fixed node dimensions**: node kinds use fixed box sizes so font rendering does not shift layout.
72
72
  10. **Reduced motion**: removes all animations while preserving static borders and status glows.
73
- 11. **Thin links, dotted canvas**: links are hairlines (about 1.1 px) and the canvas background is faint dots, not a grid. Both come from tokens (`--edge-w`, `--dot`, ...), so an app can change them with one CSS line; see `references/design-language.md`.
73
+ 11. **Thin links, dotted canvas**: links are hairlines (about 0.8 px, with a 6 px arrowhead) and the canvas background is faint dots, not a grid. Both come from tokens (`--edge-w`, `--dot`, ...), so an app can change them with one CSS line; see `references/design-language.md`.
74
74
 
75
75
  ## Model summary
76
76
 
@@ -28,11 +28,11 @@ Tokens are defined on `:root` and adapted for dark mode via `@media (prefers-col
28
28
  | `--dot` | `rgba(31,35,40,.18)` | `rgba(255,255,255,.16)` | Colour of the canvas background dots |
29
29
  | `--dot-size` | `1.2px` | Same | Radius of a background dot |
30
30
  | `--dot-gap` | `24px` | Same | Spacing of the background dots |
31
- | `--edge-w` | `1.1px` | Same | Width of a normal link (thin, like a hairline; motion carries the state) |
32
- | `--edge-w-hot` | `1.5px` | Same | Width of a traced or selected link |
33
- | `--edge-w-play` | `1.6px` | Same | Width of a link while playback runs along it |
34
- | `--trail-w` | `1.8px` | Same | Width of the draw-in trail on a selected path (divided by the zoom) |
35
- | `--ripple-w` | `2px` | Same | Width of the what-if ripple (divided by the zoom) |
31
+ | `--edge-w` | `0.8px` | Same | Width of a normal link (thin, like a hairline; motion carries the state) |
32
+ | `--edge-w-hot` | `1px` | Same | Width of a traced or selected link |
33
+ | `--edge-w-play` | `1.2px` | Same | Width of a link while playback runs along it |
34
+ | `--trail-w` | `1.3px` | Same | Width of the draw-in trail on a selected path (divided by the zoom) |
35
+ | `--ripple-w` | `1.5px` | Same | Width of the what-if ripple (divided by the zoom) |
36
36
  | `--ok-bg` | `rgba(26,127,55,.10)` | `rgba(63,185,80,.12)` | Subtle tinted background for healthy states |
37
37
  | `--warn-bg` | `rgba(154,103,0,.10)` | `rgba(210,153,34,.14)` | Subtle tinted background for warning states |
38
38
  | `--crit-bg` | `rgba(207,34,46,.08)` | `rgba(248,81,73,.12)` | Subtle tinted background for critical states |
@@ -22,14 +22,23 @@ test("dots are set by tokens: size, spacing and a colour for light and dark", ()
22
22
  });
23
23
 
24
24
  test("links are thin by default and every width comes from a token", () => {
25
- assert.match(css, /--edge-w:\s*1\.1px/);
25
+ assert.match(css, /--edge-w:\s*0\.8px/);
26
26
  assert.match(css, /path\.edge \{ fill:none; stroke-width:var\(--edge-w\)/);
27
27
  assert.match(css, /path\.edge\.hot \{[^}]*stroke-width:var\(--edge-w-hot\)/);
28
28
  assert.match(css, /path\.trail \{[^}]*stroke-width:calc\(var\(--trail-w\)/);
29
29
  assert.match(css, /path\.ripple \{[^}]*stroke-width:calc\(var\(--ripple-w\)/);
30
- // Thin means at most 1.5 px for a plain link and 2 px for the highlighted one.
31
- const w = (name) => Number(css.match(new RegExp(`${name}:\s*([0-9.]+)px`))[1]);
32
- assert.ok(w("--edge-w") <= 1.5 && w("--edge-w-hot") <= 2 && w("--ripple-w") <= 2.5 && w("--trail-w") <= 2.5);
30
+ // Thin means a hairline: at most 1 px for a plain link, 1.2 px for a highlighted or playing one.
31
+ const w = (name) => Number(css.match(new RegExp(`${name}: *([0-9.]+)px`))[1]);
32
+ assert.ok(w("--edge-w") <= 1 && w("--edge-w-hot") <= 1.2 && w("--edge-w-play") <= 1.2 && w("--ripple-w") <= 1.5 && w("--trail-w") <= 1.5);
33
+ });
34
+
35
+ test("the arrowhead is small: at most 7 px, and the playback dot at most 5 px in radius", () => {
36
+ const shell = readFileSync(join(root, "templates/shell-body.html"), "utf8");
37
+ const marker = shell.match(/<marker id="arr"[^>]*markerWidth="(\d+)" markerHeight="(\d+)"/);
38
+ assert.ok(marker, "the arrow marker exists");
39
+ assert.ok(Number(marker[1]) <= 7 && Number(marker[2]) <= 7, `arrowhead is ${marker[1]} x ${marker[2]}`);
40
+ const playback = readFileSync(join(root, "src/playbackui.ts"), "utf8");
41
+ assert.ok(Number(playback.match(/class: "token", r: "(\d+(?:\.\d+)?)"/)[1]) <= 5);
33
42
  });
34
43
 
35
44
  test("forced-colours mode drops the dot pattern", () => {
@@ -23,7 +23,7 @@ export function install(ctx: Ctx): PlaybackController {
23
23
  // Only the elements marked in the last draw are cleared again, so a frame does not walk the whole graph.
24
24
  let markedEdges: SVGPathElement[] = [];
25
25
  let markedNodes: HTMLElement[] = [];
26
- const token = sv("circle", { id: "token", class: "token", r: "6" }) as SVGCircleElement;
26
+ const token = sv("circle", { id: "token", class: "token", r: "4" }) as SVGCircleElement;
27
27
  e.layer.appendChild(token);
28
28
  const pathOf = (link: string): SVGPathElement | undefined => ctx.edgeEls[ctx.links.findIndex(l => l.id === link)];
29
29
  const nodeOf = (id: string) => ctx.nodeEls.get(id);
@@ -1,7 +1,7 @@
1
1
  :root {
2
2
  --bg:#f6f8fa; --panel:#ffffff; --panel2:#f6f8fa; --edge:#d0d7de; --rule:#d8dee4; --fg:#1f2328; --muted:#59636e;
3
3
  --accent:#0969da; --accent-solid:#0969da; --ok:#1a7f37; --warn:#9a6700; --crit:#cf222e; --glow:rgba(9,105,218,.22);
4
- --up:#0a7ea4; --down:#8250df; --shadow:0 1px 0 rgba(31,35,40,.04), 0 6px 18px rgba(31,35,40,.08); --grid:rgba(31,35,40,.035); --dot:rgba(31,35,40,.18); --dot-size:1.2px; --dot-gap:24px; --edge-w:1.1px; --edge-w-hot:1.5px; --edge-w-play:1.6px; --trail-w:1.8px; --ripple-w:2px;
4
+ --up:#0a7ea4; --down:#8250df; --shadow:0 1px 0 rgba(31,35,40,.04), 0 6px 18px rgba(31,35,40,.08); --grid:rgba(31,35,40,.035); --dot:rgba(31,35,40,.18); --dot-size:1.2px; --dot-gap:24px; --edge-w:0.8px; --edge-w-hot:1px; --edge-w-play:1.2px; --trail-w:1.3px; --ripple-w:1.5px;
5
5
  --warn-bg:rgba(154,103,0,.10); --ok-bg:rgba(26,127,55,.10); --crit-bg:rgba(207,34,46,.08); --info-bg:rgba(9,105,218,.10);
6
6
  --mono:"IBM Plex Mono", ui-monospace, "Cascadia Mono", Consolas, Menlo, monospace;
7
7
  }
@@ -15,7 +15,7 @@
15
15
  <style>:root {
16
16
  --bg:#f6f8fa; --panel:#ffffff; --panel2:#f6f8fa; --edge:#d0d7de; --rule:#d8dee4; --fg:#1f2328; --muted:#59636e;
17
17
  --accent:#0969da; --accent-solid:#0969da; --ok:#1a7f37; --warn:#9a6700; --crit:#cf222e; --glow:rgba(9,105,218,.22);
18
- --up:#0a7ea4; --down:#8250df; --shadow:0 1px 0 rgba(31,35,40,.04), 0 6px 18px rgba(31,35,40,.08); --grid:rgba(31,35,40,.035); --dot:rgba(31,35,40,.18); --dot-size:1.2px; --dot-gap:24px; --edge-w:1.1px; --edge-w-hot:1.5px; --edge-w-play:1.6px; --trail-w:1.8px; --ripple-w:2px;
18
+ --up:#0a7ea4; --down:#8250df; --shadow:0 1px 0 rgba(31,35,40,.04), 0 6px 18px rgba(31,35,40,.08); --grid:rgba(31,35,40,.035); --dot:rgba(31,35,40,.18); --dot-size:1.2px; --dot-gap:24px; --edge-w:0.8px; --edge-w-hot:1px; --edge-w-play:1.2px; --trail-w:1.3px; --ripple-w:1.5px;
19
19
  --warn-bg:rgba(154,103,0,.10); --ok-bg:rgba(26,127,55,.10); --crit-bg:rgba(207,34,46,.08); --info-bg:rgba(9,105,218,.10);
20
20
  --mono:"IBM Plex Mono", ui-monospace, "Cascadia Mono", Consolas, Menlo, monospace;
21
21
  }
@@ -229,7 +229,7 @@
229
229
  <div class="workspace" id="workspace">
230
230
  <section class="canvaswrap">
231
231
  <div id="viewport" tabindex="0" role="application" aria-label="Flow canvas. Scroll to zoom, drag to pan. Keys: V select, H hand, plus and minus zoom, 0 fit, 1 actual size, F zoom to selection, arrows pan.">
232
- <div id="world"><svg id="edges" aria-hidden="true"><defs><marker id="arr" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="9" markerHeight="9" markerUnits="userSpaceOnUse" orient="auto"><path d="M0 0 L8 4 L0 8 z" fill="context-stroke"/></marker></defs><g id="edgeLayer"></g></svg></div>
232
+ <div id="world"><svg id="edges" aria-hidden="true"><defs><marker id="arr" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="6" markerHeight="6" markerUnits="userSpaceOnUse" orient="auto"><path d="M0 0 L8 4 L0 8 z" fill="context-stroke"/></marker></defs><g id="edgeLayer"></g></svg></div>
233
233
  <div id="cols" aria-hidden="true"></div>
234
234
  <div id="tools" role="toolbar" aria-label="Canvas controls" aria-orientation="vertical"></div>
235
235
  <div id="zoomLabel" aria-hidden="true">100%</div>
@@ -2183,7 +2183,7 @@
2183
2183
  let t = 0, idx = -1, playing = false, frame = 0, timer = 0, stamp = 0, choicesKey = "";
2184
2184
  let markedEdges = [];
2185
2185
  let markedNodes = [];
2186
- const token = sv("circle", { id: "token", class: "token", r: "6" });
2186
+ const token = sv("circle", { id: "token", class: "token", r: "4" });
2187
2187
  e.layer.appendChild(token);
2188
2188
  const pathOf = (link) => ctx.edgeEls[ctx.links.findIndex((l) => l.id === link)];
2189
2189
  const nodeOf = (id) => ctx.nodeEls.get(id);
@@ -1,7 +1,7 @@
1
1
  :root {
2
2
  --bg:#f6f8fa; --panel:#ffffff; --panel2:#f6f8fa; --edge:#d0d7de; --rule:#d8dee4; --fg:#1f2328; --muted:#59636e;
3
3
  --accent:#0969da; --accent-solid:#0969da; --ok:#1a7f37; --warn:#9a6700; --crit:#cf222e; --glow:rgba(9,105,218,.22);
4
- --up:#0a7ea4; --down:#8250df; --shadow:0 1px 0 rgba(31,35,40,.04), 0 6px 18px rgba(31,35,40,.08); --grid:rgba(31,35,40,.035); --dot:rgba(31,35,40,.18); --dot-size:1.2px; --dot-gap:24px; --edge-w:1.1px; --edge-w-hot:1.5px; --edge-w-play:1.6px; --trail-w:1.8px; --ripple-w:2px;
4
+ --up:#0a7ea4; --down:#8250df; --shadow:0 1px 0 rgba(31,35,40,.04), 0 6px 18px rgba(31,35,40,.08); --grid:rgba(31,35,40,.035); --dot:rgba(31,35,40,.18); --dot-size:1.2px; --dot-gap:24px; --edge-w:0.8px; --edge-w-hot:1px; --edge-w-play:1.2px; --trail-w:1.3px; --ripple-w:1.5px;
5
5
  --warn-bg:rgba(154,103,0,.10); --ok-bg:rgba(26,127,55,.10); --crit-bg:rgba(207,34,46,.08); --info-bg:rgba(9,105,218,.10);
6
6
  --mono:"IBM Plex Mono", ui-monospace, "Cascadia Mono", Consolas, Menlo, monospace;
7
7
  }
@@ -1983,7 +1983,7 @@ function install7(ctx) {
1983
1983
  let t = 0, idx = -1, playing = false, frame = 0, timer = 0, stamp = 0, choicesKey = "";
1984
1984
  let markedEdges = [];
1985
1985
  let markedNodes = [];
1986
- const token = sv("circle", { id: "token", class: "token", r: "6" });
1986
+ const token = sv("circle", { id: "token", class: "token", r: "4" });
1987
1987
  e.layer.appendChild(token);
1988
1988
  const pathOf = (link) => ctx.edgeEls[ctx.links.findIndex((l) => l.id === link)];
1989
1989
  const nodeOf = (id) => ctx.nodeEls.get(id);
@@ -2448,7 +2448,7 @@ function mount(root, model, opts = {}) {
2448
2448
  }
2449
2449
 
2450
2450
  // src/lib.ts
2451
- var SHELL_BODY_HTML = "<header class=\"top\">\r\n <span class=\"brand\"><i></i>flow-studio</span>\r\n <span class=\"spacer\"></span>\r\n <button id=\"notesBtn\" type=\"button\" aria-pressed=\"false\">Implementation notes (N)</button>\r\n <button id=\"theme\" type=\"button\" aria-label=\"Toggle light and dark\">☀ / ☾</button>\r\n</header>\r\n<main>\r\n <div class=\"titlerow\"><h1 id=\"title\"></h1><span class=\"readline\" id=\"readat\"></span></div>\r\n <p class=\"help\" id=\"help\"></p>\r\n <div id=\"banner\" role=\"alert\" hidden></div>\r\n\r\n <section class=\"summary\"><div class=\"layerstrip\" id=\"layerstrip\" aria-label=\"Completeness by column\"></div><div class=\"tiles\" id=\"kpis\" aria-label=\"Summary\"></div></section>\r\n\r\n <div class=\"toolbar\">\r\n <div class=\"seggrp\" role=\"tablist\" aria-label=\"View\" id=\"modeSwitch\"></div>\r\n <input id=\"q\" type=\"search\" placeholder=\"Find a group or member… ( / )\" aria-label=\"Find a group or member\">\r\n <select id=\"hops\" aria-label=\"Search radius\"><option value=\"0\">This group</option><option value=\"1\">+1 hop</option><option value=\"2\">+2 hops</option><option value=\"3\">+3 hops</option></select>\r\n <span id=\"matchinfo\" class=\"note\" style=\"margin:0\"></span>\r\n </div>\r\n\r\n <div class=\"workspace\" id=\"workspace\">\r\n <section class=\"canvaswrap\">\r\n <div id=\"viewport\" tabindex=\"0\" role=\"application\" aria-label=\"Flow canvas. Scroll to zoom, drag to pan. Keys: V select, H hand, plus and minus zoom, 0 fit, 1 actual size, F zoom to selection, arrows pan.\">\r\n <div id=\"world\"><svg id=\"edges\" aria-hidden=\"true\"><defs><marker id=\"arr\" viewBox=\"0 0 8 8\" refX=\"7\" refY=\"4\" markerWidth=\"9\" markerHeight=\"9\" markerUnits=\"userSpaceOnUse\" orient=\"auto\"><path d=\"M0 0 L8 4 L0 8 z\" fill=\"context-stroke\"/></marker></defs><g id=\"edgeLayer\"></g></svg></div>\r\n <div id=\"cols\" aria-hidden=\"true\"></div>\r\n <div id=\"tools\" role=\"toolbar\" aria-label=\"Canvas controls\" aria-orientation=\"vertical\"></div>\r\n <div id=\"zoomLabel\" aria-hidden=\"true\">100%</div>\r\n </div>\r\n <div id=\"playbar\" hidden aria-label=\"Scenario playback\">\r\n <select id=\"pbScenario\" aria-label=\"Scenario\"></select>\r\n <button id=\"pbPlay\" type=\"button\" aria-label=\"Play\">▶</button>\r\n <button id=\"pbStep\" type=\"button\" aria-label=\"Step\">Step</button>\r\n <button id=\"pbReset\" type=\"button\" aria-label=\"Reset\">Reset</button>\r\n <select id=\"pbSpeed\" aria-label=\"Speed\"><option value=\"0.5\">0.5x</option><option value=\"1\" selected>1x</option><option value=\"2\">2x</option></select>\r\n <input id=\"pbScrub\" type=\"range\" min=\"0\" max=\"1000\" value=\"0\" aria-label=\"Playback position\" aria-valuetext=\"Step 1 of 1\">\r\n <span id=\"pbCaption\"></span><span id=\"pbCounter\"></span><span id=\"pbChoices\"></span>\r\n </div>\r\n <div class=\"keyline\" id=\"legend\" aria-label=\"Legend\"></div>\r\n <aside class=\"insp\" id=\"inspector\" aria-label=\"Inspector\" aria-hidden=\"true\"></aside>\r\n </section>\r\n </div>\r\n <div id=\"live\" class=\"sr\" role=\"status\" aria-live=\"polite\"></div>\r\n\r\n <div class=\"ledger\" id=\"ledger\"></div>\r\n <details class=\"tableview\"><summary>Table view</summary><table class=\"sources\" id=\"tbl\"></table></details>\r\n <details class=\"spec\" id=\"spec\"><summary>Model reference (for the person or agent who builds this)</summary><div id=\"specbody\"></div></details>\r\n</main>\r\n<footer id=\"footer\"></footer>\r\n<aside id=\"drawer\" aria-label=\"Implementation notes\"><h2>Implementation notes</h2><p class=\"note\">Numbered markers on the page match these cards. Hover a card to find its place; click a marker to jump to its card.</p><div id=\"notelist\"></div></aside>\r\n\r\n";
2451
+ var SHELL_BODY_HTML = "<header class=\"top\">\r\n <span class=\"brand\"><i></i>flow-studio</span>\r\n <span class=\"spacer\"></span>\r\n <button id=\"notesBtn\" type=\"button\" aria-pressed=\"false\">Implementation notes (N)</button>\r\n <button id=\"theme\" type=\"button\" aria-label=\"Toggle light and dark\">☀ / ☾</button>\r\n</header>\r\n<main>\r\n <div class=\"titlerow\"><h1 id=\"title\"></h1><span class=\"readline\" id=\"readat\"></span></div>\r\n <p class=\"help\" id=\"help\"></p>\r\n <div id=\"banner\" role=\"alert\" hidden></div>\r\n\r\n <section class=\"summary\"><div class=\"layerstrip\" id=\"layerstrip\" aria-label=\"Completeness by column\"></div><div class=\"tiles\" id=\"kpis\" aria-label=\"Summary\"></div></section>\r\n\r\n <div class=\"toolbar\">\r\n <div class=\"seggrp\" role=\"tablist\" aria-label=\"View\" id=\"modeSwitch\"></div>\r\n <input id=\"q\" type=\"search\" placeholder=\"Find a group or member… ( / )\" aria-label=\"Find a group or member\">\r\n <select id=\"hops\" aria-label=\"Search radius\"><option value=\"0\">This group</option><option value=\"1\">+1 hop</option><option value=\"2\">+2 hops</option><option value=\"3\">+3 hops</option></select>\r\n <span id=\"matchinfo\" class=\"note\" style=\"margin:0\"></span>\r\n </div>\r\n\r\n <div class=\"workspace\" id=\"workspace\">\r\n <section class=\"canvaswrap\">\r\n <div id=\"viewport\" tabindex=\"0\" role=\"application\" aria-label=\"Flow canvas. Scroll to zoom, drag to pan. Keys: V select, H hand, plus and minus zoom, 0 fit, 1 actual size, F zoom to selection, arrows pan.\">\r\n <div id=\"world\"><svg id=\"edges\" aria-hidden=\"true\"><defs><marker id=\"arr\" viewBox=\"0 0 8 8\" refX=\"7\" refY=\"4\" markerWidth=\"6\" markerHeight=\"6\" markerUnits=\"userSpaceOnUse\" orient=\"auto\"><path d=\"M0 0 L8 4 L0 8 z\" fill=\"context-stroke\"/></marker></defs><g id=\"edgeLayer\"></g></svg></div>\r\n <div id=\"cols\" aria-hidden=\"true\"></div>\r\n <div id=\"tools\" role=\"toolbar\" aria-label=\"Canvas controls\" aria-orientation=\"vertical\"></div>\r\n <div id=\"zoomLabel\" aria-hidden=\"true\">100%</div>\r\n </div>\r\n <div id=\"playbar\" hidden aria-label=\"Scenario playback\">\r\n <select id=\"pbScenario\" aria-label=\"Scenario\"></select>\r\n <button id=\"pbPlay\" type=\"button\" aria-label=\"Play\">▶</button>\r\n <button id=\"pbStep\" type=\"button\" aria-label=\"Step\">Step</button>\r\n <button id=\"pbReset\" type=\"button\" aria-label=\"Reset\">Reset</button>\r\n <select id=\"pbSpeed\" aria-label=\"Speed\"><option value=\"0.5\">0.5x</option><option value=\"1\" selected>1x</option><option value=\"2\">2x</option></select>\r\n <input id=\"pbScrub\" type=\"range\" min=\"0\" max=\"1000\" value=\"0\" aria-label=\"Playback position\" aria-valuetext=\"Step 1 of 1\">\r\n <span id=\"pbCaption\"></span><span id=\"pbCounter\"></span><span id=\"pbChoices\"></span>\r\n </div>\r\n <div class=\"keyline\" id=\"legend\" aria-label=\"Legend\"></div>\r\n <aside class=\"insp\" id=\"inspector\" aria-label=\"Inspector\" aria-hidden=\"true\"></aside>\r\n </section>\r\n </div>\r\n <div id=\"live\" class=\"sr\" role=\"status\" aria-live=\"polite\"></div>\r\n\r\n <div class=\"ledger\" id=\"ledger\"></div>\r\n <details class=\"tableview\"><summary>Table view</summary><table class=\"sources\" id=\"tbl\"></table></details>\r\n <details class=\"spec\" id=\"spec\"><summary>Model reference (for the person or agent who builds this)</summary><div id=\"specbody\"></div></details>\r\n</main>\r\n<footer id=\"footer\"></footer>\r\n<aside id=\"drawer\" aria-label=\"Implementation notes\"><h2>Implementation notes</h2><p class=\"note\">Numbered markers on the page match these cards. Hover a card to find its place; click a marker to jump to its card.</p><div id=\"notelist\"></div></aside>\r\n\r\n";
2452
2452
  export {
2453
2453
  K_MAX,
2454
2454
  K_MIN,
@@ -21,7 +21,7 @@
21
21
  <div class="workspace" id="workspace">
22
22
  <section class="canvaswrap">
23
23
  <div id="viewport" tabindex="0" role="application" aria-label="Flow canvas. Scroll to zoom, drag to pan. Keys: V select, H hand, plus and minus zoom, 0 fit, 1 actual size, F zoom to selection, arrows pan.">
24
- <div id="world"><svg id="edges" aria-hidden="true"><defs><marker id="arr" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="9" markerHeight="9" markerUnits="userSpaceOnUse" orient="auto"><path d="M0 0 L8 4 L0 8 z" fill="context-stroke"/></marker></defs><g id="edgeLayer"></g></svg></div>
24
+ <div id="world"><svg id="edges" aria-hidden="true"><defs><marker id="arr" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="6" markerHeight="6" markerUnits="userSpaceOnUse" orient="auto"><path d="M0 0 L8 4 L0 8 z" fill="context-stroke"/></marker></defs><g id="edgeLayer"></g></svg></div>
25
25
  <div id="cols" aria-hidden="true"></div>
26
26
  <div id="tools" role="toolbar" aria-label="Canvas controls" aria-orientation="vertical"></div>
27
27
  <div id="zoomLabel" aria-hidden="true">100%</div>
@@ -70,7 +70,7 @@ In either route, derive node and link IDs from immutable business keys. Preserve
70
70
  8. **Short titles**: titles fit one line at column width. Long descriptions belong in subtitles or member lists.
71
71
  9. **Fixed node dimensions**: node kinds use fixed box sizes so font rendering does not shift layout.
72
72
  10. **Reduced motion**: removes all animations while preserving static borders and status glows.
73
- 11. **Thin links, dotted canvas**: links are hairlines (about 1.1 px) and the canvas background is faint dots, not a grid. Both come from tokens (`--edge-w`, `--dot`, ...), so an app can change them with one CSS line; see `references/design-language.md`.
73
+ 11. **Thin links, dotted canvas**: links are hairlines (about 0.8 px, with a 6 px arrowhead) and the canvas background is faint dots, not a grid. Both come from tokens (`--edge-w`, `--dot`, ...), so an app can change them with one CSS line; see `references/design-language.md`.
74
74
 
75
75
  ## Model summary
76
76
 
@@ -28,11 +28,11 @@ Tokens are defined on `:root` and adapted for dark mode via `@media (prefers-col
28
28
  | `--dot` | `rgba(31,35,40,.18)` | `rgba(255,255,255,.16)` | Colour of the canvas background dots |
29
29
  | `--dot-size` | `1.2px` | Same | Radius of a background dot |
30
30
  | `--dot-gap` | `24px` | Same | Spacing of the background dots |
31
- | `--edge-w` | `1.1px` | Same | Width of a normal link (thin, like a hairline; motion carries the state) |
32
- | `--edge-w-hot` | `1.5px` | Same | Width of a traced or selected link |
33
- | `--edge-w-play` | `1.6px` | Same | Width of a link while playback runs along it |
34
- | `--trail-w` | `1.8px` | Same | Width of the draw-in trail on a selected path (divided by the zoom) |
35
- | `--ripple-w` | `2px` | Same | Width of the what-if ripple (divided by the zoom) |
31
+ | `--edge-w` | `0.8px` | Same | Width of a normal link (thin, like a hairline; motion carries the state) |
32
+ | `--edge-w-hot` | `1px` | Same | Width of a traced or selected link |
33
+ | `--edge-w-play` | `1.2px` | Same | Width of a link while playback runs along it |
34
+ | `--trail-w` | `1.3px` | Same | Width of the draw-in trail on a selected path (divided by the zoom) |
35
+ | `--ripple-w` | `1.5px` | Same | Width of the what-if ripple (divided by the zoom) |
36
36
  | `--ok-bg` | `rgba(26,127,55,.10)` | `rgba(63,185,80,.12)` | Subtle tinted background for healthy states |
37
37
  | `--warn-bg` | `rgba(154,103,0,.10)` | `rgba(210,153,34,.14)` | Subtle tinted background for warning states |
38
38
  | `--crit-bg` | `rgba(207,34,46,.08)` | `rgba(248,81,73,.12)` | Subtle tinted background for critical states |
@@ -22,14 +22,23 @@ test("dots are set by tokens: size, spacing and a colour for light and dark", ()
22
22
  });
23
23
 
24
24
  test("links are thin by default and every width comes from a token", () => {
25
- assert.match(css, /--edge-w:\s*1\.1px/);
25
+ assert.match(css, /--edge-w:\s*0\.8px/);
26
26
  assert.match(css, /path\.edge \{ fill:none; stroke-width:var\(--edge-w\)/);
27
27
  assert.match(css, /path\.edge\.hot \{[^}]*stroke-width:var\(--edge-w-hot\)/);
28
28
  assert.match(css, /path\.trail \{[^}]*stroke-width:calc\(var\(--trail-w\)/);
29
29
  assert.match(css, /path\.ripple \{[^}]*stroke-width:calc\(var\(--ripple-w\)/);
30
- // Thin means at most 1.5 px for a plain link and 2 px for the highlighted one.
31
- const w = (name) => Number(css.match(new RegExp(`${name}:\s*([0-9.]+)px`))[1]);
32
- assert.ok(w("--edge-w") <= 1.5 && w("--edge-w-hot") <= 2 && w("--ripple-w") <= 2.5 && w("--trail-w") <= 2.5);
30
+ // Thin means a hairline: at most 1 px for a plain link, 1.2 px for a highlighted or playing one.
31
+ const w = (name) => Number(css.match(new RegExp(`${name}: *([0-9.]+)px`))[1]);
32
+ assert.ok(w("--edge-w") <= 1 && w("--edge-w-hot") <= 1.2 && w("--edge-w-play") <= 1.2 && w("--ripple-w") <= 1.5 && w("--trail-w") <= 1.5);
33
+ });
34
+
35
+ test("the arrowhead is small: at most 7 px, and the playback dot at most 5 px in radius", () => {
36
+ const shell = readFileSync(join(root, "templates/shell-body.html"), "utf8");
37
+ const marker = shell.match(/<marker id="arr"[^>]*markerWidth="(\d+)" markerHeight="(\d+)"/);
38
+ assert.ok(marker, "the arrow marker exists");
39
+ assert.ok(Number(marker[1]) <= 7 && Number(marker[2]) <= 7, `arrowhead is ${marker[1]} x ${marker[2]}`);
40
+ const playback = readFileSync(join(root, "src/playbackui.ts"), "utf8");
41
+ assert.ok(Number(playback.match(/class: "token", r: "(\d+(?:\.\d+)?)"/)[1]) <= 5);
33
42
  });
34
43
 
35
44
  test("forced-colours mode drops the dot pattern", () => {
@@ -23,7 +23,7 @@ export function install(ctx: Ctx): PlaybackController {
23
23
  // Only the elements marked in the last draw are cleared again, so a frame does not walk the whole graph.
24
24
  let markedEdges: SVGPathElement[] = [];
25
25
  let markedNodes: HTMLElement[] = [];
26
- const token = sv("circle", { id: "token", class: "token", r: "6" }) as SVGCircleElement;
26
+ const token = sv("circle", { id: "token", class: "token", r: "4" }) as SVGCircleElement;
27
27
  e.layer.appendChild(token);
28
28
  const pathOf = (link: string): SVGPathElement | undefined => ctx.edgeEls[ctx.links.findIndex(l => l.id === link)];
29
29
  const nodeOf = (id: string) => ctx.nodeEls.get(id);
@@ -1,7 +1,7 @@
1
1
  :root {
2
2
  --bg:#f6f8fa; --panel:#ffffff; --panel2:#f6f8fa; --edge:#d0d7de; --rule:#d8dee4; --fg:#1f2328; --muted:#59636e;
3
3
  --accent:#0969da; --accent-solid:#0969da; --ok:#1a7f37; --warn:#9a6700; --crit:#cf222e; --glow:rgba(9,105,218,.22);
4
- --up:#0a7ea4; --down:#8250df; --shadow:0 1px 0 rgba(31,35,40,.04), 0 6px 18px rgba(31,35,40,.08); --grid:rgba(31,35,40,.035); --dot:rgba(31,35,40,.18); --dot-size:1.2px; --dot-gap:24px; --edge-w:1.1px; --edge-w-hot:1.5px; --edge-w-play:1.6px; --trail-w:1.8px; --ripple-w:2px;
4
+ --up:#0a7ea4; --down:#8250df; --shadow:0 1px 0 rgba(31,35,40,.04), 0 6px 18px rgba(31,35,40,.08); --grid:rgba(31,35,40,.035); --dot:rgba(31,35,40,.18); --dot-size:1.2px; --dot-gap:24px; --edge-w:0.8px; --edge-w-hot:1px; --edge-w-play:1.2px; --trail-w:1.3px; --ripple-w:1.5px;
5
5
  --warn-bg:rgba(154,103,0,.10); --ok-bg:rgba(26,127,55,.10); --crit-bg:rgba(207,34,46,.08); --info-bg:rgba(9,105,218,.10);
6
6
  --mono:"IBM Plex Mono", ui-monospace, "Cascadia Mono", Consolas, Menlo, monospace;
7
7
  }
@@ -15,7 +15,7 @@
15
15
  <style>:root {
16
16
  --bg:#f6f8fa; --panel:#ffffff; --panel2:#f6f8fa; --edge:#d0d7de; --rule:#d8dee4; --fg:#1f2328; --muted:#59636e;
17
17
  --accent:#0969da; --accent-solid:#0969da; --ok:#1a7f37; --warn:#9a6700; --crit:#cf222e; --glow:rgba(9,105,218,.22);
18
- --up:#0a7ea4; --down:#8250df; --shadow:0 1px 0 rgba(31,35,40,.04), 0 6px 18px rgba(31,35,40,.08); --grid:rgba(31,35,40,.035); --dot:rgba(31,35,40,.18); --dot-size:1.2px; --dot-gap:24px; --edge-w:1.1px; --edge-w-hot:1.5px; --edge-w-play:1.6px; --trail-w:1.8px; --ripple-w:2px;
18
+ --up:#0a7ea4; --down:#8250df; --shadow:0 1px 0 rgba(31,35,40,.04), 0 6px 18px rgba(31,35,40,.08); --grid:rgba(31,35,40,.035); --dot:rgba(31,35,40,.18); --dot-size:1.2px; --dot-gap:24px; --edge-w:0.8px; --edge-w-hot:1px; --edge-w-play:1.2px; --trail-w:1.3px; --ripple-w:1.5px;
19
19
  --warn-bg:rgba(154,103,0,.10); --ok-bg:rgba(26,127,55,.10); --crit-bg:rgba(207,34,46,.08); --info-bg:rgba(9,105,218,.10);
20
20
  --mono:"IBM Plex Mono", ui-monospace, "Cascadia Mono", Consolas, Menlo, monospace;
21
21
  }
@@ -229,7 +229,7 @@
229
229
  <div class="workspace" id="workspace">
230
230
  <section class="canvaswrap">
231
231
  <div id="viewport" tabindex="0" role="application" aria-label="Flow canvas. Scroll to zoom, drag to pan. Keys: V select, H hand, plus and minus zoom, 0 fit, 1 actual size, F zoom to selection, arrows pan.">
232
- <div id="world"><svg id="edges" aria-hidden="true"><defs><marker id="arr" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="9" markerHeight="9" markerUnits="userSpaceOnUse" orient="auto"><path d="M0 0 L8 4 L0 8 z" fill="context-stroke"/></marker></defs><g id="edgeLayer"></g></svg></div>
232
+ <div id="world"><svg id="edges" aria-hidden="true"><defs><marker id="arr" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="6" markerHeight="6" markerUnits="userSpaceOnUse" orient="auto"><path d="M0 0 L8 4 L0 8 z" fill="context-stroke"/></marker></defs><g id="edgeLayer"></g></svg></div>
233
233
  <div id="cols" aria-hidden="true"></div>
234
234
  <div id="tools" role="toolbar" aria-label="Canvas controls" aria-orientation="vertical"></div>
235
235
  <div id="zoomLabel" aria-hidden="true">100%</div>
@@ -2183,7 +2183,7 @@
2183
2183
  let t = 0, idx = -1, playing = false, frame = 0, timer = 0, stamp = 0, choicesKey = "";
2184
2184
  let markedEdges = [];
2185
2185
  let markedNodes = [];
2186
- const token = sv("circle", { id: "token", class: "token", r: "6" });
2186
+ const token = sv("circle", { id: "token", class: "token", r: "4" });
2187
2187
  e.layer.appendChild(token);
2188
2188
  const pathOf = (link) => ctx.edgeEls[ctx.links.findIndex((l) => l.id === link)];
2189
2189
  const nodeOf = (id) => ctx.nodeEls.get(id);
@@ -1,7 +1,7 @@
1
1
  :root {
2
2
  --bg:#f6f8fa; --panel:#ffffff; --panel2:#f6f8fa; --edge:#d0d7de; --rule:#d8dee4; --fg:#1f2328; --muted:#59636e;
3
3
  --accent:#0969da; --accent-solid:#0969da; --ok:#1a7f37; --warn:#9a6700; --crit:#cf222e; --glow:rgba(9,105,218,.22);
4
- --up:#0a7ea4; --down:#8250df; --shadow:0 1px 0 rgba(31,35,40,.04), 0 6px 18px rgba(31,35,40,.08); --grid:rgba(31,35,40,.035); --dot:rgba(31,35,40,.18); --dot-size:1.2px; --dot-gap:24px; --edge-w:1.1px; --edge-w-hot:1.5px; --edge-w-play:1.6px; --trail-w:1.8px; --ripple-w:2px;
4
+ --up:#0a7ea4; --down:#8250df; --shadow:0 1px 0 rgba(31,35,40,.04), 0 6px 18px rgba(31,35,40,.08); --grid:rgba(31,35,40,.035); --dot:rgba(31,35,40,.18); --dot-size:1.2px; --dot-gap:24px; --edge-w:0.8px; --edge-w-hot:1px; --edge-w-play:1.2px; --trail-w:1.3px; --ripple-w:1.5px;
5
5
  --warn-bg:rgba(154,103,0,.10); --ok-bg:rgba(26,127,55,.10); --crit-bg:rgba(207,34,46,.08); --info-bg:rgba(9,105,218,.10);
6
6
  --mono:"IBM Plex Mono", ui-monospace, "Cascadia Mono", Consolas, Menlo, monospace;
7
7
  }
@@ -1983,7 +1983,7 @@ function install7(ctx) {
1983
1983
  let t = 0, idx = -1, playing = false, frame = 0, timer = 0, stamp = 0, choicesKey = "";
1984
1984
  let markedEdges = [];
1985
1985
  let markedNodes = [];
1986
- const token = sv("circle", { id: "token", class: "token", r: "6" });
1986
+ const token = sv("circle", { id: "token", class: "token", r: "4" });
1987
1987
  e.layer.appendChild(token);
1988
1988
  const pathOf = (link) => ctx.edgeEls[ctx.links.findIndex((l) => l.id === link)];
1989
1989
  const nodeOf = (id) => ctx.nodeEls.get(id);
@@ -2448,7 +2448,7 @@ function mount(root, model, opts = {}) {
2448
2448
  }
2449
2449
 
2450
2450
  // src/lib.ts
2451
- var SHELL_BODY_HTML = "<header class=\"top\">\r\n <span class=\"brand\"><i></i>flow-studio</span>\r\n <span class=\"spacer\"></span>\r\n <button id=\"notesBtn\" type=\"button\" aria-pressed=\"false\">Implementation notes (N)</button>\r\n <button id=\"theme\" type=\"button\" aria-label=\"Toggle light and dark\">☀ / ☾</button>\r\n</header>\r\n<main>\r\n <div class=\"titlerow\"><h1 id=\"title\"></h1><span class=\"readline\" id=\"readat\"></span></div>\r\n <p class=\"help\" id=\"help\"></p>\r\n <div id=\"banner\" role=\"alert\" hidden></div>\r\n\r\n <section class=\"summary\"><div class=\"layerstrip\" id=\"layerstrip\" aria-label=\"Completeness by column\"></div><div class=\"tiles\" id=\"kpis\" aria-label=\"Summary\"></div></section>\r\n\r\n <div class=\"toolbar\">\r\n <div class=\"seggrp\" role=\"tablist\" aria-label=\"View\" id=\"modeSwitch\"></div>\r\n <input id=\"q\" type=\"search\" placeholder=\"Find a group or member… ( / )\" aria-label=\"Find a group or member\">\r\n <select id=\"hops\" aria-label=\"Search radius\"><option value=\"0\">This group</option><option value=\"1\">+1 hop</option><option value=\"2\">+2 hops</option><option value=\"3\">+3 hops</option></select>\r\n <span id=\"matchinfo\" class=\"note\" style=\"margin:0\"></span>\r\n </div>\r\n\r\n <div class=\"workspace\" id=\"workspace\">\r\n <section class=\"canvaswrap\">\r\n <div id=\"viewport\" tabindex=\"0\" role=\"application\" aria-label=\"Flow canvas. Scroll to zoom, drag to pan. Keys: V select, H hand, plus and minus zoom, 0 fit, 1 actual size, F zoom to selection, arrows pan.\">\r\n <div id=\"world\"><svg id=\"edges\" aria-hidden=\"true\"><defs><marker id=\"arr\" viewBox=\"0 0 8 8\" refX=\"7\" refY=\"4\" markerWidth=\"9\" markerHeight=\"9\" markerUnits=\"userSpaceOnUse\" orient=\"auto\"><path d=\"M0 0 L8 4 L0 8 z\" fill=\"context-stroke\"/></marker></defs><g id=\"edgeLayer\"></g></svg></div>\r\n <div id=\"cols\" aria-hidden=\"true\"></div>\r\n <div id=\"tools\" role=\"toolbar\" aria-label=\"Canvas controls\" aria-orientation=\"vertical\"></div>\r\n <div id=\"zoomLabel\" aria-hidden=\"true\">100%</div>\r\n </div>\r\n <div id=\"playbar\" hidden aria-label=\"Scenario playback\">\r\n <select id=\"pbScenario\" aria-label=\"Scenario\"></select>\r\n <button id=\"pbPlay\" type=\"button\" aria-label=\"Play\">▶</button>\r\n <button id=\"pbStep\" type=\"button\" aria-label=\"Step\">Step</button>\r\n <button id=\"pbReset\" type=\"button\" aria-label=\"Reset\">Reset</button>\r\n <select id=\"pbSpeed\" aria-label=\"Speed\"><option value=\"0.5\">0.5x</option><option value=\"1\" selected>1x</option><option value=\"2\">2x</option></select>\r\n <input id=\"pbScrub\" type=\"range\" min=\"0\" max=\"1000\" value=\"0\" aria-label=\"Playback position\" aria-valuetext=\"Step 1 of 1\">\r\n <span id=\"pbCaption\"></span><span id=\"pbCounter\"></span><span id=\"pbChoices\"></span>\r\n </div>\r\n <div class=\"keyline\" id=\"legend\" aria-label=\"Legend\"></div>\r\n <aside class=\"insp\" id=\"inspector\" aria-label=\"Inspector\" aria-hidden=\"true\"></aside>\r\n </section>\r\n </div>\r\n <div id=\"live\" class=\"sr\" role=\"status\" aria-live=\"polite\"></div>\r\n\r\n <div class=\"ledger\" id=\"ledger\"></div>\r\n <details class=\"tableview\"><summary>Table view</summary><table class=\"sources\" id=\"tbl\"></table></details>\r\n <details class=\"spec\" id=\"spec\"><summary>Model reference (for the person or agent who builds this)</summary><div id=\"specbody\"></div></details>\r\n</main>\r\n<footer id=\"footer\"></footer>\r\n<aside id=\"drawer\" aria-label=\"Implementation notes\"><h2>Implementation notes</h2><p class=\"note\">Numbered markers on the page match these cards. Hover a card to find its place; click a marker to jump to its card.</p><div id=\"notelist\"></div></aside>\r\n\r\n";
2451
+ var SHELL_BODY_HTML = "<header class=\"top\">\r\n <span class=\"brand\"><i></i>flow-studio</span>\r\n <span class=\"spacer\"></span>\r\n <button id=\"notesBtn\" type=\"button\" aria-pressed=\"false\">Implementation notes (N)</button>\r\n <button id=\"theme\" type=\"button\" aria-label=\"Toggle light and dark\">☀ / ☾</button>\r\n</header>\r\n<main>\r\n <div class=\"titlerow\"><h1 id=\"title\"></h1><span class=\"readline\" id=\"readat\"></span></div>\r\n <p class=\"help\" id=\"help\"></p>\r\n <div id=\"banner\" role=\"alert\" hidden></div>\r\n\r\n <section class=\"summary\"><div class=\"layerstrip\" id=\"layerstrip\" aria-label=\"Completeness by column\"></div><div class=\"tiles\" id=\"kpis\" aria-label=\"Summary\"></div></section>\r\n\r\n <div class=\"toolbar\">\r\n <div class=\"seggrp\" role=\"tablist\" aria-label=\"View\" id=\"modeSwitch\"></div>\r\n <input id=\"q\" type=\"search\" placeholder=\"Find a group or member… ( / )\" aria-label=\"Find a group or member\">\r\n <select id=\"hops\" aria-label=\"Search radius\"><option value=\"0\">This group</option><option value=\"1\">+1 hop</option><option value=\"2\">+2 hops</option><option value=\"3\">+3 hops</option></select>\r\n <span id=\"matchinfo\" class=\"note\" style=\"margin:0\"></span>\r\n </div>\r\n\r\n <div class=\"workspace\" id=\"workspace\">\r\n <section class=\"canvaswrap\">\r\n <div id=\"viewport\" tabindex=\"0\" role=\"application\" aria-label=\"Flow canvas. Scroll to zoom, drag to pan. Keys: V select, H hand, plus and minus zoom, 0 fit, 1 actual size, F zoom to selection, arrows pan.\">\r\n <div id=\"world\"><svg id=\"edges\" aria-hidden=\"true\"><defs><marker id=\"arr\" viewBox=\"0 0 8 8\" refX=\"7\" refY=\"4\" markerWidth=\"6\" markerHeight=\"6\" markerUnits=\"userSpaceOnUse\" orient=\"auto\"><path d=\"M0 0 L8 4 L0 8 z\" fill=\"context-stroke\"/></marker></defs><g id=\"edgeLayer\"></g></svg></div>\r\n <div id=\"cols\" aria-hidden=\"true\"></div>\r\n <div id=\"tools\" role=\"toolbar\" aria-label=\"Canvas controls\" aria-orientation=\"vertical\"></div>\r\n <div id=\"zoomLabel\" aria-hidden=\"true\">100%</div>\r\n </div>\r\n <div id=\"playbar\" hidden aria-label=\"Scenario playback\">\r\n <select id=\"pbScenario\" aria-label=\"Scenario\"></select>\r\n <button id=\"pbPlay\" type=\"button\" aria-label=\"Play\">▶</button>\r\n <button id=\"pbStep\" type=\"button\" aria-label=\"Step\">Step</button>\r\n <button id=\"pbReset\" type=\"button\" aria-label=\"Reset\">Reset</button>\r\n <select id=\"pbSpeed\" aria-label=\"Speed\"><option value=\"0.5\">0.5x</option><option value=\"1\" selected>1x</option><option value=\"2\">2x</option></select>\r\n <input id=\"pbScrub\" type=\"range\" min=\"0\" max=\"1000\" value=\"0\" aria-label=\"Playback position\" aria-valuetext=\"Step 1 of 1\">\r\n <span id=\"pbCaption\"></span><span id=\"pbCounter\"></span><span id=\"pbChoices\"></span>\r\n </div>\r\n <div class=\"keyline\" id=\"legend\" aria-label=\"Legend\"></div>\r\n <aside class=\"insp\" id=\"inspector\" aria-label=\"Inspector\" aria-hidden=\"true\"></aside>\r\n </section>\r\n </div>\r\n <div id=\"live\" class=\"sr\" role=\"status\" aria-live=\"polite\"></div>\r\n\r\n <div class=\"ledger\" id=\"ledger\"></div>\r\n <details class=\"tableview\"><summary>Table view</summary><table class=\"sources\" id=\"tbl\"></table></details>\r\n <details class=\"spec\" id=\"spec\"><summary>Model reference (for the person or agent who builds this)</summary><div id=\"specbody\"></div></details>\r\n</main>\r\n<footer id=\"footer\"></footer>\r\n<aside id=\"drawer\" aria-label=\"Implementation notes\"><h2>Implementation notes</h2><p class=\"note\">Numbered markers on the page match these cards. Hover a card to find its place; click a marker to jump to its card.</p><div id=\"notelist\"></div></aside>\r\n\r\n";
2452
2452
  export {
2453
2453
  K_MAX,
2454
2454
  K_MIN,
@@ -21,7 +21,7 @@
21
21
  <div class="workspace" id="workspace">
22
22
  <section class="canvaswrap">
23
23
  <div id="viewport" tabindex="0" role="application" aria-label="Flow canvas. Scroll to zoom, drag to pan. Keys: V select, H hand, plus and minus zoom, 0 fit, 1 actual size, F zoom to selection, arrows pan.">
24
- <div id="world"><svg id="edges" aria-hidden="true"><defs><marker id="arr" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="9" markerHeight="9" markerUnits="userSpaceOnUse" orient="auto"><path d="M0 0 L8 4 L0 8 z" fill="context-stroke"/></marker></defs><g id="edgeLayer"></g></svg></div>
24
+ <div id="world"><svg id="edges" aria-hidden="true"><defs><marker id="arr" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="6" markerHeight="6" markerUnits="userSpaceOnUse" orient="auto"><path d="M0 0 L8 4 L0 8 z" fill="context-stroke"/></marker></defs><g id="edgeLayer"></g></svg></div>
25
25
  <div id="cols" aria-hidden="true"></div>
26
26
  <div id="tools" role="toolbar" aria-label="Canvas controls" aria-orientation="vertical"></div>
27
27
  <div id="zoomLabel" aria-hidden="true">100%</div>
@@ -1,7 +1,7 @@
1
1
  {
2
- "poolVersion": "1.3.134",
2
+ "poolVersion": "1.3.135",
3
3
  "bundles": {
4
- "antigravity-plugin": "1.3.134",
4
+ "antigravity-plugin": "1.3.135",
5
5
  "antigravity-plugin-extras": "1.3.41",
6
6
  "codex": "unknown"
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caddis/cli",
3
- "version": "0.4.48",
3
+ "version": "0.4.49",
4
4
  "description": "Install, update and diagnose the caddis dev harness across every coding agent you use (Claude Code, agy/Antigravity, and more).",
5
5
  "keywords": [
6
6
  "caddis",