@milehimikey/em 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/.claude/skills/event-modeling/SKILL.md +62 -17
  2. package/.claude/skills/event-modeling/reference/conform.md +33 -6
  3. package/.claude/skills/event-modeling/reference/em-dsl.md +138 -43
  4. package/.claude/skills/event-modeling/reference/extract.md +12 -2
  5. package/.claude/skills/event-modeling/reference/methodology.md +29 -14
  6. package/.claude/skills/event-modeling/templates/conformance-report.md +9 -7
  7. package/.claude/skills/event-modeling/templates/slice.md +6 -1
  8. package/.claude/skills/event-modeling/templates/state.md +8 -0
  9. package/README.md +1 -0
  10. package/dist/catalog/build.d.ts +33 -0
  11. package/dist/catalog/build.js +117 -0
  12. package/dist/catalog/build.js.map +1 -0
  13. package/dist/catalog/classify.d.ts +14 -0
  14. package/dist/catalog/classify.js +44 -0
  15. package/dist/catalog/classify.js.map +1 -0
  16. package/dist/catalog/pages.d.ts +45 -0
  17. package/dist/catalog/pages.js +174 -0
  18. package/dist/catalog/pages.js.map +1 -0
  19. package/dist/catalog/sliceDoc.d.ts +10 -0
  20. package/dist/catalog/sliceDoc.js +20 -0
  21. package/dist/catalog/sliceDoc.js.map +1 -0
  22. package/dist/cli/catalog-inputs.d.ts +16 -0
  23. package/dist/cli/catalog-inputs.js +18 -0
  24. package/dist/cli/catalog-inputs.js.map +1 -0
  25. package/dist/cli/glossary-inputs.d.ts +15 -0
  26. package/dist/cli/glossary-inputs.js +17 -0
  27. package/dist/cli/glossary-inputs.js.map +1 -0
  28. package/dist/cli/render-inputs.d.ts +27 -0
  29. package/dist/cli/render-inputs.js +41 -0
  30. package/dist/cli/render-inputs.js.map +1 -0
  31. package/dist/cli.js +188 -10
  32. package/dist/cli.js.map +1 -1
  33. package/dist/emit/diffJson.d.ts +1 -1
  34. package/dist/emit/diffJson.js +8 -1
  35. package/dist/emit/diffJson.js.map +1 -1
  36. package/dist/emit/dot.d.ts +7 -0
  37. package/dist/emit/dot.js +10 -4
  38. package/dist/emit/dot.js.map +1 -1
  39. package/dist/emit/glossaryJson.d.ts +13 -0
  40. package/dist/emit/glossaryJson.js +36 -0
  41. package/dist/emit/glossaryJson.js.map +1 -0
  42. package/dist/emit/json.d.ts +16 -2
  43. package/dist/emit/json.js +57 -6
  44. package/dist/emit/json.js.map +1 -1
  45. package/dist/emit/theme.d.ts +3 -0
  46. package/dist/emit/theme.js +19 -0
  47. package/dist/emit/theme.js.map +1 -1
  48. package/dist/model/diff.d.ts +36 -1
  49. package/dist/model/diff.js +198 -35
  50. package/dist/model/diff.js.map +1 -1
  51. package/dist/model/edges.d.ts +12 -1
  52. package/dist/model/edges.js +19 -8
  53. package/dist/model/edges.js.map +1 -1
  54. package/dist/model/glossary.d.ts +68 -0
  55. package/dist/model/glossary.js +138 -0
  56. package/dist/model/glossary.js.map +1 -0
  57. package/dist/model/model.d.ts +35 -0
  58. package/dist/model/model.js +41 -0
  59. package/dist/model/model.js.map +1 -1
  60. package/dist/model/validate.js +98 -1
  61. package/dist/model/validate.js.map +1 -1
  62. package/dist/parser/ast.d.ts +20 -0
  63. package/dist/parser/parser.js +158 -29
  64. package/dist/parser/parser.js.map +1 -1
  65. package/dist/render/drawNotes.d.ts +7 -3
  66. package/dist/render/drawNotes.js +75 -28
  67. package/dist/render/drawNotes.js.map +1 -1
  68. package/dist/render/render.d.ts +20 -1
  69. package/dist/render/render.js +52 -14
  70. package/dist/render/render.js.map +1 -1
  71. package/dist/render/serve.js +155 -0
  72. package/dist/render/serve.js.map +1 -1
  73. package/dist/render/sliceDiagram.d.ts +11 -0
  74. package/dist/render/sliceDiagram.js +198 -0
  75. package/dist/render/sliceDiagram.js.map +1 -0
  76. package/dist/render/sliceOverlay.d.ts +21 -0
  77. package/dist/render/sliceOverlay.js +111 -0
  78. package/dist/render/sliceOverlay.js.map +1 -0
  79. package/dist/render/sliceStatus.d.ts +5 -0
  80. package/dist/render/sliceStatus.js +26 -0
  81. package/dist/render/sliceStatus.js.map +1 -0
  82. package/dist/render/statusOverlay.d.ts +16 -0
  83. package/dist/render/statusOverlay.js +110 -0
  84. package/dist/render/statusOverlay.js.map +1 -0
  85. package/dist/render/svgGeometry.d.ts +30 -0
  86. package/dist/render/svgGeometry.js +33 -14
  87. package/dist/render/svgGeometry.js.map +1 -1
  88. package/dist/render/watch.d.ts +1 -1
  89. package/dist/render/watch.js +5 -2
  90. package/dist/render/watch.js.map +1 -1
  91. package/package.json +3 -2
