@diagrammo/dgmo 0.31.0 → 0.32.1

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 (72) hide show
  1. package/.cursorrules +4 -1
  2. package/.github/copilot-instructions.md +4 -1
  3. package/.windsurfrules +4 -1
  4. package/SKILL.md +4 -1
  5. package/dist/advanced.cjs +1297 -358
  6. package/dist/advanced.d.cts +117 -15
  7. package/dist/advanced.d.ts +117 -15
  8. package/dist/advanced.js +1291 -358
  9. package/dist/auto.cjs +1087 -316
  10. package/dist/auto.js +98 -98
  11. package/dist/auto.mjs +1087 -316
  12. package/dist/cli.cjs +140 -140
  13. package/dist/index.cjs +1090 -397
  14. package/dist/index.js +1090 -397
  15. package/docs/ai-integration.md +4 -1
  16. package/docs/language-reference.md +282 -27
  17. package/gallery/fixtures/boxes-and-lines.dgmo +2 -2
  18. package/gallery/fixtures/c4-full.dgmo +4 -5
  19. package/gallery/fixtures/c4.dgmo +2 -3
  20. package/package.json +7 -1
  21. package/src/advanced.ts +7 -0
  22. package/src/boxes-and-lines/focus.ts +257 -0
  23. package/src/boxes-and-lines/layout-search.ts +131 -65
  24. package/src/boxes-and-lines/layout.ts +7 -1
  25. package/src/boxes-and-lines/parser.ts +19 -4
  26. package/src/boxes-and-lines/renderer.ts +54 -3
  27. package/src/c4/parser.ts +8 -7
  28. package/src/chart-type-registry.ts +129 -4
  29. package/src/chart-types.ts +4 -4
  30. package/src/chart.ts +18 -1
  31. package/src/colors.ts +225 -2
  32. package/src/cycle/parser.ts +2 -7
  33. package/src/d3.ts +67 -54
  34. package/src/diagnostics.ts +17 -0
  35. package/src/dimensions.ts +9 -13
  36. package/src/echarts.ts +42 -14
  37. package/src/er/parser.ts +6 -1
  38. package/src/gantt/parser.ts +44 -7
  39. package/src/graph/flowchart-parser.ts +77 -3
  40. package/src/graph/state-renderer.ts +2 -2
  41. package/src/infra/parser.ts +80 -0
  42. package/src/journey-map/parser.ts +8 -7
  43. package/src/kanban/parser.ts +8 -7
  44. package/src/map/context-labels.ts +134 -27
  45. package/src/map/geo.ts +10 -2
  46. package/src/map/layout.ts +259 -4
  47. package/src/map/parser.ts +2 -0
  48. package/src/map/renderer.ts +22 -11
  49. package/src/map/resolver.ts +68 -19
  50. package/src/mindmap/parser.ts +15 -7
  51. package/src/mindmap/renderer.ts +50 -12
  52. package/src/org/parser.ts +8 -7
  53. package/src/org/renderer.ts +22 -7
  54. package/src/palettes/color-utils.ts +12 -2
  55. package/src/palettes/index.ts +1 -0
  56. package/src/pert/renderer.ts +2 -2
  57. package/src/pyramid/parser.ts +2 -7
  58. package/src/quadrant/renderer.ts +2 -2
  59. package/src/raci/parser.ts +2 -7
  60. package/src/raci/renderer.ts +4 -4
  61. package/src/ring/parser.ts +2 -7
  62. package/src/sequence/parser.ts +18 -7
  63. package/src/sequence/renderer.ts +4 -4
  64. package/src/sitemap/parser.ts +8 -7
  65. package/src/sitemap/renderer.ts +2 -2
  66. package/src/tech-radar/parser.ts +2 -7
  67. package/src/timeline/renderer.ts +15 -5
  68. package/src/utils/parsing.ts +13 -1
  69. package/src/utils/scaling.ts +38 -81
  70. package/src/utils/tag-groups.ts +38 -0
  71. package/src/visualizations/parse.ts +6 -1
  72. package/src/wireframe/parser.ts +6 -1
