@caddis/cli 0.4.47 → 0.4.48

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 (27) 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/model.schema.json +1 -1
  4. package/bundles/antigravity-plugin/skills/flow-studio/references/model-schema.md +1 -0
  5. package/bundles/antigravity-plugin/skills/flow-studio/src/chrome.ts +9 -6
  6. package/bundles/antigravity-plugin/skills/flow-studio/src/controls.ts +1 -1
  7. package/bundles/antigravity-plugin/skills/flow-studio/src/model.test.ts +8 -0
  8. package/bundles/antigravity-plugin/skills/flow-studio/src/model.ts +2 -1
  9. package/bundles/antigravity-plugin/skills/flow-studio/src/notes.ts +1 -1
  10. package/bundles/antigravity-plugin/skills/flow-studio/src/rules.test.ts +10 -1
  11. package/bundles/antigravity-plugin/skills/flow-studio/src/rules.ts +6 -0
  12. package/bundles/antigravity-plugin/skills/flow-studio/templates/engine.html +17 -7
  13. package/bundles/antigravity-plugin/skills/flow-studio/templates/flow-studio.lib.mjs +19 -7
  14. package/bundles/codex/.codex/skills/frontend/flow-studio/SKILL.md +1 -1
  15. package/bundles/codex/.codex/skills/frontend/flow-studio/model.schema.json +1 -1
  16. package/bundles/codex/.codex/skills/frontend/flow-studio/references/model-schema.md +1 -0
  17. package/bundles/codex/.codex/skills/frontend/flow-studio/src/chrome.ts +9 -6
  18. package/bundles/codex/.codex/skills/frontend/flow-studio/src/controls.ts +1 -1
  19. package/bundles/codex/.codex/skills/frontend/flow-studio/src/model.test.ts +8 -0
  20. package/bundles/codex/.codex/skills/frontend/flow-studio/src/model.ts +2 -1
  21. package/bundles/codex/.codex/skills/frontend/flow-studio/src/notes.ts +1 -1
  22. package/bundles/codex/.codex/skills/frontend/flow-studio/src/rules.test.ts +10 -1
  23. package/bundles/codex/.codex/skills/frontend/flow-studio/src/rules.ts +6 -0
  24. package/bundles/codex/.codex/skills/frontend/flow-studio/templates/engine.html +17 -7
  25. package/bundles/codex/.codex/skills/frontend/flow-studio/templates/flow-studio.lib.mjs +19 -7
  26. package/bundles/manifest.json +2 -2
  27. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "caddis",
3
- "version": "1.3.133",
3
+ "version": "1.3.134",
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
  }
@@ -76,7 +76,7 @@ In either route, derive node and link IDs from immutable business keys. Preserve
76
76
 