@@ -0,0 +1,111 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Tags each element's rendered node group with its slice index, and embeds a
3
+ // <metadata> block describing every slice's name and x-range in the SVG.
4
+ //
5
+ // This is what lets the storyboard mode in the live viewer (`em watch --serve`,
6
+ // see src/render/serve.ts) highlight and pan/zoom to one slice at a time
7
+ // entirely client-side, with no separate data fetch — everything the browser
8
+ // needs travels inside the SVG it already loads.
9
+ //
10
+ // A slice's x-range comes from its header-cell rect (headerCellId), not from
11
+ // element rects — a slice can have empty rows, which would give an element-based
12
+ // bbox a wrong or missing range.
13
+ //
14
+ // The row-label (swimlane) column has no rect at all in parseNodeRects' sense:
15
+ // row labels render as bare `shape=plaintext` Graphviz nodes, which emit only a
16
+ // `<text>` element (no `<polygon>`/`<path>`), so they fall outside
17
+ // parseNodeRects' polygon/path-based extraction entirely. Their x-range is
18
+ // instead estimated straight from each `<text>` element: an exact center
19
+ // (Graphviz always emits `text-anchor="middle"` here) and a width approximated
20
+ // from character count and font size. That's a deliberate approximation local
21
+ // to this file — good enough to keep the swimlane labels in frame when the
22
+ // storyboard viewer pans/zooms to a slice, not meant to be pixel-accurate, and
23
+ // not worth teaching the shared parseNodeRects() a text-measuring fallback for
24
+ // its one caller that needs it.
25
+ import { headerCellId } from "../layout/grid.js";
26
+ import { rowLabelId } from "../emit/dot.js";
27
+ import { decode, readGraphTransform, NODE_GROUP, TITLE } from "./svgGeometry.js";
28
+ const TEXT = /<text\b([^>]*)>([^<]*)<\/text>/;
29
+ /** Every non-element node id the slice overlay needs a rect for (header cells),
30
+ * to fold into the same parseNodeRects() call as element ids. */
31
+ export function sliceOverlayIds(grid) {
32
+ const ids = [];
33
+ for (let c = 0; c < grid.cols; c++)
34
+ ids.push(headerCellId(c));
35
+ return ids;
36
+ }
37
+ /** Splice `data-slice="<index>"` onto every element's node group. */
38
+ export function tagSliceAttrs(svg, model) {
39
+ return svg.replace(NODE_GROUP, (block) => {
40
+ const id = decode(TITLE.exec(block)?.[1] ?? "");
41
+ const el = model.byId.get(id);
42
+ if (!el)
43
+ return block; // header cell, row label, or placeholder — leave untagged
44
+ return block.replace(/^<g\b/, `<g data-slice="${el.sliceIndex}"`);
45
+ });
46
+ }
47
+ /** `<metadata>` (slice ranges, as JSON) + `<style>` (the dim class) block, spliced
48
+ * just inside the root `<svg>`. `svg` is scanned (post-fitCanvas/tagSliceAttrs is
49
+ * fine — neither touches `<text>` elements) for the row-label column's x-range.
50
+ * Returns "" if no slice has a rect — the model is empty, or the SVG shape wasn't
51
+ * what parseNodeRects expected.
52
+ *
53
+ * rects (from parseNodeRects) and the row-label `<text>` x positions scanned below
54
+ * are both in graph-space — the coordinate system *inside* Graphviz's
55
+ * `<g class="graph" transform="scale(...) translate(...)">` wrapper — not the root
56
+ * SVG/viewBox space the served viewer sets `viewBox` in. Every x0/x1 here goes
57
+ * through readGraphTransform()'s toRoot() before it's embedded, the same
58
+ * conversion fitCanvas() applies to the edge-detour bbox for the same reason. */
59
+ export function buildSliceOverlay(svg, grid, rects) {
60
+ const transform = readGraphTransform(svg);
61
+ const toRootX = (x) => transform.toRoot(x, 0).x;
62
+ const slices = [];
63
+ for (let c = 0; c < grid.cols; c++) {
64
+ const r = rects.get(headerCellId(c));
65
+ if (!r)
66
+ continue;
67
+ slices.push({ index: c, name: grid.sliceNames[c], x0: toRootX(r.left), x1: toRootX(r.right) });
68
+ }
69
+ if (slices.length === 0)
70
+ return "";
71
+ const rowLabels = rowLabelXRange(svg, grid.rows.length, toRootX);
72
+ const payload = { slices, rowLabels };
73
+ const metadata = `<metadata id="em-slices">${escXml(JSON.stringify(payload))}</metadata>`;
74
+ const style = `<style>.em-slice-dim{opacity:.15;transition:opacity .15s ease}</style>`;
75
+ return metadata + style;
76
+ }
77
+ /** Estimate the row-label column's x-range from its `<text>` elements — see the
78
+ * file header comment for why this can't go through parseNodeRects. `toRootX`
79
+ * converts the graph-space text position (and the estimated half-width around
80
+ * it) into root/viewBox space — see buildSliceOverlay's comment. Returns null
81
+ * if no row label rendered any text (e.g. an empty model). */
82
+ function rowLabelXRange(svg, rowCount, toRootX) {
83
+ let range = null;
84
+ for (let r = 0; r < rowCount; r++) {
85
+ // Scoped to this row's own node group (up to its closing </g>) rather than a
86
+ // fixed-length lookahead — the header row's label is always "" (no <text> of
87
+ // its own), and a fixed window would otherwise pick up the *next* row's text.
88
+ const body = new RegExp(`<title>${rowLabelId(r)}<\\/title>([\\s\\S]*?)<\\/g>`).exec(svg)?.[1];
89
+ if (!body)
90
+ continue;
91
+ const m = TEXT.exec(body);
92
+ if (!m)
93
+ continue; // the header row's label is "" — plaintext emits no <text> at all
94
+ const x = +(attr(m[1], "x") ?? NaN);
95
+ if (Number.isNaN(x))
96
+ continue;
97
+ const fontSize = +(attr(m[1], "font-size") ?? 10);
98
+ const halfWidth = (decode(m[2]).length * fontSize) / 2 + 4; // + a little breathing room
99
+ const x0 = toRootX(x - halfWidth);
100
+ const x1 = toRootX(x + halfWidth);
101
+ range = range ? { x0: Math.min(range.x0, x0), x1: Math.max(range.x1, x1) } : { x0, x1 };
102
+ }
103
+ return range;
104
+ }
105
+ function attr(attrs, name) {
106
+ return new RegExp(`\\b${name}="(-?[\\d.]+)"`).exec(attrs)?.[1];
107
+ }
108
+ function escXml(s) {
109
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
110
+ }
111
+ //# sourceMappingURL=sliceOverlay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sliceOverlay.js","sourceRoot":"","sources":["../../src/render/sliceOverlay.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { NormalizedModel } from "../model/model.js";
2
+ /** One status per slice, same order as model.slices (and so the same order as
3
+ * the header row's columns — see layout/grid.ts's sliceNames). null means no
4
+ * doc was found, or the doc has no `- **Status:** ...` line. */
5
+ export declare function readSliceStatuses(model: NormalizedModel, baseDir: string): (string | null)[];
@@ -0,0 +1,26 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Reads each slice's development status from its sibling design doc
3
+ // (slices/<kebab-slug>.md, see .claude/skills/event-modeling/templates/slice.md),
4
+ // for the main render pipeline (composeSvg) to color slice headers with.
5
+ //
6
+ // Same doc-path convention `em catalog` already uses (src/catalog/build.ts):
7
+ // the *deduped* export key from computeRefs(), not a fresh kebabSlug(name) —
8
+ // so two same-named slices resolve statuses the same way `em catalog` resolves
9
+ // docs for them (first one gets the real file, the second honestly gets null).
10
+ import { existsSync, readFileSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { computeRefs } from "../emit/json.js";
13
+ import { parseSliceDoc } from "../catalog/sliceDoc.js";
14
+ /** One status per slice, same order as model.slices (and so the same order as
15
+ * the header row's columns — see layout/grid.ts's sliceNames). null means no
16
+ * doc was found, or the doc has no `- **Status:** ...` line. */
17
+ export function readSliceStatuses(model, baseDir) {
18
+ const { sliceKeys } = computeRefs(model);
19
+ return model.slices.map((_slice, i) => {
20
+ const docPath = join(baseDir, "slices", `${sliceKeys[i]}.md`);
21
+ if (!existsSync(docPath))
22
+ return null;
23
+ return parseSliceDoc(readFileSync(docPath, "utf8")).status;
24
+ });
25
+ }
26
+ //# sourceMappingURL=sliceStatus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sliceStatus.js","sourceRoot":"","sources":["../../src/render/sliceStatus.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,oEAAoE;AACpE,kFAAkF;AAClF,yEAAyE;AACzE,EAAE;AACF,6EAA6E;AAC7E,6EAA6E;AAC7E,+EAA+E;AAC/E,+EAA+E;AAE/E,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD;;iEAEiE;AACjE,MAAM,UAAU,iBAAiB,CAAC,KAAsB,EAAE,OAAe;IACvE,MAAM,EAAE,SAAS,EAAE,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IACzC,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACpC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAC9D,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC;QACtC,OAAO,aAAa,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { Grid } from "../layout/grid.js";
2
+ /** Recolor each header cell whose slice resolved to a known status (see
3
+ * statusStyle()). Slices with no doc, `draft`, or an unrecognized status
4
+ * string are left with the default header colors, untouched. */
5
+ export declare function applyStatusColors(svg: string, grid: Grid, statuses: (string | null)[]): string;
6
+ /**
7
+ * Grow the canvas and append a "Slice Status" legend below the diagram, one
8
+ * swatch+label row per distinct known status actually present among
9
+ * `statuses`. Returns the SVG unchanged if no slice resolved to a known
10
+ * status (nobody's using slice docs yet, or none set a recognized value), or
11
+ * if the SVG's dimensions can't be parsed. Independent of appendNoteLegend
12
+ * (src/render/drawNotes.ts) — safe to call after it since it recomputes the
13
+ * (possibly already-grown) viewBox itself, same as every other
14
+ * append-below-diagram step in the render pipeline.
15
+ */
16
+ export declare function appendStatusLegend(svg: string, grid: Grid, statuses: (string | null)[]): string;
@@ -0,0 +1,110 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Recolors each slice header cell's fill/stroke/font in a Graphviz-rendered SVG
3
+ // based on that slice's development status (see src/render/sliceStatus.ts),
4
+ // and appends a small legend explaining the colors actually used.
5
+ //
6
+ // This is a post-layout overlay, the same pattern as drawEdges.ts/drawNotes.ts:
7
+ // Graphviz always lays out every header cell identically (status never affects
8
+ // size, only color), so recoloring is a scoped string replace on the already-
9
+ // rendered header node group rather than anything that needs new geometry.
10
+ import { headerCellId } from "../layout/grid.js";
11
+ import { HEADER_FILL, HEADER_BORDER, HEADER_FONT } from "../emit/dot.js";
12
+ import { statusStyle } from "../emit/theme.js";
13
+ // Graphviz lowercases hex colors in its SVG output (e.g. our "#E3E7EB" comes
14
+ // back as "#e3e7eb"), so matching must be case-insensitive even though we
15
+ // authored the DOT-side constants in uppercase.
16
+ function attrRe(name, value) {
17
+ return new RegExp(`${name}="${value}"`, "gi");
18
+ }
19
+ /** Recolor each header cell whose slice resolved to a known status (see
20
+ * statusStyle()). Slices with no doc, `draft`, or an unrecognized status
21
+ * string are left with the default header colors, untouched. */
22
+ export function applyStatusColors(svg, grid, statuses) {
23
+ let out = svg;
24
+ for (let c = 0; c < grid.cols; c++) {
25
+ const style = statusStyle(statuses[c] ?? null);
26
+ if (!style)
27
+ continue;
28
+ const id = headerCellId(c);
29
+ const re = new RegExp(`(<title>${id}</title>)([\\s\\S]*?)(</g>)`);
30
+ out = out.replace(re, (_whole, title, body, close) => {
31
+ const recolored = body
32
+ .replace(attrRe("fill", HEADER_FILL), `fill="${style.fill}"`)
33
+ .replace(attrRe("stroke", HEADER_BORDER), `stroke="${style.stroke}"`)
34
+ .replace(attrRe("fill", HEADER_FONT), `fill="${style.fontColor}"`);
35
+ return title + recolored + close;
36
+ });
37
+ }
38
+ return out;
39
+ }
40
+ // ---- legend ----
41
+ const PAD_X = 16;
42
+ const PAD_TOP = 14;
43
+ const HEAD_H = 22;
44
+ const LINE_H = 18;
45
+ const PAD_BOTTOM = 14;
46
+ const SWATCH = 11;
47
+ /** Canonical display order, independent of column order — a model's slices can
48
+ * land in any status in any order, but the legend should read the same way
49
+ * (earliest-to-latest lifecycle stage) every time. */
50
+ const STATUS_ORDER = ["reviewed", "ready-to-implement", "implemented"];
51
+ function humanizeStatus(status) {
52
+ return status.charAt(0).toUpperCase() + status.slice(1).replace(/-/g, " ");
53
+ }
54
+ /**
55
+ * Grow the canvas and append a "Slice Status" legend below the diagram, one
56
+ * swatch+label row per distinct known status actually present among
57
+ * `statuses`. Returns the SVG unchanged if no slice resolved to a known
58
+ * status (nobody's using slice docs yet, or none set a recognized value), or
59
+ * if the SVG's dimensions can't be parsed. Independent of appendNoteLegend
60
+ * (src/render/drawNotes.ts) — safe to call after it since it recomputes the
61
+ * (possibly already-grown) viewBox itself, same as every other
62
+ * append-below-diagram step in the render pipeline.
63
+ */
64
+ export function appendStatusLegend(svg, grid, statuses) {
65
+ const present = new Set(statuses.slice(0, grid.cols).map((s) => s?.toLowerCase()).filter((s) => !!s));
66
+ const rows = STATUS_ORDER.filter((s) => present.has(s));
67
+ if (rows.length === 0)
68
+ return svg;
69
+ const vb = /viewBox="([\d.eE+-]+)\s+([\d.eE+-]+)\s+([\d.eE+-]+)\s+([\d.eE+-]+)"/.exec(svg);
70
+ const hpt = /height="([\d.eE+-]+)pt"/.exec(svg);
71
+ if (!vb || !hpt)
72
+ return svg;
73
+ const minX = +vb[1];
74
+ const minY = +vb[2];
75
+ const vw = +vb[3];
76
+ const vh = +vb[4];
77
+ const legendH = PAD_TOP + HEAD_H + rows.length * LINE_H + PAD_BOTTOM;
78
+ const newVh = vh + legendH;
79
+ const newHpt = +hpt[1] * (newVh / vh);
80
+ const top = minY + vh;
81
+ let out = svg;
82
+ out = out.replace(hpt[0], `height="${n(newHpt)}pt"`);
83
+ out = out.replace(vb[0], `viewBox="${vb[1]} ${vb[2]} ${vb[3]} ${n(newVh)}"`);
84
+ const sectionY = top + PAD_TOP;
85
+ let sections = `<text x="${n(minX + PAD_X)}" y="${n(sectionY + 14)}" font-family="Helvetica" ` +
86
+ `font-weight="bold" font-size="12" fill="#202124">Slice Status</text>`;
87
+ sections += rows
88
+ .map((status, i) => {
89
+ const style = statusStyle(status);
90
+ const y = sectionY + HEAD_H + i * LINE_H;
91
+ return (`<rect x="${n(minX + PAD_X)}" y="${n(y)}" width="${SWATCH}" height="${SWATCH}" ` +
92
+ `fill="${style.fill}" stroke="${style.stroke}"/>` +
93
+ `<text x="${n(minX + PAD_X + SWATCH + 6)}" y="${n(y + SWATCH - 1)}" font-family="Helvetica" ` +
94
+ `font-size="11" fill="#3C4043">${esc(humanizeStatus(status))}</text>`);
95
+ })
96
+ .join("");
97
+ const legend = `<g class="em-status-legend">` +
98
+ `<rect x="${n(minX)}" y="${n(top)}" width="${n(vw)}" height="${n(legendH)}" fill="#FFFFFF"/>` +
99
+ `<line x1="${n(minX)}" y1="${n(top)}" x2="${n(minX + vw)}" y2="${n(top)}" stroke="#D0D0D0" stroke-width="1"/>` +
100
+ sections +
101
+ `</g>`;
102
+ return out.replace(/(<\/g>\s*)(<\/svg>)/, `$1${legend}$2`);
103
+ }
104
+ function n(v) {
105
+ return String(Math.round(v * 100) / 100);
106
+ }
107
+ function esc(s) {
108
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
109
+ }
110
+ //# sourceMappingURL=statusOverlay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"statusOverlay.js","sourceRoot":"","sources":["../../src/render/statusOverlay.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,8 @@ export interface Rect {
6
6
  cx: number;
7
7
  cy: number;
8
8
  }
