@dxos/diagram 0.11.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/dist/lib/chunk-parse.mjs +9406 -0
  2. package/dist/lib/chunk-parse.mjs.map +1 -0
  3. package/dist/lib/extension.mjs +204 -0
  4. package/dist/lib/extension.mjs.map +1 -0
  5. package/dist/lib/index.mjs +497 -2846
  6. package/dist/lib/index.mjs.map +1 -1
  7. package/dist/types/src/aesthetics.d.ts +12 -0
  8. package/dist/types/src/aesthetics.d.ts.map +1 -0
  9. package/dist/types/src/aesthetics.test.d.ts +2 -0
  10. package/dist/types/src/aesthetics.test.d.ts.map +1 -0
  11. package/dist/types/src/appeal.d.ts +55 -0
  12. package/dist/types/src/appeal.d.ts.map +1 -0
  13. package/dist/types/src/appeal.test.d.ts +2 -0
  14. package/dist/types/src/appeal.test.d.ts.map +1 -0
  15. package/dist/types/src/architecture.d.ts +86 -0
  16. package/dist/types/src/architecture.d.ts.map +1 -0
  17. package/dist/types/src/architecture.test.d.ts +2 -0
  18. package/dist/types/src/architecture.test.d.ts.map +1 -0
  19. package/dist/types/src/compact.d.ts +39 -0
  20. package/dist/types/src/compact.d.ts.map +1 -0
  21. package/dist/types/src/compact.test.d.ts +2 -0
  22. package/dist/types/src/compact.test.d.ts.map +1 -0
  23. package/dist/types/src/diagnostics.d.ts +70 -3
  24. package/dist/types/src/diagnostics.d.ts.map +1 -1
  25. package/dist/types/src/dsl/cli.d.ts +2 -0
  26. package/dist/types/src/dsl/cli.d.ts.map +1 -0
  27. package/dist/types/src/dsl/compile.d.ts +12 -0
  28. package/dist/types/src/dsl/compile.d.ts.map +1 -0
  29. package/dist/types/src/dsl/convert.d.ts +45 -0
  30. package/dist/types/src/dsl/convert.d.ts.map +1 -0
  31. package/dist/types/src/dsl/convert.test.d.ts +2 -0
  32. package/dist/types/src/dsl/convert.test.d.ts.map +1 -0
  33. package/dist/types/src/dsl/dsl.test.d.ts +2 -0
  34. package/dist/types/src/dsl/dsl.test.d.ts.map +1 -0
  35. package/dist/types/src/dsl/gen/diagram.d.ts +3 -0
  36. package/dist/types/src/dsl/gen/diagram.d.ts.map +1 -0
  37. package/dist/types/src/dsl/index.d.ts +7 -0
  38. package/dist/types/src/dsl/index.d.ts.map +1 -0
  39. package/dist/types/src/dsl/parse.d.ts +49 -0
  40. package/dist/types/src/dsl/parse.d.ts.map +1 -0
  41. package/dist/types/src/dsl/print.d.ts +19 -0
  42. package/dist/types/src/dsl/print.d.ts.map +1 -0
  43. package/dist/types/src/dsl/semantic.test.d.ts +2 -0
  44. package/dist/types/src/dsl/semantic.test.d.ts.map +1 -0
  45. package/dist/types/src/dsl/vocabulary.d.ts +61 -0
  46. package/dist/types/src/dsl/vocabulary.d.ts.map +1 -0
  47. package/dist/types/src/dx-svg.d.ts +12 -0
  48. package/dist/types/src/dx-svg.d.ts.map +1 -0
  49. package/dist/types/src/dx-svg.test.d.ts +2 -0
  50. package/dist/types/src/dx-svg.test.d.ts.map +1 -0
  51. package/dist/types/src/extension/complete.d.ts +3 -0
  52. package/dist/types/src/extension/complete.d.ts.map +1 -0
  53. package/dist/types/src/extension/extension.test.d.ts +2 -0
  54. package/dist/types/src/extension/extension.test.d.ts.map +1 -0
  55. package/dist/types/src/extension/highlight.d.ts +14 -0
  56. package/dist/types/src/extension/highlight.d.ts.map +1 -0
  57. package/dist/types/src/extension/index.d.ts +6 -0
  58. package/dist/types/src/extension/index.d.ts.map +1 -0
  59. package/dist/types/src/extension/language.d.ts +13 -0
  60. package/dist/types/src/extension/language.d.ts.map +1 -0
  61. package/dist/types/src/extension/lint.d.ts +21 -0
  62. package/dist/types/src/extension/lint.d.ts.map +1 -0
  63. package/dist/types/src/extension/syntax.d.ts +3 -0
  64. package/dist/types/src/extension/syntax.d.ts.map +1 -0
  65. package/dist/types/src/index.d.ts +14 -0
  66. package/dist/types/src/index.d.ts.map +1 -1
  67. package/dist/types/src/mermaid-engine.d.ts +40 -1
  68. package/dist/types/src/mermaid-engine.d.ts.map +1 -1
  69. package/dist/types/src/mermaid.d.ts +3 -3
  70. package/dist/types/src/mermaid.d.ts.map +1 -1
  71. package/dist/types/src/nudge.d.ts +30 -0
  72. package/dist/types/src/nudge.d.ts.map +1 -0
  73. package/dist/types/src/nudge.test.d.ts +2 -0
  74. package/dist/types/src/nudge.test.d.ts.map +1 -0
  75. package/dist/types/src/objective.d.ts +4 -0
  76. package/dist/types/src/objective.d.ts.map +1 -1
  77. package/dist/types/src/ortho-router.d.ts +13 -1
  78. package/dist/types/src/ortho-router.d.ts.map +1 -1
  79. package/dist/types/src/ports.d.ts +40 -0
  80. package/dist/types/src/ports.d.ts.map +1 -0
  81. package/dist/types/src/ports.test.d.ts +2 -0
  82. package/dist/types/src/ports.test.d.ts.map +1 -0
  83. package/dist/types/src/relations.test.d.ts +2 -0
  84. package/dist/types/src/relations.test.d.ts.map +1 -0
  85. package/dist/types/src/rules.d.ts +30 -0
  86. package/dist/types/src/rules.d.ts.map +1 -0
  87. package/dist/types/src/scene.d.ts +108 -74
  88. package/dist/types/src/scene.d.ts.map +1 -1
  89. package/dist/types/src/score.d.ts +54 -0
  90. package/dist/types/src/score.d.ts.map +1 -0
  91. package/dist/types/src/score.test.d.ts +2 -0
  92. package/dist/types/src/score.test.d.ts.map +1 -0
  93. package/dist/types/src/semantic-engine.d.ts +41 -0
  94. package/dist/types/src/semantic-engine.d.ts.map +1 -0
  95. package/dist/types/src/semantic-engine.test.d.ts +2 -0
  96. package/dist/types/src/semantic-engine.test.d.ts.map +1 -0
  97. package/dist/types/src/semantic-place.d.ts +81 -0
  98. package/dist/types/src/semantic-place.d.ts.map +1 -0
  99. package/dist/types/src/semantic-route.d.ts +133 -0
  100. package/dist/types/src/semantic-route.d.ts.map +1 -0
  101. package/dist/types/src/semantic.d.ts +164 -0
  102. package/dist/types/src/semantic.d.ts.map +1 -0
  103. package/dist/types/src/uml-grid.d.ts.map +1 -1
  104. package/dist/types/src/uml.d.ts +3 -5
  105. package/dist/types/src/uml.d.ts.map +1 -1
  106. package/dist/types/src/view.d.ts +53 -0
  107. package/dist/types/src/view.d.ts.map +1 -0
  108. package/dist/types/src/view.test.d.ts +2 -0
  109. package/dist/types/src/view.test.d.ts.map +1 -0
  110. package/dist/types/tsconfig.tsbuildinfo +1 -1
  111. package/package.json +23 -5
  112. package/rules/DIAGRAM.mdl +388 -0
  113. package/src/aesthetics.test.ts +89 -0
  114. package/src/aesthetics.ts +148 -0
  115. package/src/appeal.test.ts +144 -0
  116. package/src/appeal.ts +586 -0
  117. package/src/architecture.test.ts +94 -0
  118. package/src/architecture.ts +215 -0
  119. package/src/compact.test.ts +67 -0
  120. package/src/compact.ts +219 -0
  121. package/src/diagnostics.test.ts +207 -1
  122. package/src/diagnostics.ts +481 -37
  123. package/src/dsl/cli.ts +46 -0
  124. package/src/dsl/compile.ts +28 -0
  125. package/src/dsl/convert.test.ts +51 -0
  126. package/src/dsl/convert.ts +98 -0
  127. package/src/dsl/diagram.grammar +195 -0
  128. package/src/dsl/dsl.test.ts +286 -0
  129. package/src/dsl/gen/diagram.ts +22 -0
  130. package/src/dsl/index.ts +44 -0
  131. package/src/dsl/parse.ts +1054 -0
  132. package/src/dsl/print.ts +198 -0
  133. package/src/dsl/semantic.test.ts +216 -0
  134. package/src/dsl/vocabulary.ts +150 -0
  135. package/src/dx-svg.test.ts +44 -0
  136. package/src/dx-svg.ts +77 -0
  137. package/src/extension/complete.ts +105 -0
  138. package/src/extension/extension.test.ts +135 -0
  139. package/src/extension/highlight.ts +23 -0
  140. package/src/extension/index.ts +9 -0
  141. package/src/extension/language.ts +31 -0
  142. package/src/extension/lint.ts +60 -0
  143. package/src/extension/syntax.ts +62 -0
  144. package/src/index.ts +14 -0
  145. package/src/mermaid-engine.test.ts +6 -6
  146. package/src/mermaid-engine.ts +569 -119
  147. package/src/mermaid.test.ts +17 -1
  148. package/src/mermaid.ts +30 -18
  149. package/src/nudge.test.ts +186 -0
  150. package/src/nudge.ts +387 -0
  151. package/src/objective.test.ts +2 -0
  152. package/src/objective.ts +17 -1
  153. package/src/ortho-router.ts +240 -80
  154. package/src/ports.test.ts +157 -0
  155. package/src/ports.ts +186 -0
  156. package/src/relations.test.ts +98 -0
  157. package/src/rules.ts +117 -0
  158. package/src/scene.ts +66 -5
  159. package/src/score.test.ts +64 -0
  160. package/src/score.ts +130 -0
  161. package/src/semantic-engine.test.ts +635 -0
  162. package/src/semantic-engine.ts +891 -0
  163. package/src/semantic-place.ts +1508 -0
  164. package/src/semantic-route.ts +1136 -0
  165. package/src/semantic.ts +278 -0
  166. package/src/typings.d.ts +8 -0
  167. package/src/uml-grid.ts +6 -1
  168. package/src/uml.test.ts +5 -1
  169. package/src/uml.ts +24 -11
  170. package/src/view.test.ts +139 -0
  171. package/src/view.ts +376 -0
@@ -1,23 +1,13 @@
1
+ import { $ as scene_exports, A as compile$7, B as compile$5, C as SEMANTIC_KEYWORDS, D as mermaid_engine_exports, E as compile$6, F as objective_exports, H as parse$1, I as emit, J as layout_exports, K as diagnostics_exports, L as measureCell, M as appeal_exports, N as view_exports, O as ports_exports, P as rules_exports, Q as Command, R as uml_grid_exports, S as RESERVED, T as semantic_exports, U as relationRanks, V as isClassDiagram, W as uml_exports, X as architecture_exports, Y as rank, Z as judge$1, _ as ELEMENT_KINDS, a as withLayout, b as OBJECT_ATTRS, c as semantic_engine_exports, d as BARE_ID, et as __exportAll, f as BARE_REF, g as ELEMENT_ATTRS, h as EDGE_ATTRS, i as toScene, j as mermaid_exports, k as nudge_exports, l as semantic_route_exports, m as DIAGRAM_ATTRS, n as parseScene, o as parser, p as CLAUSE_KEYWORDS, q as backEdges, r as read, s as compile$8, t as parse, u as semantic_place_exports, v as GROUP_ATTRS, w as STATEMENT_KEYWORDS, x as RELATION_WORDS, y as NODE_ATTRS, z as zRouter } from "./chunk-parse.mjs";
1
2
  import { invariant } from "@dxos/invariant";
2
3
  import * as Effect from "effect/Effect";
3
4
  import * as Schema from "effect/Schema";
5
+ import * as Data from "effect/Data";
4
6
  import ELK from "elkjs/lib/elk.bundled.js";
5
7
  import * as SchemaAST from "effect/SchemaAST";
6
8
  import { graphlib, layout } from "@dagrejs/dagre";
7
- //#region \0rolldown/runtime.js
8
- var __defProp = Object.defineProperty;
9
- var __exportAll = (all, no_symbols) => {
10
- let target = {};
11
- for (var name in all) __defProp(target, name, {
12
- get: all[name],
13
- enumerable: true
14
- });
15
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
16
- return target;
17
- };
18
- //#endregion
19
9
  //#region src/content.ts
20
- var __dxlog_file$3 = "/Users/burdon/Code/dxos/dxos/.claude/worktrees/infinite-canvas-depth-7e0949/packages/common/diagram/src/content.ts";
10
+ var __dxlog_file$1 = "/__w/dxos/dxos/packages/common/diagram/src/content.ts";
21
11
  /** Apply a batch of edit commands to the canvas content (mutates `content`). */