77
77
  ```
78
78
  version 1, meta {title, help, asOf, source, labels?}, layout "columns-lanes" | "free" | "auto"
79
- columns [{id, title, short?, width, phase?}] stages, left to right
79
+ columns [{id, title, short?, width, phase?, continues?}] stages, left to right
80
80
  lanes [{id, title, badge?, badgeTone?, note?, gap?}] rows; gap true = dashed amber lane
81
81
  states {id: {word, tone, border, edge, motion, bucket}} tone: ok|warn|crit|info|muted|accent; bucket: good|neutral|gap
82
82
  nodes [{id, kind, lane?, col?, colSpan?, row?, x?, y?, w?, h?, state, title, subtitle?, members?, weight?,
@@ -26,7 +26,7 @@
26
26
  "type": "object", "required": ["id", "title", "width"], "additionalProperties": false,
27
27
  "properties": {
28
28
  "id": { "type": "string" }, "title": { "type": "string" }, "short": { "type": "string" },
29
- "width": { "type": "number", "minimum": 60 }, "phase": { "type": "string" }
29
+ "width": { "type": "number", "minimum": 60 }, "phase": { "type": "string" }, "continues": { "type": "boolean" }
30
30
  }
31
31
  }
32
32
  },
@@ -48,6 +48,7 @@ Columns provide vertical partitioning for time, stages, or architecture tiers.
48
48
  | `short` | `string` | Optional | - | Compact abbreviated title for tight viewports. |
49
49
  | `width` | `number` | Required | - | Column width in pixels (minimum 60). |
50
50
  | `phase` | `string` | Optional | - | Super-category phase grouping shown in top banner. |
51
+ | `continues` | `boolean` | Optional | `false` | This column is a wrapped continuation of the column before it (for example one long list laid out in three side-by-side columns). It shows no header label and no strip cell of its own: its nodes count into the cell of the column it continues. Not allowed on the first column. |
51
52
 
52
53
  ---
53
54
 
@@ -4,7 +4,7 @@
4
4
  import { el, on, type Ctx } from "./ctx.ts";
5
5
  import { esc } from "./model.ts";
6
6
  import { DASH, toneVar, cssId } from "./stateCss.ts";
7
- import { bucketOf, columnTitle, effectiveLinks, nodeStateId, nodeVisible } from "./rules.ts";
7
+ import { bucketOf, columnTitle, effectiveLinks, nodeStateId, nodeVisible, stripHeads } from "./rules.ts";
8
8
  import { matches } from "./interact.ts";
9
9
 
10
10
  const DEFAULT_LABELS: Record<string, string> = {
@@ -34,16 +34,19 @@ export function install(ctx: Ctx): void {
34
34
 
35
35
  function buildStrip(): void {
36
36
  const box = els.strip;
37
+ // One cell per head column: a column that `continues` another counts into the cell before it.
38
+ const heads = stripHeads(model), cells = model.columns.map((c, i) => ({ c, i })).filter(({ i }) => heads[i] === i);
37
39
  box.style.display = model.columns.length ? "" : "none";
38
- box.style.gridTemplateColumns = `repeat(${Math.max(1, model.columns.length)}, minmax(0, 1fr))`;
40
+ box.style.gridTemplateColumns = `repeat(${Math.max(1, cells.length)}, minmax(0, 1fr))`;
39
41
  if (!box.firstChild) {
40
- box.innerHTML = model.columns.map(c => `<div class="lcell"><div class="ln">${esc(c.title)}</div><div class="lb"><i class="good"></i><i class="neutral"></i><i class="gap"></i></div><div class="lv"></div></div>`).join("");
42
+ box.innerHTML = cells.map(({ c }) => `<div class="lcell"><div class="ln">${esc(c.title)}</div><div class="lb"><i class="good"></i><i class="neutral"></i><i class="gap"></i></div><div class="lv"></div></div>`).join("");
41
43
  }
42
- box.querySelectorAll<HTMLElement>(".lcell").forEach((cell, i) => {
43
- cell.style.setProperty("--i", String(i));
44
+ box.querySelectorAll<HTMLElement>(".lcell").forEach((cell, cellIndex) => {
45
+ const i = cells[cellIndex]?.i ?? cellIndex;
46
+ cell.style.setProperty("--i", String(cellIndex));
44
47
  let tot = 0, good = 0, neutral = 0, gap = 0;
45
48
  for (const n of model.nodes) {
46
- if (n.col !== i || n.kind === "chip") continue;
49
+ if (heads[n.col ?? -1] !== i || n.kind === "chip") continue;
47
50
  if (!visible(n.id)) continue;
48
51
  const w = Math.max(1, n.weight ?? 1);
49
52
  tot += w;
@@ -71,7 +71,7 @@ export function install(ctx: Ctx): void {
71
71
  const cols = els.cols;
72
72
  cols.style.display = model.columns.length ? "" : "none";
73
73
  if (!cols.firstChild) {
74
- model.columns.forEach((c, i) => { const h = el("div", "hd"); h.textContent = (c.short ?? c.title).toUpperCase(); h.title = c.title; h.dataset.i = String(i); cols.appendChild(h); });
74
+ model.columns.forEach((c, i) => { const h = el("div", "hd"); h.textContent = c.continues ? "" : (c.short ?? c.title).toUpperCase(); h.title = c.title; h.dataset.i = String(i); cols.appendChild(h); });
75
75
  let i = 0;
76
76
  while (i < model.columns.length) {
77
77
  const name = model.columns[i]?.phase;
@@ -165,3 +165,11 @@ test("group containment validates ids, ownership, self-reference, and skips grou
165
165
  assert.ok(found.includes("contains-undefined"));
166
166
  assert.ok(found.includes("contains-duplicate"));
167
167
  });
168
+ test("a column may continue the one before it", () => {
169
+ const m = base(); m.columns.push({ id: "c2", title: "B", width: 150, continues: true });
170
+ assert.deepEqual(codes(m), []);
171
+ });
172
+ test("the first column cannot continue a column that does not exist", () => {
173
+ const m = base(); m.columns[0]!.continues = true;
174
+ assert.ok(codes(m).includes("column-continues-first"));
175
+ });
@@ -16,7 +16,7 @@ export interface StateDef {
16
16
  motion: Motion;
17
17
  bucket: Bucket;
18
18
  }
19
- export interface ColumnDef { id: string; title: string; short?: string; width: number; phase?: string }
19
+ export interface ColumnDef { id: string; title: string; short?: string; width: number; phase?: string; continues?: boolean }
20
20
  export interface LaneDef { id: string; title: string; badge?: string; badgeTone?: "ok" | "warn" | "muted"; note?: string; gap?: boolean }
21
21
  export interface NodeDef {
22
22
  id: string;
@@ -111,6 +111,7 @@ export function validateModel(input: unknown): Issue[] {
111
111
  if (!Array.isArray(m.lanes) || (m.layout !== "auto" && m.lanes.length === 0)) add("error", "lanes-missing", "lanes", "At least one lane is required.");
112
112
  if (!m.states || typeof m.states !== "object" || Object.keys(m.states).length === 0) add("error", "states-missing", "states", "At least one state is required.");
113
113
  const columns = Array.isArray(m.columns) ? m.columns : [];
114
+ if (columns[0]?.continues) add("error", "column-continues-first", "columns[0]", "The first column cannot continue a column before it.");
114
115
  const lanes = Array.isArray(m.lanes) ? m.lanes : [];
115
116
  const states = m.states && typeof m.states === "object" ? m.states : {};
116
117
  if (!m.meta || typeof m.meta !== "object" || typeof m.meta.title !== "string") add("error", "meta-missing", "meta", "meta with a title is required.");
@@ -70,7 +70,7 @@ export function install(ctx: Ctx): void {
70
70
  const markdown = (): string => {
71
71
  const out = [`# ${model.meta.title}`, "", model.meta.help, "", `As of ${model.meta.asOf}. Source: ${model.meta.source}`, "", "## States"];
72
72
  for (const [id, s] of Object.entries(model.states)) out.push(`- **${s.word}** (${id}): tone ${s.tone}, border ${s.border}, link ${s.edge}, motion ${s.motion}, bucket ${s.bucket}`);
73
- out.push("", "## Columns", ...model.columns.map((c, i) => `${i + 1}. ${c.title}${c.phase ? ` (${c.phase})` : ""}`));
73
+ out.push("", "## Columns", ...model.columns.filter(c => !c.continues).map((c, i) => `${i + 1}. ${c.title}${c.phase ? ` (${c.phase})` : ""}`));
74
74
  out.push("", "## Lanes", ...model.lanes.map(l => `- ${l.title}${l.badge ? ` [${l.badge}]` : ""}${l.note ? `: ${l.note}` : ""}`));
75
75
  out.push("", `## Size`, `${model.nodes.length} nodes, ${model.links.length} links.`);
76
76
  if (notes.length) out.push("", "## Notes", ...notes.map(n => `${n.id}. **${n.title}**: ${n.text}`));
@@ -1,7 +1,7 @@
1
1
  import { test } from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
  import type { Model } from "./model.ts";
4
- import { baselineView, defaultView, nodeVisible, nodeStateId, linkVisible, linkStateId, isNew, isImproved, bucketOf, canonOf, effectiveLinks, initialCollapsed, columnTitle, toggleGroupState } from "./rules.ts";
4
+ import { baselineView, defaultView, nodeVisible, nodeStateId, linkVisible, linkStateId, isNew, isImproved, bucketOf, canonOf, effectiveLinks, initialCollapsed, columnTitle, toggleGroupState, stripHeads } from "./rules.ts";
5
5
 