9
+ export declare const NODE_GROUP: RegExp;
10
+ export declare const TITLE: RegExp;
9
11
  /** Map each requested node id to its rectangle in SVG coordinates. */
10
12
  export declare function parseNodeRects(svg: string, ids: Set<string>): Map<string, Rect>;
11
13
  /** A bounding box in the graph's own (post-transform) coordinate space. */
@@ -15,9 +17,37 @@ export interface Box {
15
17
  maxX: number;
16
18
  maxY: number;
17
19
  }
20
+ /** scale/translate read from the graph's own transform, and the root-space
21
+ * conversion it implies — see readGraphTransform() for where this comes from. */
22
+ export interface GraphTransform {
23
+ sx: number;
24
+ sy: number;
25
+ tx: number;
26
+ ty: number;
27
+ /** Convert a graph-space (pre-transform) point — e.g. a rect from
28
+ * parseNodeRects(), or a raw `<text>` x/y — into root/viewBox space. */
29
+ toRoot(x: number, y: number): {
30
+ x: number;
31
+ y: number;
32
+ };
33
+ }
34
+ /**
35
+ * Graphviz wraps the graph in `transform="scale(sx sy) rotate(0) translate(tx ty)"`,
36
+ * applied right to left, so a graph-space point lands at `scale * (p + translate)`.
37
+ * Anything computed from parseNodeRects()'s box coordinates (or a `<text>` element's
38
+ * raw x/y) is in that same pre-transform graph space — it must go through
39
+ * `toRoot()` before it's valid as a viewBox coordinate or compared against one.
40
+ * Defaults to the identity transform (scale 1, translate 0) if the graph group or
41
+ * its transform attribute isn't present, matching what Graphviz emits when neither
42
+ * scaling nor an offset was needed.
43
+ */
44
+ export declare function readGraphTransform(svg: string): GraphTransform;
18
45
  /**
19
46
  * Grow the canvas so content drawn in graph space isn't clipped by the root viewBox.
20
47
  * Edge detours can legitimately run outside the box grid Graphviz sized the canvas for
21
48
  * (an arc under the bottom lane, say), and anything past the viewBox is simply lost.
22
49
  */
