@milehimikey/em 1.1.1 → 1.3.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 +74 -25
- package/.claude/skills/event-modeling/reference/em-dsl.md +45 -8
- package/.claude/skills/event-modeling/reference/extract.md +124 -0
- package/.claude/skills/event-modeling/reference/methodology.md +31 -0
- package/.claude/skills/event-modeling/templates/live.html +65 -13
- package/.claude/skills/event-modeling/templates/model-readme.md +11 -6
- package/.claude/skills/event-modeling/templates/slice.md +11 -2
- package/.claude/skills/event-modeling/templates/state.md +29 -9
- package/README.md +45 -172
- package/dist/cli/diff-inputs.d.ts +44 -0
- package/dist/cli/diff-inputs.js +62 -0
- package/dist/cli/diff-inputs.js.map +1 -0
- package/dist/cli.js +150 -7
- package/dist/cli.js.map +1 -1
- package/dist/emit/json.d.ts +28 -0
- package/dist/emit/json.js +121 -0
- package/dist/emit/json.js.map +1 -0
- package/dist/model/diff.d.ts +52 -0
- package/dist/model/diff.js +351 -0
- package/dist/model/diff.js.map +1 -0
- package/dist/model/model.d.ts +2 -0
- package/dist/model/model.js +1 -0
- package/dist/model/model.js.map +1 -1
- package/dist/model/validate.js +74 -0
- package/dist/model/validate.js.map +1 -1
- package/dist/parser/ast.d.ts +2 -0
- package/dist/parser/parser.js +8 -0
- package/dist/parser/parser.js.map +1 -1
- package/dist/render/drawNotes.d.ts +8 -2
- package/dist/render/drawNotes.js +106 -42
- package/dist/render/drawNotes.js.map +1 -1
- package/dist/render/render.js +5 -4
- package/dist/render/render.js.map +1 -1
- package/dist/render/serve.d.ts +20 -0
- package/dist/render/serve.js +202 -0
- package/dist/render/serve.js.map +1 -0
- package/package.json +3 -3
package/dist/render/drawNotes.js
CHANGED
|
@@ -1,24 +1,39 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// Draws "has notes" affordances on top of a Graphviz-rendered SVG:
|
|
3
|
-
// - a small folded-corner (dog-ear) marker
|
|
4
|
-
//
|
|
5
|
-
// - a
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
2
|
+
// Draws "has notes" and "has issues" affordances on top of a Graphviz-rendered SVG:
|
|
3
|
+
// - a small folded-corner (dog-ear) marker per noted box, top-right, amber, carrying
|
|
4
|
+
// a footnote number, linked to the note file;
|
|
5
|
+
// - a small folded-corner marker per element with an open `issue`, top-left, red,
|
|
6
|
+
// carrying its own footnote number — no link (there's nothing to open), just a
|
|
7
|
+
// `<title>` tooltip, since the full text lives in the legend;
|
|
8
|
+
// - a legend appended below the diagram: a "Notes" section mapping each amber number
|
|
9
|
+
// to its element and note file, and (when present) an "Issues" section mapping each
|
|
10
|
+
// red number to its element and issue text.
|
|
11
|
+
// An element may carry both a note and an issue at once: the two markers sit on
|
|
12
|
+
// opposite top corners of the box, so they never overlap, and each keeps its own
|
|
13
|
+
// independent numbering (a "1" note and a "1" issue on the same box are unambiguous —
|
|
14
|
+
// distinct color, distinct corner, distinct legend section).
|
|
15
|
+
// In SVG the note markers/rows are anchors (click to open the markdown); issue
|
|
16
|
+
// markers/rows never are, since inline text has no file to link to. Raster output
|
|
17
|
+
// (PNG/PDF) can't carry links or tooltips either way, so the legend is what tells you
|
|
18
|
+
// which note or issue belongs to which element there.
|
|
10
19
|
//
|
|
11
20
|
// Marker coordinates come from parseNodeRects (the box coordinate space, inside
|
|
12
21
|
// Graphviz's transform group). The legend is laid out in the SVG's root/viewBox
|
|
13
22
|
// space, so it is appended after the diagram and the canvas is grown to fit.
|
|
14
23
|
const FOLD = 13; // leg length of the dog-ear, in user units
|
|
15
24
|
const INSET = 5; // pull off the bounding corner so it clears rounded box corners
|
|
16
|
-
const FILL = "#F4C430"; // sticky-note amber
|
|
25
|
+
const FILL = "#F4C430"; // sticky-note amber (notes)
|
|
17
26
|
const STROKE = "#7A5200"; // dark amber — reads on light and orange fills alike
|
|
27
|
+
const ISSUE_FILL = "#E53935"; // red sticky (open issues)
|
|
28
|
+
const ISSUE_STROKE = "#8B0000"; // dark red
|
|
18
29
|
/** Elements carrying a note, in document order. Index + 1 is the footnote number. */
|
|
19
30
|
export function notedElements(model) {
|
|
20
31
|
return model.elements.filter((el) => el.note);
|
|
21
32
|
}
|
|
33
|
+
/** Elements carrying an open issue, in document order. Index + 1 is the footnote number. */
|
|
34
|
+
export function issuedElements(model) {
|
|
35
|
+
return model.elements.filter((el) => el.issue);
|
|
36
|
+
}
|
|
22
37
|
const rawNote = (el) => el.note ?? "";
|
|
23
38
|
/** SVG group with a numbered corner marker per noted element (inside the graph group). */
|
|
24
39
|
export function buildNoteMarkers(model, rects, hrefOf = rawNote) {
|
|
@@ -27,30 +42,53 @@ export function buildNoteMarkers(model, rects, hrefOf = rawNote) {
|
|
|
27
42
|
const r = rects.get(el.id);
|
|
28
43
|
if (!r)
|
|
29
44
|
return;
|
|
30
|
-
markers.push(
|
|
45
|
+
markers.push(noteMarker(r, hrefOf(el), i + 1));
|
|
31
46
|
});
|
|
32
47
|
return `<g class="em-notes">${markers.join("")}</g>`;
|
|
33
48
|
}
|
|
34
|
-
/**
|
|
35
|
-
function
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
49
|
+
/** SVG group with a numbered corner marker per element with an open issue. */
|
|
50
|
+
export function buildIssueMarkers(model, rects) {
|
|
51
|
+
const markers = [];
|
|
52
|
+
issuedElements(model).forEach((el, i) => {
|
|
53
|
+
const r = rects.get(el.id);
|
|
54
|
+
if (!r)
|
|
55
|
+
return;
|
|
56
|
+
markers.push(issueMarker(r, el.issue ?? "", i + 1));
|
|
57
|
+
});
|
|
58
|
+
return `<g class="em-issues">${markers.join("")}</g>`;
|
|
59
|
+
}
|
|
60
|
+
/** The dog-ear glyph + footnote number, anchored at one top corner of the box. */
|
|
61
|
+
function cornerGlyph(r, num, corner, fill, stroke) {
|
|
40
62
|
const y = r.top + INSET; // y grows downward, so this nudges down from the top edge
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
const
|
|
63
|
+
const x = corner === "right" ? r.right - INSET : r.left + INSET;
|
|
64
|
+
const foldX = corner === "right" ? x - FOLD : x + FOLD;
|
|
65
|
+
const tri = `M${n(foldX)},${n(y)} L${n(x)},${n(y)} L${n(x)},${n(y + FOLD)} Z`;
|
|
66
|
+
const crease = `M${n(foldX)},${n(y)} L${n(x)},${n(y + FOLD)}`;
|
|
67
|
+
const numX = corner === "right" ? foldX - 2 : foldX + 2;
|
|
45
68
|
const numY = y + 9;
|
|
69
|
+
const anchor = corner === "right" ? "end" : "start";
|
|
70
|
+
return (`<path d="${tri}" fill="${fill}" stroke="${stroke}" stroke-width="1" stroke-linejoin="round"/>` +
|
|
71
|
+
`<path d="${crease}" fill="none" stroke="${stroke}" stroke-width="0.7"/>` +
|
|
72
|
+
`<text x="${n(numX)}" y="${n(numY)}" text-anchor="${anchor}" font-family="Helvetica" ` +
|
|
73
|
+
`font-weight="bold" font-size="10" fill="${stroke}">${num}</text>`);
|
|
74
|
+
}
|
|
75
|
+
/** A folded-corner glyph + footnote number at the box's top-right, linked to the note. */
|
|
76
|
+
function noteMarker(r, note, num) {
|
|
77
|
+
const href = esc(note);
|
|
46
78
|
return (`<a xlink:href="${href}" href="${href}" target="_blank">` +
|
|
47
79
|
`<title>${num}. ${href}</title>` +
|
|
48
|
-
|
|
49
|
-
`<path d="${crease}" fill="none" stroke="${STROKE}" stroke-width="0.7"/>` +
|
|
50
|
-
`<text x="${n(numX)}" y="${n(numY)}" text-anchor="end" font-family="Helvetica" ` +
|
|
51
|
-
`font-weight="bold" font-size="10" fill="${STROKE}">${num}</text>` +
|
|
80
|
+
cornerGlyph(r, num, "right", FILL, STROKE) +
|
|
52
81
|
`</a>`);
|
|
53
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* A folded-corner glyph + footnote number at the box's top-left, marking an open
|
|
85
|
+
* issue. Unlike a note there's nothing to link to, so this carries a `<title>`
|
|
86
|
+
* tooltip instead of an `<a>` wrapper — the legend below is the reliable way to
|
|
87
|
+
* read the full issue text (tooltips don't survive rasterization either).
|
|
88
|
+
*/
|
|
89
|
+
function issueMarker(r, issue, num) {
|
|
90
|
+
return `<g><title>${num}. ${esc(issue)}</title>${cornerGlyph(r, num, "left", ISSUE_FILL, ISSUE_STROKE)}</g>`;
|
|
91
|
+
}
|
|
54
92
|
// ---- legend ----
|
|
55
93
|
const PAD_X = 16;
|
|
56
94
|
const PAD_TOP = 14;
|
|
@@ -58,12 +96,15 @@ const HEAD_H = 22;
|
|
|
58
96
|
const LINE_H = 18;
|
|
59
97
|
const PAD_BOTTOM = 14;
|
|
60
98
|
/**
|
|
61
|
-
* Grow the canvas and append a
|
|
62
|
-
*
|
|
99
|
+
* Grow the canvas and append a legend below the diagram: a "Notes" section (if any
|
|
100
|
+
* element has a note) and an "Issues" section (if any element has an open issue).
|
|
101
|
+
* Returns the SVG unchanged if there's nothing to show or the dimensions can't be
|
|
102
|
+
* parsed.
|
|
63
103
|
*/
|
|
64
104
|
export function appendNoteLegend(svg, model, hrefOf = rawNote) {
|
|
65
105
|
const noted = notedElements(model);
|
|
66
|
-
|
|
106
|
+
const issued = issuedElements(model);
|
|
107
|
+
if (noted.length === 0 && issued.length === 0)
|
|
67
108
|
return svg;
|
|
68
109
|
const vb = /viewBox="([\d.eE+-]+)\s+([\d.eE+-]+)\s+([\d.eE+-]+)\s+([\d.eE+-]+)"/.exec(svg);
|
|
69
110
|
const hpt = /height="([\d.eE+-]+)pt"/.exec(svg);
|
|
@@ -73,36 +114,59 @@ export function appendNoteLegend(svg, model, hrefOf = rawNote) {
|
|
|
73
114
|
const minY = +vb[2];
|
|
74
115
|
const vw = +vb[3];
|
|
75
116
|
const vh = +vb[4];
|
|
76
|
-
const
|
|
117
|
+
const notesH = noted.length > 0 ? HEAD_H + noted.length * LINE_H : 0;
|
|
118
|
+
const issuesH = issued.length > 0 ? HEAD_H + issued.length * LINE_H : 0;
|
|
119
|
+
const legendH = PAD_TOP + notesH + issuesH + PAD_BOTTOM;
|
|
77
120
|
const newVh = vh + legendH;
|
|
78
121
|
const newHpt = (+hpt[1]) * (newVh / vh); // keep the pt:viewBox ratio (handles scaling)
|
|
79
122
|
const top = minY + vh; // legend sits just below the diagram, in root coords
|
|
80
123
|
let out = svg;
|
|
81
124
|
out = out.replace(hpt[0], `height="${n(newHpt)}pt"`);
|
|
82
125
|
out = out.replace(vb[0], `viewBox="${vb[1]} ${vb[2]} ${vb[3]} ${n(newVh)}"`);
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
126
|
+
let sectionY = top + PAD_TOP;
|
|
127
|
+
let sections = "";
|
|
128
|
+
if (noted.length > 0) {
|
|
129
|
+
sections +=
|
|
130
|
+
`<text x="${n(minX + PAD_X)}" y="${n(sectionY + 14)}" font-family="Helvetica" ` +
|
|
131
|
+
`font-weight="bold" font-size="12" fill="#202124">Notes</text>`;
|
|
132
|
+
sections += noted
|
|
133
|
+
.map((el, i) => row(el, hrefOf(el), i + 1, minX + PAD_X, sectionY + HEAD_H + i * LINE_H + 13, "note"))
|
|
134
|
+
.join("");
|
|
135
|
+
sectionY += notesH;
|
|
136
|
+
}
|
|
137
|
+
if (issued.length > 0) {
|
|
138
|
+
sections +=
|
|
139
|
+
`<text x="${n(minX + PAD_X)}" y="${n(sectionY + 14)}" font-family="Helvetica" ` +
|
|
140
|
+
`font-weight="bold" font-size="12" fill="${ISSUE_STROKE}">Issues</text>`;
|
|
141
|
+
sections += issued
|
|
142
|
+
.map((el, i) => row(el, el.issue ?? "", i + 1, minX + PAD_X, sectionY + HEAD_H + i * LINE_H + 13, "issue"))
|
|
143
|
+
.join("");
|
|
144
|
+
sectionY += issuesH;
|
|
145
|
+
}
|
|
86
146
|
const legend = `<g class="em-note-legend">` +
|
|
87
147
|
`<rect x="${n(minX)}" y="${n(top)}" width="${n(vw)}" height="${n(legendH)}" fill="#FFFFFF"/>` +
|
|
88
148
|
`<line x1="${n(minX)}" y1="${n(top)}" x2="${n(minX + vw)}" y2="${n(top)}" stroke="#D0D0D0" stroke-width="1"/>` +
|
|
89
|
-
|
|
90
|
-
`font-weight="bold" font-size="12" fill="#202124">Notes</text>` +
|
|
91
|
-
rows +
|
|
149
|
+
sections +
|
|
92
150
|
`</g>`;
|
|
93
151
|
return out.replace(/(<\/g>\s*)(<\/svg>)/, `$1${legend}$2`);
|
|
94
152
|
}
|
|
95
|
-
/**
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
153
|
+
/**
|
|
154
|
+
* One legend row: "N. Element name — text". Notes rows link to the note file;
|
|
155
|
+
* issue rows carry the full issue text and have no link target.
|
|
156
|
+
*/
|
|
157
|
+
function row(el, hrefOrText, num, x, y, kind = "note") {
|
|
158
|
+
const color = kind === "issue" ? ISSUE_STROKE : STROKE;
|
|
159
|
+
const label = esc(kind === "issue" ? el.issue ?? "" : el.note ?? ""); // readable text
|
|
99
160
|
const name = esc(el.name);
|
|
100
|
-
|
|
101
|
-
`<
|
|
102
|
-
`<tspan font-weight="bold" fill="${STROKE}">${num}.</tspan>` +
|
|
161
|
+
const text = `<text x="${n(x)}" y="${n(y)}" font-family="Helvetica" font-size="11" fill="#202124">` +
|
|
162
|
+
`<tspan font-weight="bold" fill="${color}">${num}.</tspan>` +
|
|
103
163
|
`<tspan dx="6">${name}</tspan>` +
|
|
104
164
|
`<tspan dx="6" fill="#5F6368">— ${label}</tspan>` +
|
|
105
|
-
`</text
|
|
165
|
+
`</text>`;
|
|
166
|
+
if (kind === "issue")
|
|
167
|
+
return text; // nothing to link to
|
|
168
|
+
const href = esc(hrefOrText); // resolved link target
|
|
169
|
+
return `<a xlink:href="${href}" href="${href}" target="_blank">${text}</a>`;
|
|
106
170
|
}
|
|
107
171
|
function esc(s) {
|
|
108
172
|
return s
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drawNotes.js","sourceRoot":"","sources":["../../src/render/drawNotes.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,
|
|
1
|
+
{"version":3,"file":"drawNotes.js","sourceRoot":"","sources":["../../src/render/drawNotes.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,oFAAoF;AACpF,uFAAuF;AACvF,kDAAkD;AAClD,oFAAoF;AACpF,mFAAmF;AACnF,kEAAkE;AAClE,uFAAuF;AACvF,wFAAwF;AACxF,gDAAgD;AAChD,gFAAgF;AAChF,iFAAiF;AACjF,sFAAsF;AACtF,6DAA6D;AAC7D,+EAA+E;AAC/E,kFAAkF;AAClF,sFAAsF;AACtF,sDAAsD;AACtD,EAAE;AACF,gFAAgF;AAChF,gFAAgF;AAChF,6EAA6E;AAK7E,MAAM,IAAI,GAAG,EAAE,CAAC,CAAC,2CAA2C;AAC5D,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,gEAAgE;AACjF,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,4BAA4B;AACpD,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,qDAAqD;AAC/E,MAAM,UAAU,GAAG,SAAS,CAAC,CAAC,2BAA2B;AACzD,MAAM,YAAY,GAAG,SAAS,CAAC,CAAC,WAAW;AAE3C,qFAAqF;AACrF,MAAM,UAAU,aAAa,CAAC,KAAsB;IAClD,OAAO,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;AAChD,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,cAAc,CAAC,KAAsB;IACnD,OAAO,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;AACjD,CAAC;AAKD,MAAM,OAAO,GAAW,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC;AAE9C,0FAA0F;AAC1F,MAAM,UAAU,gBAAgB,CAC9B,KAAsB,EACtB,KAAwB,EACxB,SAAiB,OAAO;IAExB,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,aAAa,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE;QACrC,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAC3B,IAAI,CAAC,CAAC;YAAE,OAAO;QACf,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,OAAO,uBAAuB,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC;AACvD,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,iBAAiB,CAAC,KAAsB,EAAE,KAAwB;IAChF,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,cAAc,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE;QACtC,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAC3B,IAAI,CAAC,CAAC;YAAE,OAAO;QACf,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,OAAO,wBAAwB,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC;AACxD,CAAC;AAID,kFAAkF;AAClF,SAAS,WAAW,CAAC,CAAO,EAAE,GAAW,EAAE,MAAc,EAAE,IAAY,EAAE,MAAc;IACrF,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,KAAK,CAAC,CAAC,0DAA0D;IACnF,MAAM,CAAC,GAAG,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC;IAChE,MAAM,KAAK,GAAG,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACvD,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC;IAC9E,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC;IAC9D,MAAM,IAAI,GAAG,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;IACxD,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACnB,MAAM,MAAM,GAAG,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC;IACpD,OAAO,CACL,YAAY,GAAG,WAAW,IAAI,aAAa,MAAM,8CAA8C;QAC/F,YAAY,MAAM,yBAAyB,MAAM,wBAAwB;QACzE,YAAY,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,kBAAkB,MAAM,4BAA4B;QACtF,2CAA2C,MAAM,KAAK,GAAG,SAAS,CACnE,CAAC;AACJ,CAAC;AAED,0FAA0F;AAC1F,SAAS,UAAU,CAAC,CAAO,EAAE,IAAY,EAAE,GAAW;IACpD,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC;IACvB,OAAO,CACL,kBAAkB,IAAI,WAAW,IAAI,oBAAoB;QACzD,UAAU,GAAG,KAAK,IAAI,UAAU;QAChC,WAAW,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC;QAC1C,MAAM,CACP,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,WAAW,CAAC,CAAO,EAAE,KAAa,EAAE,GAAW;IACtD,OAAO,aAAa,GAAG,KAAK,GAAG,CAAC,KAAK,CAAC,WAAW,WAAW,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,CAAC,MAAM,CAAC;AAC/G,CAAC;AAED,mBAAmB;AAEnB,MAAM,KAAK,GAAG,EAAE,CAAC;AACjB,MAAM,OAAO,GAAG,EAAE,CAAC;AACnB,MAAM,MAAM,GAAG,EAAE,CAAC;AAClB,MAAM,MAAM,GAAG,EAAE,CAAC;AAClB,MAAM,UAAU,GAAG,EAAE,CAAC;AAEtB;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAC9B,GAAW,EACX,KAAsB,EACtB,SAAiB,OAAO;IAExB,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IACnC,MAAM,MAAM,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IACrC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,GAAG,CAAC;IAE1D,MAAM,EAAE,GAAG,qEAAqE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3F,MAAM,GAAG,GAAG,yBAAyB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChD,IAAI,CAAC,EAAE,IAAI,CAAC,GAAG;QAAE,OAAO,GAAG,CAAC,CAAC,2CAA2C;IAExE,MAAM,IAAI,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACpB,MAAM,IAAI,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACpB,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAClB,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAElB,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACrE,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACxE,MAAM,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;IACxD,MAAM,KAAK,GAAG,EAAE,GAAG,OAAO,CAAC;IAC3B,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC,8CAA8C;IACvF,MAAM,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC,CAAC,qDAAqD;IAE5E,IAAI,GAAG,GAAG,GAAG,CAAC;IACd,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACrD,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAE7E,IAAI,QAAQ,GAAG,GAAG,GAAG,OAAO,CAAC;IAC7B,IAAI,QAAQ,GAAG,EAAE,CAAC;IAElB,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,QAAQ;YACN,YAAY,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC,4BAA4B;gBAC/E,+DAA+D,CAAC;QAClE,QAAQ,IAAI,KAAK;aACd,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CACb,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,KAAK,EAAE,QAAQ,GAAG,MAAM,GAAG,CAAC,GAAG,MAAM,GAAG,EAAE,EAAE,MAAM,CAAC,CACtF;aACA,IAAI,CAAC,EAAE,CAAC,CAAC;QACZ,QAAQ,IAAI,MAAM,CAAC;IACrB,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,QAAQ;YACN,YAAY,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC,4BAA4B;gBAC/E,2CAA2C,YAAY,iBAAiB,CAAC;QAC3E,QAAQ,IAAI,MAAM;aACf,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CACb,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,KAAK,EAAE,QAAQ,GAAG,MAAM,GAAG,CAAC,GAAG,MAAM,GAAG,EAAE,EAAE,OAAO,CAAC,CAC3F;aACA,IAAI,CAAC,EAAE,CAAC,CAAC;QACZ,QAAQ,IAAI,OAAO,CAAC;IACtB,CAAC;IAED,MAAM,MAAM,GACV,4BAA4B;QAC5B,YAAY,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC,oBAAoB;QAC7F,aAAa,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,uCAAuC;QAC9G,QAAQ;QACR,MAAM,CAAC;IAET,OAAO,GAAG,CAAC,OAAO,CAAC,qBAAqB,EAAE,KAAK,MAAM,IAAI,CAAC,CAAC;AAC7D,CAAC;AAED;;;GAGG;AACH,SAAS,GAAG,CACV,EAAW,EACX,UAAkB,EAClB,GAAW,EACX,CAAS,EACT,CAAS,EACT,OAAyB,MAAM;IAE/B,MAAM,KAAK,GAAG,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC;IACvD,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,gBAAgB;IACtF,MAAM,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;IAC1B,MAAM,IAAI,GACR,YAAY,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,0DAA0D;QACtF,mCAAmC,KAAK,KAAK,GAAG,WAAW;QAC3D,iBAAiB,IAAI,UAAU;QAC/B,kCAAkC,KAAK,UAAU;QACjD,SAAS,CAAC;IACZ,IAAI,IAAI,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC,CAAC,qBAAqB;IACxD,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,uBAAuB;IACrD,OAAO,kBAAkB,IAAI,WAAW,IAAI,qBAAqB,IAAI,MAAM,CAAC;AAC9E,CAAC;AAED,SAAS,GAAG,CAAC,CAAS;IACpB,OAAO,CAAC;SACL,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7B,CAAC;AAED,SAAS,CAAC,CAAC,CAAS;IAClB,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;AACnC,CAAC"}
|
package/dist/render/render.js
CHANGED
|
@@ -14,7 +14,7 @@ import { Graphviz } from "@hpcc-js/wasm-graphviz";
|
|
|
14
14
|
import { Resvg } from "@resvg/resvg-js";
|
|
15
15
|
import { parseNodeRects } from "./svgGeometry.js";
|
|
16
16
|
import { buildEdgeOverlay } from "./drawEdges.js";
|
|
17
|
-
import { buildNoteMarkers, appendNoteLegend } from "./drawNotes.js";
|
|
17
|
+
import { buildNoteMarkers, buildIssueMarkers, appendNoteLegend } from "./drawNotes.js";
|
|
18
18
|
const RSVG_BIN = process.env.EM_RSVG || "rsvg-convert";
|
|
19
19
|
// The WASM Graphviz module is loaded once and reused across renders.
|
|
20
20
|
let graphvizModule;
|
|
@@ -67,6 +67,7 @@ function withOverlays(svg, model, hrefOf) {
|
|
|
67
67
|
const rects = parseNodeRects(svg, new Set(model.byId.keys()));
|
|
68
68
|
const { defs, group } = buildEdgeOverlay(model, rects);
|
|
69
69
|
const notes = buildNoteMarkers(model, rects, hrefOf);
|
|
70
|
+
const issues = buildIssueMarkers(model, rects);
|
|
70
71
|
let out = svg;
|
|
71
72
|
// arrowhead markers go just inside <svg …>
|
|
72
73
|
out = out.replace(/(<svg\b[^>]*>)/, `$1${defs}`);
|
|
@@ -76,10 +77,10 @@ function withOverlays(svg, model, hrefOf) {
|
|
|
76
77
|
out = out.slice(0, nodeAt) + group + out.slice(nodeAt);
|
|
77
78
|
else
|
|
78
79
|
out = out.replace(/<\/svg>/, `${group}</svg>`);
|
|
79
|
-
// note markers go on top of the boxes — inside the graph transform group
|
|
80
|
+
// note/issue markers go on top of the boxes — inside the graph transform group
|
|
80
81
|
// (so they share the box coordinate space) but after every node, as the last
|
|
81
|
-
//
|
|
82
|
-
out = out.replace(/(<\/g>\s*)(<\/svg>)/, `${notes}$1$2`);
|
|
82
|
+
// children of that group, making them the topmost clickable layer.
|
|
83
|
+
out = out.replace(/(<\/g>\s*)(<\/svg>)/, `${notes}${issues}$1$2`);
|
|
83
84
|
// grow the canvas and append the legend below the diagram (root coords)
|
|
84
85
|
out = appendNoteLegend(out, model, hrefOf);
|
|
85
86
|
return out;
|
|
@@ -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;AAExC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/render/render.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface LiveServer {
|
|
2
|
+
/** Base URL the viewer is served at, e.g. http://localhost:5173 */
|
|
3
|
+
url: string;
|
|
4
|
+
/** The port actually bound (differs from the request if it was taken). */
|
|
5
|
+
port: number;
|
|
6
|
+
/** Push a reload to every connected browser (call after a successful render). */
|
|
7
|
+
notify: () => void;
|
|
8
|
+
/** Stop the server and drop all SSE connections. */
|
|
9
|
+
close: () => Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export interface StartOptions {
|
|
12
|
+
/** Directory to serve (the model dir); static files are resolved within it. */
|
|
13
|
+
dir: string;
|
|
14
|
+
/** Preferred port; falls forward to the next free one if taken. */
|
|
15
|
+
port?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Start the live server for `dir`. Resolves once it is listening.
|
|
19
|
+
*/
|
|
20
|
+
export declare function startLiveServer(opts: StartOptions): Promise<LiveServer>;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Optional localhost dev server for `em watch --serve`.
|
|
3
|
+
//
|
|
4
|
+
// Serves the model directory over HTTP and pushes an instant reload to the
|
|
5
|
+
// browser via Server-Sent Events (SSE) after each successful re-render. This
|
|
6
|
+
// replaces the poll-and-cache-bust loop of the static live.html viewer: no
|
|
7
|
+
// polling, instant updates, and zero idle work between edits.
|
|
8
|
+
//
|
|
9
|
+
// Uses only Node's built-in `http` — no extra dependencies. Binds to
|
|
10
|
+
// 127.0.0.1 (loopback) so the model is never exposed off the machine.
|
|
11
|
+
import { createServer } from "node:http";
|
|
12
|
+
import { createReadStream } from "node:fs";
|
|
13
|
+
import { stat } from "node:fs/promises";
|
|
14
|
+
import { extname, join, relative, resolve, sep } from "node:path";
|
|
15
|
+
const HOST = "127.0.0.1";
|
|
16
|
+
const DEFAULT_PORT = 5173;
|
|
17
|
+
// Number of sequential ports to try if the preferred one is taken.
|
|
18
|
+
const PORT_TRIES = 10;
|
|
19
|
+
// Keepalive comment cadence so proxies/browsers don't drop the idle SSE stream.
|
|
20
|
+
const SSE_KEEPALIVE_MS = 15000;
|
|
21
|
+
const CONTENT_TYPES = {
|
|
22
|
+
".svg": "image/svg+xml; charset=utf-8",
|
|
23
|
+
".png": "image/png",
|
|
24
|
+
".html": "text/html; charset=utf-8",
|
|
25
|
+
".css": "text/css; charset=utf-8",
|
|
26
|
+
".js": "text/javascript; charset=utf-8",
|
|
27
|
+
".json": "application/json; charset=utf-8",
|
|
28
|
+
".pdf": "application/pdf",
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Start the live server for `dir`. Resolves once it is listening.
|
|
32
|
+
*/
|
|
33
|
+
export async function startLiveServer(opts) {
|
|
34
|
+
const root = resolve(opts.dir);
|
|
35
|
+
const clients = new Set();
|
|
36
|
+
const server = createServer((req, res) => handle(req, res, root, clients));
|
|
37
|
+
const port = await listen(server, opts.port ?? DEFAULT_PORT);
|
|
38
|
+
const keepalive = setInterval(() => {
|
|
39
|
+
for (const res of clients)
|
|
40
|
+
res.write(": keepalive\n\n");
|
|
41
|
+
}, SSE_KEEPALIVE_MS);
|
|
42
|
+
// Don't let the keepalive timer hold the process open on its own.
|
|
43
|
+
keepalive.unref?.();
|
|
44
|
+
return {
|
|
45
|
+
url: `http://localhost:${port}`,
|
|
46
|
+
port,
|
|
47
|
+
notify() {
|
|
48
|
+
for (const res of clients)
|
|
49
|
+
res.write("data: reload\n\n");
|
|
50
|
+
},
|
|
51
|
+
async close() {
|
|
52
|
+
clearInterval(keepalive);
|
|
53
|
+
for (const res of clients)
|
|
54
|
+
res.end();
|
|
55
|
+
clients.clear();
|
|
56
|
+
await new Promise((res) => server.close(() => res()));
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** Try to listen on `port`, falling forward a few times if it's in use. */
|
|
61
|
+
function listen(server, port) {
|
|
62
|
+
return new Promise((resolvePort, reject) => {
|
|
63
|
+
let attempt = 0;
|
|
64
|
+
const tryPort = (p) => {
|
|
65
|
+
const onError = (err) => {
|
|
66
|
+
if (err.code === "EADDRINUSE" && attempt < PORT_TRIES - 1) {
|
|
67
|
+
attempt += 1;
|
|
68
|
+
tryPort(p + 1);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
reject(err);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
server.once("error", onError);
|
|
75
|
+
server.listen(p, HOST, () => {
|
|
76
|
+
server.removeListener("error", onError);
|
|
77
|
+
// With p === 0 the OS assigns a free port; read the real one back.
|
|
78
|
+
resolvePort(server.address().port);
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
tryPort(port);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function handle(req, res, root, clients) {
|
|
85
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
86
|
+
const pathname = decodeURIComponent(url.pathname);
|
|
87
|
+
if (pathname === "/__events") {
|
|
88
|
+
openEventStream(res, clients);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (pathname === "/") {
|
|
92
|
+
res.writeHead(200, {
|
|
93
|
+
"content-type": "text/html; charset=utf-8",
|
|
94
|
+
"cache-control": "no-store",
|
|
95
|
+
});
|
|
96
|
+
res.end(VIEWER_HTML);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
serveStatic(pathname, root, res);
|
|
100
|
+
}
|
|
101
|
+
function openEventStream(res, clients) {
|
|
102
|
+
res.writeHead(200, {
|
|
103
|
+
"content-type": "text/event-stream",
|
|
104
|
+
"cache-control": "no-store",
|
|
105
|
+
connection: "keep-alive",
|
|
106
|
+
});
|
|
107
|
+
res.write(": connected\n\n");
|
|
108
|
+
clients.add(res);
|
|
109
|
+
res.on("close", () => clients.delete(res));
|
|
110
|
+
}
|
|
111
|
+
async function serveStatic(pathname, root, res) {
|
|
112
|
+
// Resolve within root and reject anything that escapes it (path traversal).
|
|
113
|
+
const target = resolve(join(root, "." + pathname));
|
|
114
|
+
const rel = relative(root, target);
|
|
115
|
+
if (rel === ".." || rel.startsWith(".." + sep) || (rel === "" && pathname !== "/")) {
|
|
116
|
+
res.writeHead(403).end("forbidden");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
const info = await stat(target);
|
|
121
|
+
if (!info.isFile()) {
|
|
122
|
+
res.writeHead(404).end("not found");
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
res.writeHead(200, {
|
|
126
|
+
"content-type": CONTENT_TYPES[extname(target).toLowerCase()] ?? "application/octet-stream",
|
|
127
|
+
"cache-control": "no-store",
|
|
128
|
+
});
|
|
129
|
+
createReadStream(target).pipe(res);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
res.writeHead(404).end("not found");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Served viewer page. Same double-buffered, no-flicker swap as the static
|
|
136
|
+
// live.html fallback, but driven by SSE push instead of a poll loop — the
|
|
137
|
+
// browser reloads only when the server says the SVG actually changed.
|
|
138
|
+
const VIEWER_HTML = `<!doctype html>
|
|
139
|
+
<html lang="en">
|
|
140
|
+
<head>
|
|
141
|
+
<meta charset="utf-8" />
|
|
142
|
+
<title>Event Model — Live</title>
|
|
143
|
+
<style>
|
|
144
|
+
:root { color-scheme: light dark; }
|
|
145
|
+
body { margin: 0; font-family: system-ui, sans-serif; background: #fafafa; }
|
|
146
|
+
header {
|
|
147
|
+
display: flex; align-items: center; gap: .75rem;
|
|
148
|
+
padding: .5rem .9rem; background: #1f2933; color: #fff; font-size: 14px;
|
|
149
|
+
}
|
|
150
|
+
header .dot { width: 9px; height: 9px; border-radius: 50%; background: #34c759; transition: background .3s; }
|
|
151
|
+
header .dot.stale { background: #f0a020; }
|
|
152
|
+
header .stamp { margin-left: auto; opacity: .7; font-variant-numeric: tabular-nums; }
|
|
153
|
+
#stage { padding: 1rem; }
|
|
154
|
+
object { width: 100%; height: calc(100vh - 60px); border: 0; }
|
|
155
|
+
</style>
|
|
156
|
+
</head>
|
|
157
|
+
<body>
|
|
158
|
+
<header>
|
|
159
|
+
<span class="dot" id="dot"></span>
|
|
160
|
+
<span>Event Model — live (push)</span>
|
|
161
|
+
<span class="stamp" id="stamp">connecting…</span>
|
|
162
|
+
</header>
|
|
163
|
+
<div id="stage">
|
|
164
|
+
<object id="svg" type="image/svg+xml"></object>
|
|
165
|
+
</div>
|
|
166
|
+
<script>
|
|
167
|
+
// The SVG filename comes from ?svg=<name>.svg (defaults to model.svg), so one
|
|
168
|
+
// server serves any model without editing this page.
|
|
169
|
+
const SVG_FILE = new URLSearchParams(location.search).get("svg") || "model.svg";
|
|
170
|
+
const stage = document.getElementById("stage");
|
|
171
|
+
const stamp = document.getElementById("stamp");
|
|
172
|
+
const dot = document.getElementById("dot");
|
|
173
|
+
let current = document.getElementById("svg");
|
|
174
|
+
|
|
175
|
+
// Double-buffer: load into a hidden <object>, swap on load, so the shared
|
|
176
|
+
// screen never flashes white during a reload. Keep <object> (not <img>) so
|
|
177
|
+
// note "..." links inside the SVG stay clickable.
|
|
178
|
+
function reload() {
|
|
179
|
+
dot.classList.add("stale");
|
|
180
|
+
const next = document.createElement("object");
|
|
181
|
+
next.type = "image/svg+xml";
|
|
182
|
+
next.style.cssText = current.style.cssText;
|
|
183
|
+
next.addEventListener("load", () => {
|
|
184
|
+
stage.replaceChild(next, current);
|
|
185
|
+
current = next;
|
|
186
|
+
stamp.textContent = new Date().toLocaleTimeString();
|
|
187
|
+
dot.classList.remove("stale");
|
|
188
|
+
}, { once: true });
|
|
189
|
+
next.setAttribute("data", SVG_FILE + "?t=" + Date.now());
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
reload();
|
|
193
|
+
|
|
194
|
+
const es = new EventSource("/__events");
|
|
195
|
+
es.onopen = () => { stamp.textContent = new Date().toLocaleTimeString(); };
|
|
196
|
+
es.onmessage = reload;
|
|
197
|
+
es.onerror = () => { dot.classList.add("stale"); };
|
|
198
|
+
</script>
|
|
199
|
+
</body>
|
|
200
|
+
</html>
|
|
201
|
+
`;
|
|
202
|
+
//# sourceMappingURL=serve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve.js","sourceRoot":"","sources":["../../src/render/serve.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@milehimikey/em",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "AI-friendly Event Modeling
|
|
3
|
+
"version": "1.3.0",
|
|
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": {
|
|
7
7
|
"em": "./dist/cli.js"
|
|
@@ -59,4 +59,4 @@
|
|
|
59
59
|
"typescript": "^5.6.0",
|
|
60
60
|
"vitest": "^2.1.0"
|
|
61
61
|
}
|
|
62
|
-
}
|
|
62
|
+
}
|