6
6
  const m = (): Model => ({
7
7
  version: 1,
@@ -133,3 +133,12 @@ test("toggleGroup selects a visible child on expansion and its group on collapse
133
133
  assert.equal(folded.collapsed.has("group"), true);
134
134
  assert.equal(toggleGroupState(mm, "full", starting, "missing"), null);
135
135
  });
136
+
137
+ test("stripHeads: a continuing column joins the one before it", () => {
138
+ const mm = m();
139
+ mm.columns = [{ id: "a", title: "A", width: 100 }, { id: "p", title: "P", width: 100 }, { id: "p2", title: "P", width: 100, continues: true }, { id: "p3", title: "P", width: 100, continues: true }, { id: "z", title: "Z", width: 100 }];
140
+ assert.deepEqual(stripHeads(mm), [0, 1, 1, 1, 4]);
141
+ });
142
+ test("stripHeads: with no continuing column every column is its own head", () => {
143
+ assert.deepEqual(stripHeads(m()), [0, 1]);
144
+ });
@@ -10,6 +10,12 @@ export const defaultView = (m: Model): string => (m.views?.find(v => v.default)
10
10
  /** A group that contains nodes starts folded unless it explicitly opts out. */
11
11
  export const initialCollapsed = (m: Model): Set<string> => new Set(m.nodes
12
12
  .filter(n => n.kind === "group" && n.contains && n.collapsed !== false).map(n => n.id));
13
+ /** For each column, the index of the column whose header and strip cell it shares: itself, or (when it
14
+ * `continues` another) the nearest earlier column that does not. */
15
+ export const stripHeads = (m: Model): number[] => {
16
+ let head = 0;
17
+ return m.columns.map((c, i) => { if (!c.continues || i === 0) head = i; return head; });
18
+ };
13
19
  export const columnTitle = (m: Model, n: NodeDef): string => n.col === undefined ? "" : m.columns[n.col]?.title ?? "";
14
20
 
15
21
  export function groupFor(m: Model, nodeId: string): NodeDef | undefined {
@@ -286,6 +286,7 @@
286
286
  if (!Array.isArray(m.lanes) || m.layout !== "auto" && m.lanes.length === 0) add("error", "lanes-missing", "lanes", "At least one lane is required.");
287
287
  if (!m.states || typeof m.states !== "object" || Object.keys(m.states).length === 0) add("error", "states-missing", "states", "At least one state is required.");
288
288
  const columns = Array.isArray(m.columns) ? m.columns : [];
289
+ if (columns[0]?.continues) add("error", "column-continues-first", "columns[0]", "The first column cannot continue a column before it.");
289
290
  const lanes = Array.isArray(m.lanes) ? m.lanes : [];
290
291
  const states = m.states && typeof m.states === "object" ? m.states : {};
291
292
  if (!m.meta || typeof m.meta !== "object" || typeof m.meta.title !== "string") add("error", "meta-missing", "meta", "meta with a title is required.");
@@ -591,6 +592,13 @@
591
592
  var baselineView = (m) => m.views?.[0]?.id ?? "all";
592
593
  var defaultView = (m) => (m.views?.find((v) => v.default) ?? m.views?.[0])?.id ?? "all";
593
594
  var initialCollapsed = (m) => new Set(m.nodes.filter((n) => n.kind === "group" && n.contains && n.collapsed !== false).map((n) => n.id));
595
+ var stripHeads = (m) => {
596
+ let head = 0;
597
+ return m.columns.map((c, i) => {
598
+ if (!c.continues || i === 0) head = i;
599
+ return head;
600
+ });
601
+ };
594
602
  var columnTitle = (m, n) => n.col === void 0 ? "" : m.columns[n.col]?.title ?? "";
595
603
  function groupFor(m, nodeId) {
596
604
  return m.nodes.find((n) => n.kind === "group" && n.contains?.includes(nodeId));
@@ -1415,7 +1423,7 @@
1415
1423
  if (!cols.firstChild) {
1416
1424
  model.columns.forEach((c, i2) => {
1417
1425
  const h = el("div", "hd");
1418
- h.textContent = (c.short ?? c.title).toUpperCase();
1426
+ h.textContent = c.continues ? "" : (c.short ?? c.title).toUpperCase();
1419
1427
  h.title = c.title;
1420
1428
  h.dataset.i = String(i2);
1421
1429
  cols.appendChild(h);
@@ -1801,16 +1809,18 @@
1801
1809
  }
1802
1810
  function buildStrip() {
1803
1811
  const box = els.strip;
1812
+ const heads = stripHeads(model), cells = model.columns.map((c, i) => ({ c, i })).filter(({ i }) => heads[i] === i);
1804
1813
  box.style.display = model.columns.length ? "" : "none";
1805
- box.style.gridTemplateColumns = `repeat(${Math.max(1, model.columns.length)}, minmax(0, 1fr))`;
1814
+ box.style.gridTemplateColumns = `repeat(${Math.max(1, cells.length)}, minmax(0, 1fr))`;
1806
1815
  if (!box.firstChild) {
1807
- box.innerHTML = model.columns.map((c) => `<div class="lcell"><div class="ln">${esc(c.title)}</div><div class="lb"><i class="good"></i><i class="neutral"></i><i class="gap"></i></div><div class="lv"></div></div>`).join("");
1816
+ box.innerHTML = cells.map(({ c }) => `<div class="lcell"><div class="ln">${esc(c.title)}</div><div class="lb"><i class="good"></i><i class="neutral"></i><i class="gap"></i></div><div class="lv"></div></div>`).join("");
1808
1817
  }
1809
- box.querySelectorAll(".lcell").forEach((cell, i) => {
1810
- cell.style.setProperty("--i", String(i));
1818
+ box.querySelectorAll(".lcell").forEach((cell, cellIndex) => {
1819
+ const i = cells[cellIndex]?.i ?? cellIndex;
1820
+ cell.style.setProperty("--i", String(cellIndex));
1811
1821
  let tot = 0, good = 0, neutral = 0, gap = 0;
1812
1822
  for (const n of model.nodes) {
1813
- if (n.col !== i || n.kind === "chip") continue;
1823
+ if (heads[n.col ?? -1] !== i || n.kind === "chip") continue;
1814
1824
  if (!visible(n.id)) continue;
1815
1825
  const w = Math.max(1, n.weight ?? 1);
1816
1826
  tot += w;
@@ -2029,7 +2039,7 @@
2029
2039
  const markdown = () => {
2030
2040
  const out = [`# ${model.meta.title}`, "", model.meta.help, "", `As of ${model.meta.asOf}. Source: ${model.meta.source}`, "", "## States"];
2031
2041
  for (const [id, s] of Object.entries(model.states)) out.push(`- **${s.word}** (${id}): tone ${s.tone}, border ${s.border}, link ${s.edge}, motion ${s.motion}, bucket ${s.bucket}`);
2032
- out.push("", "## Columns", ...model.columns.map((c, i) => `${i + 1}. ${c.title}${c.phase ? ` (${c.phase})` : ""}`));
2042
+ out.push("", "## Columns", ...model.columns.filter((c) => !c.continues).map((c, i) => `${i + 1}. ${c.title}${c.phase ? ` (${c.phase})` : ""}`));
2033
2043
  out.push("", "## Lanes", ...model.lanes.map((l) => `- ${l.title}${l.badge ? ` [${l.badge}]` : ""}${l.note ? `: ${l.note}` : ""}`));
2034
2044
  out.push("", `## Size`, `${model.nodes.length} nodes, ${model.links.length} links.`);
2035
2045
  if (notes.length) out.push("", "## Notes", ...notes.map((n) => `${n.id}. **${n.title}**: ${n.text}`));
@@ -29,6 +29,7 @@ function validateModel(input) {
29
29
  if (!Array.isArray(m.lanes) || m.layout !== "auto" && m.lanes.length === 0) add("error", "lanes-missing", "lanes", "At least one lane is required.");
30
30
  if (!m.states || typeof m.states !== "object" || Object.keys(m.states).length === 0) add("error", "states-missing", "states", "At least one state is required.");
31
31
  const columns = Array.isArray(m.columns) ? m.columns : [];
32
+ if (columns[0]?.continues) add("error", "column-continues-first", "columns[0]", "The first column cannot continue a column before it.");
32
33
  const lanes = Array.isArray(m.lanes) ? m.lanes : [];
33
34
  const states = m.states && typeof m.states === "object" ? m.states : {};
34
35
  if (!m.meta || typeof m.meta !== "object" || typeof m.meta.title !== "string") add("error", "meta-missing", "meta", "meta with a title is required.");
@@ -347,11 +348,19 @@ __export(rules_exports, {
347
348
  linkVisible: () => linkVisible,
348
349
  nodeStateId: () => nodeStateId,
349
350
  nodeVisible: () => nodeVisible,
351
+ stripHeads: () => stripHeads,
350
352
  toggleGroupState: () => toggleGroupState
351
353
  });
352
354
  var baselineView = (m) => m.views?.[0]?.id ?? "all";
353
355
  var defaultView = (m) => (m.views?.find((v) => v.default) ?? m.views?.[0])?.id ?? "all";
354
356
  var initialCollapsed = (m) => new Set(m.nodes.filter((n) => n.kind === "group" && n.contains && n.collapsed !== false).map((n) => n.id));
357
+ var stripHeads = (m) => {
358
+ let head = 0;
359
+ return m.columns.map((c, i) => {
360
+ if (!c.continues || i === 0) head = i;
361
+ return head;
362
+ });
363
+ };
355
364
  var columnTitle = (m, n) => n.col === void 0 ? "" : m.columns[n.col]?.title ?? "";
356
365
  function groupFor(m, nodeId) {
357
366
  return m.nodes.find((n) => n.kind === "group" && n.contains?.includes(nodeId));
@@ -1203,7 +1212,7 @@ function install3(ctx) {
1203
1212
  if (!cols.firstChild) {
1204
1213
  model.columns.forEach((c, i2) => {
1205
1214
  const h = el("div", "hd");
1206
- h.textContent = (c.short ?? c.title).toUpperCase();
1215
+ h.textContent = c.continues ? "" : (c.short ?? c.title).toUpperCase();
1207
1216
  h.title = c.title;
1208
1217
  h.dataset.i = String(i2);
1209
1218
  cols.appendChild(h);
@@ -1589,16 +1598,18 @@ function install5(ctx) {
1589
1598
  }
1590
1599
  function buildStrip() {
1591
1600
  const box = els.strip;
1601
+ const heads = stripHeads(model), cells = model.columns.map((c, i) => ({ c, i })).filter(({ i }) => heads[i] === i);
1592
1602
  box.style.display = model.columns.length ? "" : "none";
1593
- box.style.gridTemplateColumns = `repeat(${Math.max(1, model.columns.length)}, minmax(0, 1fr))`;
1603
+ box.style.gridTemplateColumns = `repeat(${Math.max(1, cells.length)}, minmax(0, 1fr))`;
1594
1604
  if (!box.firstChild) {
1595
- box.innerHTML = model.columns.map((c) => `<div class="lcell"><div class="ln">${esc(c.title)}</div><div class="lb"><i class="good"></i><i class="neutral"></i><i class="gap"></i></div><div class="lv"></div></div>`).join("");
1605
+ box.innerHTML = cells.map(({ c }) => `<div class="lcell"><div class="ln">${esc(c.title)}</div><div class="lb"><i class="good"></i><i class="neutral"></i><i class="gap"></i></div><div class="lv"></div></div>`).join("");
1596
1606
  }
1597
- box.querySelectorAll(".lcell").forEach((cell, i) => {
1598
- cell.style.setProperty("--i", String(i));
1607
+ box.querySelectorAll(".lcell").forEach((cell, cellIndex) => {
1608
+ const i = cells[cellIndex]?.i ?? cellIndex;
1609
+ cell.style.setProperty("--i", String(cellIndex));
1599
1610
  let tot = 0, good = 0, neutral = 0, gap = 0;
1600
1611
  for (const n of model.nodes) {
1601
- if (n.col !== i || n.kind === "chip") continue;
1612
+ if (heads[n.col ?? -1] !== i || n.kind === "chip") continue;
1602
1613
  if (!visible(n.id)) continue;
1603
1614
  const w = Math.max(1, n.weight ?? 1);
1604
1615
  tot += w;
@@ -1817,7 +1828,7 @@ function install6(ctx) {
1817
1828
  const markdown = () => {
1818
1829
  const out = [`# ${model.meta.title}`, "", model.meta.help, "", `As of ${model.meta.asOf}. Source: ${model.meta.source}`, "", "## States"];
1819
1830
  for (const [id, s] of Object.entries(model.states)) out.push(`- **${s.word}** (${id}): tone ${s.tone}, border ${s.border}, link ${s.edge}, motion ${s.motion}, bucket ${s.bucket}`);
1820
- out.push("", "## Columns", ...model.columns.map((c, i) => `${i + 1}. ${c.title}${c.phase ? ` (${c.phase})` : ""}`));
1831
+ out.push("", "## Columns", ...model.columns.filter((c) => !c.continues).map((c, i) => `${i + 1}. ${c.title}${c.phase ? ` (${c.phase})` : ""}`));
1821
1832
  out.push("", "## Lanes", ...model.lanes.map((l) => `- ${l.title}${l.badge ? ` [${l.badge}]` : ""}${l.note ? `: ${l.note}` : ""}`));
1822
1833
  out.push("", `## Size`, `${model.nodes.length} nodes, ${model.links.length} links.`);
1823
1834
  if (notes.length) out.push("", "## Notes", ...notes.map((n) => `${n.id}. **${n.title}**: ${n.text}`));
@@ -2482,6 +2493,7 @@ export {
2482
2493
  rules_exports as rules,
2483
2494
  slaState,
2484
2495
  stateCss,
2496
+ stripHeads,
2485
2497
  suggestedExit,
2486
2498
  toggleGroupState,
2487
2499
  trace_exports as trace,
@@ -76,7 +76,7 @@ In either route, derive node and link IDs from immutable business keys. Preserve
76
76
 
77
77
  ```
78
78
  version 1, meta {title, help, asOf, source, labels?}, layout "columns-lanes" | "free" | "auto"
79
- columns [{id, title, short?, width, phase?}] stages, left to right
79
+ columns [{id, title, short?, width, phase?, continues?}] stages, left to right
80
80
  lanes [{id, title, badge?, badgeTone?, note?, gap?}] rows; gap true = dashed amber lane
81
81
  states {id: {word, tone, border, edge, motion, bucket}} tone: ok|warn|crit|info|muted|accent; bucket: good|neutral|gap
82
82
  nodes [{id, kind, lane?, col?, colSpan?, row?, x?, y?, w?, h?, state, title, subtitle?, members?, weight?,
@@ -26,7 +26,7 @@
26
26
  "type": "object", "required": ["id", "title", "width"], "additionalProperties": false,
27
27
  "properties": {
28
28
  "id": { "type": "string" }, "title": { "type": "string" }, "short": { "type": "string" },
29
- "width": { "type": "number", "minimum": 60 }, "phase": { "type": "string" }
29
+ "width": { "type": "number", "minimum": 60 }, "phase": { "type": "string" }, "continues": { "type": "boolean" }
30
30
  }
31
31
  }
32
32
  },
@@ -48,6 +48,7 @@ Columns provide vertical partitioning for time, stages, or architecture tiers.
48
48
  | `short` | `string` | Optional | - | Compact abbreviated title for tight viewports. |
49
49
  | `width` | `number` | Required | - | Column width in pixels (minimum 60). |
50
50
  | `phase` | `string` | Optional | - | Super-category phase grouping shown in top banner. |
51
+ | `continues` | `boolean` | Optional | `false` | This column is a wrapped continuation of the column before it (for example one long list laid out in three side-by-side columns). It shows no header label and no strip cell of its own: its nodes count into the cell of the column it continues. Not allowed on the first column. |
51
52
 
52
53
  ---
53
54
 
@@ -4,7 +4,7 @@
4
4
  import { el, on, type Ctx } from "./ctx.ts";
5
5
  import { esc } from "./model.ts";
6
6
  import { DASH, toneVar, cssId } from "./stateCss.ts";
7
- import { bucketOf, columnTitle, effectiveLinks, nodeStateId, nodeVisible } from "./rules.ts";
7
+ import { bucketOf, columnTitle, effectiveLinks, nodeStateId, nodeVisible, stripHeads } from "./rules.ts";
8
8
  import { matches } from "./interact.ts";
9
9
 
10
10
  const DEFAULT_LABELS: Record<string, string> = {
@@ -34,16 +34,19 @@ export function install(ctx: Ctx): void {
34
34
 
35
35
  function buildStrip(): void {
36
36
  const box = els.strip;
37
+ // One cell per head column: a column that `continues` another counts into the cell before it.
38
+ const heads = stripHeads(model), cells = model.columns.map((c, i) => ({ c, i })).filter(({ i }) => heads[i] === i);
37
39
  box.style.display = model.columns.length ? "" : "none";
38
- box.style.gridTemplateColumns = `repeat(${Math.max(1, model.columns.length)}, minmax(0, 1fr))`;
40
+ box.style.gridTemplateColumns = `repeat(${Math.max(1, cells.length)}, minmax(0, 1fr))`;
39
41
  if (!box.firstChild) {
40
- box.innerHTML = model.columns.map(c => `<div class="lcell"><div class="ln">${esc(c.title)}</div><div class="lb"><i class="good"></i><i class="neutral"></i><i class="gap"></i></div><div class="lv"></div></div>`).join("");
42
+ box.innerHTML = cells.map(({ c }) => `<div class="lcell"><div class="ln">${esc(c.title)}</div><div class="lb"><i class="good"></i><i class="neutral"></i><i class="gap"></i></div><div class="lv"></div></div>`).join("");
41
43
  }
42
- box.querySelectorAll<HTMLElement>(".lcell").forEach((cell, i) => {
43
- cell.style.setProperty("--i", String(i));
44
+ box.querySelectorAll<HTMLElement>(".lcell").forEach((cell, cellIndex) => {
45
+ const i = cells[cellIndex]?.i ?? cellIndex;
46
+ cell.style.setProperty("--i", String(cellIndex));
44
47
  let tot = 0, good = 0, neutral = 0, gap = 0;
45
48
  for (const n of model.nodes) {
46
- if (n.col !== i || n.kind === "chip") continue;
49
+ if (heads[n.col ?? -1] !== i || n.kind === "chip") continue;
47
50
  if (!visible(n.id)) continue;
48
51
  const w = Math.max(1, n.weight ?? 1);
49
52
  tot += w;
@@ -71,7 +71,7 @@ export function install(ctx: Ctx): void {
71
71
  const cols = els.cols;
72
72
  cols.style.display = model.columns.length ? "" : "none";
73
73
  if (!cols.firstChild) {
74
- model.columns.forEach((c, i) => { const h = el("div", "hd"); h.textContent = (c.short ?? c.title).toUpperCase(); h.title = c.title; h.dataset.i = String(i); cols.appendChild(h); });
74
+ model.columns.forEach((c, i) => { const h = el("div", "hd"); h.textContent = c.continues ? "" : (c.short ?? c.title).toUpperCase(); h.title = c.title; h.dataset.i = String(i); cols.appendChild(h); });
75
75
  let i = 0;
76
76
  while (i < model.columns.length) {
77
77
  const name = model.columns[i]?.phase;
@@ -165,3 +165,11 @@ test("group containment validates ids, ownership, self-reference, and skips grou
165
165
  assert.ok(found.includes("contains-undefined"));
166
166
  assert.ok(found.includes("contains-duplicate"));
167
167
  });
168
+ test("a column may continue the one before it", () => {
169
+ const m = base(); m.columns.push({ id: "c2", title: "B", width: 150, continues: true });
170
+ assert.deepEqual(codes(m), []);
171
+ });
172
+ test("the first column cannot continue a column that does not exist", () => {
173
+ const m = base(); m.columns[0]!.continues = true;
174
+ assert.ok(codes(m).includes("column-continues-first"));
175
+ });
@@ -16,7 +16,7 @@ export interface StateDef {
16
16
  motion: Motion;
17
17
  bucket: Bucket;
18
18
  }
19
- export interface ColumnDef { id: string; title: string; short?: string; width: number; phase?: string }
19
+ export interface ColumnDef { id: string; title: string; short?: string; width: number; phase?: string; continues?: boolean }
20
20
  export interface LaneDef { id: string; title: string; badge?: string; badgeTone?: "ok" | "warn" | "muted"; note?: string; gap?: boolean }
21
21
  export interface NodeDef {
22
22
  id: string;
@@ -111,6 +111,7 @@ export function validateModel(input: unknown): Issue[] {
111
111
  if (!Array.isArray(m.lanes) || (m.layout !== "auto" && m.lanes.length === 0)) add("error", "lanes-missing", "lanes", "At least one lane is required.");
112
112
  if (!m.states || typeof m.states !== "object" || Object.keys(m.states).length === 0) add("error", "states-missing", "states", "At least one state is required.");
113
113
  const columns = Array.isArray(m.columns) ? m.columns : [];
114
+ if (columns[0]?.continues) add("error", "column-continues-first", "columns[0]", "The first column cannot continue a column before it.");
114
115
  const lanes = Array.isArray(m.lanes) ? m.lanes : [];
115
116
  const states = m.states && typeof m.states === "object" ? m.states : {};
116
117
  if (!m.meta || typeof m.meta !== "object" || typeof m.meta.title !== "string") add("error", "meta-missing", "meta", "meta with a title is required.");
@@ -70,7 +70,7 @@ export function install(ctx: Ctx): void {
70
70
  const markdown = (): string => {
71
71
  const out = [`# ${model.meta.title}`, "", model.meta.help, "", `As of ${model.meta.asOf}. Source: ${model.meta.source}`, "", "## States"];
72
72
  for (const [id, s] of Object.entries(model.states)) out.push(`- **${s.word}** (${id}): tone ${s.tone}, border ${s.border}, link ${s.edge}, motion ${s.motion}, bucket ${s.bucket}`);
73
- out.push("", "## Columns", ...model.columns.map((c, i) => `${i + 1}. ${c.title}${c.phase ? ` (${c.phase})` : ""}`));
73
+ out.push("", "## Columns", ...model.columns.filter(c => !c.continues).map((c, i) => `${i + 1}. ${c.title}${c.phase ? ` (${c.phase})` : ""}`));
74
74
  out.push("", "## Lanes", ...model.lanes.map(l => `- ${l.title}${l.badge ? ` [${l.badge}]` : ""}${l.note ? `: ${l.note}` : ""}`));
75
75
  out.push("", `## Size`, `${model.nodes.length} nodes, ${model.links.length} links.`);
76
76
  if (notes.length) out.push("", "## Notes", ...notes.map(n => `${n.id}. **${n.title}**: ${n.text}`));
@@ -1,7 +1,7 @@
1
1
  import { test } from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
  import type { Model } from "./model.ts";
4
- import { baselineView, defaultView, nodeVisible, nodeStateId, linkVisible, linkStateId, isNew, isImproved, bucketOf, canonOf, effectiveLinks, initialCollapsed, columnTitle, toggleGroupState } from "./rules.ts";
4
+ import { baselineView, defaultView, nodeVisible, nodeStateId, linkVisible, linkStateId, isNew, isImproved, bucketOf, canonOf, effectiveLinks, initialCollapsed, columnTitle, toggleGroupState, stripHeads } from "./rules.ts";
5
5
 
6
6
  const m = (): Model => ({
7
7
  version: 1,
@@ -133,3 +133,12 @@ test("toggleGroup selects a visible child on expansion and its group on collapse
133
133
  assert.equal(folded.collapsed.has("group"), true);
134
134
  assert.equal(toggleGroupState(mm, "full", starting, "missing"), null);
135
135
  });
136
+
137
+ test("stripHeads: a continuing column joins the one before it", () => {
138
+ const mm = m();
139
+ mm.columns = [{ id: "a", title: "A", width: 100 }, { id: "p", title: "P", width: 100 }, { id: "p2", title: "P", width: 100, continues: true }, { id: "p3", title: "P", width: 100, continues: true }, { id: "z", title: "Z", width: 100 }];
140
+ assert.deepEqual(stripHeads(mm), [0, 1, 1, 1, 4]);
141
+ });
142
+ test("stripHeads: with no continuing column every column is its own head", () => {
143
+ assert.deepEqual(stripHeads(m()), [0, 1]);
144
+ });
@@ -10,6 +10,12 @@ export const defaultView = (m: Model): string => (m.views?.find(v => v.default)
10
10
  /** A group that contains nodes starts folded unless it explicitly opts out. */
11
11
  export const initialCollapsed = (m: Model): Set<string> => new Set(m.nodes
12
12
  .filter(n => n.kind === "group" && n.contains && n.collapsed !== false).map(n => n.id));
13
+ /** For each column, the index of the column whose header and strip cell it shares: itself, or (when it
14
+ * `continues` another) the nearest earlier column that does not. */
15
+ export const stripHeads = (m: Model): number[] => {
16
+ let head = 0;
17
+ return m.columns.map((c, i) => { if (!c.continues || i === 0) head = i; return head; });
18
+ };
13
19
  export const columnTitle = (m: Model, n: NodeDef): string => n.col === undefined ? "" : m.columns[n.col]?.title ?? "";
14
20
 
15
21
  export function groupFor(m: Model, nodeId: string): NodeDef | undefined {
@@ -286,6 +286,7 @@
286
286
  if (!Array.isArray(m.lanes) || m.layout !== "auto" && m.lanes.length === 0) add("error", "lanes-missing", "lanes", "At least one lane is required.");
287
287
  if (!m.states || typeof m.states !== "object" || Object.keys(m.states).length === 0) add("error", "states-missing", "states", "At least one state is required.");
288
288
  const columns = Array.isArray(m.columns) ? m.columns : [];
289
+ if (columns[0]?.continues) add("error", "column-continues-first", "columns[0]", "The first column cannot continue a column before it.");
289
290
  const lanes = Array.isArray(m.lanes) ? m.lanes : [];
290
291
  const states = m.states && typeof m.states === "object" ? m.states : {};
291
292
  if (!m.meta || typeof m.meta !== "object" || typeof m.meta.title !== "string") add("error", "meta-missing", "meta", "meta with a title is required.");
@@ -591,6 +592,13 @@
591
592
  var baselineView = (m) => m.views?.[0]?.id ?? "all";
592
593
  var defaultView = (m) => (m.views?.find((v) => v.default) ?? m.views?.[0])?.id ?? "all";
593
594
  var initialCollapsed = (m) => new Set(m.nodes.filter((n) => n.kind === "group" && n.contains && n.collapsed !== false).map((n) => n.id));
595
+ var stripHeads = (m) => {
596
+ let head = 0;
597
+ return m.columns.map((c, i) => {
598
+ if (!c.continues || i === 0) head = i;
599
+ return head;
600
+ });
601
+ };
594
602
  var columnTitle = (m, n) => n.col === void 0 ? "" : m.columns[n.col]?.title ?? "";
595
603
  function groupFor(m, nodeId) {
596
604
  return m.nodes.find((n) => n.kind === "group" && n.contains?.includes(nodeId));
@@ -1415,7 +1423,7 @@
1415
1423
  if (!cols.firstChild) {
1416
1424
  model.columns.forEach((c, i2) => {
1417
1425
  const h = el("div", "hd");
1418
- h.textContent = (c.short ?? c.title).toUpperCase();
1426
+ h.textContent = c.continues ? "" : (c.short ?? c.title).toUpperCase();
1419
1427
  h.title = c.title;
1420
1428
  h.dataset.i = String(i2);
1421
1429
  cols.appendChild(h);
@@ -1801,16 +1809,18 @@
1801
1809
  }
1802
1810
  function buildStrip() {
1803
1811
  const box = els.strip;
1812
+ const heads = stripHeads(model), cells = model.columns.map((c, i) => ({ c, i })).filter(({ i }) => heads[i] === i);
1804
1813
  box.style.display = model.columns.length ? "" : "none";
1805
- box.style.gridTemplateColumns = `repeat(${Math.max(1, model.columns.length)}, minmax(0, 1fr))`;
1814
+ box.style.gridTemplateColumns = `repeat(${Math.max(1, cells.length)}, minmax(0, 1fr))`;
1806
1815
  if (!box.firstChild) {
1807
- box.innerHTML = model.columns.map((c) => `<div class="lcell"><div class="ln">${esc(c.title)}</div><div class="lb"><i class="good"></i><i class="neutral"></i><i class="gap"></i></div><div class="lv"></div></div>`).join("");
1816
+ box.innerHTML = cells.map(({ c }) => `<div class="lcell"><div class="ln">${esc(c.title)}</div><div class="lb"><i class="good"></i><i class="neutral"></i><i class="gap"></i></div><div class="lv"></div></div>`).join("");
1808
1817
  }
1809
- box.querySelectorAll(".lcell").forEach((cell, i) => {
1810
- cell.style.setProperty("--i", String(i));
1818
+ box.querySelectorAll(".lcell").forEach((cell, cellIndex) => {
1819
+ const i = cells[cellIndex]?.i ?? cellIndex;
1820
+ cell.style.setProperty("--i", String(cellIndex));
1811
1821
  let tot = 0, good = 0, neutral = 0, gap = 0;
1812
1822
  for (const n of model.nodes) {
1813
- if (n.col !== i || n.kind === "chip") continue;
1823
+ if (heads[n.col ?? -1] !== i || n.kind === "chip") continue;
1814
1824
  if (!visible(n.id)) continue;
1815
1825
  const w = Math.max(1, n.weight ?? 1);
1816
1826
  tot += w;
@@ -2029,7 +2039,7 @@
2029
2039
  const markdown = () => {
2030
2040
  const out = [`# ${model.meta.title}`, "", model.meta.help, "", `As of ${model.meta.asOf}. Source: ${model.meta.source}`, "", "## States"];
2031
2041
  for (const [id, s] of Object.entries(model.states)) out.push(`- **${s.word}** (${id}): tone ${s.tone}, border ${s.border}, link ${s.edge}, motion ${s.motion}, bucket ${s.bucket}`);
2032
- out.push("", "## Columns", ...model.columns.map((c, i) => `${i + 1}. ${c.title}${c.phase ? ` (${c.phase})` : ""}`));
2042
+ out.push("", "## Columns", ...model.columns.filter((c) => !c.continues).map((c, i) => `${i + 1}. ${c.title}${c.phase ? ` (${c.phase})` : ""}`));
2033
2043
  out.push("", "## Lanes", ...model.lanes.map((l) => `- ${l.title}${l.badge ? ` [${l.badge}]` : ""}${l.note ? `: ${l.note}` : ""}`));
2034
2044
  out.push("", `## Size`, `${model.nodes.length} nodes, ${model.links.length} links.`);
2035
2045
  if (notes.length) out.push("", "## Notes", ...notes.map((n) => `${n.id}. **${n.title}**: ${n.text}`));
@@ -29,6 +29,7 @@ function validateModel(input) {
29
29
  if (!Array.isArray(m.lanes) || m.layout !== "auto" && m.lanes.length === 0) add("error", "lanes-missing", "lanes", "At least one lane is required.");
30
30
  if (!m.states || typeof m.states !== "object" || Object.keys(m.states).length === 0) add("error", "states-missing", "states", "At least one state is required.");
31
31
  const columns = Array.isArray(m.columns) ? m.columns : [];
32
+ if (columns[0]?.continues) add("error", "column-continues-first", "columns[0]", "The first column cannot continue a column before it.");
32
33
  const lanes = Array.isArray(m.lanes) ? m.lanes : [];
33
34
  const states = m.states && typeof m.states === "object" ? m.states : {};
34
35
  if (!m.meta || typeof m.meta !== "object" || typeof m.meta.title !== "string") add("error", "meta-missing", "meta", "meta with a title is required.");
@@ -347,11 +348,19 @@ __export(rules_exports, {
347
348
  linkVisible: () => linkVisible,
348
349
  nodeStateId: () => nodeStateId,
349
350
  nodeVisible: () => nodeVisible,
351
+ stripHeads: () => stripHeads,
350
352
  toggleGroupState: () => toggleGroupState
351
353
  });
352
354
  var baselineView = (m) => m.views?.[0]?.id ?? "all";
353
355
  var defaultView = (m) => (m.views?.find((v) => v.default) ?? m.views?.[0])?.id ?? "all";
354
356
  var initialCollapsed = (m) => new Set(m.nodes.filter((n) => n.kind === "group" && n.contains && n.collapsed !== false).map((n) => n.id));
357
+ var stripHeads = (m) => {
358
+ let head = 0;
359
+ return m.columns.map((c, i) => {
360
+ if (!c.continues || i === 0) head = i;
361
+ return head;
362
+ });
363
+ };
355
364
  var columnTitle = (m, n) => n.col === void 0 ? "" : m.columns[n.col]?.title ?? "";
356
365
  function groupFor(m, nodeId) {
357
366
  return m.nodes.find((n) => n.kind === "group" && n.contains?.includes(nodeId));
@@ -1203,7 +1212,7 @@ function install3(ctx) {
1203
1212
  if (!cols.firstChild) {
1204
1213
  model.columns.forEach((c, i2) => {
1205
1214
  const h = el("div", "hd");
1206
- h.textContent = (c.short ?? c.title).toUpperCase();
1215
+ h.textContent = c.continues ? "" : (c.short ?? c.title).toUpperCase();
1207
1216
  h.title = c.title;
1208
1217
  h.dataset.i = String(i2);
1209
1218
  cols.appendChild(h);
@@ -1589,16 +1598,18 @@ function install5(ctx) {
1589
1598
  }
1590
1599
  function buildStrip() {
1591
1600
  const box = els.strip;
1601
+ const heads = stripHeads(model), cells = model.columns.map((c, i) => ({ c, i })).filter(({ i }) => heads[i] === i);
1592
1602
  box.style.display = model.columns.length ? "" : "none";
1593
- box.style.gridTemplateColumns = `repeat(${Math.max(1, model.columns.length)}, minmax(0, 1fr))`;
1603
+ box.style.gridTemplateColumns = `repeat(${Math.max(1, cells.length)}, minmax(0, 1fr))`;
1594
1604
  if (!box.firstChild) {
1595
- box.innerHTML = model.columns.map((c) => `<div class="lcell"><div class="ln">${esc(c.title)}</div><div class="lb"><i class="good"></i><i class="neutral"></i><i class="gap"></i></div><div class="lv"></div></div>`).join("");
1605
+ box.innerHTML = cells.map(({ c }) => `<div class="lcell"><div class="ln">${esc(c.title)}</div><div class="lb"><i class="good"></i><i class="neutral"></i><i class="gap"></i></div><div class="lv"></div></div>`).join("");
1596
1606
  }
1597
- box.querySelectorAll(".lcell").forEach((cell, i) => {
1598
- cell.style.setProperty("--i", String(i));
1607
+ box.querySelectorAll(".lcell").forEach((cell, cellIndex) => {
1608
+ const i = cells[cellIndex]?.i ?? cellIndex;
1609
+ cell.style.setProperty("--i", String(cellIndex));
1599
1610
  let tot = 0, good = 0, neutral = 0, gap = 0;
1600
1611
  for (const n of model.nodes) {
1601
- if (n.col !== i || n.kind === "chip") continue;
1612
+ if (heads[n.col ?? -1] !== i || n.kind === "chip") continue;
1602
1613
  if (!visible(n.id)) continue;
1603
1614
  const w = Math.max(1, n.weight ?? 1);
1604
1615
  tot += w;
@@ -1817,7 +1828,7 @@ function install6(ctx) {
1817
1828
  const markdown = () => {
1818
1829
  const out = [`# ${model.meta.title}`, "", model.meta.help, "", `As of ${model.meta.asOf}. Source: ${model.meta.source}`, "", "## States"];
1819
1830
  for (const [id, s] of Object.entries(model.states)) out.push(`- **${s.word}** (${id}): tone ${s.tone}, border ${s.border}, link ${s.edge}, motion ${s.motion}, bucket ${s.bucket}`);
1820
- out.push("", "## Columns", ...model.columns.map((c, i) => `${i + 1}. ${c.title}${c.phase ? ` (${c.phase})` : ""}`));
1831
+ out.push("", "## Columns", ...model.columns.filter((c) => !c.continues).map((c, i) => `${i + 1}. ${c.title}${c.phase ? ` (${c.phase})` : ""}`));
1821
1832
  out.push("", "## Lanes", ...model.lanes.map((l) => `- ${l.title}${l.badge ? ` [${l.badge}]` : ""}${l.note ? `: ${l.note}` : ""}`));
1822
1833
  out.push("", `## Size`, `${model.nodes.length} nodes, ${model.links.length} links.`);
1823
1834
  if (notes.length) out.push("", "## Notes", ...notes.map((n) => `${n.id}. **${n.title}**: ${n.text}`));
@@ -2482,6 +2493,7 @@ export {
2482
2493
  rules_exports as rules,
2483
2494
  slaState,
2484
2495
  stateCss,
2496
+ stripHeads,
2485
2497
  suggestedExit,
2486
2498
  toggleGroupState,
2487
2499
  trace_exports as trace,
@@ -1,7 +1,7 @@
1
1
  {
2
- "poolVersion": "1.3.133",
2
+ "poolVersion": "1.3.134",
3
3
  "bundles": {
4
- "antigravity-plugin": "1.3.133",
4
+ "antigravity-plugin": "1.3.134",
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.47",
3
+ "version": "0.4.48",
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",