@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
package/dist/render/render.d.ts
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
import { NormalizedModel } from "../model/model.js";
|
|
2
|
+
import { Grid } from "../layout/grid.js";
|
|
2
3
|
export declare function formatFromPath(outPath: string, fallback?: string): string;
|
|
3
|
-
|
|
4
|
+
/** Run the Graphviz WASM layout pass only — the part shared by every caller that
|
|
5
|
+
* needs to compose the same layout for more than one output location (a full
|
|
6
|
+
* diagram and a slice diagram of it, note hrefs recomputed for each — see
|
|
7
|
+
* buildCatalog and `em render --slice`), so it isn't paid for twice. */
|
|
8
|
+
export declare function layoutDot(dot: string): Promise<string>;
|
|
9
|
+
/**
|
|
10
|
+
* Compose a laid-out SVG with edges, notes, slice tags/metadata, and the legend —
|
|
11
|
+
* the pure/string half of rendering, split out from layoutDot's WASM call. `outDir`
|
|
12
|
+
* is where the composed SVG will ultimately be written; note links are authored
|
|
13
|
+
* relative to `baseDir` (the .em file) and get rewritten relative to `outDir` here,
|
|
14
|
+
* so a caller composing the *same* layout for two different output locations (e.g.
|
|
15
|
+
* a full diagram and a slice diagram one directory deeper) must call this twice,
|
|
16
|
+
* once per real outDir, rather than reuse one composed string for both.
|
|
17
|
+
*/
|
|
18
|
+
export declare function composeSvg(rawSvg: string, model: NormalizedModel, grid: Grid, baseDir: string, outDir: string): string;
|
|
19
|
+
/** Serialize a composed SVG string to `outPath` in the requested format — split out
|
|
20
|
+
* so a caller can transform the composed string in between composing and writing it. */
|
|
21
|
+
export declare function writeRendered(svg: string, outPath: string, format?: string): Promise<void>;
|
|
22
|
+
export declare function renderDot(dot: string, model: NormalizedModel, grid: Grid, outPath: string, format?: string, baseDir?: string): Promise<void>;
|
|
4
23
|
/**
|
|
5
24
|
* Resolve a note path (authored relative to the .em file) into an href relative
|
|
6
25
|
* to the output SVG's directory, so links stay valid wherever the SVG is
|
package/dist/render/render.js
CHANGED
|
@@ -14,7 +14,10 @@ import { Graphviz } from "@hpcc-js/wasm-graphviz";
|
|
|
14
14
|
import { Resvg } from "@resvg/resvg-js";
|
|
15
15
|
import { fitCanvas, parseNodeRects } from "./svgGeometry.js";
|
|
16
16
|
import { buildEdgeOverlay } from "./drawEdges.js";
|
|
17
|
-
import { buildNoteMarkers, buildIssueMarkers, appendNoteLegend } from "./drawNotes.js";
|
|
17
|
+
import { buildNoteMarkers, buildIssueMarkers, buildDivergenceMarkers, appendNoteLegend } from "./drawNotes.js";
|
|
18
|
+
import { sliceOverlayIds, tagSliceAttrs, buildSliceOverlay } from "./sliceOverlay.js";
|
|
19
|
+
import { readSliceStatuses } from "./sliceStatus.js";
|
|
20
|
+
import { applyStatusColors, appendStatusLegend } from "./statusOverlay.js";
|
|
18
21
|
const RSVG_BIN = process.env.EM_RSVG || "rsvg-convert";
|
|
19
22
|
// The WASM Graphviz module is loaded once and reused across renders.
|
|
20
23
|
let graphvizModule;
|
|
@@ -25,13 +28,31 @@ export function formatFromPath(outPath, fallback = "svg") {
|
|
|
25
28
|
const ext = extname(outPath).replace(/^\./, "").toLowerCase();
|
|
26
29
|
return ext || fallback;
|
|
27
30
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
/** Run the Graphviz WASM layout pass only — the part shared by every caller that
|
|
32
|
+
* needs to compose the same layout for more than one output location (a full
|
|
33
|
+
* diagram and a slice diagram of it, note hrefs recomputed for each — see
|
|
34
|
+
* buildCatalog and `em render --slice`), so it isn't paid for twice. */
|
|
35
|
+
export async function layoutDot(dot) {
|
|
33
36
|
const gv = await graphviz();
|
|
34
|
-
|
|
37
|
+
return gv.layout(dot, "svg", "dot");
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Compose a laid-out SVG with edges, notes, slice tags/metadata, and the legend —
|
|
41
|
+
* the pure/string half of rendering, split out from layoutDot's WASM call. `outDir`
|
|
42
|
+
* is where the composed SVG will ultimately be written; note links are authored
|
|
43
|
+
* relative to `baseDir` (the .em file) and get rewritten relative to `outDir` here,
|
|
44
|
+
* so a caller composing the *same* layout for two different output locations (e.g.
|
|
45
|
+
* a full diagram and a slice diagram one directory deeper) must call this twice,
|
|
46
|
+
* once per real outDir, rather than reuse one composed string for both.
|
|
47
|
+
*/
|
|
48
|
+
export function composeSvg(rawSvg, model, grid, baseDir, outDir) {
|
|
49
|
+
const hrefOf = (el) => noteHref(el.note ?? "", baseDir, outDir);
|
|
50
|
+
const statuses = readSliceStatuses(model, baseDir);
|
|
51
|
+
return withOverlays(rawSvg, model, grid, hrefOf, statuses);
|
|
52
|
+
}
|
|
53
|
+
/** Serialize a composed SVG string to `outPath` in the requested format — split out
|
|
54
|
+
* so a caller can transform the composed string in between composing and writing it. */
|
|
55
|
+
export async function writeRendered(svg, outPath, format = formatFromPath(outPath)) {
|
|
35
56
|
if (format === "svg") {
|
|
36
57
|
await writeFile(outPath, svg, "utf8");
|
|
37
58
|
return;
|
|
@@ -49,6 +70,12 @@ export async function renderDot(dot, model, outPath, format = formatFromPath(out
|
|
|
49
70
|
throw new Error(`format '${format}' is not built in (svg and png are). Install librsvg ` +
|
|
50
71
|
`(provides '${RSVG_BIN}') to render '${format}', or use -o file.svg / -o file.png.`);
|
|
51
72
|
}
|
|
73
|
+
export async function renderDot(dot, model, grid, outPath, format = formatFromPath(outPath), baseDir = process.cwd()) {
|
|
74
|
+
const outDir = dirname(outPath);
|
|
75
|
+
const raw = await layoutDot(dot);
|
|
76
|
+
const svg = composeSvg(raw, model, grid, baseDir, outDir);
|
|
77
|
+
await writeRendered(svg, outPath, format);
|
|
78
|
+
}
|
|
52
79
|
/**
|
|
53
80
|
* Resolve a note path (authored relative to the .em file) into an href relative
|
|
54
81
|
* to the output SVG's directory, so links stay valid wherever the SVG is
|
|
@@ -63,28 +90,39 @@ export function noteHref(note, baseDir, outDir) {
|
|
|
63
90
|
return rel.split(sep).join("/"); // posix separators for URLs
|
|
64
91
|
}
|
|
65
92
|
/** Draw the semantic edges and note markers into a Graphviz-rendered SVG. */
|
|
66
|
-
function withOverlays(svg, model, hrefOf) {
|
|
67
|
-
const rects = parseNodeRects(svg, new Set(model.byId.keys()));
|
|
93
|
+
function withOverlays(svg, model, grid, hrefOf, statuses) {
|
|
94
|
+
const rects = parseNodeRects(svg, new Set([...model.byId.keys(), ...sliceOverlayIds(grid)]));
|
|
68
95
|
const { defs, group, bbox } = buildEdgeOverlay(model, rects);
|
|
69
96
|
const notes = buildNoteMarkers(model, rects, hrefOf);
|
|
70
97
|
const issues = buildIssueMarkers(model, rects);
|
|
98
|
+
const divergences = buildDivergenceMarkers(model, rects);
|
|
71
99
|
// an edge detour can run outside the box grid Graphviz sized the canvas for, so make
|
|
72
100
|
// room before anything else measures or appends to the viewBox
|
|
73
101
|
let out = fitCanvas(svg, bbox);
|
|
74
|
-
//
|
|
75
|
-
|
|
102
|
+
// recolor each slice's header cell per its doc status (src/render/sliceStatus.ts) —
|
|
103
|
+
// a scoped string replace keyed on each header's own <title> id, independent of
|
|
104
|
+
// tagSliceAttrs/buildSliceOverlay below, so the ordering here isn't load-bearing.
|
|
105
|
+
out = applyStatusColors(out, grid, statuses);
|
|
106
|
+
// tag each element's node group with data-slice="<index>" so the storyboard
|
|
107
|
+
// viewer (`em watch --serve`) can highlight/zoom to one slice at a time
|
|
108
|
+
// client-side, purely from what's already in the SVG.
|
|
109
|
+
out = tagSliceAttrs(out, model);
|
|
110
|
+
const sliceOverlay = buildSliceOverlay(out, grid, rects);
|
|
111
|
+
// arrowhead markers + slice metadata/style go just inside <svg …>
|
|
112
|
+
out = out.replace(/(<svg\b[^>]*>)/, `$1${defs}${sliceOverlay}`);
|
|
76
113
|
// edges go under the boxes: just before the first node group
|
|
77
114
|
const nodeAt = out.search(/<g\b[^>]*class="node"[^>]*>/);
|
|
78
115
|
if (nodeAt >= 0)
|
|
79
116
|
out = out.slice(0, nodeAt) + group + out.slice(nodeAt);
|
|
80
117
|
else
|
|
81
118
|
out = out.replace(/<\/svg>/, `${group}</svg>`);
|
|
82
|
-
// note/issue markers go on top of the boxes — inside the graph transform
|
|
83
|
-
// (so they share the box coordinate space) but after every node, as the last
|
|
119
|
+
// note/issue/divergence markers go on top of the boxes — inside the graph transform
|
|
120
|
+
// group (so they share the box coordinate space) but after every node, as the last
|
|
84
121
|
// children of that group, making them the topmost clickable layer.
|
|
85
|
-
out = out.replace(/(<\/g>\s*)(<\/svg>)/, `${notes}${issues}$1$2`);
|
|
122
|
+
out = out.replace(/(<\/g>\s*)(<\/svg>)/, `${notes}${issues}${divergences}$1$2`);
|
|
86
123
|
// grow the canvas and append the legend below the diagram (root coords)
|
|
87
124
|
out = appendNoteLegend(out, model, hrefOf);
|
|
125
|
+
out = appendStatusLegend(out, grid, statuses);
|
|
88
126
|
return out;
|
|
89
127
|
}
|
|
90
128
|
/** Convert an SVG string to the requested format with an optional system rsvg-convert. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/render/render.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,+BAA+B;AAC/B,EAAE;AACF,+EAA+E;AAC/E,iFAAiF;AACjF,6EAA6E;AAC7E,+EAA+E;AAC/E,gFAAgF;AAChF,sEAAsE;AAEtE,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACjF,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/render/render.ts"],"names":[],"mappings":"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"}
|
package/dist/render/serve.js
CHANGED
|
@@ -135,6 +135,14 @@ async function serveStatic(pathname, root, res) {
|
|
|
135
135
|
// Served viewer page. Same double-buffered, no-flicker swap as the static
|
|
136
136
|
// live.html fallback, but driven by SSE push instead of a poll loop — the
|
|
137
137
|
// browser reloads only when the server says the SVG actually changed.
|
|
138
|
+
//
|
|
139
|
+
// Also carries stakeholder review mode: a Prev/Next storyboard that highlights
|
|
140
|
+
// and pans/zooms to one slice at a time, reading the `data-slice`
|
|
141
|
+
// attributes/`<metadata id="em-slices">` that src/render/sliceOverlay.ts
|
|
142
|
+
// embeds in every rendered SVG. Review-mode state (`reviewMode`/`activeSlice`)
|
|
143
|
+
// deliberately lives outside reload()'s closure and is re-applied after every
|
|
144
|
+
// SSE-triggered swap — a facilitator hand-editing an `issue "..."` clause live
|
|
145
|
+
// during a session should see it appear without losing their place.
|
|
138
146
|
const VIEWER_HTML = `<!doctype html>
|
|
139
147
|
<html lang="en">
|
|
140
148
|
<head>
|
|
@@ -150,6 +158,26 @@ const VIEWER_HTML = `<!doctype html>
|
|
|
150
158
|
header .dot { width: 9px; height: 9px; border-radius: 50%; background: #34c759; transition: background .3s; }
|
|
151
159
|
header .dot.stale { background: #f0a020; }
|
|
152
160
|
header .stamp { margin-left: auto; opacity: .7; font-variant-numeric: tabular-nums; }
|
|
161
|
+
.btn {
|
|
162
|
+
background: #3b4754; color: #fff; border: 0; border-radius: 4px;
|
|
163
|
+
padding: .3rem .7rem; font-size: 13px; cursor: pointer; font-family: inherit;
|
|
164
|
+
}
|
|
165
|
+
.btn:hover { background: #4b5763; }
|
|
166
|
+
.btn:disabled { opacity: .4; cursor: default; }
|
|
167
|
+
.btn.active { background: #2f6fed; }
|
|
168
|
+
#storyboard {
|
|
169
|
+
display: flex; align-items: center; gap: .5rem; padding: .4rem .9rem;
|
|
170
|
+
background: #e8eaed; border-bottom: 1px solid #d0d3d7;
|
|
171
|
+
}
|
|
172
|
+
#storyboard[hidden] { display: none; }
|
|
173
|
+
#storyboard .nav { background: none; border: 0; font-size: 18px; line-height: 1; cursor: pointer; color: #3b4754; padding: 0 .3rem; }
|
|
174
|
+
#storyboard .nav:disabled { opacity: .3; cursor: default; }
|
|
175
|
+
#filmstrip { display: flex; gap: .35rem; overflow-x: auto; }
|
|
176
|
+
#filmstrip .slice {
|
|
177
|
+
padding: .25rem .6rem; border-radius: 12px; background: #fff; border: 1px solid #d0d3d7;
|
|
178
|
+
font-size: 12px; white-space: nowrap; cursor: pointer; color: #3b4754; font-family: inherit;
|
|
179
|
+
}
|
|
180
|
+
#filmstrip .slice.active { background: #2f6fed; border-color: #2f6fed; color: #fff; font-weight: 600; }
|
|
153
181
|
#stage { padding: 1rem; }
|
|
154
182
|
object { width: 100%; height: calc(100vh - 60px); border: 0; }
|
|
155
183
|
</style>
|
|
@@ -158,8 +186,14 @@ const VIEWER_HTML = `<!doctype html>
|
|
|
158
186
|
<header>
|
|
159
187
|
<span class="dot" id="dot"></span>
|
|
160
188
|
<span>Event Model — live (push)</span>
|
|
189
|
+
<button class="btn" id="reviewToggle" title="Step through one slice at a time">Review mode</button>
|
|
161
190
|
<span class="stamp" id="stamp">connecting…</span>
|
|
162
191
|
</header>
|
|
192
|
+
<nav id="storyboard" hidden>
|
|
193
|
+
<button class="nav" id="prevSlice" aria-label="Previous slice">‹</button>
|
|
194
|
+
<div id="filmstrip"></div>
|
|
195
|
+
<button class="nav" id="nextSlice" aria-label="Next slice">›</button>
|
|
196
|
+
</nav>
|
|
163
197
|
<div id="stage">
|
|
164
198
|
<object id="svg" type="image/svg+xml"></object>
|
|
165
199
|
</div>
|
|
@@ -170,8 +204,21 @@ const VIEWER_HTML = `<!doctype html>
|
|
|
170
204
|
const stage = document.getElementById("stage");
|
|
171
205
|
const stamp = document.getElementById("stamp");
|
|
172
206
|
const dot = document.getElementById("dot");
|
|
207
|
+
const reviewToggle = document.getElementById("reviewToggle");
|
|
208
|
+
const storyboard = document.getElementById("storyboard");
|
|
209
|
+
const filmstrip = document.getElementById("filmstrip");
|
|
210
|
+
const prevBtn = document.getElementById("prevSlice");
|
|
211
|
+
const nextBtn = document.getElementById("nextSlice");
|
|
173
212
|
let current = document.getElementById("svg");
|
|
174
213
|
|
|
214
|
+
// --- storyboard state: outside reload()'s closure on purpose, see the
|
|
215
|
+
// file-level comment above — an SSE reload must not reset these. ---
|
|
216
|
+
let reviewMode = false;
|
|
217
|
+
let activeSlice = null; // slice index, or null for "show all"
|
|
218
|
+
let slices = []; // [{index, name, x0, x1}], from this load's <metadata>
|
|
219
|
+
let rowLabels = null; // {x0, x1} | null, the swimlane label column's range
|
|
220
|
+
let naturalViewBox = null; // this load's pre-zoom viewBox, cached once per load
|
|
221
|
+
|
|
175
222
|
// Double-buffer: load into a hidden <object>, swap on load, so the shared
|
|
176
223
|
// screen never flashes white during a reload. Keep <object> (not <img>) so
|
|
177
224
|
// note "..." links inside the SVG stay clickable.
|
|
@@ -185,10 +232,118 @@ const VIEWER_HTML = `<!doctype html>
|
|
|
185
232
|
current = next;
|
|
186
233
|
stamp.textContent = new Date().toLocaleTimeString();
|
|
187
234
|
dot.classList.remove("stale");
|
|
235
|
+
onSvgLoaded();
|
|
188
236
|
}, { once: true });
|
|
189
237
|
next.setAttribute("data", SVG_FILE + "?t=" + Date.now());
|
|
190
238
|
}
|
|
191
239
|
|
|
240
|
+
function readSliceMeta(doc) {
|
|
241
|
+
const el = doc.getElementById("em-slices");
|
|
242
|
+
if (!el) return null;
|
|
243
|
+
try { return JSON.parse(el.textContent); } catch { return null; }
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// The currently-loaded SVG's own document + root element, or null before
|
|
247
|
+
// the first load / if the served file isn't a well-formed SVG.
|
|
248
|
+
function currentSvgDoc() {
|
|
249
|
+
const doc = current.contentDocument;
|
|
250
|
+
const svgRoot = doc && doc.querySelector("svg");
|
|
251
|
+
return doc && svgRoot ? { doc, svgRoot } : null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Re-read this load's slice metadata + natural viewBox, then reapply
|
|
255
|
+
// whatever storyboard state was already in effect (a fresh SSE push mid-
|
|
256
|
+
// review shouldn't reset the facilitator's position).
|
|
257
|
+
function onSvgLoaded() {
|
|
258
|
+
const loaded = currentSvgDoc();
|
|
259
|
+
if (!loaded) return;
|
|
260
|
+
|
|
261
|
+
naturalViewBox = loaded.svgRoot.getAttribute("viewBox");
|
|
262
|
+
const meta = readSliceMeta(loaded.doc);
|
|
263
|
+
slices = (meta && meta.slices) || [];
|
|
264
|
+
rowLabels = (meta && meta.rowLabels) || null;
|
|
265
|
+
// The model may have changed shape (slice added/removed) between loads.
|
|
266
|
+
if (activeSlice !== null && !slices.some((s) => s.index === activeSlice)) {
|
|
267
|
+
activeSlice = slices.length ? slices[0].index : null;
|
|
268
|
+
}
|
|
269
|
+
render();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Apply reviewMode/activeSlice to the currently-loaded SVG + the control bar.
|
|
273
|
+
// Safe to call directly from UI handlers (Prev/Next/toggle/arrow keys) — it
|
|
274
|
+
// never re-reads metadata, just re-applies the cached state.
|
|
275
|
+
function render() {
|
|
276
|
+
storyboard.hidden = slices.length === 0;
|
|
277
|
+
reviewToggle.classList.toggle("active", reviewMode);
|
|
278
|
+
reviewToggle.disabled = slices.length === 0;
|
|
279
|
+
renderFilmstrip();
|
|
280
|
+
|
|
281
|
+
const loaded = currentSvgDoc();
|
|
282
|
+
if (!loaded) return;
|
|
283
|
+
const { doc, svgRoot } = loaded;
|
|
284
|
+
|
|
285
|
+
doc.querySelectorAll("[data-slice]").forEach((node) => {
|
|
286
|
+
const dim = reviewMode && activeSlice !== null &&
|
|
287
|
+
Number(node.getAttribute("data-slice")) !== activeSlice;
|
|
288
|
+
node.classList.toggle("em-slice-dim", dim);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
if (!naturalViewBox) return;
|
|
292
|
+
if (!reviewMode || activeSlice === null) {
|
|
293
|
+
svgRoot.setAttribute("viewBox", naturalViewBox);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const slice = slices.find((s) => s.index === activeSlice);
|
|
297
|
+
if (!slice) return;
|
|
298
|
+
const [nx, ny, nw, nh] = naturalViewBox.split(/\\s+/).map(Number);
|
|
299
|
+
const PAD = 24; // breathing room around the framed slice
|
|
300
|
+
let x0 = rowLabels ? Math.min(rowLabels.x0, slice.x0) : slice.x0;
|
|
301
|
+
let x1 = slice.x1;
|
|
302
|
+
x0 = Math.max(nx, x0 - PAD);
|
|
303
|
+
x1 = Math.min(nx + nw, x1 + PAD);
|
|
304
|
+
svgRoot.setAttribute("viewBox", x0 + " " + ny + " " + Math.max(1, x1 - x0) + " " + nh);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function renderFilmstrip() {
|
|
308
|
+
filmstrip.innerHTML = "";
|
|
309
|
+
for (const s of slices) {
|
|
310
|
+
const b = document.createElement("button");
|
|
311
|
+
b.className = "slice" + (s.index === activeSlice ? " active" : "");
|
|
312
|
+
b.textContent = s.name;
|
|
313
|
+
b.addEventListener("click", () => goTo(s.index));
|
|
314
|
+
filmstrip.appendChild(b);
|
|
315
|
+
}
|
|
316
|
+
const first = slices[0], last = slices[slices.length - 1];
|
|
317
|
+
prevBtn.disabled = activeSlice === null || !first || activeSlice <= first.index;
|
|
318
|
+
nextBtn.disabled = activeSlice === null || !last || activeSlice >= last.index;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function goTo(index) {
|
|
322
|
+
activeSlice = index;
|
|
323
|
+
render();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function step(delta) {
|
|
327
|
+
if (!slices.length) return;
|
|
328
|
+
if (activeSlice === null) { goTo(slices[0].index); return; }
|
|
329
|
+
const i = slices.findIndex((s) => s.index === activeSlice);
|
|
330
|
+
const next = slices[Math.min(slices.length - 1, Math.max(0, i + delta))];
|
|
331
|
+
if (next) goTo(next.index);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
reviewToggle.addEventListener("click", () => {
|
|
335
|
+
reviewMode = !reviewMode;
|
|
336
|
+
if (reviewMode && activeSlice === null && slices.length) activeSlice = slices[0].index;
|
|
337
|
+
render();
|
|
338
|
+
});
|
|
339
|
+
prevBtn.addEventListener("click", () => step(-1));
|
|
340
|
+
nextBtn.addEventListener("click", () => step(1));
|
|
341
|
+
document.addEventListener("keydown", (e) => {
|
|
342
|
+
if (!reviewMode) return;
|
|
343
|
+
if (e.key === "ArrowLeft") step(-1);
|
|
344
|
+
else if (e.key === "ArrowRight") step(1);
|
|
345
|
+
});
|
|
346
|
+
|
|
192
347
|
reload();
|
|
193
348
|
|
|
194
349
|
const es = new EventSource("/__events");
|
package/dist/render/serve.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve.js","sourceRoot":"","sources":["../../src/render/serve.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"serve.js","sourceRoot":"","sources":["../../src/render/serve.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { NormalizedModel } from "../model/model.js";
|
|
2
|
+
import { Grid, LayoutOptions } from "../layout/grid.js";
|
|
3
|
+
export interface SliceDiagramResult {
|
|
4
|
+
model: NormalizedModel;
|
|
5
|
+
grid: Grid;
|
|
6
|
+
dot: string;
|
|
7
|
+
}
|
|
8
|
+
/** Build a small, standalone diagram for one slice — real Graphviz layout, not a crop.
|
|
9
|
+
* `opts` is forwarded to layout() unchanged, so `--keep-empty-lanes` behaves the same
|
|
10
|
+
* way here as it does for the full model instead of being silently dropped. */
|
|
11
|
+
export declare function buildSliceDiagram(model: NormalizedModel, sliceIndex: number, opts?: LayoutOptions): SliceDiagramResult;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Renders a single slice's own diagram, matching the canonical shape of
|
|
3
|
+
// whichever of the 4 Event Modeling patterns it is (State Change, State View,
|
|
4
|
+
// Automation, Translation) — instead of cropping the full multi-slice diagram
|
|
5
|
+
// down to one column (which forces a narrow crop to still show a wide swimlane
|
|
6
|
+
// label, and can pull in most of a neighboring slice; see git history).
|
|
7
|
+
//
|
|
8
|
+
// The key trick: every piece of "immediate context" a slice needs to draw its
|
|
9
|
+
// own shape is already one hop away in the compiled model — a view's upstream
|
|
10
|
+
// event(s) resolve via `from` (resolveFromSource, shared with semanticEdges),
|
|
11
|
+
// and an automation/translation's watched view is just whatever `view` sits in
|
|
12
|
+
// the same slice. So this builds a tiny *synthetic* NormalizedModel — reusing
|
|
13
|
+
// the real, already-normalized Element objects (same ids, same fields, same
|
|
14
|
+
// notes), shallow-cloned only to reassign which of a handful of new columns
|
|
15
|
+
// they land in — and runs it through the exact same layout()/emitDot() the
|
|
16
|
+
// full model uses, unmodified. Edge-drawing comes free too: semanticEdges()
|
|
17
|
+
// resolves `from` via byName, so as long as the synthetic model's byName map
|
|
18
|
+
// is populated, the arrows get drawn by the existing inference with zero new
|
|
19
|
+
// edge-drawing code.
|
|
20
|
+
//
|
|
21
|
+
// This never chases more than one hop away (previous slice for a triggerless
|
|
22
|
+
// State Change; a view's own `from` sources for State View; the next slice for
|
|
23
|
+
// Automation/Translation — semanticEdges itself never looks further than i+1),
|
|
24
|
+
// so a slice diagram can never balloon into showing most of the model, the way
|
|
25
|
+
// a naive crop or a naive "just extract this slice and recompile" both could.
|
|
26
|
+
import { AUTOMATION_KINDS } from "../parser/ast.js";
|
|
27
|
+
import { normalizeName } from "../model/model.js";
|
|
28
|
+
import { resolveFromSource } from "../model/edges.js";
|
|
29
|
+
import { layout } from "../layout/grid.js";
|
|
30
|
+
import { emitDot } from "../emit/dot.js";
|
|
31
|
+
import { classifySlicePattern } from "../catalog/classify.js";
|
|
32
|
+
/** Build a small, standalone diagram for one slice — real Graphviz layout, not a crop.
|
|
33
|
+
* `opts` is forwarded to layout() unchanged, so `--keep-empty-lanes` behaves the same
|
|
34
|
+
* way here as it does for the full model instead of being silently dropped. */
|
|
35
|
+
export function buildSliceDiagram(model, sliceIndex, opts = {}) {
|
|
36
|
+
const slice = model.slices[sliceIndex];
|
|
37
|
+
const pattern = classifySlicePattern(slice);
|
|
38
|
+
const columns = mergeColumns(collectColumns(model, sliceIndex, pattern));
|
|
39
|
+
const synthetic = assembleSyntheticModel(model, columns, slice.name);
|
|
40
|
+
const grid = layout(synthetic, opts);
|
|
41
|
+
const dot = emitDot(synthetic, grid);
|
|
42
|
+
return { model: synthetic, grid, dot };
|
|
43
|
+
}
|
|
44
|
+
function collectColumns(model, sliceIndex, pattern) {
|
|
45
|
+
switch (pattern) {
|
|
46
|
+
case "state-change":
|
|
47
|
+
return columnsForStateChange(model, sliceIndex);
|
|
48
|
+
case "state-view":
|
|
49
|
+
return columnsForStateView(model, sliceIndex);
|
|
50
|
+
case "automation":
|
|
51
|
+
case "translation":
|
|
52
|
+
return columnsForReaction(model, sliceIndex);
|
|
53
|
+
default: // "unclassified"
|
|
54
|
+
return [{ sourceSliceIndex: sliceIndex, elements: model.slices[sliceIndex].elements }];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** State Change: UI -> Command -> Event, all in this slice. If there's no local
|
|
58
|
+
* UI, the command's real trigger is the previous slice's automation/translation
|
|
59
|
+
* reaction — pull in just that one element as earlier context. */
|
|
60
|
+
function columnsForStateChange(model, i) {
|
|
61
|
+
const slice = model.slices[i];
|
|
62
|
+
const hasUi = slice.elements.some((e) => e.kind === "ui");
|
|
63
|
+
const cols = [{ sourceSliceIndex: i, elements: slice.elements }];
|
|
64
|
+
if (!hasUi) {
|
|
65
|
+
const prev = model.slices[i - 1];
|
|
66
|
+
const trigger = prev?.elements.find((e) => AUTOMATION_KINDS.has(e.kind));
|
|
67
|
+
if (trigger)
|
|
68
|
+
cols.push({ sourceSliceIndex: i - 1, elements: [trigger] });
|
|
69
|
+
}
|
|
70
|
+
return cols;
|
|
71
|
+
}
|
|
72
|
+
/** State View: Event(s) -> View -> UI. Each view's `from` names resolve to their
|
|
73
|
+
* real source Elements (same rule semanticEdges uses) — one column per distinct
|
|
74
|
+
* origin slice, so a multi-source view (e.g. a dashboard reading two events from
|
|
75
|
+
* two different, possibly far-apart slices) naturally gets one column per source,
|
|
76
|
+
* not a single blown-out column. */
|
|
77
|
+
function columnsForStateView(model, i) {
|
|
78
|
+
const slice = model.slices[i];
|
|
79
|
+
const cols = [];
|
|
80
|
+
for (const view of slice.elements.filter((e) => e.kind === "view")) {
|
|
81
|
+
for (const name of view.from ?? []) {
|
|
82
|
+
const src = resolveFromSource(model, view, name);
|
|
83
|
+
if (src)
|
|
84
|
+
cols.push({ sourceSliceIndex: src.sliceIndex, elements: [src] });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
cols.push({ sourceSliceIndex: i, elements: slice.elements });
|
|
88
|
+
return cols;
|
|
89
|
+
}
|
|
90
|
+
/** Automation/Translation share one shape: [View ->] Automation (this slice),
|
|
91
|
+
* then Command -> Event (the immediately next slice — never further, mirroring
|
|
92
|
+
* semanticEdges' own i+1-only rule). The watched view is same-slice adjacency,
|
|
93
|
+
* not a `from` hop, so it's already included via this slice's own elements —
|
|
94
|
+
* deliberately not chased any further back. Only command/event are pulled from
|
|
95
|
+
* the next slice; a stray `ui` there belongs to *that* slice's own diagram. */
|
|
96
|
+
function columnsForReaction(model, i) {
|
|
97
|
+
const slice = model.slices[i];
|
|
98
|
+
const cols = [{ sourceSliceIndex: i, elements: slice.elements }];
|
|
99
|
+
const next = model.slices[i + 1];
|
|
100
|
+
const forward = next?.elements.filter((e) => e.kind === "command" || e.kind === "event") ?? [];
|
|
101
|
+
if (forward.length > 0)
|
|
102
|
+
cols.push({ sourceSliceIndex: i + 1, elements: forward });
|
|
103
|
+
return cols;
|
|
104
|
+
}
|
|
105
|
+
/** Merge columns sharing a source slice (two `from` names can resolve to the same
|
|
106
|
+
* origin slice), dedupe elements by id, then sort chronologically. `from`/reaction
|
|
107
|
+
* wiring only ever points backward or to the immediate next slice, so a plain
|
|
108
|
+
* ascending sort by real slice index is sufficient for correct left-to-right
|
|
109
|
+
* placement across every pattern — no pattern-specific ordering code needed. */
|
|
110
|
+
function mergeColumns(columns) {
|
|
111
|
+
const bySource = new Map();
|
|
112
|
+
for (const col of columns) {
|
|
113
|
+
let elements = bySource.get(col.sourceSliceIndex);
|
|
114
|
+
if (!elements) {
|
|
115
|
+
elements = new Map();
|
|
116
|
+
bySource.set(col.sourceSliceIndex, elements);
|
|
117
|
+
}
|
|
118
|
+
for (const el of col.elements)
|
|
119
|
+
elements.set(el.id, el);
|
|
120
|
+
}
|
|
121
|
+
return [...bySource.entries()]
|
|
122
|
+
.map(([sourceSliceIndex, elements]) => ({ sourceSliceIndex, elements: [...elements.values()] }))
|
|
123
|
+
.sort((a, b) => a.sourceSliceIndex - b.sourceSliceIndex);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Assemble a small, valid NormalizedModel from the given columns — real Element
|
|
127
|
+
* objects, shallow-cloned only to reassign `sliceIndex` to their new synthetic
|
|
128
|
+
* column (never mutating the real model). Two details matter for correctness:
|
|
129
|
+
*
|
|
130
|
+
* - `byId` keeps each clone's *original* id — everything downstream (emitDot's
|
|
131
|
+
* node ids, styleFor, note markers, parseNodeRects) keys off Element.id, so
|
|
132
|
+
* preserving it is what keeps the whole rest of the render pipeline working
|
|
133
|
+
* completely unmodified.
|
|
134
|
+
* - `byName` is populated from the clones actually included, using their
|
|
135
|
+
* untouched `name`/`from` strings — this is the entire trick that makes edge
|
|
136
|
+
* drawing free: semanticEdges() (called later, unmodified, inside
|
|
137
|
+
* composeSvg) resolves `from` via byName exactly as it does for the full
|
|
138
|
+
* model, so it finds the clones and draws the arrows automatically.
|
|
139
|
+
*
|
|
140
|
+
* `personas`/`contexts` are derived from only the included elements, not
|
|
141
|
+
* copied from the full model — copying them was the exact class of bug that
|
|
142
|
+
* made the previous crop-based approach barely narrower than the full diagram
|
|
143
|
+
* (an unused row still reserves its own swimlane).
|
|
144
|
+
*/
|
|
145
|
+
function assembleSyntheticModel(model, columns, title) {
|
|
146
|
+
const slices = [];
|
|
147
|
+
const elements = [];
|
|
148
|
+
const byId = new Map();
|
|
149
|
+
const byName = new Map();
|
|
150
|
+
columns.forEach((col, syntheticIndex) => {
|
|
151
|
+
const origin = model.slices[col.sourceSliceIndex];
|
|
152
|
+
const cloned = col.elements.map((el) => ({ ...el, sliceIndex: syntheticIndex }));
|
|
153
|
+
slices.push({ name: origin.name, index: syntheticIndex, source: origin.source, elements: cloned, line: origin.line });
|
|
154
|
+
for (const el of cloned) {
|
|
155
|
+
elements.push(el);
|
|
156
|
+
byId.set(el.id, el);
|
|
157
|
+
const key = normalizeName(el.name);
|
|
158
|
+
const bucket = byName.get(key);
|
|
159
|
+
if (bucket)
|
|
160
|
+
bucket.push(el);
|
|
161
|
+
else
|
|
162
|
+
byName.set(key, [el]);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
return {
|
|
166
|
+
name: title,
|
|
167
|
+
personas: dedupeDefined(elements.filter((e) => e.kind === "ui").map((e) => e.persona)),
|
|
168
|
+
contexts: dedupeDefined(elements.filter((e) => e.kind === "event").map((e) => e.context)),
|
|
169
|
+
hasAutomation: elements.some((e) => AUTOMATION_KINDS.has(e.kind)),
|
|
170
|
+
slices,
|
|
171
|
+
elements,
|
|
172
|
+
byId,
|
|
173
|
+
byName,
|
|
174
|
+
// v1 gap, deliberate: explicit DSL `arrow` statements are rare (they exist for
|
|
175
|
+
// relationships semanticEdges' pattern inference can't derive on its own) and
|
|
176
|
+
// dropping them from a single-slice extraction is narrow, not silent — a
|
|
177
|
+
// follow-up could remap model.arrows entries whose both endpoints resolve
|
|
178
|
+
// through an original-id -> cloned-id map, but it's not needed to ship this.
|
|
179
|
+
arrows: [],
|
|
180
|
+
// Unused by layout()/emitDot() — fieldLabel() prints a field's type as a raw
|
|
181
|
+
// string, never resolves it — so passthrough is free and avoids handing back
|
|
182
|
+
// broken/empty maps for no benefit.
|
|
183
|
+
types: model.types,
|
|
184
|
+
typesByName: model.typesByName,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function dedupeDefined(values) {
|
|
188
|
+
const seen = new Set();
|
|
189
|
+
const out = [];
|
|
190
|
+
for (const v of values) {
|
|
191
|
+
if (v && !seen.has(v)) {
|
|
192
|
+
seen.add(v);
|
|
193
|
+
out.push(v);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return out;
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=sliceDiagram.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sliceDiagram.js","sourceRoot":"","sources":["../../src/render/sliceDiagram.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { NormalizedModel } from "../model/model.js";
|
|
2
|
+
import { Grid } from "../layout/grid.js";
|
|
3
|
+
import { Rect } from "./svgGeometry.js";
|
|
4
|
+
/** Every non-element node id the slice overlay needs a rect for (header cells),
|
|
5
|
+
* to fold into the same parseNodeRects() call as element ids. */
|
|
6
|
+
export declare function sliceOverlayIds(grid: Grid): string[];
|
|
7
|
+
/** Splice `data-slice="<index>"` onto every element's node group. */
|
|
8
|
+
export declare function tagSliceAttrs(svg: string, model: NormalizedModel): string;
|
|
9
|
+
/** `<metadata>` (slice ranges, as JSON) + `<style>` (the dim class) block, spliced
|
|
10
|
+
* just inside the root `<svg>`. `svg` is scanned (post-fitCanvas/tagSliceAttrs is
|
|
11
|
+
* fine — neither touches `<text>` elements) for the row-label column's x-range.
|
|
12
|
+
* Returns "" if no slice has a rect — the model is empty, or the SVG shape wasn't
|
|
13
|
+
* what parseNodeRects expected.
|
|
14
|
+
*
|
|
15
|
+
* rects (from parseNodeRects) and the row-label `<text>` x positions scanned below
|
|
16
|
+
* are both in graph-space — the coordinate system *inside* Graphviz's
|
|
17
|
+
* `<g class="graph" transform="scale(...) translate(...)">` wrapper — not the root
|
|
18
|
+
* SVG/viewBox space the served viewer sets `viewBox` in. Every x0/x1 here goes
|
|
19
|
+
* through readGraphTransform()'s toRoot() before it's embedded, the same
|
|
20
|
+
* conversion fitCanvas() applies to the edge-detour bbox for the same reason. */
|
|
21
|
+
export declare function buildSliceOverlay(svg: string, grid: Grid, rects: Map<string, Rect>): string;
|