@diagrammo/dgmo 0.8.14 → 0.8.16

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.
@@ -46,6 +46,8 @@ export const rosePinePalette: PaletteConfig = {
46
46
  teal: '#286983', // Pine
47
47
  cyan: '#56949f', // Foam
48
48
  gray: '#9893a5', // Muted
49
+ black: '#575279',
50
+ white: '#fffaf3',
49
51
  },
50
52
  },
51
53
  dark: {
@@ -69,6 +71,8 @@ export const rosePinePalette: PaletteConfig = {
69
71
  teal: '#3e8fb0', // Pine
70
72
  cyan: '#9ccfd8', // Foam
71
73
  gray: '#6e6a86', // Muted
74
+ black: '#2a273f',
75
+ white: '#e0def4',
72
76
  },
73
77
  },
74
78
  };
@@ -39,6 +39,8 @@ export const solarizedPalette: PaletteConfig = {
39
39
  teal: '#2aa198',
40
40
  cyan: '#2aa198', // Solarized has no separate cyan — reuse teal
41
41
  gray: '#586e75', // base01
42
+ black: '#657b83',
43
+ white: '#eee8d5',
42
44
  },
43
45
  },
44
46
  dark: {
@@ -62,6 +64,8 @@ export const solarizedPalette: PaletteConfig = {
62
64
  teal: '#2aa198',
63
65
  cyan: '#2aa198',
64
66
  gray: '#586e75', // base01
67
+ black: '#073642',
68
+ white: '#839496',
65
69
  },
66
70
  },
67
71
  };
@@ -48,6 +48,8 @@ export const tokyoNightPalette: PaletteConfig = {
48
48
  teal: '#118c74',
49
49
  cyan: '#007197',
50
50
  gray: '#8990b3', // Day dark3
51
+ black: '#1a1b26',
52
+ white: '#d0d5e3',
51
53
  },
52
54
  },
53
55
  dark: {
@@ -71,6 +73,8 @@ export const tokyoNightPalette: PaletteConfig = {
71
73
  teal: '#1abc9c',
72
74
  cyan: '#7dcfff',
73
75
  gray: '#565f89', // Night comment
76
+ black: '#292e42',
77
+ white: '#c0caf5',
74
78
  },
75
79
  },
76
80
  };
@@ -48,6 +48,8 @@ export interface PaletteColors {
48
48
  teal: string;
49
49
  cyan: string;
50
50
  gray: string;
51
+ black: string;
52
+ white: string;
51
53
  };
52
54
  }
53
55
 
@@ -604,7 +604,12 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
604
604
  // First entry is the default unless another is marked `default`