package/src/colors.ts CHANGED
@@ -117,6 +117,211 @@ export function resolveColor(
117
117
  import type { DgmoError } from './diagnostics';
118
118
  import { makeDgmoError, suggest } from './diagnostics';
119
119
 
120
+ /**
121
+ * Stable diagnostic code for "this token is not one of the 11 named palette
122
+ * colors" — covers both hex/CSS literals (emitted as `error`) and unrecognized
123
+ * bare words like `crimson` (emitted as `warning`). Consumers that want to
124
+ * HARD-BLOCK invalid colors regardless of severity (e.g. the MCP render gate)
125
+ * filter on this code rather than re-deriving the rule.
126
+ */
127
+ export const INVALID_COLOR_CODE = 'E_INVALID_COLOR';
128
+
129
+ /**
130
+ * CSS / X11 color names that are NOT one of DGMO's 11 — mapped to their hex so
131
+ * a "nearest valid color" hint can be computed. This is the blocklist that lets
132
+ * the trailing-token rule tell an *intended-but-invalid* color (`pink`,
133
+ * `crimson`, `navy`) apart from an ordinary label word (`Zinfandel`, `Blanc`):
134
+ * a lowercase trailing token found here is flagged, anything else stays label
135
+ * text. Our 11 valid names are deliberately excluded. Extend freely — it only
136
+ * sharpens detection. (Case-sensitive lowercase, matching the §1.5 color rule.)
137
+ */
138
+ export const INVALID_CSS_COLOR_HEX: Readonly<Record<string, string>> =
139
+ Object.freeze({
140
+ pink: '#ffc0cb',
141
+ hotpink: '#ff69b4',
142
+ deeppink: '#ff1493',
143
+ lightpink: '#ffb6c1',
144
+ palevioletred: '#db7093',
145
+ crimson: '#dc143c',
146
+ scarlet: '#ff2400',
147
+ firebrick: '#b22222',
148
+ darkred: '#8b0000',
149
+ maroon: '#800000',
150
+ salmon: '#fa8072',
151
+ lightsalmon: '#ffa07a',
152
+ darksalmon: '#e9967a',
153
+ coral: '#ff7f50',
154
+ lightcoral: '#f08080',
155
+ tomato: '#ff6347',
156
+ orangered: '#ff4500',
157
+ darkorange: '#ff8c00',
158
+ gold: '#ffd700',
159
+ goldenrod: '#daa520',
160
+ darkgoldenrod: '#b8860b',
161
+ khaki: '#f0e68c',
162
+ darkkhaki: '#bdb76b',
163
+ amber: '#ffbf00',
164
+ lavender: '#e6e6fa',
165
+ violet: '#ee82ee',
166
+ magenta: '#ff00ff',
167
+ fuchsia: '#ff00ff',
168
+ orchid: '#da70d6',
169
+ plum: '#dda0dd',
170
+ indigo: '#4b0082',
171
+ navy: '#000080',
172
+ midnightblue: '#191970',
173
+ darkblue: '#00008b',
174
+ mediumblue: '#0000cd',
175
+ royalblue: '#4169e1',
176
+ cornflowerblue: '#6495ed',
177
+ dodgerblue: '#1e90ff',
178
+ deepskyblue: '#00bfff',
179
+ skyblue: '#87ceeb',
180
+ lightskyblue: '#87cefa',
181
+ lightblue: '#add8e6',
182
+ powderblue: '#b0e0e6',
183
+ steelblue: '#4682b4',
184
+ slateblue: '#6a5acd',
185
+ cadetblue: '#5f9ea0',
186
+ turquoise: '#40e0d0',
187
+ aqua: '#00ffff',
188
+ aquamarine: '#7fffd4',
189
+ lime: '#00ff00',
190
+ limegreen: '#32cd32',
191
+ lightgreen: '#90ee90',
192
+ palegreen: '#98fb98',
193
+ seagreen: '#2e8b57',
194
+ mediumseagreen: '#3cb371',
195
+ forestgreen: '#228b22',
196
+ darkgreen: '#006400',
197
+ olive: '#808000',
198
+ olivedrab: '#6b8e23',
199
+ darkolivegreen: '#556b2f',
200
+ chartreuse: '#7fff00',
201
+ lawngreen: '#7cfc00',
202
+ springgreen: '#00ff7f',
203
+ greenyellow: '#adff2f',
204
+ brown: '#a52a2a',
205
+ sienna: '#a0522d',
206
+ chocolate: '#d2691e',
207
+ peru: '#cd853f',
208
+ tan: '#d2b48c',
209
+ beige: '#f5f5dc',
210
+ wheat: '#f5deb3',
211
+ ivory: '#fffff0',
212
+ silver: '#c0c0c0',
213
+ lightgray: '#d3d3d3',
214
+ lightgrey: '#d3d3d3',
215
+ darkgray: '#a9a9a9',
216
+ darkgrey: '#a9a9a9',
217
+ dimgray: '#696969',
218
+ dimgrey: '#696969',
219
+ slategray: '#708090',
220
+ slategrey: '#708090',
221
+ gainsboro: '#dcdcdc',
222
+ grey: '#808080',
223
+ });
224
+
225
+ /**
226
+ * Best-effort nearest recognized color NAME for an unsupported hex value.
227
+ * Matches by HUE (with a low-saturation cutoff routing to black/white/gray),
228
+ * NOT raw RGB distance to the muted palette hexes — vivid LLM colors like a
229
+ * `#3cb44b` green would otherwise snap to `gray` against a desaturated sage.
230
+ * Returns null for non-hex input (CSS function/keyword colors, no RGB to read).
231
+ * Used ONLY to enrich a diagnostic — never to silently accept the value.
232
+ */
233
+ export function nearestNamedColor(input: string): string | null {
234
+ // CSS color name → resolve to its hex first so hue-matching works on it too.
235
+ const cssHex = INVALID_CSS_COLOR_HEX[input.trim().toLowerCase()];
236
+ if (cssHex) input = cssHex;
237
+ const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(input.trim());
238
+ if (!m) return null;
239
+ let h = m[1]!.toLowerCase();
240
+ if (h.length === 3)
241
+ h = h
242
+ .split('')
243
+ .map((c) => c + c)
244
+ .join('');
245
+ const r = parseInt(h.slice(0, 2), 16) / 255;
246
+ const g = parseInt(h.slice(2, 4), 16) / 255;
247
+ const b = parseInt(h.slice(4, 6), 16) / 255;
248
+ const max = Math.max(r, g, b);
249
+ const min = Math.min(r, g, b);
250
+ const delta = max - min;
251
+ const l = (max + min) / 2;
252
+ const s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
253
+ // Low chroma → a neutral; pick by lightness.
254
+ if (s < 0.15) {
255
+ if (l < 0.2) return 'black';
256
+ if (l > 0.85) return 'white';
257
+ return 'gray';
258
+ }
259
+ let hue: number;
260
+ if (max === r) hue = 60 * (((g - b) / delta) % 6);
261
+ else if (max === g) hue = 60 * ((b - r) / delta + 2);
262
+ else hue = 60 * ((r - g) / delta + 4);
263
+ if (hue < 0) hue += 360;
264
+ // Canonical hue anchors for the 8 chromatic names (red appears at both 0 and
265
+ // 360 so wrap-around resolves correctly).
266
+ const anchors: readonly [string, number][] = [
267
+ ['red', 0],
268
+ ['orange', 30],
269
+ ['yellow', 55],
270
+ ['green', 120],
271
+ ['teal', 170],
272
+ ['cyan', 190],
273
+ ['blue', 225],
274
+ ['purple', 285],
275
+ ['red', 360],
276
+ ];
277
+ let best = 'red';
278
+ let bestD = Infinity;
279
+ for (const [name, deg] of anchors) {
280
+ const d = Math.abs(hue - deg);
281
+ if (d < bestD) {
282
+ bestD = d;
283
+ best = name;
284
+ }
285
+ }
286
+ return best;
287
+ }
288
+
289
+ /**
290
+ * True iff `token` is an INTENDED-but-invalid color: a hex/`rgb()`/`hsl()`
291
+ * literal, or a known CSS color name that isn't one of DGMO's 11. Lets the
292
+ * trailing-token rule flag `Rosé pink` / `Foo #e6194b` while leaving genuine
293
+ * label words (`Zinfandel`) untouched.
294
+ */
295
+ export function isInvalidColorToken(token: string): boolean {
296
+ if (/^(#|rgba?\(|hsla?\()/i.test(token)) return true;
297
+ const lower = token.toLowerCase();
298
+ return (
299
+ INVALID_CSS_COLOR_HEX[lower] !== undefined && !isRecognizedColorName(lower)
300
+ );
301
+ }
302
+
303
+ /**
304
+ * Build an `E_INVALID_COLOR` diagnostic for an intended-but-invalid trailing
305
+ * color token, or return null if `token` isn't color-like (so it stays label
306
+ * text). Severity is `warning` so the library degrades gracefully (the value
307
+ * just keeps the word); the MCP render gate blocks on the code regardless.
308
+ * Used by `extractColor` to close the trailing-token "silent swallow" gap.
309
+ */
310
+ export function invalidColorDiagnostic(
311
+ token: string,
312
+ line: number
313
+ ): DgmoError | null {
314
+ if (!isInvalidColorToken(token)) return null;
315
+ const nearest = nearestNamedColor(token);
316
+ const near = nearest ? ` Nearest: ${nearest}.` : '';
317
+ return makeDgmoError(
318
+ line,
319
+ `Color "${token}" is not a valid DGMO color — DGMO accepts only these 11 named colors: ${RECOGNIZED_COLOR_NAMES.join(', ')} (no hex, no CSS color names).${near}`,
320
+ 'warning',
321
+ INVALID_COLOR_CODE
322
+ );
323
+ }
324
+
120
325
  /**
121
326
  * Resolves a color name and pushes a warning diagnostic on failure.
122
327
  * Returns the hex string for valid names, or `undefined` for unknown
@@ -131,13 +336,31 @@ export function resolveColorWithDiagnostic(
131
336
  ): string | undefined {
132
337
  const resolved = resolveColor(color, palette);
133
338
  if (resolved !== null) return resolved;
339
+ // Literal color values (hex `#e6194b`, `rgb(...)`, `hsl(...)`) are NOT part
340
+ // of the DGMO language — only the 11 named palette colors are accepted, so
341
+ // that a single source recolors correctly across every palette and theme.
342
+ // Flag these with a precise error rather than a typo suggestion.
343
+ if (/^(#|rgba?\(|hsla?\()/i.test(color)) {
344
+ const nearest = nearestNamedColor(color);
345
+ const near = nearest ? ` Nearest: ${nearest}.` : '';
346
+ diagnostics.push(
347
+ makeDgmoError(
348
+ line,
349
+ `Color "${color}" is not supported — DGMO does not accept hex or CSS color values. Use a named palette color: ${RECOGNIZED_COLOR_NAMES.join(', ')}.${near}`,
350
+ 'error',
351
+ INVALID_COLOR_CODE
352
+ )
353
+ );
354
+ return undefined;
355
+ }
134
356
  const hint = suggest(color, RECOGNIZED_COLOR_NAMES as readonly string[]);
135
357
  const suggestion = hint ? ` ${hint}` : '';
136
358
  diagnostics.push(
137
359
  makeDgmoError(
138
360
  line,
139
- `Unknown color "${color}". Allowed: ${RECOGNIZED_COLOR_NAMES.join(', ')}.${suggestion}`,
140
- 'warning'
361
+ `Unknown color "${color}". DGMO accepts only these 11 named colors: ${RECOGNIZED_COLOR_NAMES.join(', ')} (no hex, no CSS color names).${suggestion}`,
362
+ 'warning',
363
+ INVALID_COLOR_CODE
141
364
  )
142
365
  );
143
366
  return undefined;
@@ -3,8 +3,8 @@
3
3
  // ============================================================
4
4
 
5
5
  import {
6
- formatDgmoError,
7
6
  makeDgmoError,
7
+ makeFail,
8
8
  METADATA_DIAGNOSTIC_CODES,
9
9
  pipeOperatorRemovedMessage,
10
10
  } from '../diagnostics';
@@ -64,12 +64,7 @@ export function parseCycle(content: string): ParsedCycle {
64
64
  let currentEdge: Writable<CycleEdge> | null = null;
65
65
  // nodeBaseIndent tracking removed — indent-based nesting not used in cycle
66
66
 
67
- const fail = (line: number, message: string): ParsedCycle => {
68
- const diag = makeDgmoError(line, message);
69
- result.diagnostics.push(diag);
70
- result.error = formatDgmoError(diag);
71
- return result;
72
- };
67
+ const fail = makeFail(result);
73
68
 
74
69
  const warn = (
75
70
  line: number,