22
12
  var applyCommands = (content, commands, handler) => {
23
13
  handler.scaffold?.(content);
@@ -60,7 +50,7 @@ var applyCommands = (content, commands, handler) => {
60
50
  const previous = find(command.objectId);
61
51
  invariant(previous, `unknown object: ${command.objectId} (use upsert-object to create it)`, {
62
52
  "~LogMeta": "~LogMeta",
63
- F: __dxlog_file$3,
53
+ F: __dxlog_file$1,
64
54
  L: 128,
65
55
  S: void 0,
66
56
  A: ["previous", "`unknown object: ${command.objectId} (use upsert-object to create it)`"]
@@ -76,2865 +66,525 @@ var applyCommands = (content, commands, handler) => {
76
66
  }
77
67
  case "remove-elements": {
78
68
  const ids = new Set(command.elementIds);
79
- removed += remove((identity) => identity.object === command.objectId && ids.has(identity.element));
80
- break;
81
- }
82
- case "remove-object":
83
- removed += remove((identity) => identity.object === command.objectId);
84
- break;
85
- case "move-object": {
86
- const previous = find(command.objectId);
87
- invariant(previous, `unknown object: ${command.objectId}`, {
88
- "~LogMeta": "~LogMeta",
89
- F: __dxlog_file$3,
90
- L: 149,
91
- S: void 0,
92
- A: ["previous", "`unknown object: ${command.objectId}`"]
93
- });
94
- const delta = {
95
- x: command.origin.x - previous.origin.x,
96
- y: command.origin.y - previous.origin.y
97
- };
98
- for (const record of Object.values(content)) if (handler.identify(record)?.object === command.objectId) handler.translate(record, delta);
99
- upserted.add(command.objectId);
100
- break;
101
- }
102
- }
103
- return {
104
- upserted: [...upserted],
105
- removed
106
- };
107
- };
108
- /** Seed a fractional-index counter past any existing `a<n>` indexes. */
109
- var nextIndex = (content, isCounted = () => true) => {
110
- let max = 0;
111
- let count = 0;
112
- for (const record of Object.values(content)) {
113
- if (!record || !isCounted(record)) continue;
114
- count++;
115
- const match = /^a(\d+)$/.exec(record.index ?? "");
116
- if (match) max = Math.max(max, Number(match[1]));
117
- }
118
- return Math.max(max, count);
119
- };
120
- //#endregion
121
- //#region src/scene.ts
122
- var scene_exports = /* @__PURE__ */ __exportAll({
123
- Arc: () => Arc,
124
- Arrow: () => Arrow,
125
- ArrowHead: () => ArrowHead,
126
- ArrowTail: () => ArrowTail,
127
- Box: () => Box,
128
- Circle: () => Circle,
129
- Color: () => Color,
130
- Command: () => Command,
131
- Curve: () => Curve,
132
- Element: () => Element,
133
- Fill: () => Fill,
134
- MoveObject: () => MoveObject,
135
- Point: () => Point,
136
- Polyline: () => Polyline,
137
- Portal: () => Portal,
138
- RemoveElements: () => RemoveElements,
139
- RemoveObject: () => RemoveObject,
140
- Scene: () => Scene,
141
- Stroke: () => Stroke,
142
- Text: () => Text,
143
- UpsertElements: () => UpsertElements,
144
- UpsertObject: () => UpsertObject,
145
- Weight: () => Weight,
146
- WorldObject: () => WorldObject,
147
- formatRef: () => formatRef,
148
- parseRef: () => parseRef,
149
- resolveRef: () => resolveRef
150
- });
151
- var Color = Schema.Literals([
152
- "black",
153
- "grey",
154
- "light-violet",
155
- "violet",
156
- "blue",
157
- "light-blue",
158
- "yellow",
159
- "orange",
160
- "green",
161
- "light-green",
162
- "light-red",
163
- "red",
164
- "white"
165
- ]);
166
- var Fill = Schema.Literals([
167
- "none",
168
- "solid",
169
- "pattern"
170
- ]);
171
- var Stroke = Schema.Literals([
172
- "sketchy",
173
- "solid",
174
- "dashed",
175
- "dotted"
176
- ]);
177
- var Weight = Schema.Literals([
178
- "s",
179
- "m",
180
- "l",
181
- "xl"
182
- ]);
183
- var Point = Schema.Struct({
184
- x: Schema.Number,
185
- y: Schema.Number
186
- });
187
- /** Style props shared by all elements; every field is optional with sensible defaults. */
188
- var styleFields = {
189
- color: Schema.optional(Color).annotate({ description: "Stroke/text color." }),
190
- fill: Schema.optional(Fill).annotate({ description: "Interior fill of closed shapes." }),
191
- stroke: Schema.optional(Stroke).annotate({ description: "Stroke rendering; sketchy is hand-drawn." }),
192
- weight: Schema.optional(Weight).annotate({ description: "Stroke weight / text size." })
193
- };
194
- var id = Schema.String.annotate({ description: "Stable element id, unique within the object (e.g. \"left-eye\"). Used to edit or delete the element." });
195
- /** Closed shape drawn inside a local bounding box. */
196
- var Box = Schema.Struct({
197
- kind: Schema.Literals([
198
- "rect",
199
- "ellipse",
200
- "diamond",
201
- "triangle"
202
- ]),
203
- id,
204
- x: Schema.Number.annotate({ description: "Left edge (object-local units)." }),
205
- y: Schema.Number.annotate({ description: "Top edge (object-local units)." }),
206
- w: Schema.Number,
207
- h: Schema.Number,
208
- rotation: Schema.optional(Schema.Number).annotate({ description: "Clockwise rotation in degrees." }),
209
- text: Schema.optional(Schema.String).annotate({ description: "Centered label." }),
210
- corners: Schema.optional(Schema.Literals([
211
- "top",
212
- "bottom",
213
- "none"
214
- ])).annotate({ description: "Rects only: round just these corners (default all); backends without corner control ignore it." }),
215
- ...styleFields
216
- });
217
- /** Circle sugar; reads back as an ellipse. */
218
- var Circle = Schema.Struct({
219
- kind: Schema.Literal("circle"),
220
- id,
221
- cx: Schema.Number.annotate({ description: "Center x (object-local units)." }),
222
- cy: Schema.Number.annotate({ description: "Center y (object-local units)." }),
223
- r: Schema.Number,
224
- text: Schema.optional(Schema.String),
225
- ...styleFields
226
- });
227
- /** Straight segment or open polyline through the given points. */
228
- var Polyline = Schema.Struct({
229
- kind: Schema.Literal("line"),
230
- id,
231
- points: Schema.Array(Point).annotate({ description: "Two or more points (object-local units)." }),
232
- closed: Schema.optional(Schema.Boolean).annotate({ description: "Close the path back to the first point." }),
233
- ...styleFields
234
- });
235
- /** Smooth curve through the given points (spline). */
236
- var Curve = Schema.Struct({
237
- kind: Schema.Literal("curve"),
238
- id,
239
- points: Schema.Array(Point).annotate({ description: "Control points the curve passes through." }),
240
- ...styleFields
241
- });
242
- /**
243
- * Circular arc sugar; reads back as a curve. Angles are degrees, measured clockwise from
244
- * the positive x-axis in screen coordinates (y grows downward): 0–180 is the lower half
245
- * (a smile), 180–360 the upper half (a frown).
246
- */
247
- var Arc = Schema.Struct({
248
- kind: Schema.Literal("arc"),
249
- id,
250
- cx: Schema.Number,
251
- cy: Schema.Number,
252
- r: Schema.Number,
253
- startAngle: Schema.Number,
254
- endAngle: Schema.Number,
255
- ...styleFields
256
- });
257
- /** Free-standing text. */
258
- var Text = Schema.Struct({
259
- kind: Schema.Literal("text"),
260
- id,
261
- x: Schema.Number,
262
- y: Schema.Number,
263
- w: Schema.optional(Schema.Number).annotate({ description: "Wrap width; omit for auto-size." }),
264
- text: Schema.String,
265
- ...styleFields
266
- });
267
- /** UML-style end markers: the head sits at the target, the tail at the source. */
268
- var ArrowHead = Schema.Literals([
269
- "arrow",
270
- "triangle",
271
- "crowsfoot",
272
- "none"
273
- ]);
274
- var ArrowTail = Schema.Literals(["none", "circle"]);
275
- /**
276
- * Connector. Endpoints are element refs — `"<elementId>"` within the same object or
277
- * `"<objectId>/<elementId>"` across objects, either with an optional `#<port>` naming an
278
- * attachment point on the element — or explicit local points. Bound endpoints track their
279
- * target when it moves; without a port the renderer picks the attachment point.
280
- */
281
- var Arrow = Schema.Struct({
282
- kind: Schema.Literal("arrow"),
283
- id,
284
- from: Schema.optional(Schema.String).annotate({ description: "Source element ref, optionally `#port`." }),
285
- to: Schema.optional(Schema.String).annotate({ description: "Target element ref, optionally `#port`." }),
286
- start: Schema.optional(Point).annotate({ description: "Explicit start (used when `from` is omitted)." }),
287
- end: Schema.optional(Point).annotate({ description: "Explicit end (used when `to` is omitted)." }),
288
- text: Schema.optional(Schema.String).annotate({ description: "Label at the arrow midpoint." }),
289
- head: Schema.optional(ArrowHead).annotate({ description: "Marker at the target end (default arrow): triangle for inheritance, crowsfoot for has-many." }),
290
- tail: Schema.optional(ArrowTail).annotate({ description: "Marker at the source end (default none): circle for containment." }),
291
- ...styleFields
292
- });
293
- /** Parse `"[object/]element[#port]"`; element ids may not contain `/` or `#`. */
294
- var parseRef = (ref) => {
295
- const hash = ref.indexOf("#");
296
- const target = hash === -1 ? ref : ref.slice(0, hash);
297
- const port = hash === -1 ? void 0 : ref.slice(hash + 1);
298
- const slash = target.indexOf("/");
299
- return {
300
- ...slash === -1 ? {} : { object: target.slice(0, slash) },
301
- element: slash === -1 ? target : target.slice(slash + 1),
302
- ...port ? { port } : {}
303
- };
304
- };
305
- /** Inverse of {@link parseRef}. */
306
- var formatRef = ({ object, element, port }) => `${object === void 0 ? "" : `${object}/`}${element}${port === void 0 ? "" : `#${port}`}`;
307
- /**
308
- * Canonical `object/element` handle of a ref, resolving a bare element id against the owning
309
- * object and dropping the port, so renderers without ports bind to the element itself.
310
- */
311
- var resolveRef = (ref, objectId) => {
312
- const { object = objectId, element } = parseRef(ref);
313
- return `${object}/${element}`;
314
- };
315
- /**
316
- * Window onto another drawing: the referenced scene is rendered inside the box as a nested level
317
- * of detail. Renderers without nesting ignore it.
318
- */
319
- var Portal = Schema.Struct({
320
- kind: Schema.Literal("portal"),
321
- id,
322
- x: Schema.Number.annotate({ description: "Left edge (object-local units)." }),
323
- y: Schema.Number.annotate({ description: "Top edge (object-local units)." }),
324
- w: Schema.Number,
325
- h: Schema.Number,
326
- ref: Schema.String.annotate({ description: "The drawing shown inside the box: an ECHO object reference (DXN)." }),
327
- text: Schema.optional(Schema.String).annotate({ description: "Title shown on the frame." }),
328
- ...styleFields
329
- });
330
- var Element = Schema.Union([
331
- Box,
332
- Circle,
333
- Polyline,
334
- Curve,
335
- Arc,
336
- Text,
337
- Arrow,
338
- Portal
339
- ]);
340
- /**
341
- * A semantic group of elements ("face", "hat") authored in its own local unit space.
342
- * `origin` (canvas px) and `scale` (px per unit) place it on the canvas:
343
- * `canvas = origin + local * scale`. On read, `origin` is derived as the top-left of the
344
- * object's bounding box, so the model survives users dragging shapes around.
345
- */
346
- var WorldObject = Schema.Struct({
347
- id: Schema.String.annotate({ description: "Stable object id naming the thing depicted (e.g. \"face\", \"hat\")." }),
348
- origin: Schema.optional(Point).annotate({ description: "Canvas position (px). Omit on upsert to keep the current position." }),
349
- scale: Schema.optional(Schema.Number).annotate({ description: "Canvas px per local unit (default 1)." }),
350
- index: Schema.optional(Schema.String).annotate({ description: "Fractional z-order index (e.g. \"a1\"); objects with one paint above those without, in index order." }),
351
- ref: Schema.optional(Schema.String).annotate({ description: "What the object depicts: an ECHO object reference (activating the node opens it) or any other URI or path, carried for tooling." }),
352
- elements: Schema.Array(Element)
353
- });
354
- var Scene = Schema.Struct({ objects: Schema.Array(WorldObject) });
355
- var UpsertObject = Schema.Struct({
356
- op: Schema.Literal("upsert-object"),
357
- object: WorldObject
358
- }).annotate({ description: "Create an object, or replace an existing one wholesale (same id)." });
359
- var UpsertElements = Schema.Struct({
360
- op: Schema.Literal("upsert-elements"),
361
- objectId: Schema.String,
362
- elements: Schema.Array(Element)
363
- }).annotate({ description: "Add elements to an existing object, replacing any with matching ids." });
364
- var RemoveElements = Schema.Struct({
365
- op: Schema.Literal("remove-elements"),
366
- objectId: Schema.String,
367
- elementIds: Schema.Array(Schema.String)
368
- });
369
- var RemoveObject = Schema.Struct({
370
- op: Schema.Literal("remove-object"),
371
- objectId: Schema.String
372
- });
373
- var MoveObject = Schema.Struct({
374
- op: Schema.Literal("move-object"),
375
- objectId: Schema.String,
376
- origin: Point.annotate({ description: "New canvas position (px) for the object top-left." })
377
- });
378
- var Command = Schema.Union([
379
- UpsertObject,
380
- UpsertElements,
381
- RemoveElements,
382
- RemoveObject,
383
- MoveObject
384
- ]);
385
- //#endregion
386
- //#region src/dialect.ts
387
- var __dxlog_file$2 = "/Users/burdon/Code/dxos/dxos/.claude/worktrees/infinite-canvas-depth-7e0949/packages/common/diagram/src/dialect.ts";
388
- /** The core dialect: input is already scene edit commands (identity compile). */
389
- var sceneDialect = {
390
- id: "scene",
391
- description: "Positioned world objects and elements; the agent does the layout.",
392
- input: Schema.Array(Command),
393
- compile: Effect.succeed
394
- };
395
- var DialectRegistry = class {
396
- #dialects = /* @__PURE__ */ new Map();
397
- constructor(dialects = [sceneDialect]) {
398
- dialects.forEach((dialect) => this.register(dialect));
399
- }
400
- register(dialect) {
401
- invariant(!this.#dialects.has(dialect.id), `duplicate dialect: ${dialect.id}`, {
402
- "~LogMeta": "~LogMeta",
403
- F: __dxlog_file$2,
404
- L: 42,
405
- S: this,
406
- A: ["!this.#dialects.has(dialect.id)", "`duplicate dialect: ${dialect.id}`"]
407
- });
408
- this.#dialects.set(dialect.id, dialect);
409
- }
410
- get(id) {
411
- return this.#dialects.get(id);
412
- }
413
- list() {
414
- return [...this.#dialects.values()];
415
- }
416
- };
417
- //#endregion
418
- //#region src/layout.ts
419
- var layout_exports = /* @__PURE__ */ __exportAll({
420
- FONT_METRICS: () => FONT_METRICS,
421
- backEdges: () => backEdges,
422
- rank: () => rank$1
423
- });
424
- /**
425
- * Empirical text metrics per scene weight, calibrated to tldraw's draw font (`size` s/m/l/xl runs
426
- * ~18/25/38/48 px): charW ≈ 0.6–0.67 em, lineH ≈ 1.35 em. Dialects size boxes from these so text
427
- * fits without the renderer growing shapes over their neighbours.
428
- */
429
- var FONT_METRICS = {
430
- s: {
431
- charW: 12,
432
- lineH: 26
433
- },
434
- m: {
435
- charW: 15,
436
- lineH: 34
437
- },
438
- l: {
439
- charW: 23,
440
- lineH: 51
441
- },
442
- xl: {
443
- charW: 29,
444
- lineH: 65
445
- }
446
- };
447
- /**
448
- * Edges that close a cycle, found by DFS: an edge into a node still on the stack points backwards.
449
- * Ranking ignores them, so `C --> Y --> C` ranks C by its forward predecessors alone instead of
450
- * chasing the loop.
451
- */
452
- var backEdges = (nodeIds, edges) => {
453
- const outgoing = /* @__PURE__ */ new Map();
454
- for (const edge of edges) outgoing.set(edge.from, [...outgoing.get(edge.from) ?? [], edge]);
455
- const back = /* @__PURE__ */ new Set();
456
- const done = /* @__PURE__ */ new Set();
457
- const onStack = /* @__PURE__ */ new Set();
458
- const visit = (id) => {
459
- onStack.add(id);
460
- for (const edge of outgoing.get(id) ?? []) if (onStack.has(edge.to)) back.add(edge);
461
- else if (!done.has(edge.to)) visit(edge.to);
462
- onStack.delete(id);
463
- done.add(id);
464
- };
465
- const targets = new Set(edges.map((edge) => edge.to));
466
- for (const id of nodeIds) if (!targets.has(id) && !done.has(id)) visit(id);
467
- for (const id of nodeIds) if (!done.has(id)) visit(id);
468
- return back;
469
- };
470
- /** Assign each node a rank one past its deepest forward predecessor. */
471
- var rank$1 = (nodeIds, edges) => {
472
- const back = backEdges(nodeIds, edges);
473
- const forward = edges.filter((edge) => !back.has(edge));
474
- const ranks = new Map(nodeIds.map((id) => [id, 0]));
475
- for (let pass = 0; pass < nodeIds.length; pass++) {
476
- let changed = false;
477
- for (const edge of forward) {
478
- const next = (ranks.get(edge.from) ?? 0) + 1;
479
- if (next > (ranks.get(edge.to) ?? 0)) {
480
- ranks.set(edge.to, next);
481
- changed = true;
482
- }
483
- }
484
- if (!changed) break;
485
- }
486
- return ranks;
487
- };
488
- //#endregion
489
- //#region src/diagnostics.ts
490
- var diagnostics_exports = /* @__PURE__ */ __exportAll({
491
- Code: () => Code,
492
- Diagnostic: () => Diagnostic,
493
- SEVERITY: () => SEVERITY,
494
- Severity: () => Severity,
495
- analyze: () => analyze,
496
- errors: () => errors
497
- });
498
- var Severity = Schema.Literals(["error", "warning"]);
499
- /**
500
- * `error` codes are objective defects — the picture is wrong, not merely busy — and gate CI.
501
- * `warning` codes are quality metrics that trend rather than pass/fail; they are golden-filed so
502
- * a layout change reads as a reviewable diff instead of a threshold argument.
503
- */
504
- var Code = Schema.Literals([
505
- "node-overlap",
506
- "route-through-node",
507
- "label-overflow",
508
- "edge-crossing",
509
- "excessive-bends"
510
- ]);
511
- var SEVERITY = {
512
- "node-overlap": "error",
513
- "route-through-node": "error",
514
- "label-overflow": "error",
515
- "edge-crossing": "warning",
516
- "excessive-bends": "warning"
517
- };
518
- /** A schema so operations can return diagnostics to the agent — the repair loop. */
519
- var Diagnostic = Schema.Struct({
520
- code: Code,
521
- severity: Severity,
522
- message: Schema.String,
523
- refs: Schema.Array(Schema.String).annotate({ description: "Scene refs implicated: `objectId` or `objectId/elementId`, as `Arrow.from`/`to` spell them." })
524
- });
525
- /** Tolerance in scene units; ports land on a half-grid, so this only absorbs float error. */
526
- var EPSILON = .5;
527
- var place$1 = (object, point) => {
528
- const { x = 0, y = 0 } = object.origin ?? {};
529
- const scale = object.scale ?? 1;
530
- return {
531
- x: x + point.x * scale,
532
- y: y + point.y * scale
533
- };
534
- };
535
- var isBox = (element) => element.kind === "rect" || element.kind === "ellipse" || element.kind === "diamond" || element.kind === "triangle";
536
- /** Absolute rects for every closed shape, keyed by scene ref. */
537
- var nodes = (objects) => objects.flatMap((object) => object.elements.filter(isBox).map((element) => {
538
- const origin = place$1(object, {
539
- x: element.x,
540
- y: element.y
541
- });
542
- const scale = object.scale ?? 1;
543
- return {
544
- ref: `${object.id}/${element.id}`,
545
- rect: {
546
- x: origin.x,
547
- y: origin.y,
548
- w: element.w * scale,
549
- h: element.h * scale
550
- },
551
- text: element.text,
552
- weight: element.weight ?? "m"
553
- };
554
- }));
555
- /**
556
- * Connector paths, rejoining the `line` prefix and `arrow` head the emitters split a routed edge
557
- * into (`<id>-path` + `<id>`); a plain arrow is a one-segment path.
558
- */
559
- var connectors = (objects) => {
560
- const paths = /* @__PURE__ */ new Map();
561
- for (const object of objects) {
562
- for (const element of object.elements) if (element.kind === "line") {
563
- const id = element.id.replace(/-path$/, "");
564
- const ref = `${object.id}/${id}`;
565
- paths.set(ref, {
566
- ref,
567
- points: element.points.map((point) => place$1(object, point))
568
- });
569
- }
570
- for (const element of object.elements) if (element.kind === "arrow" && element.start && element.end) {
571
- const ref = `${object.id}/${element.id}`;
572
- const head = [place$1(object, element.start), place$1(object, element.end)];
573
- const existing = paths.get(ref);
574
- paths.set(ref, {
575
- ref,
576
- points: existing ? [...existing.points.slice(0, -1), ...head] : head
577
- });
578
- }
579
- }
580
- return [...paths.values()];
581
- };
582
- var segmentsOf$1 = ({ points }) => points.slice(0, -1).map((point, index) => [point, points[index + 1]]);
583
- var orientation = (origin, a, b) => (a.x - origin.x) * (b.y - origin.y) - (a.y - origin.y) * (b.x - origin.x);
584
- var coincident = (a, b) => Math.abs(a.x - b.x) < EPSILON && Math.abs(a.y - b.y) < EPSILON;
585
- /** Proper crossing: the segments intersect away from all four endpoints (shared ports do not count). */
586
- var properlyCrosses = ([a1, a2], [b1, b2]) => {
587
- if ([a1, a2].some((a) => [b1, b2].some((b) => coincident(a, b)))) return false;
588
- const d1 = orientation(a1, a2, b1);
589
- const d2 = orientation(a1, a2, b2);
590
- const d3 = orientation(b1, b2, a1);
591
- const d4 = orientation(b1, b2, a2);
592
- return d1 > 0 !== d2 > 0 && d3 > 0 !== d4 > 0;
593
- };
594
- /** Overlap area of two rects; 0 when they merely touch. */
595
- var overlapArea = (a, b) => {
596
- const w = Math.min(a.x + a.w, b.x + b.w) - Math.max(a.x, b.x);
597
- const h = Math.min(a.y + a.h, b.y + b.h) - Math.max(a.y, b.y);
598
- return w > EPSILON && h > EPSILON ? w * h : 0;
599
- };
600
- /**
601
- * Strict enclosure: the inner rect fits inside AND is smaller. Two identical rects are stacked
602
- * nodes — the strongest overlap — not a container and its member.
603
- */
604
- var contains = (outer, inner) => outer.x - EPSILON <= inner.x && outer.y - EPSILON <= inner.y && outer.x + outer.w + EPSILON >= inner.x + inner.w && outer.y + outer.h + EPSILON >= inner.y + inner.h && (inner.w < outer.w - EPSILON || inner.h < outer.h - EPSILON);
605
- /**
606
- * Length of the segment's run through the rect's INTERIOR (Liang-Barsky). A connector legitimately
607
- * terminating at a node only touches its border, so an interior run identifies a route drawn
608
- * across a node it does not belong to — no knowledge of the edge's terminals required.
609
- */
610
- var interiorRun = ([start, end], rect) => {
611
- const dx = end.x - start.x;
612
- const dy = end.y - start.y;
613
- const box = {
614
- x: rect.x + EPSILON,
615
- y: rect.y + EPSILON,
616
- w: rect.w - EPSILON * 2,
617
- h: rect.h - EPSILON * 2
618
- };
619
- if (box.w <= 0 || box.h <= 0) return 0;
620
- let enter = 0;
621
- let exit = 1;
622
- for (const [p, q] of [
623
- [-dx, start.x - box.x],
624
- [dx, box.x + box.w - start.x],
625
- [-dy, start.y - box.y],
626
- [dy, box.y + box.h - start.y]
627
- ]) {
628
- if (p === 0) {
629
- if (q < 0) return 0;
630
- continue;
631
- }
632
- const r = q / p;
633
- if (p < 0) enter = Math.max(enter, r);
634
- else exit = Math.min(exit, r);
635
- }
636
- return enter >= exit ? 0 : Math.hypot(dx, dy) * (exit - enter);
637
- };
638
- /** Bends in an orthogonal path: waypoints where the run changes axis. */
639
- var bendCount = ({ points }) => {
640
- let bends = 0;
641
- for (let index = 1; index < points.length - 1; index++) {
642
- const [previous, point, next] = [
643
- points[index - 1],
644
- points[index],
645
- points[index + 1]
646
- ];
647
- if (!(Math.abs(previous.x - point.x) < EPSILON && Math.abs(point.x - next.x) < EPSILON || Math.abs(previous.y - point.y) < EPSILON && Math.abs(point.y - next.y) < EPSILON)) bends++;
648
- }
649
- return bends;
650
- };
651
- /**
652
- * Estimated bounds of a wrapped label, using the shared font metrics the dialects size boxes by.
653
- * The last line carries no trailing leading: `lineH ≈ 1.35em` (see `FONT_METRICS`), so the glyph
654
- * block is `(lines − 1) · lineH + em`.
655
- */
656
- var labelFits = ({ rect, text, weight }) => {
657
- if (!text) return true;
658
- const font = FONT_METRICS[weight];
659
- const perLine = Math.max(1, Math.floor(rect.w / font.charW));
660
- return (text.split("\n").reduce((total, line) => total + Math.max(1, Math.ceil(line.length / perLine)), 0) - 1) * font.lineH + font.lineH / 1.35 <= rect.h + EPSILON;
661
- };
662
- /**
663
- * Analyzes an emitted scene for layout defects and quality metrics. Pure: the same scene always
664
- * yields the same report, so the soft metrics can be golden-filed and the hard codes asserted.
665
- */
666
- var analyze = (objects, { maxBends = 3 } = {}) => {
667
- const diagnostics = [];
668
- const allNodes = nodes(objects);
669
- const allConnectors = connectors(objects);
670
- const ownerOf = (ref) => ref.slice(0, ref.indexOf("/"));
671
- const containers = new Set(allNodes.filter((outer) => allNodes.some((inner) => ownerOf(inner.ref) !== ownerOf(outer.ref) && contains(outer.rect, inner.rect))).map(({ ref }) => ref));
672
- for (let i = 0; i < allNodes.length; i++) for (let j = i + 1; j < allNodes.length; j++) {
673
- const [left, right] = [allNodes[i], allNodes[j]];
674
- if (ownerOf(left.ref) === ownerOf(right.ref)) continue;
675
- if (contains(left.rect, right.rect) || contains(right.rect, left.rect)) continue;
676
- if (overlapArea(left.rect, right.rect) > 0) diagnostics.push({
677
- code: "node-overlap",
678
- severity: SEVERITY["node-overlap"],
679
- message: `Nodes "${left.ref}" and "${right.ref}" overlap.`,
680
- refs: [left.ref, right.ref]
681
- });
682
- }
683
- for (const connector of allConnectors) for (const node of allNodes) {
684
- if (ownerOf(node.ref) === ownerOf(connector.ref) || containers.has(node.ref)) continue;
685
- if (segmentsOf$1(connector).reduce((total, segment) => total + interiorRun(segment, node.rect), 0) > EPSILON) diagnostics.push({
686
- code: "route-through-node",
687
- severity: SEVERITY["route-through-node"],
688
- message: `Connector "${connector.ref}" is drawn across node "${node.ref}".`,
689
- refs: [connector.ref, node.ref]
690
- });
691
- }
692
- for (const node of allNodes) if (!labelFits(node)) diagnostics.push({
693
- code: "label-overflow",
694
- severity: SEVERITY["label-overflow"],
695
- message: `Label of "${node.ref}" does not fit its shape.`,
696
- refs: [node.ref]
697
- });
698
- let crossings = 0;
699
- for (let i = 0; i < allConnectors.length; i++) for (let j = i + 1; j < allConnectors.length; j++) {
700
- const [left, right] = [allConnectors[i], allConnectors[j]];
701
- if (segmentsOf$1(left).some((a) => segmentsOf$1(right).some((b) => properlyCrosses(a, b)))) {
702
- crossings++;
703
- diagnostics.push({
704
- code: "edge-crossing",
705
- severity: SEVERITY["edge-crossing"],
706
- message: `Connectors "${left.ref}" and "${right.ref}" cross.`,
707
- refs: [left.ref, right.ref]
708
- });
709
- }
710
- }
711
- let bends = 0;
712
- let length = 0;
713
- for (const connector of allConnectors) {
714
- length += segmentsOf$1(connector).reduce((total, [a, b]) => total + Math.hypot(b.x - a.x, b.y - a.y), 0);
715
- const count = bendCount(connector);
716
- bends += count;
717
- if (count > maxBends) diagnostics.push({
718
- code: "excessive-bends",
719
- severity: SEVERITY["excessive-bends"],
720
- message: `Connector "${connector.ref}" takes ${count} bends.`,
721
- refs: [connector.ref]
722
- });
723
- }
724
- const xs = allNodes.flatMap(({ rect }) => [rect.x, rect.x + rect.w]);
725
- const ys = allNodes.flatMap(({ rect }) => [rect.y, rect.y + rect.h]);
726
- const frameRects = allNodes.filter(({ ref }) => containers.has(ref)).map(({ rect }) => rect);
727
- const gapBetween = (a, b) => Math.max(a.x - (b.x + b.w), b.x - (a.x + a.w), a.y - (b.y + b.h), b.y - (a.y + a.h));
728
- const nearestGaps = frameRects.flatMap((frame, index) => {
729
- const gaps = frameRects.filter((_, other) => other !== index).map((other) => gapBetween(frame, other)).filter((gap) => gap >= 0);
730
- return gaps.length ? [Math.min(...gaps)] : [];
731
- });
732
- return {
733
- diagnostics,
734
- metrics: {
735
- nodes: allNodes.length,
736
- connectors: allConnectors.length,
737
- overlaps: diagnostics.filter(({ code }) => code === "node-overlap").length,
738
- routesThroughNodes: diagnostics.filter(({ code }) => code === "route-through-node").length,
739
- labelOverflows: diagnostics.filter(({ code }) => code === "label-overflow").length,
740
- crossings,
741
- bends,
742
- length,
743
- width: xs.length ? Math.max(...xs) - Math.min(...xs) : 0,
744
- height: ys.length ? Math.max(...ys) - Math.min(...ys) : 0,
745
- containers: frameRects.length,
746
- frameGapMin: nearestGaps.length ? Math.min(...nearestGaps) : Infinity,
747
- frameGapSpread: nearestGaps.length >= 2 ? Math.max(...nearestGaps) - Math.min(...nearestGaps) : 0
748
- }
749
- };
750
- };
751
- /** Errors only — the set that gates CI and that an agent must fix before the diagram is usable. */
752
- var errors = (report) => report.diagnostics.filter(({ severity }) => severity === "error");
753
- //#endregion
754
- //#region src/mermaid.ts
755
- var mermaid_exports = /* @__PURE__ */ __exportAll({
756
- compile: () => compile$7,
757
- markers: () => markers,
758
- parse: () => parse$1,
759
- toStandard: () => toStandard
760
- });
761
- /**
762
- * Edge tokens: mermaid's own arrows read as references; the classDiagram-style `--|>` (hollow
763
- * triangle) and ER-style `--{` (crow's foot) and `o-->` (circle at the source) extend the flowchart
764
- * grammar with the UML kinds, at the cost of mermaid.js rejecting those lines.
765
- */
766
- var EDGE_KINDS = {
767
- "-->": "reference",
768
- "---": "reference",
769
- "-.->": "reference",
770
- "==>": "reference",
771
- "--|>": "inheritance",
772
- "--{": "hasMany",
773
- "o-->": "contains"
774
- };
775
- /** Reference-render labels for the UML kinds, used when the edge carries none of its own. */
776
- var UML_LABELS = {
777
- "--|>": "extends",
778
- "--{": "has many",
779
- "o-->": "contains"
780
- };
781
- var UML_EDGE = /^(\s*\S+)\s*(o-->|--\|>|--\{)\s*(?:\|(.*?)\|\s*)?(\S+)\s*$/;
782
- /**
783
- * Rewrite the UML edge tokens into mermaid-legal labelled arrows, for rendering the source with
784
- * mermaid.js itself (as a reference beside our layout). Lossy by design: mermaid flowcharts have no
785
- * triangle or crow's foot, so the kind becomes the label unless the edge already has one; `%% ref`
786
- * lines are already comments.
787
- */
788
- var toStandard = (source) => source.split("\n").map((line) => {
789
- const match = UML_EDGE.exec(line);
790
- return match ? `${match[1]} -->|${match[3] ?? UML_LABELS[match[2]]}| ${match[4]}` : line;
791
- }).join("\n");
792
- /** Scene arrow markers for a relationship kind. */
793
- var markers = (kind) => {
794
- switch (kind) {
795
- case "inheritance": return { head: "triangle" };
796
- case "hasMany": return { head: "crowsfoot" };
797
- case "contains": return { tail: "circle" };
798
- default: return {};
799
- }
800
- };
801
- var DIRECTIONS$1 = [
802
- "TB",
803
- "BT",
804
- "LR",
805
- "RL"
806
- ];
807
- var NODE = /^([A-Za-z0-9_-]+)(?:\[(.*?)\]|\((.*?)\)|\{(.*?)\})?$/;
808
- var EDGE = /^(.+?)\s*(o-->|--\|>|--\{|-->|---|-\.->|==>)\s*(?:\|(.*?)\|\s*)?(.+)$/;
809
- var SUBGRAPH = /^subgraph\s+([A-Za-z0-9_-]+)(?:\s*\[(.*?)\])?\s*$/;
810
- var REF = /^%%\s*ref\s+([A-Za-z0-9_-]+)\s+(\S+)\s*$/;
811
- var unquote = (value) => value.trim().replace(/^"(.*)"$/, "$1");
812
- /** Parse a mermaid flowchart into a neutral graph. Unrecognised lines are ignored. */
813
- var parse$1 = (source) => {
814
- const nodes = /* @__PURE__ */ new Map();
815
- const groups = [];
816
- const edges = [];
817
- let direction = "TB";
818
- const stack = [];
819
- const declare = (token) => {
820
- const match = NODE.exec(token.trim());
821
- if (!match) return;
822
- const [, id, square, round, curly] = match;
823
- const label = unquote(square ?? round ?? curly ?? id);
824
- const group = stack[stack.length - 1];
825
- const existing = nodes.get(id);
826
- if (existing) {
827
- if (square ?? round ?? curly) existing.label = label;
828
- return id;
829
- }
830
- nodes.set(id, {
831
- id,
832
- label,
833
- ...group ? { group } : {}
834
- });
835
- if (group) groups.find((entry) => entry.id === group)?.children.push(id);
836
- return id;
837
- };
838
- const refs = /* @__PURE__ */ new Map();
839
- for (const raw of source.split("\n")) {
840
- const line = raw.trim();
841
- const ref = REF.exec(line);
842
- if (ref) {
843
- refs.set(ref[1], ref[2]);
844
- continue;
845
- }
846
- if (!line || line.startsWith("%%")) continue;
847
- const header = /^(?:flowchart|graph)\s+([A-Za-z]{2})$/.exec(line);
848
- if (header) {
849
- const value = header[1].toUpperCase();
850
- direction = DIRECTIONS$1.includes(value) ? value : "TB";
851
- continue;
852
- }
853
- const subgraph = SUBGRAPH.exec(line);
854
- if (subgraph) {
855
- const [, id, label] = subgraph;
856
- groups.push({
857
- id,
858
- label: unquote(label ?? id),
859
- children: []
860
- });
861
- stack.push(id);
862
- continue;
863
- }
864
- if (line === "end") {
865
- stack.pop();
866
- continue;
867
- }
868
- const edge = EDGE.exec(line);
869
- if (edge) {
870
- const [, from, token, label, to] = edge;
871
- const fromId = declare(from);
872
- const toId = declare(to);
873
- if (fromId && toId) edges.push({
874
- from: fromId,
875
- to: toId,
876
- kind: EDGE_KINDS[token] ?? "reference",
877
- ...label ? { label: unquote(label) } : {}
878
- });
879
- continue;
880
- }
881
- declare(line);
882
- }
883
- return {
884
- direction,
885
- nodes: [...nodes.values()].map((node) => refs.has(node.id) ? {
886
- ...node,
887
- ref: refs.get(node.id)
888
- } : node),
889
- groups,
890
- edges
891
- };
892
- };
893
- /** Layout constants in scene units. */
894
- var NODE_W = 100;
895
- var NODE_H = 50;
896
- var GAP_MAIN$3 = 60;
897
- var GAP_CROSS$3 = 40;
898
- var GROUP_PAD = 24;
899
- /** Assign each node a rank one past its deepest forward predecessor. */
900
- var rank = (graph) => rank$1(graph.nodes.map((node) => node.id), graph.edges);
901
- /**
902
- * Compile a mermaid flowchart into scene commands: one world object per node (element `box`),
903
- * one per subgraph frame, and an `edges` object holding the connectors as bound arrows.
904
- */
905
- var compile$7 = (source, options = {}) => {
906
- const graph = parse$1(source);
907
- const { origin = {
908
- x: 0,
909
- y: 0
910
- }, scale = 1, gapMain = GAP_MAIN$3, gapCross = GAP_CROSS$3 } = options;
911
- const ranks = rank(graph);
912
- const horizontal = graph.direction === "LR" || graph.direction === "RL";
913
- const lanes = /* @__PURE__ */ new Map();
914
- for (const node of graph.nodes) {
915
- const value = ranks.get(node.id) ?? 0;
916
- lanes.set(value, [...lanes.get(value) ?? [], node]);
917
- }
918
- const widest = Math.max(...[...lanes.values()].map((lane) => lane.length), 1);
919
- const mainSize = horizontal ? NODE_W : NODE_H;
920
- const crossSize = horizontal ? NODE_H : NODE_W;
921
- const positions = /* @__PURE__ */ new Map();
922
- for (const [lane, members] of lanes) {
923
- const span = members.length * crossSize + (members.length - 1) * gapCross;
924
- const offset = (widest * crossSize + (widest - 1) * gapCross - span) / 2;
925
- members.forEach((node, index) => {
926
- const cross = offset + index * (crossSize + gapCross);
927
- const main = lane * (mainSize + gapMain);
928
- positions.set(node.id, horizontal ? {
929
- x: main,
930
- y: cross
931
- } : {
932
- x: cross,
933
- y: main
934
- });
935
- });
936
- }
937
- const commands = [];
938
- for (const group of graph.groups) {
939
- const members = group.children.map((id) => positions.get(id)).filter((point) => !!point);
940
- if (members.length === 0) continue;
941
- const minX = Math.min(...members.map((point) => point.x)) - GROUP_PAD;
942
- const minY = Math.min(...members.map((point) => point.y)) - GROUP_PAD;
943
- const maxX = Math.max(...members.map((point) => point.x)) + NODE_W + GROUP_PAD;
944
- const maxY = Math.max(...members.map((point) => point.y)) + NODE_H + GROUP_PAD;
945
- commands.push({
946
- op: "upsert-object",
947
- object: {
948
- id: group.id,
949
- origin: {
950
- x: origin.x + minX * scale,
951
- y: origin.y + minY * scale
952
- },
953
- scale,
954
- elements: [{
955
- kind: "rect",
956
- id: "frame",
957
- x: 0,
958
- y: 0,
959
- w: maxX - minX,
960
- h: maxY - minY,
961
- stroke: "dashed",
962
- color: "grey",
963
- ...group.label.trim() ? { text: group.label } : {}
964
- }]
965
- }
966
- });
967
- }
968
- for (const node of graph.nodes) {
969
- const point = positions.get(node.id);
970
- commands.push({
971
- op: "upsert-object",
972
- object: {
973
- id: node.id,
974
- origin: {
975
- x: origin.x + point.x * scale,
976
- y: origin.y + point.y * scale
977
- },
978
- scale,
979
- ...node.ref ? { ref: node.ref } : {},
980
- elements: [{
981
- kind: "rect",
982
- id: "box",
983
- x: 0,
984
- y: 0,
985
- w: NODE_W,
986
- h: NODE_H,
987
- text: node.label
988
- }]
989
- }
990
- });
991
- }
992
- if (graph.edges.length > 0) commands.push({
993
- op: "upsert-object",
994
- object: {
995
- id: "edges",
996
- origin,
997
- scale,
998
- elements: graph.edges.map((edge) => ({
999
- kind: "arrow",
1000
- id: `${edge.from}-${edge.to}`,
1001
- from: `${edge.from}/box`,
1002
- to: `${edge.to}/box`,
1003
- ...markers(edge.kind),
1004
- ...edge.label ? { text: edge.label } : {}
1005
- }))
1006
- }
1007
- });
1008
- return commands;
1009
- };
1010
- //#endregion
1011
- //#region src/ortho-router.ts
1012
- /** Clearance kept between a route and any node border, in steps. */
1013
- var CLEARANCE = 1;
1014
- /** One bend costs as much as this many steps — turns dominate, distance breaks ties. */
1015
- var TURN_COST = 1e3;
1016
- /** Traversing a cell another edge already used costs this many extra steps. */
1017
- var USED_COST = 24;
1018
- /** Margin around the diagram bounds the search may roam into, in steps. */
1019
- var MARGIN = 8;
1020
- /** Directions: right, down, left, up. */
1021
- var DX = [
1022
- 1,
1023
- 0,
1024
- -1,
1025
- 0
1026
- ];
1027
- var DY = [
1028
- 0,
1029
- 1,
1030
- 0,
1031
- -1
1032
- ];
1033
- /**
1034
- * Creates a router that avoids the given node rects. Stateful across edges: earlier routes
1035
- * penalize (not block) the cells they occupy, spreading parallel runs apart. `fallback` handles
1036
- * the (fenced-in) edges the search cannot reach. The step is passed in rather than derived from
1037
- * `uml-grid`'s `GRID`, which keeps this module free of a value import cycle with the dialects.
1038
- */
1039
- var makeAvoidingRouter = (obstacles, fallback, { step: STEP }) => {
1040
- const xs = obstacles.flatMap((rect) => [rect.x, rect.x + rect.w]);
1041
- const ys = obstacles.flatMap((rect) => [rect.y, rect.y + rect.h]);
1042
- const bounds = {
1043
- x0: Math.floor(Math.min(...xs) / STEP) - MARGIN,
1044
- y0: Math.floor(Math.min(...ys) / STEP) - MARGIN,
1045
- x1: Math.ceil(Math.max(...xs) / STEP) + MARGIN,
1046
- y1: Math.ceil(Math.max(...ys) / STEP) + MARGIN
1047
- };
1048
- const width = bounds.x1 - bounds.x0 + 1;
1049
- const height = bounds.y1 - bounds.y0 + 1;
1050
- const inBounds = (x, y) => x >= bounds.x0 && x <= bounds.x1 && y >= bounds.y0 && y <= bounds.y1;
1051
- const cellIndex = (x, y) => (y - bounds.y0) * width + (x - bounds.x0);
1052
- const blockedGrid = new Uint8Array(width * height);
1053
- for (const rect of obstacles) {
1054
- const x0 = Math.floor(rect.x / STEP) - CLEARANCE;
1055
- const y0 = Math.floor(rect.y / STEP) - CLEARANCE;
1056
- const x1 = Math.ceil((rect.x + rect.w) / STEP) + CLEARANCE;
1057
- const y1 = Math.ceil((rect.y + rect.h) / STEP) + CLEARANCE;
1058
- for (let y = Math.max(bounds.y0, y0 + 1); y < Math.min(bounds.y1 + 1, y1); y++) for (let x = Math.max(bounds.x0, x0 + 1); x < Math.min(bounds.x1 + 1, x1); x++) blockedGrid[cellIndex(x, y)] = 1;
1059
- }
1060
- const blocked = (x, y) => inBounds(x, y) && blockedGrid[cellIndex(x, y)] !== 0;
1061
- const usedGrid = new Uint8Array(width * height);
1062
- const isUsed = (x, y) => inBounds(x, y) && usedGrid[cellIndex(x, y)] !== 0;
1063
- const markUsed = (x, y) => {
1064
- if (inBounds(x, y)) usedGrid[cellIndex(x, y)] = 1;
1065
- };
1066
- const settledCost = new Float64Array(width * height * 4);
1067
- const settledGen = new Int32Array(width * height * 4);
1068
- let generation = 0;
1069
- /** Binary min-heap on cost + estimate, so each pop is O(log n) instead of a frontier scan. */
1070
- const heapPush = (heap, state) => {
1071
- heap.push(state);
1072
- let index = heap.length - 1;
1073
- while (index > 0) {
1074
- const parent = index - 1 >> 1;
1075
- if (heap[parent].f <= heap[index].f) break;
1076
- [heap[parent], heap[index]] = [heap[index], heap[parent]];
1077
- index = parent;
1078
- }
1079
- };
1080
- const heapPop = (heap) => {
1081
- const top = heap[0];
1082
- const last = heap.pop();
1083
- if (heap.length > 0) {
1084
- heap[0] = last;
1085
- let index = 0;
1086
- for (;;) {
1087
- const left = index * 2 + 1;
1088
- const right = left + 1;
1089
- let smallest = index;
1090
- if (left < heap.length && heap[left].f < heap[smallest].f) smallest = left;
1091
- if (right < heap.length && heap[right].f < heap[smallest].f) smallest = right;
1092
- if (smallest === index) break;
1093
- [heap[smallest], heap[index]] = [heap[index], heap[smallest]];
1094
- index = smallest;
1095
- }
1096
- }
1097
- return top;
1098
- };
1099
- /**
1100
- * Fewest turns any path from (x, y) heading `dir` needs to reach the target: none while aligned
1101
- * and heading at it, two when heading away along its axis, otherwise one. Admissible, and since
1102
- * a turn costs as much as a thousand steps it is what makes the search converge on a diagram
1103
- * hundreds of cells across — distance alone leaves it exploring almost uniformly.
1104
- */
1105
- const turnsNeeded = (x, y, dir, target) => {
1106
- const dx = target.x - x;
1107
- const dy = target.y - y;
1108
- if (dx !== 0 && dy !== 0) return 1;
1109
- if (dx === 0 && dy === 0) return 0;
1110
- const towards = dx !== 0 ? dx > 0 ? 0 : 2 : dy > 0 ? 1 : 3;
1111
- return dir === towards ? 0 : (dir + 2) % 4 === towards ? 2 : 1;
1112
- };
1113
- const estimateFrom = (x, y, dir, target) => Math.abs(target.x - x) + Math.abs(target.y - y) + turnsNeeded(x, y, dir, target) * TURN_COST;
1114
- const budget = Math.max(5e4, (bounds.x1 - bounds.x0) * (bounds.y1 - bounds.y0) * 4);
1115
- /**
1116
- * Bounded A* between two stub ends (grid coordinates). Successors already dominated by a
1117
- * settled state are pruned before pushing; undefined when the target is unreachable.
1118
- */
1119
- const search = (source, startDir, target, endDir) => {
1120
- const open = [{
1121
- x: source.x,
1122
- y: source.y,
1123
- dir: startDir,
1124
- cost: 0,
1125
- f: estimateFrom(source.x, source.y, startDir, target)
1126
- }];
1127
- generation++;
1128
- const settledAt = (index) => settledGen[index] === generation ? settledCost[index] : void 0;
1129
- let found;
1130
- for (let iterations = 0; open.length > 0 && iterations < budget; iterations++) {
1131
- const current = heapPop(open);
1132
- if (current.x === target.x && current.y === target.y) {
1133
- found = current;
1134
- break;
1135
- }
1136
- const stateIndex = cellIndex(current.x, current.y) * 4 + current.dir;
1137
- const seen = settledAt(stateIndex);
1138
- if (seen !== void 0 && seen <= current.cost) continue;
1139
- settledGen[stateIndex] = generation;
1140
- settledCost[stateIndex] = current.cost;
1141
- for (let dir = 0; dir < 4; dir++) {
1142
- if ((dir + 2) % 4 === current.dir) continue;
1143
- const x = current.x + DX[dir];
1144
- const y = current.y + DY[dir];
1145
- if (!inBounds(x, y)) continue;
1146
- const cell = cellIndex(x, y);
1147
- if (blockedGrid[cell] !== 0) continue;
1148
- const cost = current.cost + 1 + (dir === current.dir ? 0 : TURN_COST) + (usedGrid[cell] !== 0 ? USED_COST : 0) + (x === target.x && y === target.y && dir !== endDir ? TURN_COST : 0);
1149
- const dominated = settledAt(cell * 4 + dir);
1150
- if (dominated !== void 0 && dominated <= cost) continue;
1151
- heapPush(open, {
1152
- x,
1153
- y,
1154
- dir,
1155
- cost,
1156
- f: cost + estimateFrom(x, y, dir, target),
1157
- prev: current
1158
- });
1159
- }
1160
- }
1161
- if (!found) return;
1162
- const cells = [];
1163
- for (let state = found; state; state = state.prev) cells.unshift({
1164
- x: state.x,
1165
- y: state.y
1166
- });
1167
- return {
1168
- cost: found.cost,
1169
- cells
1170
- };
1171
- };
1172
- return (edge) => {
1173
- const { from, to, horizontal, ports } = edge;
1174
- const sameLane = horizontal ? from.x === to.x : from.y === to.y;
1175
- const alongY = horizontal ? sameLane : !sameLane;
1176
- const down = to.y >= from.y;
1177
- const right = to.x >= from.x;
1178
- const flowStart = alongY ? {
1179
- point: {
1180
- x: ports?.start ?? from.x + from.w / 2,
1181
- y: down ? from.y + from.h : from.y
1182
- },
1183
- dir: down ? 1 : 3
1184
- } : {
1185
- point: {
1186
- x: right ? from.x + from.w : from.x,
1187
- y: ports?.start ?? from.y + from.h / 2
1188
- },
1189
- dir: right ? 0 : 2
1190
- };
1191
- const flowEnd = alongY ? {
1192
- point: {
1193
- x: ports?.end ?? to.x + to.w / 2,
1194
- y: down ? to.y : to.y + to.h
1195
- },
1196
- dir: down ? 1 : 3
1197
- } : {
1198
- point: {
1199
- x: right ? to.x : to.x + to.w,
1200
- y: ports?.end ?? to.y + to.h / 2
1201
- },
1202
- dir: right ? 0 : 2
1203
- };
1204
- const crossStart = alongY ? {
1205
- point: {
1206
- x: right ? from.x + from.w : from.x,
1207
- y: from.y + from.h / 2
1208
- },
1209
- dir: right ? 0 : 2
1210
- } : {
1211
- point: {
1212
- x: from.x + from.w / 2,
1213
- y: down ? from.y + from.h : from.y
1214
- },
1215
- dir: down ? 1 : 3
1216
- };
1217
- const crossEnd = alongY ? {
1218
- point: {
1219
- x: right ? to.x : to.x + to.w,
1220
- y: to.y + to.h / 2
1221
- },
1222
- dir: right ? 0 : 2
1223
- } : {
1224
- point: {
1225
- x: to.x + to.w / 2,
1226
- y: down ? to.y : to.y + to.h
1227
- },
1228
- dir: down ? 1 : 3
1229
- };
1230
- const configurations = (alongY ? flowStart.point.x !== flowEnd.point.x : flowStart.point.y !== flowEnd.point.y) ? [
1231
- [flowStart, flowEnd],
1232
- [crossStart, flowEnd],
1233
- [flowStart, crossEnd]
1234
- ] : [[flowStart, flowEnd]];
1235
- const stub = (point, dir, out) => {
1236
- const sign = out ? 1 : -1;
1237
- return {
1238
- x: Math.round(point.x / STEP) + DX[dir] * 2 * sign,
1239
- y: Math.round(point.y / STEP) + DY[dir] * 2 * sign
1240
- };
1241
- };
1242
- let start;
1243
- let end;
1244
- let best;
1245
- for (const [startTerminal, endTerminal] of configurations) {
1246
- const result = search(stub(startTerminal.point, startTerminal.dir, true), startTerminal.dir, stub(endTerminal.point, endTerminal.dir, false), endTerminal.dir);
1247
- if (result && (!best || result.cost < best.cost)) {
1248
- best = result;
1249
- start = startTerminal.point;
1250
- end = endTerminal.point;
1251
- }
1252
- }
1253
- if (!best || !start || !end) return fallback(edge);
1254
- const points = [
1255
- start,
1256
- ...best.cells.map((cell) => ({
1257
- x: cell.x * STEP,
1258
- y: cell.y * STEP
1259
- })),
1260
- end
1261
- ];
1262
- const simplified = [points[0]];
1263
- for (let index = 1; index < points.length - 1; index++) {
1264
- const previous = simplified[simplified.length - 1];
1265
- const next = points[index + 1];
1266
- const point = points[index];
1267
- if (!(previous.x === point.x && point.x === next.x || previous.y === point.y && point.y === next.y)) simplified.push(point);
1268
- }
1269
- simplified.push(points[points.length - 1]);
1270
- const clearRun = (a, b) => {
1271
- const ax = Math.round(a.x / STEP);
1272
- const ay = Math.round(a.y / STEP);
1273
- const bx = Math.round(b.x / STEP);
1274
- const by = Math.round(b.y / STEP);
1275
- const steps = Math.max(Math.abs(bx - ax), Math.abs(by - ay));
1276
- const dx = Math.sign(bx - ax);
1277
- const dy = Math.sign(by - ay);
1278
- for (let step = 0; step <= steps; step++) if (blocked(ax + dx * step, ay + dy * step) || isUsed(ax + dx * step, ay + dy * step)) return false;
1279
- return true;
1280
- };
1281
- const past = (from, to) => ({
1282
- x: from.x + Math.sign(to.x - from.x) * 2 * STEP,
1283
- y: from.y + Math.sign(to.y - from.y) * 2 * STEP
1284
- });
1285
- if (simplified.length === 4) {
1286
- const [p0, p1, p2, p3] = simplified;
1287
- if (p1.y === p2.y && p0.y !== p3.y) {
1288
- const mid = Math.round((p0.y + p3.y) / 2 / STEP) * STEP;
1289
- const shifted = [{
1290
- x: p1.x,
1291
- y: mid
1292
- }, {
1293
- x: p2.x,
1294
- y: mid
1295
- }];
1296
- if (clearRun(shifted[0], shifted[1]) && clearRun(past(p0, shifted[0]), shifted[0]) && clearRun(shifted[1], past(p3, shifted[1]))) {
1297
- simplified[1] = shifted[0];
1298
- simplified[2] = shifted[1];
1299
- }
1300
- } else if (p1.x === p2.x && p0.x !== p3.x) {
1301
- const mid = Math.round((p0.x + p3.x) / 2 / STEP) * STEP;
1302
- const shifted = [{
1303
- x: mid,
1304
- y: p1.y
1305
- }, {
1306
- x: mid,
1307
- y: p2.y
1308
- }];
1309
- if (clearRun(shifted[0], shifted[1]) && clearRun(past(p0, shifted[0]), shifted[0]) && clearRun(shifted[1], past(p3, shifted[1]))) {
1310
- simplified[1] = shifted[0];
1311
- simplified[2] = shifted[1];
1312
- }
1313
- }
1314
- }
1315
- for (let index = 0; index < simplified.length - 1; index++) {
1316
- const a = simplified[index];
1317
- const b = simplified[index + 1];
1318
- const ax = Math.round(a.x / STEP);
1319
- const ay = Math.round(a.y / STEP);
1320
- const steps = Math.max(Math.abs(Math.round(b.x / STEP) - ax), Math.abs(Math.round(b.y / STEP) - ay));
1321
- const dx = Math.sign(b.x - a.x);
1322
- const dy = Math.sign(b.y - a.y);
1323
- for (let step = 0; step <= steps; step++) markUsed(ax + dx * step, ay + dy * step);
1324
- }
1325
- return simplified;
1326
- };
1327
- };
1328
- //#endregion
1329
- //#region src/uml.ts
1330
- var uml_exports = /* @__PURE__ */ __exportAll({
1331
- compile: () => compile$6,
1332
- isClassDiagram: () => isClassDiagram,
1333
- parse: () => parse,
1334
- relationRanks: () => relationRanks,
1335
- relationStyle: () => relationStyle,
1336
- relationText: () => relationText
1337
- });
1338
- var DIRECTIONS = [
1339
- "TB",
1340
- "BT",
1341
- "LR",
1342
- "RL"
1343
- ];
1344
- var CLASS = /^class\s+([A-Za-z0-9_~<>-]+)(?:\s*\[\s*"(.*?)"\s*\])?\s*(\{)?\s*$/;
1345
- var INLINE_MEMBER = /^([A-Za-z0-9_~<>-]+)\s*:\s*(.+)$/;
1346
- var STEREOTYPE = /^<<\s*(.*?)\s*>>$/;
1347
- /**
1348
- * Relation arrows, normalized to (kind, whether the LEFT side is the arrow target).
1349
- * `A <|-- B` reads "B inherits A": the parent is on the flat side of the triangle.
1350
- */
1351
- var RELATIONS = [
1352
- [
1353
- "<|--",
1354
- "inheritance",
1355
- true
1356
- ],
1357
- [
1358
- "--|>",
1359
- "inheritance",
1360
- false
1361
- ],
1362
- [
1363
- "<|..",
1364
- "realization",
1365
- true
1366
- ],
1367
- [
1368
- "..|>",
1369
- "realization",
1370
- false
1371
- ],
1372
- [
1373
- "*--",
1374
- "composition",
1375
- false
1376
- ],
1377
- [
1378
- "--*",
1379
- "composition",
1380
- true
1381
- ],
1382
- [
1383
- "o--",
1384
- "aggregation",
1385
- false
1386
- ],
1387
- [
1388
- "--o",
1389
- "aggregation",
1390
- true
1391
- ],
1392
- [
1393
- "-->",
1394
- "association",
1395
- false
1396
- ],
1397
- [
1398
- "<--",
1399
- "association",
1400
- true
1401
- ],
1402
- [
1403
- "..>",
1404
- "dependency",
1405
- false
1406
- ],
1407
- [
1408
- "<..",
1409
- "dependency",
1410
- true
1411
- ],
1412
- [
1413
- "..",
1414
- "dependency",
1415
- false
1416
- ],
1417
- [
1418
- "--",
1419
- "association",
1420
- false
1421
- ]
1422
- ];
1423
- var RELATION = new RegExp(`^([A-Za-z0-9_~<>-]+)\\s*(?:"([^"]*)"\\s*)?(${RELATIONS.map(([token]) => token.replace(/[|*.>-]/g, "\\$&")).join("|")})\\s*(?:"([^"]*)"\\s*)?([A-Za-z0-9_~<>-]+)\\s*(?::\\s*(.+))?\$`);
1424
- /** Mermaid generics use `~T~`; render them as `<T>`. */
1425
- var genericLabel = (id) => id.replace(/~([^~]*)~/g, "<$1>");
1426
- /** Parse a mermaid class diagram into a neutral model. Unrecognised lines are ignored. */
1427
- var parse = (source) => {
1428
- const classes = /* @__PURE__ */ new Map();
1429
- const relations = [];
1430
- let direction = "TB";
1431
- let open;
1432
- const declare = (id) => {
1433
- let entry = classes.get(id);
1434
- if (!entry) {
1435
- entry = {
1436
- id,
1437
- label: genericLabel(id),
1438
- attributes: [],
1439
- methods: []
1440
- };
1441
- classes.set(id, entry);
1442
- }
1443
- return entry;
1444
- };
1445
- const addMember = (entry, raw) => {
1446
- const member = raw.trim();
1447
- const stereotype = STEREOTYPE.exec(member);
1448
- if (stereotype) {
1449
- entry.stereotype = stereotype[1];
1450
- return;
1451
- }
1452
- if (member) (member.includes("(") ? entry.methods : entry.attributes).push(member);
1453
- };
1454
- for (const raw of source.split("\n")) {
1455
- const line = raw.trim();
1456
- if (!line || line.startsWith("%%") || /^classDiagram(-v2)?$/.test(line)) continue;
1457
- if (open) {
1458
- if (line === "}") open = void 0;
1459
- else addMember(open, line);
1460
- continue;
1461
- }
1462
- const header = /^direction\s+([A-Za-z]{2})$/.exec(line);
1463
- if (header) {
1464
- const value = header[1].toUpperCase();
1465
- direction = DIRECTIONS.includes(value) ? value : "TB";
1466
- continue;
1467
- }
1468
- const cls = CLASS.exec(line);
1469
- if (cls) {
1470
- const [, id, label, brace] = cls;
1471
- const entry = declare(id);
1472
- if (label) entry.label = label;
1473
- if (brace) open = entry;
1474
- continue;
1475
- }
1476
- const relation = RELATION.exec(line);
1477
- if (relation) {
1478
- const [, left, leftCardinality, token, rightCardinality, right, label] = relation;
1479
- declare(left);
1480
- declare(right);
1481
- const [, kind, leftIsTarget] = RELATIONS.find(([candidate]) => candidate === token);
1482
- const [from, to] = leftIsTarget ? [right, left] : [left, right];
1483
- const [fromCardinality, toCardinality] = leftIsTarget ? [rightCardinality, leftCardinality] : [leftCardinality, rightCardinality];
1484
- relations.push({
1485
- from,
1486
- to,
1487
- kind,
1488
- ...label ? { label: label.trim() } : {},
1489
- ...fromCardinality ? { fromCardinality } : {},
1490
- ...toCardinality ? { toCardinality } : {}
1491
- });
1492
- continue;
1493
- }
1494
- const inline = INLINE_MEMBER.exec(line);
1495
- if (inline) {
1496
- const [, id, member] = inline;
1497
- addMember(declare(id), member);
1498
- }
1499
- }
1500
- return {
1501
- direction,
1502
- classes: [...classes.values()],
1503
- relations
1504
- };
1505
- };
1506
- /**
1507
- * Box geometry in scene units; text sizes come from the shared per-weight metrics table
1508
- * (`Layout.FONT_METRICS`): the title renders at the default weight (m), members at s.
1509
- */
1510
- var TITLE_FONT$1 = FONT_METRICS.m;
1511
- var MEMBER_FONT$1 = FONT_METRICS.s;
1512
- var MIN_W$2 = 160;
1513
- var MAX_W$2 = 400;
1514
- var PAD_X$2 = 24;
1515
- var TITLE_PAD$1 = 10;
1516
- var SECTION_PAD$1 = 14;
1517
- var GAP_MAIN$2 = 90;
1518
- var GAP_CROSS$2 = 60;
1519
- /** Compartment sizes derived from member counts and line lengths, per compartment font. */
1520
- var measure = (entry, maxWidth) => {
1521
- const titleW = Math.max(entry.label.length, (entry.stereotype?.length ?? 0) + 2) * TITLE_FONT$1.charW;
1522
- const memberW = Math.max(0, ...[...entry.attributes, ...entry.methods].map((line) => line.length)) * MEMBER_FONT$1.charW;
1523
- const w = Math.min(maxWidth, Math.max(MIN_W$2, Math.ceil(Math.max(titleW, memberW)) + PAD_X$2));
1524
- const lines = (text, font) => Math.max(1, Math.ceil(text.length * font.charW / (w - PAD_X$2)));
1525
- const memberH = (members) => members.length ? members.reduce((sum, line) => sum + lines(line, MEMBER_FONT$1), 0) * MEMBER_FONT$1.lineH + SECTION_PAD$1 : 0;
1526
- const titleH = (lines(entry.label, TITLE_FONT$1) + (entry.stereotype ? 1 : 0)) * TITLE_FONT$1.lineH + TITLE_PAD$1;
1527
- const attributesH = memberH(entry.attributes);
1528
- const methodsH = memberH(entry.methods);
1529
- return {
1530
- entry,
1531
- w,
1532
- h: titleH + attributesH + methodsH,
1533
- titleH,
1534
- attributesH,
1535
- methodsH
1536
- };
1537
- };
1538
- /** Arrow styling per relation kind; dependency and realization render dashed, as in UML. */
1539
- var relationStyle = (kind) => kind === "dependency" || kind === "realization" ? { stroke: "dashed" } : {};
1540
- /** Mid-arrow label: cardinalities and the diamond glyph UML puts at the whole end. */
1541
- var relationText = (relation) => {
1542
- const glyph = relation.kind === "composition" ? "◆" : relation.kind === "aggregation" ? "◇" : void 0;
1543
- return [
1544
- relation.fromCardinality,
1545
- relation.label ?? glyph,
1546
- relation.toCardinality
1547
- ].filter(Boolean).join(" ") || void 0;
1548
- };
1549
- /**
1550
- * Rank so arrows read upward where UML convention points at abstractions — inheritance,
1551
- * realization, and dependency targets sit above their sources; containment and association
1552
- * flow downward (whole/source above part/target).
1553
- */
1554
- var relationRanks = (model) => rank$1(model.classes.map((entry) => entry.id), model.relations.map((relation) => relation.kind === "inheritance" || relation.kind === "realization" || relation.kind === "dependency" ? {
1555
- from: relation.to,
1556
- to: relation.from
1557
- } : {
1558
- from: relation.from,
1559
- to: relation.to
1560
- }));
1561
- /**
1562
- * Compile a mermaid class diagram into scene commands: one world object per class (compartment
1563
- * rects `title`/`attributes`/`methods`) and an `edges` object holding the relations as bound
1564
- * arrows. Supertypes rank above subtypes, so inheritance reads downward.
1565
- */
1566
- var compile$6 = (source, options = {}) => {
1567
- const model = parse(source);
1568
- const { origin = {
1569
- x: 0,
1570
- y: 0
1571
- }, scale = 1, gapMain = GAP_MAIN$2, gapCross = GAP_CROSS$2, maxWidth = MAX_W$2 } = options;
1572
- const horizontal = model.direction === "LR" || model.direction === "RL";
1573
- const ranks = relationRanks(model);
1574
- const boxes = new Map(model.classes.map((entry) => [entry.id, measure(entry, maxWidth)]));
1575
- const lanes = /* @__PURE__ */ new Map();
1576
- for (const entry of model.classes) {
1577
- const value = ranks.get(entry.id) ?? 0;
1578
- lanes.set(value, [...lanes.get(value) ?? [], boxes.get(entry.id)]);
1579
- }
1580
- const cross = (box) => horizontal ? box.h : box.w;
1581
- const main = (box) => horizontal ? box.w : box.h;
1582
- const laneSpan = (members) => members.reduce((sum, box) => sum + cross(box), 0) + (members.length - 1) * gapCross;
1583
- const widest = Math.max(...[...lanes.values()].map(laneSpan), 0);
1584
- const positions = /* @__PURE__ */ new Map();
1585
- let mainOffset = 0;
1586
- for (const lane of [...lanes.keys()].sort((left, right) => left - right)) {
1587
- const members = lanes.get(lane);
1588
- let crossOffset = (widest - laneSpan(members)) / 2;
1589
- for (const box of members) {
1590
- positions.set(box.entry.id, horizontal ? {
1591
- x: mainOffset,
1592
- y: crossOffset
1593
- } : {
1594
- x: crossOffset,
1595
- y: mainOffset
1596
- });
1597
- crossOffset += cross(box) + gapCross;
1598
- }
1599
- mainOffset += Math.max(...members.map(main)) + gapMain;
1600
- }
1601
- const facing = (id, peerId) => {
1602
- const box = boxes.get(id);
1603
- const peer = boxes.get(peerId);
1604
- const targetY = positions.get(peerId).y + peer.h / 2;
1605
- const baseY = positions.get(id).y;
1606
- const compartments = [["title", box.titleH / 2]];
1607
- if (box.attributesH) compartments.push(["attributes", box.titleH + box.attributesH / 2]);
1608
- if (box.methodsH) compartments.push(["methods", box.titleH + box.attributesH + box.methodsH / 2]);
1609
- return compartments.reduce((best, candidate) => Math.abs(baseY + candidate[1] - targetY) < Math.abs(baseY + best[1] - targetY) ? candidate : best)[0];
1610
- };
1611
- const commands = [];
1612
- for (const entry of model.classes) {
1613
- const box = boxes.get(entry.id);
1614
- const point = positions.get(entry.id);
1615
- const title = entry.stereotype ? `«${entry.stereotype}»\n${entry.label}` : entry.label;
1616
- const elements = [{
1617
- kind: "rect",
1618
- id: "title",
1619
- x: 0,
1620
- y: 0,
1621
- w: box.w,
1622
- h: box.titleH,
1623
- text: title,
1624
- fill: "solid"
1625
- }];
1626
- if (box.attributesH) elements.push({
1627
- kind: "rect",
1628
- id: "attributes",
1629
- x: 0,
1630
- y: box.titleH,
1631
- w: box.w,
1632
- h: box.attributesH,
1633
- text: entry.attributes.join("\n"),
1634
- weight: "s"
1635
- });
1636
- if (box.methodsH) elements.push({
1637
- kind: "rect",
1638
- id: "methods",
1639
- x: 0,
1640
- y: box.titleH + box.attributesH,
1641
- w: box.w,
1642
- h: box.methodsH,
1643
- text: entry.methods.join("\n"),
1644
- weight: "s"
1645
- });
1646
- commands.push({
1647
- op: "upsert-object",
1648
- object: {
1649
- id: entry.id,
1650
- origin: {
1651
- x: origin.x + point.x * scale,
1652
- y: origin.y + point.y * scale
1653
- },
1654
- scale,
1655
- elements
1656
- }
1657
- });
1658
- }
1659
- if (model.relations.length > 0) commands.push({
1660
- op: "upsert-object",
1661
- object: {
1662
- id: "edges",
1663
- origin,
1664
- scale,
1665
- elements: model.relations.map((relation, index) => {
1666
- const text = relationText(relation);
1667
- return {
1668
- kind: "arrow",
1669
- id: `${relation.from}-${relation.to}-${index}`,
1670
- from: `${relation.from}/${facing(relation.from, relation.to)}`,
1671
- to: `${relation.to}/${facing(relation.to, relation.from)}`,
1672
- ...relationStyle(relation.kind),
1673
- ...text ? { text } : {}
1674
- };
1675
- })
1676
- }
1677
- });
1678
- return commands;
1679
- };
1680
- /** True when the mermaid source declares a class diagram rather than a flowchart. */
1681
- var isClassDiagram = (source) => /^\s*classDiagram(-v2)?\b/m.test(source);
1682
- //#endregion
1683
- //#region src/uml-grid.ts
1684
- var uml_grid_exports = /* @__PURE__ */ __exportAll({
1685
- GRID: () => 32,
1686
- compile: () => compile$5,
1687
- emit: () => emit$1,
1688
- measureCell: () => measureCell$1,
1689
- zRouter: () => zRouter
1690
- });
1691
- /** Half-cell minor grid, used for sub-cell nudges (ports, channel separation, label offsets). */
1692
- var GRID_FINE$1 = 16;
1693
- var MIN_W$1 = 64;
1694
- var MAX_W$1 = 192;
1695
- var MIN_H$1 = 64;
1696
- var MAX_H = 160;
1697
- var MIN_TITLE_H = 32;
1698
- var MAX_TITLE_H = 64;
1699
- var TITLE_PAD = 16;
1700
- var TEXT_PAD = 16;
1701
- var PAD_X$1 = 64;
1702
- var SECTION_PAD = 16;
1703
- var GAP_MAIN$1 = 128;
1704
- var GAP_CROSS$1 = 128;
1705
- var TITLE_FONT = FONT_METRICS.m;
1706
- var MEMBER_FONT = FONT_METRICS.s;
1707
- var snap$3 = (value) => Math.ceil(value / 32) * 32;
1708
- /** Z route along y: leave the facing horizontal edge, run a channel at mid, enter the peer's. */
1709
- var routeAlongY = (from, to, shift, ports) => {
1710
- const down = to.y >= from.y;
1711
- const start = {
1712
- x: ports?.start ?? from.x + from.w / 2,
1713
- y: down ? from.y + from.h : from.y
1714
- };
1715
- const end = {
1716
- x: ports?.end ?? to.x + to.w / 2,
1717
- y: down ? to.y : to.y + to.h
1718
- };
1719
- if (start.x === end.x) return [{
1720
- x: start.x + shift,
1721
- y: start.y
1722
- }, {
1723
- x: end.x + shift,
1724
- y: end.y
1725
- }];
1726
- const mid = snap$3((start.y + end.y) / 2) + shift;
1727
- return [
1728
- start,
1729
- {
1730
- x: start.x,
1731
- y: mid
1732
- },
1733
- {
1734
- x: end.x,
1735
- y: mid
1736
- },
1737
- end
1738
- ];
1739
- };
1740
- /** Z route along x; mirror of `routeAlongY`. */
1741
- var routeAlongX = (from, to, shift, ports) => {
1742
- const right = to.x >= from.x;
1743
- const start = {
1744
- x: right ? from.x + from.w : from.x,
1745
- y: ports?.start ?? from.y + from.h / 2
1746
- };
1747
- const end = {
1748
- x: right ? to.x : to.x + to.w,
1749
- y: ports?.end ?? to.y + to.h / 2
1750
- };
1751
- if (start.y === end.y) return [{
1752
- x: start.x,
1753
- y: start.y + shift
1754
- }, {
1755
- x: end.x,
1756
- y: end.y + shift
1757
- }];
1758
- const mid = snap$3((start.x + end.x) / 2) + shift;
1759
- return [
1760
- start,
1761
- {
1762
- x: mid,
1763
- y: start.y
1764
- },
1765
- {
1766
- x: mid,
1767
- y: end.y
1768
- },
1769
- end
1770
- ];
1771
- };
1772
- /**
1773
- * Default router: an orthogonal Z along the flow axis — rank-crossing edges route through the
1774
- * inter-rank gutter (channel shifted per parallel edge); same-rank edges route out the side.
1775
- */
1776
- var zRouter = ({ from, to, offset, horizontal, ports }) => {
1777
- const sameLane = horizontal ? from.x === to.x : from.y === to.y;
1778
- return ((horizontal ? sameLane : !sameLane) ? routeAlongY : routeAlongX)(from, to, offset * GRID_FINE$1, ports);
1779
- };
1780
- /**
1781
- * Pull strength per relation kind when aligning columns: parts stick to their whole's column
1782
- * before subtypes, which in turn beat loose dependencies/associations.
1783
- */
1784
- var COLUMN_WEIGHT = {
1785
- composition: 4,
1786
- aggregation: 3,
1787
- inheritance: 2,
1788
- realization: 2,
1789
- dependency: 1,
1790
- association: 1
1791
- };
1792
- /**
1793
- * Assign each node an integer column: the first lane keeps declaration order, later lanes pull
1794
- * each node toward the weighted mean column of its already-placed neighbours. The strongest-tied
1795
- * node picks its column first, so a part claims the column under its whole even when weaker
1796
- * relations compete for it; collisions take the free column nearest the desired mean. Aligned
1797
- * columns turn rank-crossing connectors into straight lines.
1798
- */
1799
- var assignColumns = (model, ranks, lanes) => {
1800
- const neighbors = /* @__PURE__ */ new Map();
1801
- for (const relation of model.relations) {
1802
- const weight = COLUMN_WEIGHT[relation.kind];
1803
- neighbors.set(relation.from, [...neighbors.get(relation.from) ?? [], {
1804
- peer: relation.to,
1805
- weight
1806
- }]);
1807
- neighbors.set(relation.to, [...neighbors.get(relation.to) ?? [], {
1808
- peer: relation.from,
1809
- weight
1810
- }]);
1811
- }
1812
- const columns = /* @__PURE__ */ new Map();
1813
- [...lanes.keys()].sort((left, right) => left - right).forEach((lane, position) => {
1814
- const desired = lanes.get(lane).map((id, index) => {
1815
- const placed = (neighbors.get(id) ?? []).filter((link) => columns.has(link.peer));
1816
- const pull = placed.reduce((sum, link) => sum + link.weight, 0);
1817
- return {
1818
- id,
1819
- mean: position > 0 && pull > 0 ? placed.reduce((sum, link) => sum + link.weight * columns.get(link.peer), 0) / pull : index,
1820
- pull,
1821
- index
1822
- };
1823
- });
1824
- desired.sort((left, right) => right.pull - left.pull || left.index - right.index);
1825
- const used = /* @__PURE__ */ new Set();
1826
- for (const entry of desired) {
1827
- const base = Math.ceil(entry.mean - .5);
1828
- let column = base;
1829
- for (let step = 1; used.has(column); step++) {
1830
- const left = base - step;
1831
- const right = base + step;
1832
- if (!used.has(left) && (used.has(right) || Math.abs(left - entry.mean) <= Math.abs(right - entry.mean))) column = left;
1833
- else if (!used.has(right)) column = right;
1834
- }
1835
- used.add(column);
1836
- columns.set(entry.id, column);
1837
- }
1838
- });
1839
- const min = Math.min(...columns.values(), 0);
1840
- return new Map([...columns].map(([id, column]) => [id, column - min]));
1841
- };
1842
- /** One cell fits the largest class: widest line (wrapped at maxWidth) and tallest member list. */
1843
- var measureCell$1 = (model, { maxWidth, cell: override = {}, titleHeight }) => {
1844
- const memberLines = (entry) => [...entry.attributes, ...entry.methods];
1845
- const titleW = Math.max(0, ...model.classes.map((entry) => Math.max(entry.label.length, (entry.stereotype?.length ?? 0) + 2))) * TITLE_FONT.charW;
1846
- const memberW = Math.max(0, ...model.classes.flatMap((entry) => memberLines(entry).map((line) => line.length))) * MEMBER_FONT.charW;
1847
- const w = snap$3(override.w ?? Math.min(maxWidth, Math.max(MIN_W$1, Math.ceil(Math.max(titleW, memberW)) + PAD_X$1)));
1848
- const wrapped = (length, font) => Math.max(1, Math.ceil(length * font.charW / (w - 32)));
1849
- const titleLines = Math.max(1, ...model.classes.map((entry) => wrapped(entry.label.length, TITLE_FONT) + (entry.stereotype ? 1 : 0)));
1850
- const bodyLines = Math.max(1, ...model.classes.map((entry) => memberLines(entry).reduce((sum, line) => sum + wrapped(line.length, MEMBER_FONT), 0) + (entry.attributes.length && entry.methods.length ? 1 : 0)));
1851
- const titleH = titleHeight !== void 0 ? Math.ceil(titleHeight / GRID_FINE$1) * GRID_FINE$1 : Math.min(MAX_TITLE_H, Math.max(MIN_TITLE_H, snap$3(titleLines * TITLE_FONT.lineH + TITLE_PAD)));
1852
- const bodyH = snap$3(bodyLines * MEMBER_FONT.lineH + SECTION_PAD);
1853
- const measured = Math.min(MAX_H, Math.max(MIN_H$1, titleH + bodyH));
1854
- return {
1855
- w,
1856
- h: override.h !== void 0 ? Math.max(snap$3(override.h), titleH + 32) : measured,
1857
- titleH
1858
- };
1859
- };
1860
- /**
1861
- * Compile a mermaid class diagram into scene commands with equal-size grid-aligned nodes: per
1862
- * class a `title` bar plus a `body` compartment filling the cell, and an `edges` object holding
1863
- * orthogonal connector paths (polyline waypoints, an arrowhead segment, and a floating label).
1864
- */
1865
- var compile$5 = (source, options = {}) => {
1866
- const model = parse(source);
1867
- const { origin = {
1868
- x: 0,
1869
- y: 0
1870
- }, scale = 1, maxWidth = MAX_W$1, route } = options;
1871
- const gapMain = snap$3(options.gapMain ?? GAP_MAIN$1);
1872
- const gapCross = snap$3(options.gapCross ?? GAP_CROSS$1);
1873
- const horizontal = model.direction === "LR" || model.direction === "RL";
1874
- const cell = measureCell$1(model, {
1875
- maxWidth,
1876
- cell: options.cell,
1877
- titleHeight: options.titleHeight
1878
- });
1879
- const ranks = relationRanks(model);
1880
- const lanes = /* @__PURE__ */ new Map();
1881
- for (const entry of model.classes) {
1882
- const value = ranks.get(entry.id) ?? 0;
1883
- lanes.set(value, [...lanes.get(value) ?? [], entry.id]);
1884
- }
1885
- const cross = horizontal ? cell.h : cell.w;
1886
- const main = horizontal ? cell.w : cell.h;
1887
- const columns = assignColumns(model, ranks, lanes);
1888
- const rects = /* @__PURE__ */ new Map();
1889
- for (const [lane, members] of lanes) for (const id of members) {
1890
- const crossOffset = columns.get(id) * (cross + gapCross);
1891
- const mainOffset = lane * (main + gapMain);
1892
- rects.set(id, {
1893
- x: horizontal ? mainOffset : crossOffset,
1894
- y: horizontal ? crossOffset : mainOffset,
1895
- w: cell.w,
1896
- h: cell.h
1897
- });
1898
- }
1899
- return emit$1({
1900
- model,
1901
- cell,
1902
- rects,
1903
- ranks
1904
- }, {
1905
- origin,
1906
- scale,
1907
- route
1908
- });
1909
- };
1910
- /**
1911
- * Emit scene commands from a placed model — the shared back half of `compile`, also used by the
1912
- * engine-backed dialects (`uml-engine.ts`) which supply their own placement.
1913
- */
1914
- var emit$1 = ({ model, cell, rects, ranks }, { origin = {
1915
- x: 0,
1916
- y: 0
1917
- }, scale = 1, route } = {}) => {
1918
- const horizontal = model.direction === "LR" || model.direction === "RL";
1919
- const router = route ?? makeAvoidingRouter([...rects.values()], zRouter, { step: 8 });
1920
- const commands = [];
1921
- for (const entry of model.classes) {
1922
- const rect = rects.get(entry.id);
1923
- const title = entry.stereotype ? `«${entry.stereotype}»\n${entry.label}` : entry.label;
1924
- const members = [
1925
- ...entry.attributes,
1926
- ...entry.attributes.length && entry.methods.length ? [""] : [],
1927
- ...entry.methods
1928
- ].join("\n");
1929
- commands.push({
1930
- op: "upsert-object",
1931
- object: {
1932
- id: entry.id,
1933
- origin: {
1934
- x: origin.x + rect.x * scale,
1935
- y: origin.y + rect.y * scale
1936
- },
1937
- scale,
1938
- elements: [
1939
- {
1940
- kind: "rect",
1941
- id: "frame",
1942
- x: 0,
1943
- y: 0,
1944
- w: cell.w,
1945
- h: cell.h
1946
- },
1947
- {
1948
- kind: "rect",
1949
- id: "title",
1950
- x: 0,
1951
- y: 0,
1952
- w: cell.w,
1953
- h: cell.titleH,
1954
- text: title,
1955
- fill: "solid",
1956
- corners: "top"
1957
- },
1958
- ...members ? [{
1959
- kind: "text",
1960
- id: "body",
1961
- x: TEXT_PAD,
1962
- y: cell.titleH + SECTION_PAD / 2,
1963
- w: cell.w - 32,
1964
- text: members,
1965
- weight: "s"
1966
- }] : []
1967
- ]
1968
- }
1969
- });
1970
- }
1971
- if (model.relations.length > 0) {
1972
- const gutterOf = (relation) => {
1973
- const from = rects.get(relation.from);
1974
- const to = rects.get(relation.to);
1975
- const sameLane = horizontal ? from.x === to.x : from.y === to.y;
1976
- if (horizontal ? sameLane : !sameLane) {
1977
- if (from.x + from.w / 2 === to.x + to.w / 2) return `v:${from.x + from.w / 2}`;
1978
- return `y:${snap$3(((to.y >= from.y ? from.y + from.h : from.y) + (to.y >= from.y ? to.y : to.y + to.h)) / 2)}`;
1979
- }
1980
- if (from.y + from.h / 2 === to.y + to.h / 2) return `h:${from.y + from.h / 2}`;
1981
- return `x:${snap$3(((to.x >= from.x ? from.x + from.w : from.x) + (to.x >= from.x ? to.x : to.x + to.w)) / 2)}`;
1982
- };
1983
- const gutters = /* @__PURE__ */ new Map();
1984
- for (const relation of model.relations) {
1985
- const gutter = gutterOf(relation);
1986
- gutters.set(gutter, (gutters.get(gutter) ?? 0) + 1);
1987
- }
1988
- const portGroups = /* @__PURE__ */ new Map();
1989
- const addSlot = (key, slot) => portGroups.set(key, [...portGroups.get(key) ?? [], slot]);
1990
- for (const relation of model.relations) {
1991
- const from = rects.get(relation.from);
1992
- const to = rects.get(relation.to);
1993
- const sameLane = horizontal ? from.x === to.x : from.y === to.y;
1994
- if (horizontal ? sameLane : !sameLane) {
1995
- const down = to.y >= from.y;
1996
- addSlot(`${relation.from}:${down ? "bottom" : "top"}`, {
1997
- relation,
1998
- end: false,
1999
- order: to.x + to.w / 2
2000
- });
2001
- addSlot(`${relation.to}:${down ? "top" : "bottom"}`, {
2002
- relation,
2003
- end: true,
2004
- order: from.x + from.w / 2
2005
- });
2006
- } else {
2007
- const right = to.x >= from.x;
2008
- addSlot(`${relation.from}:${right ? "right" : "left"}`, {
2009
- relation,
2010
- end: false,
2011
- order: to.y + to.h / 2
2012
- });
2013
- addSlot(`${relation.to}:${right ? "left" : "right"}`, {
2014
- relation,
2015
- end: true,
2016
- order: from.y + from.h / 2
2017
- });
2018
- }
2019
- }
2020
- const ports = /* @__PURE__ */ new Map();
2021
- for (const [key, slots] of portGroups) {
2022
- if (slots.length < 2) continue;
2023
- const nodeId = key.slice(0, key.lastIndexOf(":"));
2024
- const rect = rects.get(nodeId);
2025
- const across = key.endsWith(":top") || key.endsWith(":bottom");
2026
- slots.sort((left, right) => left.order - right.order);
2027
- const taken = /* @__PURE__ */ new Set();
2028
- slots.forEach((slot, index) => {
2029
- const fraction = (index + 1) / (slots.length + 1);
2030
- const exact = across ? rect.x + rect.w * fraction : rect.y + rect.h * fraction;
2031
- const rounded = Math.round(exact / GRID_FINE$1) * GRID_FINE$1;
2032
- const coord = taken.has(rounded) ? exact : rounded;
2033
- taken.add(coord);
2034
- const entry = ports.get(slot.relation) ?? {};
2035
- entry[slot.end ? "end" : "start"] = coord;
2036
- ports.set(slot.relation, entry);
2037
- });
2038
- }
2039
- const takenBySide = /* @__PURE__ */ new Map();
2040
- for (const [key, slots] of portGroups) {
2041
- const nodeId = key.slice(0, key.lastIndexOf(":"));
2042
- const rect = rects.get(nodeId);
2043
- const across = key.endsWith(":top") || key.endsWith(":bottom");
2044
- const coords = /* @__PURE__ */ new Map();
2045
- for (const slot of slots) {
2046
- const port = ports.get(slot.relation)?.[slot.end ? "end" : "start"];
2047
- coords.set(slot.relation, port ?? (across ? rect.x + rect.w / 2 : rect.y + rect.h / 2));
2048
- }
2049
- takenBySide.set(key, coords);
2050
- }
2051
- const fineSnap = (value) => Math.round(value / GRID_FINE$1) * GRID_FINE$1;
2052
- for (const relation of model.relations) {
2053
- const from = rects.get(relation.from);
2054
- const to = rects.get(relation.to);
2055
- const sameLane = horizontal ? from.x === to.x : from.y === to.y;
2056
- const alongY = horizontal ? sameLane : !sameLane;
2057
- const lo = alongY ? Math.max(from.x, to.x) + GRID_FINE$1 : Math.max(from.y, to.y) + GRID_FINE$1;
2058
- const hi = alongY ? Math.min(from.x + from.w, to.x + to.w) - GRID_FINE$1 : Math.min(from.y + from.h, to.y + to.h) - GRID_FINE$1;
2059
- if (lo > hi) continue;
2060
- const startKey = alongY ? `${relation.from}:${to.y >= from.y ? "bottom" : "top"}` : `${relation.from}:${to.x >= from.x ? "right" : "left"}`;
2061
- const endKey = alongY ? `${relation.to}:${to.y >= from.y ? "top" : "bottom"}` : `${relation.to}:${to.x >= from.x ? "left" : "right"}`;
2062
- const entry = ports.get(relation) ?? {};
2063
- const startCoord = entry.start ?? (alongY ? from.x + from.w / 2 : from.y + from.h / 2);
2064
- const endCoord = entry.end ?? (alongY ? to.x + to.w / 2 : to.y + to.h / 2);
2065
- const isFree = (coord) => ![startKey, endKey].some((side) => [...takenBySide.get(side) ?? /* @__PURE__ */ new Map()].some(([peer, taken]) => peer !== relation && Math.abs(taken - coord) < GRID_FINE$1));
2066
- const base = Math.min(hi, Math.max(lo, fineSnap((startCoord + endCoord) / 2)));
2067
- let chosen;
2068
- for (let step = 0; step <= Math.ceil((hi - lo) / GRID_FINE$1) && chosen === void 0; step++) for (const candidate of step === 0 ? [base] : [base - step * GRID_FINE$1, base + step * GRID_FINE$1]) if (candidate >= lo && candidate <= hi && isFree(candidate)) {
2069
- chosen = candidate;
2070
- break;
2071
- }
2072
- if (chosen === void 0) continue;
2073
- entry.start = chosen;
2074
- entry.end = chosen;
2075
- ports.set(relation, entry);
2076
- takenBySide.get(startKey)?.set(relation, chosen);
2077
- takenBySide.get(endKey)?.set(relation, chosen);
69
+ removed += remove((identity) => identity.object === command.objectId && ids.has(identity.element));
70
+ break;
2078
71
  }
2079
- const assigned = /* @__PURE__ */ new Map();
2080
- const elements = [];
2081
- model.relations.forEach((relation, index) => {
2082
- const gutter = gutterOf(relation);
2083
- const position = assigned.get(gutter) ?? 0;
2084
- assigned.set(gutter, position + 1);
2085
- const offset = position - Math.floor((gutters.get(gutter) - 1) / 2);
2086
- const points = router({
2087
- relation,
2088
- from: rects.get(relation.from),
2089
- to: rects.get(relation.to),
2090
- horizontal,
2091
- offset,
2092
- ports: ports.get(relation)
2093
- });
2094
- const id = `${relation.from}-${relation.to}-${index}`;
2095
- const style = relationStyle(relation.kind);
2096
- if (points.length > 2) elements.push({
2097
- kind: "line",
2098
- id: `${id}-path`,
2099
- points: points.slice(0, -1),
2100
- ...style
2101
- });
2102
- elements.push({
2103
- kind: "arrow",
2104
- id,
2105
- start: points[points.length - 2],
2106
- end: points[points.length - 1],
2107
- ...style
72
+ case "remove-object":
73
+ removed += remove((identity) => identity.object === command.objectId);
74
+ break;
75
+ case "move-object": {
76
+ const previous = find(command.objectId);
77
+ invariant(previous, `unknown object: ${command.objectId}`, {
78
+ "~LogMeta": "~LogMeta",
79
+ F: __dxlog_file$1,
80
+ L: 149,
81
+ S: void 0,
82
+ A: ["previous", "`unknown object: ${command.objectId}`"]
2108
83
  });
2109
- const text = relationText(relation);
2110
- if (text) {
2111
- const head = points[Math.floor(points.length / 2) - 1];
2112
- const tail = points[Math.floor(points.length / 2)];
2113
- elements.push({
2114
- kind: "text",
2115
- id: `${id}-label`,
2116
- x: (head.x + tail.x) / 2 + GRID_FINE$1 / 2,
2117
- y: (head.y + tail.y) / 2 - MEMBER_FONT.lineH,
2118
- text,
2119
- weight: "s"
2120
- });
2121
- }
2122
- });
2123
- commands.push({
2124
- op: "upsert-object",
2125
- object: {
2126
- id: "edges",
2127
- origin,
2128
- scale,
2129
- elements
2130
- }
2131
- });
84
+ const delta = {
85
+ x: command.origin.x - previous.origin.x,
86
+ y: command.origin.y - previous.origin.y
87
+ };
88
+ for (const record of Object.values(content)) if (handler.identify(record)?.object === command.objectId) handler.translate(record, delta);
89
+ upserted.add(command.objectId);
90
+ break;
91
+ }
2132
92
  }
2133
- return commands;
2134
- };
2135
- //#endregion
2136
- //#region src/objective.ts
2137
- var objective_exports = /* @__PURE__ */ __exportAll({
2138
- DEFAULT: () => DEFAULT,
2139
- bends: () => bends$1,
2140
- compactness: () => compactness,
2141
- connectorLength: () => connectorLength,
2142
- crossings: () => crossings,
2143
- evaluate: () => evaluate,
2144
- framesApart: () => framesApart,
2145
- noHardDefects: () => noHardDefects,
2146
- select: () => select,
2147
- unevenFrameGaps: () => unevenFrameGaps
2148
- });
2149
- var __dxlog_file$1 = "/Users/burdon/Code/dxos/dxos/.claude/worktrees/infinite-canvas-depth-7e0949/packages/common/diagram/src/objective.ts";
2150
- var evaluate = (objective, layout) => {
2151
- const violations = objective.constraints.flatMap((constraint) => constraint.violations(layout).map((message) => ({
2152
- constraint: constraint.id,
2153
- message
2154
- })));
2155
- const terms = objective.costs.map((term) => {
2156
- const value = term.measure(layout);
2157
- return {
2158
- id: term.id,
2159
- value,
2160
- weighted: value * term.weight
2161
- };
2162
- });
2163
- return {
2164
- violations,
2165
- cost: terms.reduce((total, term) => total + term.weighted, 0),
2166
- terms
2167
- };
2168
- };
2169
- /**
2170
- * Picks the best candidate: fewest constraint violations first (so an infeasible set still yields
2171
- * the least-bad layout rather than nothing), then lowest cost. Stable on ties, so the generator's
2172
- * own order breaks them.
2173
- */
2174
- var select = (objective, candidates) => {
2175
- invariant(candidates.length > 0, "select needs at least one candidate", {
2176
- "~LogMeta": "~LogMeta",
2177
- F: __dxlog_file$1,
2178
- L: 74,
2179
- S: void 0,
2180
- A: ["candidates.length > 0", "'select needs at least one candidate'"]
2181
- });
2182
- const ranked = candidates.map((candidate) => ({
2183
- candidate,
2184
- evaluation: evaluate(objective, candidate.layout)
2185
- })).sort((left, right) => left.evaluation.violations.length - right.evaluation.violations.length || left.evaluation.cost - right.evaluation.cost);
2186
93
  return {
2187
- chosen: ranked[0],
2188
- ranked
94
+ upserted: [...upserted],
95
+ removed
2189
96
  };
2190
97
  };
2191
- /** Every `error` diagnostic is a violation: overlaps, routes through nodes, labels that do not fit. */
2192
- var noHardDefects = {
2193
- id: "no-hard-defects",
2194
- description: "No node overlaps, no connector through a node, every label fits its shape.",
2195
- violations: ({ report }) => report.diagnostics.filter(({ severity }) => severity === "error").map(({ message }) => message)
2196
- };
2197
- /** Containers keep at least `gap` between them, so packages read as separate. */
2198
- var framesApart = (gap = 32) => ({
2199
- id: "frames-apart",
2200
- description: `Frames are at least ${gap} apart.`,
2201
- violations: ({ report }) => report.metrics.containers >= 2 && report.metrics.frameGapMin < gap ? [`Frames are ${report.metrics.frameGapMin} apart; at least ${gap} required.`] : []
2202
- });
2203
- var crossings = {
2204
- id: "crossings",
2205
- description: "Connector pairs that cross.",
2206
- weight: 3,
2207
- measure: ({ report }) => report.metrics.crossings
2208
- };
2209
- var bends$1 = {
2210
- id: "bends",
2211
- description: "Turns taken by connectors.",
2212
- weight: 1,
2213
- measure: ({ report }) => report.metrics.bends
2214
- };
2215
- /**
2216
- * Uneven gutters between frames, in grid units. Weighted so a whole cell of unevenness costs about
2217
- * half a crossing: on the pipeline corpus diagram a weight of 2 made the objective accept three
2218
- * crossings to even out a 7-unit spread, which no reader would.
2219
- */
2220
- var unevenFrameGaps = {
2221
- id: "uneven-frame-gaps",
2222
- description: "Spread of the gaps between neighbouring frames.",
2223
- weight: .5,
2224
- measure: ({ report }) => report.metrics.frameGapSpread / 32
2225
- };
2226
- /**
2227
- * Total connector length in grid units: a long edge is hard to follow even when it is straight.
2228
- * Weighted so ~100 grid units of extra connector cost one crossing — at 0.1 the objective traded
2229
- * crossings for shorter edges on the compute and edge corpus diagrams.
2230
- */
2231
- var connectorLength = {
2232
- id: "connector-length",
2233
- description: "Total connector length.",
2234
- weight: .03,
2235
- measure: ({ report }) => report.metrics.length / 32
98
+ /** Seed a fractional-index counter past any existing `a<n>` indexes. */
99
+ var nextIndex = (content, isCounted = () => true) => {
100
+ let max = 0;
101
+ let count = 0;
102
+ for (const record of Object.values(content)) {
103
+ if (!record || !isCounted(record)) continue;
104
+ count++;
105
+ const match = /^a(\d+)$/.exec(record.index ?? "");
106
+ if (match) max = Math.max(max, Number(match[1]));
107
+ }
108
+ return Math.max(max, count);
2236
109
  };
2237
- /** Bounding perimeter in grid units; a light preference for the tighter of otherwise-equal layouts. */
2238
- var compactness = {
2239
- id: "compactness",
2240
- description: "Bounding width + height.",
2241
- weight: .05,
2242
- measure: ({ report }) => (report.metrics.width + report.metrics.height) / 32
110
+ //#endregion
111
+ //#region src/dialect.ts
112
+ var __dxlog_file = "/__w/dxos/dxos/packages/common/diagram/src/dialect.ts";
113
+ /** The core dialect: input is already scene edit commands (identity compile). */
114
+ var sceneDialect = {
115
+ id: "scene",
116
+ description: "Positioned world objects and elements; the agent does the layout.",
117
+ input: Schema.Array(Command),
118
+ compile: Effect.succeed
2243
119
  };
2244
- var DEFAULT = {
2245
- constraints: [noHardDefects, framesApart()],
2246
- costs: [
2247
- crossings,
2248
- bends$1,
2249
- connectorLength,
2250
- unevenFrameGaps,
2251
- compactness
2252
- ]
120
+ var DialectRegistry = class {
121
+ #dialects = /* @__PURE__ */ new Map();
122
+ constructor(dialects = [sceneDialect]) {
123
+ dialects.forEach((dialect) => this.register(dialect));
124
+ }
125
+ register(dialect) {
126
+ invariant(!this.#dialects.has(dialect.id), `duplicate dialect: ${dialect.id}`, {
127
+ "~LogMeta": "~LogMeta",
128
+ F: __dxlog_file,
129
+ L: 42,
130
+ S: this,
131
+ A: ["!this.#dialects.has(dialect.id)", "`duplicate dialect: ${dialect.id}`"]
132
+ });
133
+ this.#dialects.set(dialect.id, dialect);
134
+ }
135
+ get(id) {
136
+ return this.#dialects.get(id);
137
+ }
138
+ list() {
139
+ return [...this.#dialects.values()];
140
+ }
2253
141
  };
2254
142
  //#endregion
2255
- //#region src/mermaid-engine.ts
2256
- var mermaid_engine_exports = /* @__PURE__ */ __exportAll({
2257
- compile: () => compile$4,
2258
- layout: () => layout$1
143
+ //#region src/aesthetics.ts
144
+ var aesthetics_exports = /* @__PURE__ */ __exportAll({
145
+ IMAGE_RULES: () => IMAGE_RULES,
146
+ RULES: () => RULES,
147
+ judge: () => judge
2259
148
  });
2260
- var __dxlog_file = "/Users/burdon/Code/dxos/dxos/.claude/worktrees/infinite-canvas-depth-7e0949/packages/common/diagram/src/mermaid-engine.ts";
2261
- var MIN_W = 96;
2262
- var MAX_W = 192;
2263
- var MIN_H = 64;
2264
- var PAD_X = 32;
2265
- var PAD_Y = 16;
2266
- /** Frame inset around a subgraph's members; the top also holds the frame label. */
2267
- var FRAME_PAD$1 = 32;
2268
- var FRAME_LABEL_H = 32;
2269
- /** Clear space between two frames, so packages read as separate even when their nodes are adjacent. */
2270
- var FRAME_GAP = 32;
2271
- var GRID_FINE = 16;
2272
- var FONT = FONT_METRICS.m;
2273
- var LABEL_FONT = FONT_METRICS.s;
2274
- var ELK_DIRECTION$1 = {
2275
- TB: "DOWN",
2276
- BT: "UP",
2277
- LR: "RIGHT",
2278
- RL: "LEFT"
2279
- };
2280
- var snapUp = (value) => Math.ceil(value / 32) * 32;
2281
- var fineSnap = (value) => Math.round(value / GRID_FINE) * GRID_FINE;
2282
- var LATTICES = [1.5, 2];
2283
- var ORDERS = ["model", "free"];
2284
- var ARRANGEMENTS = ["layered", "columns"];
2285
- var LAYERINGS = [
2286
- "down",
2287
- "up",
2288
- "free"
149
+ var HOW_TO_READ = "The input is a diagram as drawn: `layout` renders the page as text, a character drawing followed by a row-by-row reading of the boxes and of which way each arrow runs. Judge how the drawing reads to a person looking at it, not whether the architecture it shows is good.";
150
+ var HOW_TO_SEE = "The input is a diagram as drawn: the attached image is the page. Judge how the drawing reads to a person looking at it, not whether the architecture it shows is good.";
151
+ var rule = (id, key, description, question, criteria) => ({
152
+ id,
153
+ key,
154
+ description,
155
+ instructions: `${HOW_TO_READ} ${question}`,
156
+ criteria
157
+ });
158
+ var RULES = [
159
+ rule("arrows-follow-flow", "arrowsFollowFlow", "Arrows run with the main direction of the diagram, inside groups as well as between them.", "Do the arrows run with the main direction of the diagram (down in a top-to-bottom drawing, right in a left-to-right one), inside groups as well as between them, with at most a few pointing back against it? Arrows with a hollow triangle head (inheritance, implementation) point to the abstraction above by convention and do not count against the flow.", {
160
+ true: "Nearly every arrow runs with the flow; a reader follows dependencies in one direction.",
161
+ false: "Many arrows, or whole chains inside a group, run up or back against the flow."
162
+ }),
163
+ rule("readable-labels", "readableLabels", "Every box and arrow label can be read on its own.", "Can every box label and arrow label be read on its own, without running into another label, a line or a box?", {
164
+ true: "Every label sits clear of other text and lines.",
165
+ false: "Some label overlaps another label, a box or a line, or is cut off."
166
+ }),
167
+ rule("traceable-arrows", "traceableArrows", "Each arrow can be followed from its start to its end.", "Can a reader follow each arrow from its start to its end without ambiguity — no two arrows sharing a line segment or merged into one trunk, no arrow running through a group it does not connect to, no long detours around the page, and few crossings?", {
168
+ true: "Each arrow has its own short, direct route.",
169
+ false: "Arrows share trunks, detour far, or cross so that their ends are hard to pair up."
170
+ }),
171
+ rule("entries-face-callers", "entriesFaceCallers", "Boxes that outside arrows reach sit on the side of their group facing the callers.", "Within each group, do the boxes that arrows from outside the group reach sit on the side of the group facing those callers, so that cross-group arrows are short and do not run through or around the group?", {
172
+ true: "Cross-group arrows land on the near side of each group.",
173
+ false: "An arrow from outside travels through or around a group to reach a box on its far side."
174
+ }),
175
+ rule("compact", "compact", "The drawing is packed tightly: no large empty regions, groups sized to their content.", "Is the drawing compact — no large empty regions between or inside groups, groups no bigger than what they hold, and boxes that are connected placed next to each other?", {
176
+ true: "The page is tightly packed; connected boxes are neighbours.",
177
+ false: "There are large empty regions, stretched groups, or connected boxes far apart."
178
+ }),
179
+ rule("groups-aligned", "groupsAligned", "Groups line up with each other rather than floating at arbitrary offsets.", "Do the groups line up with one another — sharing a top edge or a left edge where they sit side by side or stacked — rather than one group floating at an arbitrary height or offset beside another?", {
180
+ true: "Groups share edges and read as one arrangement.",
181
+ false: "A group sits at an arbitrary offset from its neighbours."
182
+ }),
183
+ rule("names-only", "namesOnly", "Each box is labelled with a component name alone, without comments or qualifiers.", "Is every box labelled with just the name of what it is, with no comments, limits or qualifiers mixed into the name (such as \"no cap\", \"≤10\" or a colon followed by a description)?", {
184
+ true: "Box labels are plain names; any commentary sits on arrows or not at all.",
185
+ false: "Some box label mixes a name with a comment, a limit or a description."
186
+ }),
187
+ rule("aligned-grid", "alignedGrid", "Boxes line up in rows and columns with consistent spacing.", "Are the boxes aligned into clear rows and columns with consistent spacing, so the structure reads as a grid?", {
188
+ true: "Boxes share rows and columns with even gaps.",
189
+ false: "Boxes are staggered or unevenly spaced, with no clear rows or columns."
190
+ })
2289
191
  ];
2290
- var ALIGNMENTS = ["none", "edges"];
2291
- /** The direction perpendicular to the flow, for arranging groups across it. */
2292
- var ACROSS = {
2293
- TB: "RIGHT",
2294
- BT: "RIGHT",
2295
- LR: "DOWN",
2296
- RL: "DOWN"
2297
- };
2298
- /** One cell size for every node, sized to the longest label wrapped at `maxWidth`. */
2299
- var measureCell = (graph, maxWidth) => {
2300
- const longest = Math.max(0, ...graph.nodes.map((node) => node.label.length));
2301
- const w = snapUp(Math.min(maxWidth, Math.max(MIN_W, longest * FONT.charW + PAD_X * 2)));
2302
- const perLine = Math.max(1, Math.floor((w - PAD_X) / FONT.charW));
2303
- const lines = Math.max(1, ...graph.nodes.map((node) => Math.ceil(node.label.length / perLine)));
2304
- return {
2305
- w,
2306
- h: snapUp(Math.max(MIN_H, (lines - 1) * FONT.lineH + FONT.lineH / 1.35 + PAD_Y * 2))
2307
- };
2308
- };
192
+ /** {@link RULES} for a judge shown the page as an image (`Architecture.Subject.images`) instead of `layout`. */
193
+ var IMAGE_RULES = RULES.map((rule) => ({
194
+ ...rule,
195
+ instructions: `${HOW_TO_SEE}${rule.instructions.slice(269)}`
196
+ }));
2309
197
  /**
2310
- * Lattice pitch for a cell. With groups, the gutter between lattice rows must also hold two frame
2311
- * borders, a label band, and clear space between the frames, or adjacent frames touch or overlap
2312
- * after quantization — so that clearance is a floor on the pitch.
198
+ * Every aesthetic rule as a score from one `DecisionModel` call. Without `layout` or an image of the
199
+ * page there is nothing to judge, so each rule reports an error rather than a guess.
2313
200
  */
2314
- var pitchFor = (graph, cell, lattice) => {
2315
- const framed = graph.groups.some((group) => group.children.length > 0);
201
+ var judge = (rules = RULES) => {
202
+ const inner = judge$1(rules, "aesthetics");
2316
203
  return {
2317
- x: snapUp(Math.max(cell.w * lattice, framed ? cell.w + FRAME_PAD$1 * 2 + FRAME_GAP : 0)),
2318
- y: snapUp(Math.max(cell.h * lattice, framed ? cell.h + FRAME_PAD$1 * 2 + FRAME_LABEL_H + FRAME_GAP : 0))
204
+ entries: inner.entries,
205
+ evaluate: (subject) => subject.content.layout || subject.images?.length ? inner.evaluate(subject) : Effect.succeed(rules.map(() => ({
206
+ score: 0,
207
+ error: "No layout to judge."
208
+ })))
2319
209
  };
2320
210
  };
2321
- /**
2322
- * Quantize ELK's positions to the lattice. Each node takes the nearest lattice point; when two
2323
- * nodes claim the same point, the later one slides along the cross axis to the nearest free slot,
2324
- * so quantization can never stack nodes.
2325
- */
2326
- var quantize = (positions, pitch, horizontal) => {
2327
- const taken = /* @__PURE__ */ new Set();
2328
- const result = /* @__PURE__ */ new Map();
2329
- const ordered = [...positions.entries()].sort(([, a], [, b]) => horizontal ? a.x - b.x || a.y - b.y : a.y - b.y || a.x - b.x);
2330
- for (const [id, point] of ordered) {
2331
- const col = Math.round(point.x / pitch.x);
2332
- const row = Math.round(point.y / pitch.y);
2333
- for (let step = 0;; step++) {
2334
- const offset = step === 0 ? 0 : (step % 2 ? 1 : -1) * Math.ceil(step / 2);
2335
- const cell = horizontal ? {
2336
- col,
2337
- row: row + offset
2338
- } : {
2339
- col: col + offset,
2340
- row
2341
- };
2342
- const key = `${cell.col}:${cell.row}`;
2343
- if (!taken.has(key)) {
2344
- taken.add(key);
2345
- result.set(id, {
2346
- x: cell.col * pitch.x,
2347
- y: cell.row * pitch.y
2348
- });
2349
- break;
2350
- }
2351
- }
2352
- }
2353
- return result;
211
+ //#endregion
212
+ //#region src/dsl/print.ts
213
+ var ESCAPES = {
214
+ "\\": "\\\\",
215
+ "\"": "\\\"",
216
+ "\n": "\\n",
217
+ " ": "\\t",
218
+ "\r": "\\r"
2354
219
  };
2355
220
  /**
2356
- * Close surplus gutters between groups along the cross axis. ELK spaces compound nodes by their
2357
- * own padded extents, so after quantization two groups can sit a whole lattice pitch further apart
2358
- * than the frame clearance needs while another pair sits at the minimum — the uneven gaps a reader
2359
- * notices first. Groups are walked in cross-axis order and each, with everything beyond it, is
2360
- * pulled back by whole pitches while its gap to the previous group stays above the clearance, so
2361
- * every node remains on the lattice and cross-group connectors stay straight.
221
+ * Shortest form that reads back: integers keep no trailing zeros, negatives keep their sign, and
222
+ * the exponent notation `String` switches to outside ~1e-7..1e21 is spelled as-is, which the
223
+ * grammar accepts. `NaN` and `Infinity` have no readable form at all, so they are refused here
224
+ * rather than silently producing a document that cannot be parsed.
2362
225
  */
2363
- var compactGroups = (graph, positions, cell, pitch, horizontal) => {
2364
- const axis = horizontal ? "y" : "x";
2365
- const step = horizontal ? pitch.y : pitch.x;
2366
- const size = horizontal ? cell.h : cell.w;
2367
- const groups = graph.groups.filter((group) => group.children.some((id) => positions.has(id))).map((group) => {
2368
- const coords = group.children.flatMap((id) => positions.has(id) ? [positions.get(id)[axis]] : []);
2369
- return {
2370
- ids: group.children,
2371
- start: Math.min(...coords),
2372
- end: Math.max(...coords)
2373
- };
2374
- }).sort((left, right) => left.start - right.start);
2375
- const result = new Map(positions);
2376
- for (let index = 1; index < groups.length; index++) {
2377
- const previous = groups[index - 1];
2378
- const current = groups[index];
2379
- const gap = current.start - FRAME_PAD$1 - (previous.end + size + FRAME_PAD$1);
2380
- let surplus = Math.floor((gap - FRAME_GAP) / step) * step;
2381
- if (surplus <= 0) continue;
2382
- const moving = [...result].filter(([, point]) => point[axis] >= current.start);
2383
- const staying = new Set([...result].filter(([, point]) => point[axis] < current.start).map(([, point]) => `${point.x}:${point.y}`));
2384
- const collides = (shift) => moving.some(([, point]) => staying.has(`${axis === "x" ? point.x - shift : point.x}:${axis === "y" ? point.y - shift : point.y}`));
2385
- while (surplus > 0 && collides(surplus)) surplus -= step;
2386
- if (surplus <= 0) continue;
2387
- for (const [id, point] of moving) result.set(id, {
2388
- ...point,
2389
- [axis]: point[axis] - surplus
2390
- });
2391
- for (const later of groups.slice(index)) {
2392
- later.start -= surplus;
2393
- later.end -= surplus;
2394
- }
2395
- }
2396
- return result;
2397
- };
226
+ var formatNumber = (value) => {
227
+ if (!Number.isFinite(value)) throw new TypeError(`A scene coordinate must be finite; got ${value}.`);
228
+ return String(value);
229
+ };
230
+ var formatString = (value) => `"${value.replace(/[\\"\n\t\r]/g, (character) => ESCAPES[character] ?? character)}"`;
231
+ /** Bare where the grammar allows it, quoted where a word would be read as a keyword or fail to lex. */
232
+ var formatId = (id) => BARE_ID.test(id) && !RESERVED.has(id) ? id : formatString(id);
233
+ var formatPoint = ({ x, y }) => `${formatNumber(x)},${formatNumber(y)}`;
234
+ var formatSize = (w, h) => `${formatNumber(w)}x${formatNumber(h)}`;
2398
235
  /**
2399
- * Edges for ELK's layering. Inheritance points at the abstraction, which ranks ABOVE its
2400
- * subtypes — so those edges are reversed, as `relationRanks` does for class diagrams; has-many
2401
- * and containment already flow owner-above-owned. In `columns`, the root lays out groups without
2402
- * seeing inside them (`SEPARATE_CHILDREN`), so every edge is lifted to its endpoints' root-level
2403
- * representatives — the group, or the node itself when ungrouped — and the groups fall into
2404
- * dependency order; edges within one group stay where they are for that group's own layout.
2405
- * A reference between packages always orders the packages; within one it ranks per `layering`.
236
+ * Quoted unless every id in it is a bare word: a dialect may build a ref from a node id the
237
+ * grammar cannot lex, such as mermaid's `1st` becoming `1st/box`, and an unquoted one would make
238
+ * the printed scene fail to parse.
2406
239
  */
2407
- var layeringEdges = (graph, arrangement, layering) => {
2408
- const groupOf = new Map(graph.groups.flatMap((group) => group.children.map((id) => [id, group.id])));
2409
- const betweenGroups = (edge) => groupOf.has(edge.from) && groupOf.has(edge.to) && groupOf.get(edge.from) !== groupOf.get(edge.to);
2410
- const oriented = graph.edges.flatMap((edge) => {
2411
- if (edge.kind === "inheritance") return [{
2412
- from: edge.to,
2413
- to: edge.from
2414
- }];
2415
- if (edge.kind !== "reference" || layering === "down" || betweenGroups(edge)) return [{
2416
- from: edge.from,
2417
- to: edge.to
2418
- }];
2419
- return layering === "up" ? [{
2420
- from: edge.to,
2421
- to: edge.from
2422
- }] : [];
2423
- });
2424
- if (arrangement === "layered") return oriented.map((edge, index) => ({
2425
- id: `edge-${index}`,
2426
- sources: [edge.from],
2427
- targets: [edge.to]
2428
- }));
2429
- const edges = [];
2430
- const seen = /* @__PURE__ */ new Set();
2431
- oriented.forEach((edge, index) => {
2432
- const from = groupOf.get(edge.from);
2433
- const to = groupOf.get(edge.to);
2434
- if (from !== void 0 && from === to) {
2435
- edges.push({
2436
- id: `edge-${index}`,
2437
- sources: [edge.from],
2438
- targets: [edge.to]
2439
- });
2440
- return;
2441
- }
2442
- const lifted = {
2443
- from: from ?? edge.from,
2444
- to: to ?? edge.to
2445
- };
2446
- const key = `${lifted.from}->${lifted.to}`;
2447
- if (lifted.from !== lifted.to && !seen.has(key)) {
2448
- seen.add(key);
2449
- edges.push({
2450
- id: `group-edge-${index}`,
2451
- sources: [lifted.from],
2452
- targets: [lifted.to]
2453
- });
2454
- }
2455
- });
2456
- return edges;
240
+ var formatRef = (ref) => BARE_REF.test(ref) && !RESERVED.has(ref) ? ref : formatString(ref);
241
+ /** `_` is the end the schema leaves unset; a bound end prints as its ref, a free one as a point. */
242
+ var formatEndpoint = (ref, point) => ref !== void 0 ? formatRef(ref) : point !== void 0 ? formatPoint(point) : "_";
243
+ /** Prints `name=value` for the attributes of `kind` that are set, in the table's order. */
244
+ var formatAttrs = (kind, values) => ELEMENT_ATTRS[kind].flatMap(({ name, type }) => {
245
+ const value = values[name];
246
+ if (value === void 0) return [];
247
+ return [`${name}=${type === "string" && typeof value === "string" ? formatString(value) : String(value)}`];
248
+ });
249
+ var style = ({ color, fill, stroke, weight }) => ({
250
+ color,
251
+ fill,
252
+ stroke,
253
+ weight
254
+ });
255
+ var formatElement = (element) => {
256
+ switch (element.kind) {
257
+ case "rect":
258
+ case "ellipse":
259
+ case "diamond":
260
+ case "triangle": return [
261
+ element.kind,
262
+ formatId(element.id),
263
+ formatPoint(element),
264
+ formatSize(element.w, element.h),
265
+ ...element.text === void 0 ? [] : [formatString(element.text)],
266
+ ...formatAttrs(element.kind, {
267
+ rotation: element.rotation,
268
+ corners: element.corners,
269
+ ...style(element)
270
+ })
271
+ ].join(" ");
272
+ case "circle": return [
273
+ "circle",
274
+ formatId(element.id),
275
+ formatPoint({
276
+ x: element.cx,
277
+ y: element.cy
278
+ }),
279
+ formatNumber(element.r),
280
+ ...element.text === void 0 ? [] : [formatString(element.text)],
281
+ ...formatAttrs("circle", style(element))
282
+ ].join(" ");
283
+ case "line": return [
284
+ "line",
285
+ formatId(element.id),
286
+ ...element.points.map(formatPoint),
287
+ ...formatAttrs("line", {
288
+ closed: element.closed,
289
+ ...style(element)
290
+ })
291
+ ].join(" ");
292
+ case "curve": return [
293
+ "curve",
294
+ formatId(element.id),
295
+ ...element.points.map(formatPoint),
296
+ ...formatAttrs("curve", style(element))
297
+ ].join(" ");
298
+ case "arc": return [
299
+ "arc",
300
+ formatId(element.id),
301
+ formatPoint({
302
+ x: element.cx,
303
+ y: element.cy
304
+ }),
305
+ formatNumber(element.r),
306
+ `${formatNumber(element.startAngle)}..${formatNumber(element.endAngle)}`,
307
+ ...formatAttrs("arc", style(element))
308
+ ].join(" ");
309
+ case "text": return [
310
+ "text",
311
+ formatId(element.id),
312
+ formatPoint(element),
313
+ formatString(element.text),
314
+ ...formatAttrs("text", {
315
+ w: element.w,
316
+ ...style(element)
317
+ })
318
+ ].join(" ");
319
+ case "arrow": return [
320
+ "arrow",
321
+ formatId(element.id),
322
+ formatEndpoint(element.from, element.start),
323
+ "->",
324
+ formatEndpoint(element.to, element.end),
325
+ ...element.text === void 0 ? [] : [formatString(element.text)],
326
+ ...formatAttrs("arrow", {
327
+ head: element.head,
328
+ tail: element.tail,
329
+ relation: element.relation,
330
+ ...style(element)
331
+ })
332
+ ].join(" ");
333
+ case "portal": return [
334
+ "portal",
335
+ formatId(element.id),
336
+ formatPoint(element),
337
+ formatSize(element.w, element.h),
338
+ ...element.text === void 0 ? [] : [formatString(element.text)],
339
+ ...formatAttrs("portal", {
340
+ ref: element.ref,
341
+ ...style(element)
342
+ })
343
+ ].join(" ");
344
+ }
2457
345
  };
2458
- /**
2459
- * ELK compound layout. Groups become hierarchical nodes whose children ELK lays out inside
2460
- * them; positions come back parent-relative and are flattened here. Node origins are then
2461
- * quantized to the lattice and frames recomputed from their quantized members, so quantization
2462
- * can never break containment.
2463
- */
2464
- var place = async (graph, cell, pitch, order, arrangement, layering) => {
2465
- const horizontal = graph.direction === "LR" || graph.direction === "RL";
2466
- const grouped = new Set(graph.groups.flatMap((group) => group.children));
2467
- const leaf = (id) => ({
2468
- id,
2469
- width: cell.w,
2470
- height: cell.h
2471
- });
2472
- const spacing = {
2473
- "elk.layered.spacing.nodeNodeBetweenLayers": String(horizontal ? pitch.x - cell.w : pitch.y - cell.h),
2474
- "elk.spacing.nodeNode": String(horizontal ? pitch.y - cell.h : pitch.x - cell.w)
2475
- };
2476
- const flow = {
2477
- "elk.algorithm": "layered",
2478
- "elk.direction": ELK_DIRECTION$1[graph.direction],
2479
- "elk.layered.nodePlacement.strategy": "BRANDES_KOEPF",
2480
- "elk.layered.considerModelOrder.strategy": order === "model" ? "PREFER_NODES" : "NONE"
2481
- };
2482
- const children = [...graph.nodes.filter((node) => !grouped.has(node.id)).map((node) => leaf(node.id)), ...graph.groups.filter((group) => group.children.length > 0).map((group) => ({
2483
- id: group.id,
2484
- layoutOptions: {
2485
- ...spacing,
2486
- ...arrangement === "columns" ? flow : {},
2487
- "elk.padding": `[top=${FRAME_PAD$1 + FRAME_LABEL_H},left=${FRAME_PAD$1},bottom=${FRAME_PAD$1},right=${FRAME_PAD$1}]`
2488
- },
2489
- children: group.children.map(leaf)
2490
- }))];
2491
- const result = await new ELK().layout({
2492
- id: "root",
2493
- layoutOptions: arrangement === "columns" ? {
2494
- ...flow,
2495
- "elk.direction": ACROSS[graph.direction],
2496
- "elk.hierarchyHandling": "SEPARATE_CHILDREN",
2497
- ...spacing
2498
- } : {
2499
- ...flow,
2500
- "elk.hierarchyHandling": "INCLUDE_CHILDREN",
2501
- ...spacing
2502
- },
2503
- children,
2504
- edges: layeringEdges(graph, arrangement, layering)
2505
- });
2506
- const raw = /* @__PURE__ */ new Map();
2507
- const visit = (node, offset) => {
2508
- for (const child of node.children ?? []) {
2509
- const x = offset.x + (child.x ?? 0);
2510
- const y = offset.y + (child.y ?? 0);
2511
- if (child.children?.length) visit(child, {
2512
- x,
2513
- y
2514
- });
2515
- else raw.set(child.id, {
2516
- x,
2517
- y
2518
- });
2519
- }
2520
- };
2521
- visit(result, {
2522
- x: 0,
2523
- y: 0
2524
- });
2525
- return compactGroups(graph, quantize(raw, pitch, horizontal), cell, pitch, horizontal);
346
+ var block = (header, elements) => [
347
+ `${header} {`,
348
+ ...elements.map((element) => ` ${formatElement(element)}`),
349
+ "}"
350
+ ].join("\n");
351
+ var formatObject = (object) => block([
352
+ "object",
353
+ formatId(object.id),
354
+ ...object.origin === void 0 ? [] : ["@", formatPoint(object.origin)],
355
+ ...OBJECT_ATTRS.flatMap(({ name, type }) => {
356
+ const value = {
357
+ scale: object.scale,
358
+ index: object.index,
359
+ ref: object.ref
360
+ }[name];
361
+ if (value === void 0) return [];
362
+ return [`${name}=${type === "string" && typeof value === "string" ? formatString(value) : String(value)}`];
363
+ })
364
+ ].join(" "), object.elements);
365
+ var formatCommand = (command) => {
366
+ switch (command.op) {
367
+ case "upsert-object": return formatObject(command.object);
368
+ case "upsert-elements": return block(`elements ${formatId(command.objectId)}`, command.elements);
369
+ case "move-object": return `move ${formatId(command.objectId)} @ ${formatPoint(command.origin)}`;
370
+ case "remove-object": return `remove object ${formatId(command.objectId)}`;
371
+ case "remove-elements": return `remove elements ${formatId(command.objectId)} ${command.elementIds.map(formatId).join(" ")}`;
372
+ }
2526
373
  };
2527
- /**
2528
- * Align each package to its cross-package edges: among the whole-row shifts that put one of its
2529
- * members on the row of that member's partner, take the one leaving its edges the least total row
2530
- * offset (nearest to no shift on a tie), onto free lattice points only. Two sweeps, since a partner
2531
- * may itself move in the first.
2532
- */
2533
- var alignGroups = (graph, positions, direction, alignment) => {
2534
- if (alignment === "none") return positions;
2535
- const axis = direction === "LR" || direction === "RL" ? "x" : "y";
2536
- const groupOf = new Map(graph.groups.flatMap((group) => group.children.map((id) => [id, group.id])));
2537
- const result = new Map(positions);
2538
- for (let sweep = 0; sweep < 2; sweep++) for (const group of graph.groups) {
2539
- const members = new Set(group.children.filter((id) => result.has(id)));
2540
- const pairs = graph.edges.flatMap((edge) => {
2541
- const [member, partner] = members.has(edge.from) ? [edge.from, edge.to] : [edge.to, edge.from];
2542
- const here = result.get(member);
2543
- const there = result.get(partner);
2544
- return members.has(member) && !members.has(partner) && groupOf.has(partner) && here && there ? [{
2545
- here: here[axis],
2546
- there: there[axis]
2547
- }] : [];
2548
- });
2549
- if (pairs.length === 0) continue;
2550
- const offset = (shift) => pairs.reduce((sum, pair) => sum + Math.abs(pair.here + shift - pair.there), 0);
2551
- const shifts = [.../* @__PURE__ */ new Set([0, ...pairs.map((pair) => pair.there - pair.here)])].sort((left, right) => offset(left) - offset(right) || Math.abs(left) - Math.abs(right));
2552
- const others = new Set([...result].filter(([id]) => !members.has(id)).map(([, point]) => `${point.x}:${point.y}`));
2553
- const shift = shifts.find((candidate) => ![...members].some((id) => {
2554
- const point = result.get(id);
2555
- return point && others.has(`${axis === "x" ? point.x + candidate : point.x}:${axis === "y" ? point.y + candidate : point.y}`);
2556
- }));
2557
- if (!shift) continue;
2558
- for (const id of members) {
2559
- const point = result.get(id);
2560
- if (point) result.set(id, {
2561
- ...point,
2562
- [axis]: point[axis] + shift
2563
- });
2564
- }
374
+ /** A whole document: statements separated by a blank line, newline-terminated. */
375
+ var printCommands = (commands) => commands.length === 0 ? "" : `${commands.map(formatCommand).join("\n\n")}\n`;
376
+ /** A scene document — every statement an `object`, which is what a drawing serializes to. */
377
+ var print = (scene) => printCommands(scene.objects.map((object) => ({
378
+ op: "upsert-object",
379
+ object
380
+ })));
381
+ var SOURCES = [
382
+ {
383
+ id: "uml",
384
+ description: "Mermaid classDiagram.",
385
+ detect: isClassDiagram,
386
+ compile: (source) => Effect.sync(() => compile$5(source))
387
+ },
388
+ {
389
+ id: "mermaid",
390
+ description: "Mermaid flowchart, including the `%% ref` and UML edge-token extensions.",
391
+ detect: (source) => /^\s*(flowchart|graph)\s+/m.test(source),
392
+ compile: (source) => Effect.promise(() => compile$6(source))
393
+ },
394
+ {
395
+ id: "mermaid-layered",
396
+ description: "Mermaid flowchart with the dialect's own layered placement rather than ELK.",
397
+ detect: () => false,
398
+ compile: (source) => Effect.sync(() => compile$7(source))
399
+ }
400
+ ];
401
+ /** The language a source string is written in, or undefined when nothing claims it. */
402
+ var detect = (source) => SOURCES.find(({ detect }) => detect(source));
403
+ var known = () => SOURCES.map(({ id }) => id).join(", ");
404
+ /** Tagged so the failure channel stays discriminated rather than collapsing into bare `Error`. */
405
+ var UnknownSourceError = class extends Data.TaggedError("DiagramUnknownSource") {
406
+ get message() {
407
+ return `Unknown source language "${this.id}"; known: ${known()}.`;
2565
408
  }
2566
- return result;
2567
409
  };
2568
- /** Node rects at the origin plus the frame around each group's members. */
2569
- var frame$1 = (graph, positions, cell) => {
2570
- const minX = Math.min(0, ...[...positions.values()].map((point) => point.x));
2571
- const minY = Math.min(0, ...[...positions.values()].map((point) => point.y));
2572
- const nodes = new Map([...positions].map(([id, point]) => [id, {
2573
- x: point.x - minX,
2574
- y: point.y - minY,
2575
- w: cell.w,
2576
- h: cell.h
2577
- }]));
2578
- const frames = /* @__PURE__ */ new Map();
2579
- for (const group of graph.groups) {
2580
- const members = group.children.flatMap((id) => nodes.get(id) ?? []);
2581
- if (members.length === 0) continue;
2582
- const x = Math.min(...members.map((rect) => rect.x)) - FRAME_PAD$1;
2583
- const y = Math.min(...members.map((rect) => rect.y)) - FRAME_PAD$1 - FRAME_LABEL_H;
2584
- const right = Math.max(...members.map((rect) => rect.x + rect.w)) + FRAME_PAD$1;
2585
- const bottom = Math.max(...members.map((rect) => rect.y + rect.h)) + FRAME_PAD$1;
2586
- frames.set(group.id, {
2587
- x,
2588
- y,
2589
- w: right - x,
2590
- h: bottom - y
2591
- });
410
+ var UndetectedSourceError = class extends Data.TaggedError("DiagramUndetectedSource") {
411
+ get message() {
412
+ return `Could not tell what language this is; pass one of: ${known()}.`;
2592
413
  }
2593
- return {
2594
- nodes,
2595
- frames
2596
- };
2597
414
  };
415
+ /** Compiles `text` through its dialect and prints the result as DSL text. */
416
+ var convert = (text, { source } = {}) => Effect.gen(function* () {
417
+ const language = source === void 0 ? detect(text) : SOURCES.find(({ id }) => id === source);
418
+ if (!language) return yield* Effect.fail(source === void 0 ? new UndetectedSourceError() : new UnknownSourceError({ id: source }));
419
+ return printCommands(yield* language.compile(text));
420
+ });
421
+ //#endregion
422
+ //#region src/dsl/compile.ts
2598
423
  /**
2599
- * Straightened ports: when an edge's nodes overlap on the cross axis, both terminals take one
2600
- * shared coordinate inside the overlap, so the connector is a single straight run instead of a
2601
- * jog between nearly aligned nodes. Distinct edges leaving the same node side take distinct
2602
- * coordinates. Mirrors the straightening pass in the UML emitter.
424
+ * Reads a document and lays out its semantic statements with the full search, which also tries the
425
+ * mermaid engine's placements when nothing was hinted. Asynchronous only because that engine's ELK
426
+ * is; a document of scene statements alone reads exactly as {@link parse} reads it.
2603
427
  */
2604
- var straighten = (edges, nodes, isHorizontal) => {
2605
- const ports = /* @__PURE__ */ new Map();
2606
- const taken = /* @__PURE__ */ new Map();
2607
- for (const edge of edges) {
2608
- const from = nodes.get(edge.from);
2609
- const to = nodes.get(edge.to);
2610
- if (!from || !to) continue;
2611
- const horizontal = isHorizontal(edge);
2612
- const sameLane = horizontal ? from.x === to.x : from.y === to.y;
2613
- const alongY = horizontal ? sameLane : !sameLane;
2614
- const lo = (alongY ? Math.max(from.x, to.x) : Math.max(from.y, to.y)) + GRID_FINE;
2615
- const hi = (alongY ? Math.min(from.x + from.w, to.x + to.w) : Math.min(from.y + from.h, to.y + to.h)) - GRID_FINE;
2616
- if (lo > hi) continue;
2617
- const sides = alongY ? [`${edge.from}:${to.y >= from.y ? "bottom" : "top"}`, `${edge.to}:${to.y >= from.y ? "top" : "bottom"}`] : [`${edge.from}:${to.x >= from.x ? "right" : "left"}`, `${edge.to}:${to.x >= from.x ? "left" : "right"}`];
2618
- const center = alongY ? (from.x + from.w / 2 + to.x + to.w / 2) / 2 : (from.y + from.h / 2 + to.y + to.h / 2) / 2;
2619
- const base = Math.min(hi, Math.max(lo, fineSnap(center)));
2620
- const isFree = (coord) => sides.every((side) => (taken.get(side) ?? []).every((used) => Math.abs(used - coord) >= GRID_FINE));
2621
- let chosen;
2622
- for (let step = 0; step <= Math.ceil((hi - lo) / GRID_FINE) && chosen === void 0; step++) for (const candidate of step === 0 ? [base] : [base - step * GRID_FINE, base + step * GRID_FINE]) if (candidate >= lo && candidate <= hi && isFree(candidate)) {
2623
- chosen = candidate;
2624
- break;
2625
- }
2626
- if (chosen === void 0) continue;
2627
- ports.set(edge, {
2628
- start: chosen,
2629
- end: chosen
2630
- });
2631
- for (const side of sides) taken.set(side, [...taken.get(side) ?? [], chosen]);
2632
- }
2633
- return ports;
2634
- };
428
+ var compile$4 = Effect.fn("Dsl.compile")(function* (text, options = {}) {
429
+ const reading = read(text);
430
+ const { diagram } = reading;
431
+ if (!diagram) return withLayout(reading);
432
+ const solution = yield* Effect.promise(() => compile$8(diagram, options));
433
+ return withLayout(reading, solution);
434
+ });
435
+ //#endregion
436
+ //#region src/dsl/index.ts
437
+ var dsl_exports = /* @__PURE__ */ __exportAll({
438
+ CLAUSE_KEYWORDS: () => CLAUSE_KEYWORDS,
439
+ DIAGRAM_ATTRS: () => DIAGRAM_ATTRS,
440
+ EDGE_ATTRS: () => EDGE_ATTRS,
441
+ ELEMENT_ATTRS: () => ELEMENT_ATTRS,
442
+ ELEMENT_KINDS: () => ELEMENT_KINDS,
443
+ GROUP_ATTRS: () => GROUP_ATTRS,
444
+ NODE_ATTRS: () => NODE_ATTRS,
445
+ OBJECT_ATTRS: () => OBJECT_ATTRS,
446
+ RELATION_WORDS: () => RELATION_WORDS,
447
+ RESERVED: () => RESERVED,
448
+ SEMANTIC_KEYWORDS: () => SEMANTIC_KEYWORDS,
449
+ SOURCES: () => SOURCES,
450
+ STATEMENT_KEYWORDS: () => STATEMENT_KEYWORDS,
451
+ UndetectedSourceError: () => UndetectedSourceError,
452
+ UnknownSourceError: () => UnknownSourceError,
453
+ compile: () => compile$4,
454
+ convert: () => convert,
455
+ detect: () => detect,
456
+ formatCommand: () => formatCommand,
457
+ formatElement: () => formatElement,
458
+ formatId: () => formatId,
459
+ formatObject: () => formatObject,
460
+ formatString: () => formatString,
461
+ parse: () => parse,
462
+ parseScene: () => parseScene,
463
+ parser: () => parser,
464
+ print: () => print,
465
+ printCommands: () => printCommands,
466
+ read: () => read,
467
+ toScene: () => toScene,
468
+ withLayout: () => withLayout
469
+ });
470
+ //#endregion
471
+ //#region src/dx-svg.ts
472
+ var dx_svg_exports = /* @__PURE__ */ __exportAll({
473
+ FORMAT: () => FORMAT,
474
+ embed: () => embed,
475
+ extract: () => extract,
476
+ isDxSvg: () => isDxSvg
477
+ });
478
+ /** The container format; versions the slot and encoding, not the payload, which versions itself. */
479
+ var FORMAT = "org.dxos.dx-svg@1";
480
+ var NAMESPACE = "https://dxos.org/ns/dx-svg";
481
+ var MEDIA_TYPE = "application/vnd.dxos.dx+json";
482
+ var NOTICE = "<!-- Drawn in DXOS: the embedded data is the source; edits to the picture alone are not reflected in it. -->";
2635
483
  /**
2636
- * Inheritance bus (TB only): subtypes of one base that sit on one row above which the base sits
2637
- * connect through a shared horizontal bus and a single triangle-headed trunk, the UML tree
2638
- * connector. Returns the elements and the edges it consumed; edges that do not qualify are left
2639
- * to the per-edge router.
484
+ * JSON with `<`, `>` and `&` as unicode escapes, so the text needs neither CDATA nor entity decoding
485
+ * and `JSON.parse` restores it exactly. A top-level `objects` array goes one object per line, so a
486
+ * changed drawing diffs by object.
2640
487
  */
2641
- var inheritanceBuses = (edges, nodes) => {
2642
- const elements = [];
2643
- const consumed = /* @__PURE__ */ new Set();
2644
- const byBase = /* @__PURE__ */ new Map();
2645
- for (const edge of edges) if (edge.kind === "inheritance" && !edge.label) byBase.set(edge.to, [...byBase.get(edge.to) ?? [], edge]);
2646
- for (const [baseId, group] of byBase) {
2647
- const base = nodes.get(baseId);
2648
- const subs = group.map((edge) => nodes.get(edge.from));
2649
- if (!base || group.length < 2 || subs.some((sub) => !sub)) continue;
2650
- const rects = subs;
2651
- const row = rects[0].y;
2652
- if (rects.some((sub) => sub.y !== row) || base.y + base.h >= row) continue;
2653
- const busY = fineSnap((base.y + base.h + row) / 2);
2654
- const trunkX = fineSnap(base.x + base.w / 2);
2655
- const centers = rects.map((sub) => fineSnap(sub.x + sub.w / 2));
2656
- const span = [Math.min(trunkX, ...centers), Math.max(trunkX, ...centers)];
2657
- elements.push({
2658
- kind: "line",
2659
- id: `${baseId}-bus`,
2660
- points: [{
2661
- x: span[0],
2662
- y: busY
2663
- }, {
2664
- x: span[1],
2665
- y: busY
2666
- }]
2667
- });
2668
- group.forEach((edge, index) => {
2669
- elements.push({
2670
- kind: "line",
2671
- id: `${edge.from}-${baseId}-stub-${index}`,
2672
- points: [{
2673
- x: centers[index],
2674
- y: row
2675
- }, {
2676
- x: centers[index],
2677
- y: busY
2678
- }]
2679
- });
2680
- consumed.add(edge);
2681
- });
2682
- elements.push({
2683
- kind: "arrow",
2684
- id: `${baseId}-inherit`,
2685
- start: {
2686
- x: trunkX,
2687
- y: busY
2688
- },
2689
- end: {
2690
- x: trunkX,
2691
- y: base.y + base.h
2692
- },
2693
- head: "triangle"
2694
- });
488
+ var encode = (payload) => {
489
+ const escape = (json) => json.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
490
+ if (payload && typeof payload === "object" && !Array.isArray(payload) && "objects" in payload && Array.isArray(payload.objects)) {
491
+ const { objects, ...rest } = payload;
492
+ const head = JSON.stringify(rest);
493
+ const body = objects.map((object) => escape(JSON.stringify(object))).join(",\n");
494
+ return `${escape(head.slice(0, -1))}${head.length > 2 ? "," : ""}"objects":[\n${body}\n]}`;
2695
495
  }
2696
- return {
2697
- elements,
2698
- consumed
2699
- };
2700
- };
496
+ return escape(JSON.stringify(payload));
497
+ };
498
+ /** Embeds a payload in a standalone SVG: a marker on the root and the payload as its first child. */
499
+ var embed = (svg, payload) => {
500
+ const root = /<svg\b[^>]*>/.exec(svg);
501
+ if (!root) throw new Error("Not an SVG document: no <svg> element.");
502
+ if (/\bdx:payload=/.test(root[0])) throw new Error("The SVG already carries a dx-svg payload.");
503
+ const open = root[0].replace(/<svg\b/, `<svg xmlns:dx="${NAMESPACE}" dx:payload="${FORMAT}"`);
504
+ const metadata = `<metadata id="dx-payload" data-type="${MEDIA_TYPE}" data-version="1" data-encoding="json">\n${encode(payload)}\n</metadata>`;
505
+ return `${svg.slice(0, root.index)}${NOTICE}\n${open}${metadata}${svg.slice(root.index + root[0].length)}`;
506
+ };
507
+ /** Whether an SVG carries a payload, without parsing it. */
508
+ var isDxSvg = (svg) => /<metadata\b[^>]*\bid="dx-payload"/.test(svg);
2701
509
  /**
2702
- * Scene commands for a placement: one world object per subgraph frame (painted first) and per
2703
- * node, plus an `edges` object of connectors.
510
+ * The payload of a `.dx.svg`, or undefined for a plain SVG. Works on the text alone, so it runs in node,
511
+ * workers and the browser. An encoding this version does not know is an error, not a missing payload.
2704
512
  */
2705
- var emit = (graph, cell, { nodes, frames }, { origin, scale, bus, arrangement, route }) => {
2706
- const horizontal = graph.direction === "LR" || graph.direction === "RL";
2707
- const at = (rect) => ({
2708
- x: origin.x + rect.x * scale,
2709
- y: origin.y + rect.y * scale
2710
- });
2711
- const commands = [];
2712
- for (const group of graph.groups) {
2713
- const frame = frames.get(group.id);
2714
- if (!frame) continue;
2715
- commands.push({
2716
- op: "upsert-object",
2717
- object: {
2718
- id: group.id,
2719
- origin: at(frame),
2720
- scale,
2721
- elements: [{
2722
- kind: "rect",
2723
- id: "frame",
2724
- x: 0,
2725
- y: 0,
2726
- w: frame.w,
2727
- h: frame.h,
2728
- stroke: "dashed",
2729
- color: "grey"
2730
- }, ...group.label.trim() ? [{
2731
- kind: "text",
2732
- id: "label",
2733
- x: FRAME_PAD$1 / 2,
2734
- y: (FRAME_LABEL_H - LABEL_FONT.lineH) / 2,
2735
- text: group.label,
2736
- weight: "s",
2737
- color: "grey"
2738
- }] : []]
2739
- }
2740
- });
2741
- }
2742
- for (const node of graph.nodes) {
2743
- const rect = nodes.get(node.id);
2744
- if (!rect) continue;
2745
- commands.push({
2746
- op: "upsert-object",
2747
- object: {
2748
- id: node.id,
2749
- origin: at(rect),
2750
- scale,
2751
- ...node.ref ? { ref: node.ref } : {},
2752
- elements: [{
2753
- kind: "rect",
2754
- id: "box",
2755
- x: 0,
2756
- y: 0,
2757
- w: cell.w,
2758
- h: cell.h,
2759
- text: node.label
2760
- }]
2761
- }
2762
- });
2763
- }
2764
- if (graph.edges.length > 0) {
2765
- const router = route ?? makeAvoidingRouter([...nodes.values()], zRouter, { step: 8 });
2766
- const buses = bus && !horizontal ? inheritanceBuses(graph.edges, nodes) : {
2767
- elements: [],
2768
- consumed: /* @__PURE__ */ new Set()
513
+ var extract = (svg) => {
514
+ const match = /<metadata\b([^>]*)\bid="dx-payload"([^>]*)>([\s\S]*?)<\/metadata>/.exec(svg);
515
+ if (!match) return;
516
+ const attributes = `${match[1]} ${match[2]}`;
517
+ const encoding = /\bdata-encoding="([^"]*)"/.exec(attributes)?.[1] ?? "json";
518
+ if (encoding !== "json") throw new Error(`Unsupported dx-svg payload encoding: ${encoding}.`);
519
+ return JSON.parse(match[3]);
520
+ };
521
+ //#endregion
522
+ //#region src/score.ts
523
+ var score_exports = /* @__PURE__ */ __exportAll({
524
+ HALF_COST: () => 3,
525
+ constraintScorer: () => constraintScorer,
526
+ costScorer: () => costScorer,
527
+ evaluate: () => evaluate,
528
+ fromCost: () => fromCost,
529
+ fromObjective: () => fromObjective,
530
+ overall: () => overall
531
+ });
532
+ /** Maps a weighted cost in [0, ∞) onto (0, 1]: 1 at no cost, 0.5 at `half`, falling off hyperbolically. */
533
+ var fromCost = (weighted, half = 3) => 1 / (1 + Math.max(0, weighted) / half);
534
+ /** A hard requirement: 1 when it holds, 0 when anything violates it. */
535
+ var constraintScorer = (constraint) => ({
536
+ id: constraint.id,
537
+ kind: "constraint",
538
+ description: constraint.description,
539
+ evaluate: (layout) => Effect.sync(() => {
540
+ const violations = constraint.violations(layout);
541
+ return {
542
+ score: violations.length ? 0 : 1,
543
+ ...violations.length ? { detail: violations.join("\n") } : {}
2769
544
  };
2770
- const routed = graph.edges.filter((edge) => !buses.consumed.has(edge));
2771
- const groupOf = new Map(graph.groups.flatMap((group) => group.children.map((id) => [id, group.id])));
2772
- const rootOf = (id) => groupOf.get(id) ?? id;
2773
- const isHorizontal = (edge) => arrangement === "columns" && rootOf(edge.from) !== rootOf(edge.to) ? !horizontal : horizontal;
2774
- const ports = straighten(routed, nodes, isHorizontal);
2775
- const elements = [...buses.elements];
2776
- const terminals = /* @__PURE__ */ new Map();
2777
- const taken = (nodeId, point, role) => (terminals.get(nodeId) ?? []).some((other) => other.role !== role && Math.abs(other.point.x - point.x) < GRID_FINE && Math.abs(other.point.y - point.y) < GRID_FINE);
2778
- const NUDGES = [
2779
- [0, 0],
2780
- [1, 1],
2781
- [-1, -1],
2782
- [2, 2],
2783
- [-2, -2],
2784
- [1, 0],
2785
- [-1, 0],
2786
- [0, 1],
2787
- [0, -1]
2788
- ];
2789
- const routeEdge = (edge, from, to) => {
2790
- const horizontal = isHorizontal(edge);
2791
- const sameLane = horizontal ? from.x === to.x : from.y === to.y;
2792
- const alongY = horizontal ? sameLane : !sameLane;
2793
- const base = ports.get(edge) ?? {};
2794
- const center = (rect) => alongY ? rect.x + rect.w / 2 : rect.y + rect.h / 2;
2795
- const within = (rect, value) => alongY ? Math.min(rect.x + rect.w - GRID_FINE, Math.max(rect.x + GRID_FINE, value)) : Math.min(rect.y + rect.h - GRID_FINE, Math.max(rect.y + GRID_FINE, value));
2796
- const route = ([startNudge, endNudge]) => router({
2797
- relation: edge,
2798
- from,
2799
- to,
2800
- horizontal,
2801
- offset: 0,
2802
- ports: {
2803
- start: within(from, (base.start ?? center(from)) + startNudge * GRID_FINE),
2804
- end: within(to, (base.end ?? center(to)) + endNudge * GRID_FINE)
2805
- }
2806
- });
2807
- const free = (points) => !taken(edge.from, points[0], "exit") && !taken(edge.to, points[points.length - 1], "entry");
2808
- const direct = route(NUDGES[0]);
2809
- if (free(direct)) return direct;
2810
- let best;
2811
- for (const nudge of NUDGES.slice(1)) {
2812
- const attempt = route(nudge);
2813
- if (free(attempt) && (!best || attempt.length < best.length)) best = attempt;
2814
- }
2815
- return best ?? direct;
545
+ })
546
+ });
547
+ /** A soft preference: its weighted cost through {@link fromCost}. */
548
+ var costScorer = (term, half = 3) => ({
549
+ id: term.id,
550
+ kind: "cost",
551
+ description: term.description,
552
+ evaluate: (layout) => Effect.sync(() => {
553
+ const value = term.measure(layout);
554
+ const weighted = value * term.weight;
555
+ return {
556
+ score: fromCost(weighted, half),
557
+ detail: `${round(value)} × ${term.weight} = ${round(weighted)}`
2816
558
  };
2817
- routed.forEach((edge, index) => {
2818
- const from = nodes.get(edge.from);
2819
- const to = nodes.get(edge.to);
2820
- if (!from || !to) return;
2821
- const points = routeEdge(edge, from, to);
2822
- terminals.set(edge.from, [...terminals.get(edge.from) ?? [], {
2823
- point: points[0],
2824
- role: "exit"
2825
- }]);
2826
- terminals.set(edge.to, [...terminals.get(edge.to) ?? [], {
2827
- point: points[points.length - 1],
2828
- role: "entry"
2829
- }]);
2830
- const id = `${edge.from}-${edge.to}-${index}`;
2831
- if (points.length > 2) elements.push({
2832
- kind: "line",
2833
- id: `${id}-path`,
2834
- points: points.slice(0, -1)
2835
- });
2836
- elements.push({
2837
- kind: "arrow",
2838
- id,
2839
- start: points[points.length - 2],
2840
- end: points[points.length - 1],
2841
- ...markers(edge.kind)
2842
- });
2843
- if (edge.label) {
2844
- const head = points[Math.floor(points.length / 2) - 1];
2845
- const tail = points[Math.floor(points.length / 2)];
2846
- elements.push({
2847
- kind: "text",
2848
- id: `${id}-label`,
2849
- x: (head.x + tail.x) / 2 + 8,
2850
- y: (head.y + tail.y) / 2 - LABEL_FONT.lineH,
2851
- text: edge.label,
2852
- weight: "s"
2853
- });
2854
- }
2855
- });
2856
- commands.push({
2857
- op: "upsert-object",
2858
- object: {
2859
- id: "edges",
2860
- origin,
2861
- scale,
2862
- elements
2863
- }
2864
- });
2865
- }
2866
- return commands;
2867
- };
2868
- var objectsOf = (commands) => commands.flatMap((command) => command.op === "upsert-object" ? [command.object] : []);
559
+ })
560
+ });
561
+ /** Every constraint and cost term of an objective, constraints first. */
562
+ var fromObjective = (objective, half = 3) => [...objective.constraints.map(constraintScorer), ...objective.costs.map((term) => costScorer(term, half))];
563
+ var isBatch = (source) => "entries" in source;
564
+ /** Runs every source against one subject concurrently; results keep the sources' order, batches flattened. */
565
+ var evaluate = (sources, subject) => Effect.all(sources.map((source) => isBatch(source) ? source.evaluate(subject).pipe(Effect.map((results) => source.entries.map((entry, index) => ({
566
+ ...entry,
567
+ ...results[index]
568
+ })))) : source.evaluate(subject).pipe(Effect.map((result) => [{
569
+ id: source.id,
570
+ kind: source.kind,
571
+ description: source.description,
572
+ ...result
573
+ }]))), { concurrency: "unbounded" }).pipe(Effect.map((groups) => groups.flat()));
2869
574
  /**
2870
- * Lay out a mermaid flowchart: place once per lattice × ordering, emit with and without the
2871
- * inheritance bus, analyze every candidate, and let the objective choose. All candidates come back
2872
- * ranked so a bench can show what lost and why. Async because ELK is.
575
+ * One number for a set of scores: the worst constraint gates the mean of everything else, so a
576
+ * layout that breaks a constraint scores 0 however good it otherwise is — the same precedence
577
+ * `Objective.select` gives violations over cost — while every other kind contributes equally.
578
+ * `undefined` when nothing could be judged, so a set of failed scorers never reads as a perfect score.
2873
579
  */
2874
- var layout$1 = async (source, options = {}) => {
2875
- const graph = parse$1(source);
2876
- const { origin = {
2877
- x: 0,
2878
- y: 0
2879
- }, scale = 1, maxWidth = MAX_W, objective = DEFAULT, route } = options;
2880
- const cell = measureCell(graph, maxWidth);
2881
- const lattices = typeof options.lattice === "number" ? [options.lattice] : options.lattice ?? LATTICES;
2882
- const orders = options.order ?? ORDERS;
2883
- const buses = options.bus ?? [true, false];
2884
- const grouped = graph.groups.filter((group) => group.children.length > 0).length;
2885
- const arrangements = options.arrangement ?? (grouped >= 2 ? ARRANGEMENTS : ["layered"]);
2886
- const layerings = options.layering ?? LAYERINGS;
2887
- const alignments = options.alignment ?? ALIGNMENTS;
2888
- invariant(lattices.length > 0 && orders.length > 0 && buses.length > 0 && arrangements.length > 0 && layerings.length > 0 && alignments.length > 0, "every candidate axis needs a value", {
2889
- "~LogMeta": "~LogMeta",
2890
- F: __dxlog_file,
2891
- L: 806,
2892
- S: void 0,
2893
- A: ["lattices.length > 0 &&\n orders.length > 0 &&\n buses.length > 0 &&\n arrangements.length > 0 &&\n layerings.length > 0 &&\n alignments.length > 0", "'every candidate axis needs a value'"]
2894
- });
2895
- const candidates = [];
2896
- const seen = /* @__PURE__ */ new Set();
2897
- for (const lattice of lattices) for (const order of orders) for (const arrangement of arrangements) for (const layering of layerings) {
2898
- const positions = await place(graph, cell, pitchFor(graph, cell, lattice), order, arrangement, layering);
2899
- for (const alignment of arrangement === "columns" ? alignments : ["none"]) {
2900
- const placement = frame$1(graph, alignGroups(graph, positions, graph.direction, alignment), cell);
2901
- const key = [...placement.nodes].map(([id, rect]) => `${id}:${rect.x}:${rect.y}`).sort().join(" ");
2902
- for (const bus of buses) {
2903
- if (seen.has(`${arrangement}|${bus}|${key}`)) continue;
2904
- seen.add(`${arrangement}|${bus}|${key}`);
2905
- const commands = emit(graph, cell, placement, {
2906
- origin,
2907
- scale,
2908
- bus,
2909
- arrangement,
2910
- route
2911
- });
2912
- const objects = objectsOf(commands);
2913
- candidates.push({
2914
- lattice,
2915
- order,
2916
- arrangement,
2917
- layering,
2918
- alignment,
2919
- bus,
2920
- commands,
2921
- layout: {
2922
- objects,
2923
- report: analyze(objects)
2924
- }
2925
- });
2926
- }
2927
- }
2928
- }
2929
- const { chosen, ranked } = select(objective, candidates);
2930
- return {
2931
- commands: chosen.candidate.commands,
2932
- chosen,
2933
- ranked
2934
- };
2935
- };
2936
- /** The chosen layout's scene commands; see {@link layout} for the candidates and verdicts. */
2937
- var compile$4 = async (source, options = {}) => (await layout$1(source, options)).commands;
580
+ var overall = (all) => {
581
+ const scores = all.filter(({ error }) => error === void 0);
582
+ if (scores.length === 0) return;
583
+ const gate = Math.min(1, ...scores.filter(({ kind }) => kind === "constraint").map(({ score }) => score));
584
+ const rest = scores.filter(({ kind }) => kind !== "constraint");
585
+ return gate * (rest.length ? rest.reduce((total, { score }) => total + score, 0) / rest.length : 1);
586
+ };
587
+ var round = (value) => Number.isInteger(value) ? `${value}` : value.toFixed(2);
2938
588
  //#endregion
2939
589
  //#region src/svg-handler.ts
2940
590
  /** Discriminates the base `Drawing.Canvas` as SVG-rendered scene content. */
@@ -3451,8 +1101,8 @@ var elkPlace = async (model, { cell, gapMain, gapCross, direction }) => {
3451
1101
  */
3452
1102
  var compile$2 = async (source, options = {}) => {
3453
1103
  const { origin, scale, maxWidth = 192, engine = "elk" } = options;
3454
- const model = parse(source);
3455
- const cell = measureCell$1(model, {
1104
+ const model = parse$1(source);
1105
+ const cell = measureCell(model, {
3456
1106
  maxWidth,
3457
1107
  cell: options.cell,
3458
1108
  titleHeight: options.titleHeight
@@ -3463,10 +1113,11 @@ var compile$2 = async (source, options = {}) => {
3463
1113
  gapCross: snap$2(options.gapCross ?? GAP_CROSS),
3464
1114
  direction: model.direction
3465
1115
  };
3466
- return emit$1({
1116
+ const rects = engine === "dagre" ? dagrePlace(model, place) : await elkPlace(model, place);
1117
+ return emit({
3467
1118
  model,
3468
1119
  cell,
3469
- rects: engine === "dagre" ? dagrePlace(model, place) : await elkPlace(model, place),
1120
+ rects,
3470
1121
  ranks: relationRanks(model)
3471
1122
  }, {
3472
1123
  origin,
@@ -3644,7 +1295,7 @@ var packGroups = (model, groups) => {
3644
1295
  from: groupOf.get(edge.from).id,
3645
1296
  to: groupOf.get(edge.to).id
3646
1297
  })).filter((edge) => edge.from !== edge.to);
3647
- const groupRanks = rank$1(groups.map((group) => group.id), crossEdges.map((edge) => ({
1298
+ const groupRanks = rank(groups.map((group) => group.id), crossEdges.map((edge) => ({
3648
1299
  from: edge.to,
3649
1300
  to: edge.from
3650
1301
  })));
@@ -3722,8 +1373,8 @@ var compile$1 = (source, options = {}) => {
3722
1373
  y: 0
3723
1374
  }, scale = 1, maxWidth = 192 } = options;
3724
1375
  const rules = options.rules ?? [inheritanceTreeRule, linearChainRule];
3725
- const model = parse(source);
3726
- const cell = measureCell$1(model, {
1376
+ const model = parse$1(source);
1377
+ const cell = measureCell(model, {
3727
1378
  maxWidth,
3728
1379
  cell: options.cell,
3729
1380
  titleHeight: options.titleHeight
@@ -3734,7 +1385,7 @@ var compile$1 = (source, options = {}) => {
3734
1385
  return [...frameCommands(groups, origins, {
3735
1386
  origin,
3736
1387
  scale
3737
- }), ...emit$1({
1388
+ }), ...emit({
3738
1389
  model,
3739
1390
  cell,
3740
1391
  rects,
@@ -3945,8 +1596,8 @@ var compile = (source, options = {}) => {
3945
1596
  y: 0
3946
1597
  }, scale = 1, maxWidth = 192 } = options;
3947
1598
  const rules = options.rules ?? [inheritanceTreeRule, linearChainRule];
3948
- const model = parse(source);
3949
- const cell = measureCell$1(model, {
1599
+ const model = parse$1(source);
1600
+ const cell = measureCell(model, {
3950
1601
  maxWidth,
3951
1602
  cell: options.cell,
3952
1603
  titleHeight: options.titleHeight
@@ -3959,7 +1610,7 @@ var compile = (source, options = {}) => {
3959
1610
  return [...frameCommands(groups, origins, {
3960
1611
  origin,
3961
1612
  scale
3962
- }), ...emit$1({
1613
+ }), ...emit({
3963
1614
  model,
3964
1615
  cell,
3965
1616
  rects,
@@ -3970,6 +1621,6 @@ var compile = (source, options = {}) => {
3970
1621
  })];
3971
1622
  };
3972
1623
  //#endregion
3973
- export { diagnostics_exports as Diagnostics, DialectRegistry, layout_exports as Layout, mermaid_exports as Mermaid, mermaid_engine_exports as MermaidEngine, objective_exports as Objective, SVG_SCHEMA, scene_exports as Scene, SvgHandler, ui_exports as Ui, uml_exports as Uml, uml_engine_exports as UmlEngine, uml_grid_exports as UmlGrid, uml_rules_exports as UmlRules, uml_search_exports as UmlSearch, applyCommands, nextIndex, sceneDialect };
1624
+ export { aesthetics_exports as Aesthetics, appeal_exports as Appeal, architecture_exports as Architecture, diagnostics_exports as Diagnostics, DialectRegistry, dsl_exports as Dsl, dx_svg_exports as DxSvg, layout_exports as Layout, mermaid_exports as Mermaid, mermaid_engine_exports as MermaidEngine, nudge_exports as Nudge, objective_exports as Objective, ports_exports as Ports, rules_exports as Rules, SVG_SCHEMA, scene_exports as Scene, score_exports as Score, semantic_exports as Semantic, semantic_engine_exports as SemanticEngine, semantic_place_exports as SemanticPlace, semantic_route_exports as SemanticRoute, SvgHandler, ui_exports as Ui, uml_exports as Uml, uml_engine_exports as UmlEngine, uml_grid_exports as UmlGrid, uml_rules_exports as UmlRules, uml_search_exports as UmlSearch, view_exports as View, applyCommands, nextIndex, sceneDialect };
3974
1625
 
3975
1626
  //# sourceMappingURL=index.mjs.map