@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.
- package/.claude/skills/event-modeling/SKILL.md +62 -17
- package/.claude/skills/event-modeling/reference/conform.md +33 -6
- package/.claude/skills/event-modeling/reference/em-dsl.md +138 -43
- package/.claude/skills/event-modeling/reference/extract.md +12 -2
- package/.claude/skills/event-modeling/reference/methodology.md +29 -14
- package/.claude/skills/event-modeling/templates/conformance-report.md +9 -7
- package/.claude/skills/event-modeling/templates/slice.md +6 -1
- package/.claude/skills/event-modeling/templates/state.md +8 -0
- package/README.md +1 -0
- package/dist/catalog/build.d.ts +33 -0
- package/dist/catalog/build.js +117 -0
- package/dist/catalog/build.js.map +1 -0
- package/dist/catalog/classify.d.ts +14 -0
- package/dist/catalog/classify.js +44 -0
- package/dist/catalog/classify.js.map +1 -0
- package/dist/catalog/pages.d.ts +45 -0
- package/dist/catalog/pages.js +174 -0
- package/dist/catalog/pages.js.map +1 -0
- package/dist/catalog/sliceDoc.d.ts +10 -0
- package/dist/catalog/sliceDoc.js +20 -0
- package/dist/catalog/sliceDoc.js.map +1 -0
- package/dist/cli/catalog-inputs.d.ts +16 -0
- package/dist/cli/catalog-inputs.js +18 -0
- package/dist/cli/catalog-inputs.js.map +1 -0
- package/dist/cli/glossary-inputs.d.ts +15 -0
- package/dist/cli/glossary-inputs.js +17 -0
- package/dist/cli/glossary-inputs.js.map +1 -0
- package/dist/cli/render-inputs.d.ts +27 -0
- package/dist/cli/render-inputs.js +41 -0
- package/dist/cli/render-inputs.js.map +1 -0
- package/dist/cli.js +188 -10
- package/dist/cli.js.map +1 -1
- package/dist/emit/diffJson.d.ts +1 -1
- package/dist/emit/diffJson.js +8 -1
- package/dist/emit/diffJson.js.map +1 -1
- package/dist/emit/dot.d.ts +7 -0
- package/dist/emit/dot.js +10 -4
- package/dist/emit/dot.js.map +1 -1
- package/dist/emit/glossaryJson.d.ts +13 -0
- package/dist/emit/glossaryJson.js +36 -0
- package/dist/emit/glossaryJson.js.map +1 -0
- package/dist/emit/json.d.ts +16 -2
- package/dist/emit/json.js +57 -6
- package/dist/emit/json.js.map +1 -1
- package/dist/emit/theme.d.ts +3 -0
- package/dist/emit/theme.js +19 -0
- package/dist/emit/theme.js.map +1 -1
- package/dist/model/diff.d.ts +36 -1
- package/dist/model/diff.js +198 -35
- package/dist/model/diff.js.map +1 -1
- package/dist/model/edges.d.ts +12 -1
- package/dist/model/edges.js +19 -8
- package/dist/model/edges.js.map +1 -1
- package/dist/model/glossary.d.ts +68 -0
- package/dist/model/glossary.js +138 -0
- package/dist/model/glossary.js.map +1 -0
- package/dist/model/model.d.ts +35 -0
- package/dist/model/model.js +41 -0
- package/dist/model/model.js.map +1 -1
- package/dist/model/validate.js +98 -1
- package/dist/model/validate.js.map +1 -1
- package/dist/parser/ast.d.ts +20 -0
- package/dist/parser/parser.js +158 -29
- package/dist/parser/parser.js.map +1 -1
- package/dist/render/drawNotes.d.ts +7 -3
- package/dist/render/drawNotes.js +75 -28
- package/dist/render/drawNotes.js.map +1 -1
- package/dist/render/render.d.ts +20 -1
- package/dist/render/render.js +52 -14
- package/dist/render/render.js.map +1 -1
- package/dist/render/serve.js +155 -0
- package/dist/render/serve.js.map +1 -1
- package/dist/render/sliceDiagram.d.ts +11 -0
- package/dist/render/sliceDiagram.js +198 -0
- package/dist/render/sliceDiagram.js.map +1 -0
- package/dist/render/sliceOverlay.d.ts +21 -0
- package/dist/render/sliceOverlay.js +111 -0
- package/dist/render/sliceOverlay.js.map +1 -0
- package/dist/render/sliceStatus.d.ts +5 -0
- package/dist/render/sliceStatus.js +26 -0
- package/dist/render/sliceStatus.js.map +1 -0
- package/dist/render/statusOverlay.d.ts +16 -0
- package/dist/render/statusOverlay.js +110 -0
- package/dist/render/statusOverlay.js.map +1 -0
- package/dist/render/svgGeometry.d.ts +30 -0
- package/dist/render/svgGeometry.js +33 -14
- package/dist/render/svgGeometry.js.map +1 -1
- package/dist/render/watch.d.ts +1 -1
- package/dist/render/watch.js +5 -2
- package/dist/render/watch.js.map +1 -1
- 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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=sliceOverlay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sliceOverlay.js","sourceRoot":"","sources":["../../src/render/sliceOverlay.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,6EAA6E;AAC7E,yEAAyE;AACzE,EAAE;AACF,gFAAgF;AAChF,yEAAyE;AACzE,6EAA6E;AAC7E,iDAAiD;AACjD,EAAE;AACF,6EAA6E;AAC7E,iFAAiF;AACjF,iCAAiC;AACjC,EAAE;AACF,+EAA+E;AAC/E,gFAAgF;AAChF,mEAAmE;AACnE,2EAA2E;AAC3E,yEAAyE;AACzE,+EAA+E;AAC/E,8EAA8E;AAC9E,2EAA2E;AAC3E,+EAA+E;AAC/E,+EAA+E;AAC/E,gCAAgC;AAGhC,OAAO,EAAQ,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,UAAU,EAAE,KAAK,EAAQ,MAAM,kBAAkB,CAAC;AAEvF,MAAM,IAAI,GAAG,gCAAgC,CAAC;AAE9C;kEACkE;AAClE,MAAM,UAAU,eAAe,CAAC,IAAU;IACxC,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE;QAAE,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9D,OAAO,GAAG,CAAC;AACb,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,aAAa,CAAC,GAAW,EAAE,KAAsB;IAC/D,OAAO,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE;QACvC,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAChD,MAAM,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC9B,IAAI,CAAC,EAAE;YAAE,OAAO,KAAK,CAAC,CAAC,0DAA0D;QACjF,OAAO,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,kBAAkB,EAAE,CAAC,UAAU,GAAG,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;AACL,CAAC;AASD;;;;;;;;;;;kFAWkF;AAClF,MAAM,UAAU,iBAAiB,CAAC,GAAW,EAAE,IAAU,EAAE,KAAwB;IACjF,MAAM,SAAS,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAEhE,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;QACrC,IAAI,CAAC,CAAC;YAAE,SAAS;QACjB,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACjG,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEnC,MAAM,SAAS,GAAG,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjE,MAAM,OAAO,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IACtC,MAAM,QAAQ,GAAG,4BAA4B,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,aAAa,CAAC;IAC1F,MAAM,KAAK,GAAG,wEAAwE,CAAC;IACvF,OAAO,QAAQ,GAAG,KAAK,CAAC;AAC1B,CAAC;AAED;;;;+DAI+D;AAC/D,SAAS,cAAc,CACrB,GAAW,EACX,QAAgB,EAChB,OAA8B;IAE9B,IAAI,KAAK,GAAsC,IAAI,CAAC;IACpD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,6EAA6E;QAC7E,6EAA6E;QAC7E,8EAA8E;QAC9E,MAAM,IAAI,GAAG,IAAI,MAAM,CAAC,UAAU,UAAU,CAAC,CAAC,CAAC,8BAA8B,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC9F,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1B,IAAI,CAAC,CAAC;YAAE,SAAS,CAAC,kEAAkE;QACpF,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC;QACpC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YAAE,SAAS;QAC9B,MAAM,QAAQ,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC;QAClD,MAAM,SAAS,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,4BAA4B;QACxF,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;QAClC,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;QAClC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;IAC1F,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,IAAI,CAAC,KAAa,EAAE,IAAY;IACvC,OAAO,IAAI,MAAM,CAAC,MAAM,IAAI,gBAAgB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AACjE,CAAC;AAED,SAAS,MAAM,CAAC,CAAS;IACvB,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC9E,CAAC"}
|
|
@@ -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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
const
|
|
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
|
-
|
|
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(/</g, "<")
|
|
90
109
|
.replace(/>/g, ">")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"svgGeometry.js","sourceRoot":"","sources":["../../src/render/svgGeometry.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,4DAA4D;AAC5D,EAAE;AACF,iFAAiF;AACjF,iFAAiF;AACjF,gFAAgF;AAChF,sBAAsB;AAWtB,MAAM,UAAU,GAAG,6CAA6C,CAAC;
|
|
1
|
+
{"version":3,"file":"svgGeometry.js","sourceRoot":"","sources":["../../src/render/svgGeometry.ts"],"names":[],"mappings":"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"}
|
package/dist/render/watch.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import chokidar from "chokidar";
|
|
2
|
-
export declare function watchFile(
|
|
2
|
+
export declare function watchFile(paths: string | string[], onChange: () => void | Promise<void>, debounceMs?: number): chokidar.FSWatcher;
|
package/dist/render/watch.js
CHANGED
|
@@ -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(
|
|
5
|
-
|
|
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)
|
package/dist/render/watch.js.map
CHANGED
|
@@ -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,
|
|
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.
|
|
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",
|