23
50
  export declare function fitCanvas(svg: string, box: Box | null): string;
51
+ /** Decode the handful of entities Graphviz emits in `<title>` text. Shared with
52
+ * sliceOverlay.ts, which also needs to recover an element id from a title. */
53
+ export declare function decode(s: string): string;
@@ -5,8 +5,11 @@
5
5
  // with coordinates in the same 1:1 user space we later inject our edges into, so
6
6
  // the numbers can be used directly. We take the bounding box of the first shape
7
7
  // in each node group.
8
- const NODE_GROUP = /<g\b[^>]*class="node"[^>]*>([\s\S]*?)<\/g>/g;
9
- const TITLE = /<title>([\s\S]*?)<\/title>/;
8
+ // Exported: sliceOverlay.ts is a second consumer, tagging/reading these same
9
+ // node groups for the storyboard viewer's slice overlay — share the pattern
10
+ // rather than let it drift into a second copy.
11
+ export const NODE_GROUP = /<g\b[^>]*class="node"[^>]*>([\s\S]*?)<\/g>/g;
12
+ export const TITLE = /<title>([\s\S]*?)<\/title>/;
10
13
  const SHAPE = /<(?:polygon|path)\b[^>]*\b(?:points|d)="([^"]*)"/;
11
14
  const NUM = /-?\d*\.?\d+(?:e-?\d+)?/gi;