605
605
  if (currentTagGroup && !contentStarted && measureIndent(raw) > 0) {
606
606
  const { text: cleanEntry, isDefault } = stripDefaultModifier(trimmed);
607
- const { label, color } = extractColor(cleanEntry);
607
+ const { label, color } = extractColor(
608
+ cleanEntry,
609
+ undefined,
610
+ result.diagnostics,
611
+ lineNumber
612
+ );
608
613
  if (!color) {
609
614
  pushError(
610
615
  lineNumber,
@@ -12,7 +12,6 @@ import {
12
12
  } from '../utils/inline-markdown';
13
13
  export { parseInlineMarkdown, truncateBareUrl };
14
14
  import { FONT_FAMILY } from '../fonts';
15
- import { resolveColor } from '../colors';
16
15
  import type {
17
16
  ParsedSequenceDgmo,
18
17
  SequenceElement,
@@ -936,10 +935,7 @@ export function renderSequenceDiagram(
936
935
  );
937
936
  if (tg) {
938
937
  for (const entry of tg.entries) {
939
- tagValueToColor.set(
940
- entry.value.toLowerCase(),
941
- resolveColor(entry.color) ?? entry.color
942
- );
938
+ tagValueToColor.set(entry.value.toLowerCase(), entry.color);
943
939
  }
944
940
  }
945
941
  }
@@ -1598,7 +1594,7 @@ export function renderSequenceDiagram(
1598
1594
  name: tg.name,
1599
1595
  entries: tg.entries.map((e) => ({
1600
1596
  value: e.value,
1601
- color: resolveColor(e.color) ?? e.color,
1597
+ color: e.color,
1602
1598
  })),
1603
1599
  }));
1604
1600
  const legendConfig: LegendConfig = {
package/src/sharing.ts CHANGED
@@ -17,11 +17,13 @@ export interface DiagramViewState {
17
17
  export interface DecodedDiagramUrl {
18
18
  dsl: string;
19
19
  viewState: DiagramViewState;
20
+ filename?: string;
20
21
  }
21
22
 
22
23
  export interface EncodeDiagramUrlOptions {
23
24
  baseUrl?: string;
24
25
  viewState?: DiagramViewState;
26
+ filename?: string;
25
27
  }
26
28
 
27
29
  export type EncodeDiagramUrlResult =
@@ -80,6 +82,10 @@ export function encodeDiagramUrl(
80
82
  hash += `&th=${encodeURIComponent(options.viewState.theme)}`;
81
83
  }
82
84
 
85
+ if (options?.filename) {
86
+ hash += `&fn=${encodeURIComponent(options.filename)}`;
87
+ }
88
+
83
89
  // Encode in both query param AND hash fragment — some share mechanisms
84
90
  // strip one or the other (iOS share sheet strips #, AirDrop strips ?)
85
91
  return { url: `${baseUrl}?${hash}#${hash}` };
@@ -97,6 +103,7 @@ export function encodeDiagramUrl(
97
103
  */
98
104
  export function decodeDiagramUrl(hash: string): DecodedDiagramUrl {
99
105
  const empty: DecodedDiagramUrl = { dsl: '', viewState: {} };
106
+ let filename: string | undefined;
100
107
  if (!hash) return empty;
101
108
 
102
109
  let raw = hash;
@@ -134,6 +141,7 @@ export function decodeDiagramUrl(hash: string): DecodedDiagramUrl {
134
141
  if (key === 'pal' && val) viewState.palette = val;
135
142
  if (key === 'th' && (val === 'light' || val === 'dark'))
136
143
  viewState.theme = val;
144
+ if (key === 'fn' && val) filename = val;
137
145
  }
138
146
 
139
147
  // Strip 'dgmo=' prefix
@@ -141,12 +149,12 @@ export function decodeDiagramUrl(hash: string): DecodedDiagramUrl {
141
149
  payload = payload.slice(5);
142
150
  }
143
151
 
144
- if (!payload) return { dsl: '', viewState };
152
+ if (!payload) return { dsl: '', viewState, filename };
145
153
 
146
154
  try {
147
155
  const result = decompressFromEncodedURIComponent(payload);
148
- return { dsl: result ?? '', viewState };
156
+ return { dsl: result ?? '', viewState, filename };
149
157
  } catch {
150
- return { dsl: '', viewState };
158
+ return { dsl: '', viewState, filename };
151
159
  }
152
160
  }
@@ -3,7 +3,8 @@
3
3
  // ============================================================
4
4
 
5
5
  import type { PaletteColors } from '../palettes';
6
- import { resolveColor } from '../colors';
6
+ import { resolveColorWithDiagnostic } from '../colors';
7
+ import type { DgmoError } from '../diagnostics';
7
8
  import { makeDgmoError, formatDgmoError, suggest } from '../diagnostics';
8
9
  import type { TagGroup } from '../utils/tag-groups';
9
10
  import {
@@ -46,7 +47,9 @@ const BARE_ARROW_RE = /^->\s*(.+)$/;
46
47
 
47
48
  function parseArrowLine(
48
49
  trimmed: string,
49
- palette?: PaletteColors
50
+ palette: PaletteColors | undefined,
51
+ lineNumber: number,
52
+ diagnostics: DgmoError[]
50
53
  ): {
51
54
  label?: string;
52
55
  color?: string;
@@ -69,10 +72,22 @@ function parseArrowLine(
69
72
  if (arrowMatch) {
70
73
  const label = arrowMatch[1]?.trim() || undefined;
71
74
  let color = arrowMatch[2]
72
- ? (resolveColor(arrowMatch[2].trim(), palette) ?? undefined)
75
+ ? resolveColorWithDiagnostic(
76
+ arrowMatch[2].trim(),
77
+ lineNumber,
78
+ diagnostics,
79
+ palette
80
+ )
73
81
  : undefined;
74
82
  if (label && !color) {
75
- color = inferArrowColor(label);
83
+ const inferred = inferArrowColor(label);
84
+ if (inferred)
85
+ color = resolveColorWithDiagnostic(
86
+ inferred,
87
+ lineNumber,
88
+ diagnostics,
89
+ palette
90
+ );
76
91
  }
77
92
  const rawTarget = arrowMatch[3].trim();
78
93
  const groupMatch = rawTarget.match(/^\[(.+)\]$/);
@@ -321,7 +336,12 @@ export function parseSitemap(
321
336
 
322
337
  // Check for arrow syntax (must check before metadata — arrows contain `:` in labels
323
338
  // but also start with `-`)
324
- const arrowInfo = parseArrowLine(trimmed, palette);
339
+ const arrowInfo = parseArrowLine(
340
+ trimmed,
341
+ palette,
342
+ lineNumber,
343
+ result.diagnostics
344
+ );
325
345
  if (arrowInfo) {
326
346
  // Find the source node: the most recent node on the indent stack
327
347
  // at a shallower indent (same pattern as metadata attachment)
@@ -351,7 +371,12 @@ export function parseSitemap(
351
371
 
352
372
  if (containerMatch) {
353
373
  const rawLabel = containerMatch[1].trim();
354
- const { label, color } = extractColor(rawLabel, palette);
374
+ const { label, color } = extractColor(
375
+ rawLabel,
376
+ palette,
377
+ result.diagnostics,
378
+ lineNumber
379
+ );
355
380
 
356
381
  // Parse optional pipe metadata on the container line
357
382
  const pipeStr = containerMatch[2];
@@ -401,7 +426,8 @@ export function parseSitemap(
401
426
  palette,
402
427
  ++nodeCounter,
403
428
  aliasMap,
404
- pushWarning
429
+ pushWarning,
430
+ result.diagnostics
405
431
  );
406
432
  attachNode(node, indent, indentStack, result);
407
433
  labelToNode.set(node.label.toLowerCase(), node);
@@ -416,7 +442,8 @@ export function parseSitemap(
416
442
  palette,
417
443
  ++nodeCounter,
418
444
  aliasMap,
419
- pushWarning
445
+ pushWarning,
446
+ result.diagnostics
420
447
  );
421
448
  attachNode(node, indent, indentStack, result);
422
449
  labelToNode.set(node.label.toLowerCase(), node);
@@ -503,11 +530,17 @@ function parseNodeLabel(
503
530
  palette: PaletteColors | undefined,
504
531
  counter: number,
505
532
  aliasMap: Map<string, string> = new Map(),
506
- warnFn?: (line: number, msg: string) => void
533
+ warnFn?: (line: number, msg: string) => void,
534
+ diagnostics?: DgmoError[]
507
535
  ): SitemapNode {
508
536
  const segments = trimmed.split('|').map((s) => s.trim());
509
537
  const rawLabel = segments[0];
510
- const { label, color } = extractColor(rawLabel, palette);
538
+ const { label, color } = extractColor(
539
+ rawLabel,
540
+ palette,
541
+ diagnostics,
542
+ lineNumber
543
+ );
511
544
  const metadata = parsePipeMetadata(
512
545
  segments,
513
546
  aliasMap,
@@ -87,10 +87,19 @@ export function addBusinessDays(
87
87
  const days = Math.round(Math.abs(count));
88
88
  if (days === 0) return new Date(startDate);
89
89
 
90
+ // Guard: an invalid start date would produce an infinite loop because
91
+ // isWorkday() always returns false for NaN day-of-week. This happens
92
+ // mid-edit when the user is typing a partial date like `start 2026-`.
93
+ if (Number.isNaN(startDate.getTime())) return new Date(startDate);
94
+
95
+ // Guard: a workweek with no workdays (or fully blocked by holidays) would
96
+ // also loop forever. Bound the search at days * 14 calendar days, which
97
+ // covers up to 2 weeks of skipped days per business day requested.
90
98
  const current = new Date(startDate);
91
99
  let remaining = days;
100
+ let safety = days * 14 + 14;
92
101
 
93
- while (remaining > 0) {
102
+ while (remaining > 0 && safety-- > 0) {
94
103
  current.setDate(current.getDate() + direction);
95
104
  if (isWorkday(current, workweek, holidaySet)) {
96
105
  remaining--;
@@ -4,7 +4,8 @@
4
4
  * pipe-metadata parsing.
5
5
  */
6
6
 
7
- import { resolveColor } from '../colors';
7
+ import { resolveColor, resolveColorWithDiagnostic } from '../colors';
8
+ import type { DgmoError } from '../diagnostics';
8
9
  import type { PaletteColors } from '../palettes';
9
10
 
10
11
  // ── All known chart types ────────────────────────────────────
@@ -65,14 +66,22 @@ export const COLOR_SUFFIX_RE = /\(([^)]+)\)\s*$/;
65
66
  /** Extract an optional trailing color suffix from a label, resolving via palette. */
66
67
  export function extractColor(
67
68
  label: string,
68
- palette?: PaletteColors
69
+ palette?: PaletteColors,
70
+ diagnostics?: DgmoError[],
71
+ line?: number
69
72
  ): { label: string; color?: string } {
70
73
  const m = label.match(COLOR_SUFFIX_RE);
71
74
  if (!m) return { label };
72
75
  const colorName = m[1].trim();
76
+ let color: string | undefined;
77
+ if (diagnostics && line !== undefined) {
78
+ color = resolveColorWithDiagnostic(colorName, line, diagnostics, palette);
79
+ } else {
80
+ color = resolveColor(colorName, palette) ?? undefined;
81
+ }
73
82
  return {
74
83
  label: label.substring(0, m.index!).trim(),
75
- color: resolveColor(colorName, palette) ?? undefined,
84
+ color,
76
85
  };
77
86
  }
78
87
 
@@ -299,7 +308,8 @@ export function parseSeriesNames(
299
308
  value: string,
300
309
  lines: string[],
301
310
  lineIndex: number,
302
- palette?: PaletteColors
311
+ palette?: PaletteColors,
312
+ diagnostics?: DgmoError[]
303
313
  ): {
304
314
  series: string;
305
315
  names: string[];
@@ -328,8 +338,14 @@ export function parseSeriesNames(
328
338
  }
329
339
  const names: string[] = [];
330
340
  const nameColors: (string | undefined)[] = [];
331
- for (const raw of rawNames) {
332
- const extracted = extractColor(raw, palette);
341
+ for (let i = 0; i < rawNames.length; i++) {
342
+ const raw = rawNames[i];
343
+ const extracted = extractColor(
344
+ raw,
345
+ palette,
346
+ diagnostics,
347
+ nameLineNumbers[i]
348
+ );
333
349
  nameColors.push(extracted.color);
334
350
  names.push(extracted.label);
335
351
  }