12
15
  /** Map each requested node id to its rectangle in SVG coordinates. */
@@ -37,6 +40,24 @@ export function parseNodeRects(svg, ids) {
37
40
  }
38
41
  return rects;
39
42
  }
43
+ /**
44
+ * Graphviz wraps the graph in `transform="scale(sx sy) rotate(0) translate(tx ty)"`,
45
+ * applied right to left, so a graph-space point lands at `scale * (p + translate)`.
46
+ * Anything computed from parseNodeRects()'s box coordinates (or a `<text>` element's
47
+ * raw x/y) is in that same pre-transform graph space — it must go through
48
+ * `toRoot()` before it's valid as a viewBox coordinate or compared against one.
49
+ * Defaults to the identity transform (scale 1, translate 0) if the graph group or
50
+ * its transform attribute isn't present, matching what Graphviz emits when neither
51
+ * scaling nor an offset was needed.
52
+ */
53
+ export function readGraphTransform(svg) {
54
+ const tf = /<g\b[^>]*class="graph"[^>]*transform="([^"]*)"/.exec(svg)?.[1] ?? "";
55
+ const sc = /scale\(([\d.eE+-]+)[\s,]+([\d.eE+-]+)\)/.exec(tf);
56
+ const tr = /translate\(([\d.eE+-]+)[\s,]+([\d.eE+-]+)\)/.exec(tf);
57
+ const [sx, sy] = sc ? [+sc[1], +sc[2]] : [1, 1];
58
+ const [tx, ty] = tr ? [+tr[1], +tr[2]] : [0, 0];
59
+ return { sx, sy, tx, ty, toRoot: (x, y) => ({ x: sx * (x + tx), y: sy * (y + ty) }) };
60
+ }
40
61
  const FIT_PAD = 10; // breathing room, and enough for an arrowhead at the boundary
41
62
  /**
42
63
  * Grow the canvas so content drawn in graph space isn't clipped by the root viewBox.
@@ -51,23 +72,19 @@ export function fitCanvas(svg, box) {
51
72
  const hpt = /height="([\d.eE+-]+)pt"/.exec(svg);
52
73
  if (!vb || !wpt || !hpt)
53
74
  return svg; // can't resize safely — leave it alone
54
- // Graphviz wraps the graph in `transform="scale(sx sy) rotate(0) translate(tx ty)"`,
55
- // applied right to left, so a graph-space point lands at scale * (p + translate).
56
- const tf = /<g\b[^>]*class="graph"[^>]*transform="([^"]*)"/.exec(svg)?.[1] ?? "";
57
- const sc = /scale\(([\d.eE+-]+)[\s,]+([\d.eE+-]+)\)/.exec(tf);
58
- const tr = /translate\(([\d.eE+-]+)[\s,]+([\d.eE+-]+)\)/.exec(tf);
59
- const [sx, sy] = sc ? [+sc[1], +sc[2]] : [1, 1];
60
- const [tx, ty] = tr ? [+tr[1], +tr[2]] : [0, 0];
75
+ const { toRoot } = readGraphTransform(svg);
61
76
  const minX = +vb[1];
62
77
  const minY = +vb[2];
63
78
  const vw = +vb[3];
64
79
  const vh = +vb[4];
65
80
  if (vw <= 0 || vh <= 0)
66
81
  return svg;
67
- const x0 = Math.min(sx * (box.minX + tx), sx * (box.maxX + tx));
68
- const x1 = Math.max(sx * (box.minX + tx), sx * (box.maxX + tx));
69
- const y0 = Math.min(sy * (box.minY + ty), sy * (box.maxY + ty));
70
- const y1 = Math.max(sy * (box.minY + ty), sy * (box.maxY + ty));
82
+ const corner0 = toRoot(box.minX, box.minY);
83
+ const corner1 = toRoot(box.maxX, box.maxY);
84
+ const x0 = Math.min(corner0.x, corner1.x);
85
+ const x1 = Math.max(corner0.x, corner1.x);
86
+ const y0 = Math.min(corner0.y, corner1.y);
87
+ const y1 = Math.max(corner0.y, corner1.y);
71
88
  const left = Math.min(minX, Math.floor(x0 - FIT_PAD));
72
89
  const top = Math.min(minY, Math.floor(y0 - FIT_PAD));
73
90
  const right = Math.max(minX + vw, Math.ceil(x1 + FIT_PAD));
@@ -84,7 +101,9 @@ export function fitCanvas(svg, box) {
84
101
  function round(v) {
85
102
  return Math.round(v * 100) / 100;
86
103
  }
87
- function decode(s) {
104
+ /** Decode the handful of entities Graphviz emits in `<title>` text. Shared with
105
+ * sliceOverlay.ts, which also needs to recover an element id from a title. */
106
+ export function decode(s) {
88
107
  return s
89
108
  .replace(/&lt;/g, "<")
90
109
  .replace(/&gt;/g, ">")
@@ -1 +1 @@
1
- {"version":3,"file":"svgGeometry.js","sourceRoot":"","sources":["../../src/render/svgGeometry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"svgGeometry.js","sourceRoot":"","sources":["../../src/render/svgGeometry.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,2 @@
1
1
  import chokidar from "chokidar";
2
- export declare function watchFile(file: string, onChange: () => void | Promise<void>, debounceMs?: number): chokidar.FSWatcher;
2
+ export declare function watchFile(paths: string | string[], onChange: () => void | Promise<void>, debounceMs?: number): chokidar.FSWatcher;
@@ -1,8 +1,11 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  // Debounced file watcher for `em watch`.
3
3
  import chokidar from "chokidar";
4
- export function watchFile(file, onChange, debounceMs = 80) {
5
- const watcher = chokidar.watch(file, { ignoreInitial: true });
4
+ export function watchFile(paths, onChange, debounceMs = 80) {
5
+ // A glob path (e.g. a slices/*.md pattern) picks up files created after the
6
+ // watcher starts, not just ones that already exist — needed so authoring a
7
+ // slice's first design doc mid-session still triggers a re-render.
8
+ const watcher = chokidar.watch(paths, { ignoreInitial: true });
6
9
  let timer = null;
7
10
  const trigger = () => {
8
11
  if (timer)
@@ -1 +1 @@
1
- {"version":3,"file":"watch.js","sourceRoot":"","sources":["../../src/render/watch.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,yCAAyC;AAEzC,OAAO,QAAQ,MAAM,UAAU,CAAC;AAEhC,MAAM,UAAU,SAAS,CACvB,IAAY,EACZ,QAAoC,EACpC,UAAU,GAAG,EAAE;IAEf,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9D,IAAI,KAAK,GAA0B,IAAI,CAAC;IACxC,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,IAAI,KAAK;YAAE,YAAY,CAAC,KAAK,CAAC,CAAC;QAC/B,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YACtB,KAAK,QAAQ,EAAE,CAAC;QAClB,CAAC,EAAE,UAAU,CAAC,CAAC;IACjB,CAAC,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC9B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAC3B,OAAO,OAAO,CAAC;AACjB,CAAC"}
1
+ {"version":3,"file":"watch.js","sourceRoot":"","sources":["../../src/render/watch.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,yCAAyC;AAEzC,OAAO,QAAQ,MAAM,UAAU,CAAC;AAEhC,MAAM,UAAU,SAAS,CACvB,KAAwB,EACxB,QAAoC,EACpC,UAAU,GAAG,EAAE;IAEf,4EAA4E;IAC5E,2EAA2E;IAC3E,mEAAmE;IACnE,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/D,IAAI,KAAK,GAA0B,IAAI,CAAC;IACxC,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,IAAI,KAAK;YAAE,YAAY,CAAC,KAAK,CAAC,CAAC;QAC/B,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YACtB,KAAK,QAAQ,EAAE,CAAC;QAClB,CAAC,EAAE,UAAU,CAAC,CAAC;IACjB,CAAC,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC9B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAC3B,OAAO,OAAO,CAAC;AACjB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@milehimikey/em",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "AI-friendly Event Modeling — a slice-first text DSL rendered to a strict, self-contained Graphviz grid (SVG/PNG)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -52,7 +52,8 @@
52
52
  "@hpcc-js/wasm-graphviz": "^1.22.0",
53
53
  "@resvg/resvg-js": "^2.6.2",
54
54
  "chokidar": "^3.6.0",
55
- "commander": "^12.1.0"
55
+ "commander": "^12.1.0",
56
+ "marked": "^18.0.0"
56
57
  },
57
58
  "devDependencies": {
58
59
  "@types/node": "^22.0.0",