@diagrammo/dgmo 0.8.19 → 0.8.21

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 (74) hide show
  1. package/dist/cli.cjs +92 -131
  2. package/dist/editor.cjs +13 -1
  3. package/dist/editor.cjs.map +1 -1
  4. package/dist/editor.js +13 -1
  5. package/dist/editor.js.map +1 -1
  6. package/dist/highlight.cjs +13 -1
  7. package/dist/highlight.cjs.map +1 -1
  8. package/dist/highlight.js +13 -1
  9. package/dist/highlight.js.map +1 -1
  10. package/dist/index.cjs +4524 -1511
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +427 -186
  13. package/dist/index.d.ts +427 -186
  14. package/dist/index.js +4526 -1503
  15. package/dist/index.js.map +1 -1
  16. package/docs/guide/chart-mindmap.md +198 -0
  17. package/docs/guide/chart-sequence.md +23 -1
  18. package/docs/guide/chart-wireframe.md +100 -0
  19. package/docs/guide/index.md +8 -0
  20. package/docs/language-reference.md +210 -2
  21. package/package.json +22 -9
  22. package/src/boxes-and-lines/collapse.ts +21 -3
  23. package/src/boxes-and-lines/layout.ts +51 -9
  24. package/src/boxes-and-lines/parser.ts +16 -4
  25. package/src/boxes-and-lines/renderer.ts +121 -23
  26. package/src/boxes-and-lines/types.ts +1 -0
  27. package/src/c4/parser.ts +8 -7
  28. package/src/class/parser.ts +6 -0
  29. package/src/cli.ts +1 -9
  30. package/src/completion.ts +26 -0
  31. package/src/d3.ts +169 -266
  32. package/src/dgmo-router.ts +103 -5
  33. package/src/diagnostics.ts +16 -6
  34. package/src/echarts.ts +43 -10
  35. package/src/editor/keywords.ts +12 -0
  36. package/src/er/parser.ts +22 -2
  37. package/src/gantt/renderer.ts +2 -2
  38. package/src/graph/flowchart-parser.ts +89 -52
  39. package/src/graph/layout.ts +73 -9
  40. package/src/graph/state-collapse.ts +78 -0
  41. package/src/graph/state-parser.ts +60 -35
  42. package/src/graph/state-renderer.ts +139 -34
  43. package/src/index.ts +41 -16
  44. package/src/infra/parser.ts +9 -2
  45. package/src/kanban/renderer.ts +305 -59
  46. package/src/mindmap/collapse.ts +88 -0
  47. package/src/mindmap/layout.ts +605 -0
  48. package/src/mindmap/parser.ts +379 -0
  49. package/src/mindmap/renderer.ts +543 -0
  50. package/src/mindmap/text-wrap.ts +207 -0
  51. package/src/mindmap/types.ts +55 -0
  52. package/src/palettes/color-utils.ts +4 -12
  53. package/src/palettes/index.ts +0 -4
  54. package/src/render.ts +31 -20
  55. package/src/sequence/parser.ts +7 -2
  56. package/src/sequence/renderer.ts +141 -21
  57. package/src/sharing.ts +2 -0
  58. package/src/sitemap/layout.ts +35 -12
  59. package/src/sitemap/renderer.ts +1 -6
  60. package/src/utils/arrows.ts +180 -11
  61. package/src/utils/d3-types.ts +4 -0
  62. package/src/utils/export-container.ts +3 -2
  63. package/src/utils/legend-constants.ts +0 -4
  64. package/src/utils/legend-d3.ts +1 -0
  65. package/src/utils/legend-layout.ts +2 -2
  66. package/src/utils/parsing.ts +2 -0
  67. package/src/utils/time-ticks.ts +213 -0
  68. package/src/wireframe/layout.ts +460 -0
  69. package/src/wireframe/parser.ts +956 -0
  70. package/src/wireframe/renderer.ts +1293 -0
  71. package/src/wireframe/types.ts +110 -0
  72. package/src/branding.ts +0 -67
  73. package/src/dgmo-mermaid.ts +0 -262
  74. package/src/palettes/mermaid-bridge.ts +0 -220
package/src/d3.ts CHANGED
@@ -4,8 +4,9 @@ import * as d3Shape from 'd3-shape';
4
4
  import * as d3Array from 'd3-array';
5
5
  import cloud from 'd3-cloud';
6
6
  import { FONT_FAMILY } from './fonts';
7
- import { injectBranding } from './branding';
8
7
  import { computeQuadrantPointLabels, type LabelRect } from './label-layout';
8
+ import { MONTH_ABBR, computeTimeTicks } from './utils/time-ticks';
9
+ import type { D3ExportDimensions } from './utils/d3-types';
9
10
 
10
11
  // ============================================================
11
12
  // Types
@@ -133,10 +134,7 @@ interface QuadrantLabels {
133
134
  }
134
135
 
135
136
  /** Optional explicit dimensions for CLI/export rendering (bypasses DOM layout). */
136
- export interface D3ExportDimensions {
137
- width?: number;
138
- height?: number;
139
- }
137
+ export type { D3ExportDimensions } from './utils/d3-types';
140
138
 
141
139
  export interface ParsedVisualization {
142
140
  type: VisualizationType | null;
@@ -331,34 +329,6 @@ export function parseTimelineDate(s: string): number {
331
329
  );
332
330
  }
333
331
 
334
- /** Convert a fractional year number back to a Date (inverse of parseTimelineDate). */
335
- function fractionalYearToDate(frac: number): Date {
336
- const year = Math.floor(frac);
337
- const remainder = frac - year;
338
- // Inverse of: (month-1)/12 + (day-1)/365 + hour/8760 + minute/525600
339
- const monthFrac = remainder * 12;
340
- const month = Math.floor(monthFrac); // 0-based
341
- const monthRemainder = remainder - month / 12;
342
- const dayFrac = monthRemainder * 365; // fractional day-of-year offset
343
- const day = Math.floor(dayFrac) + 1;
344
- const dayRemainder = dayFrac - Math.floor(dayFrac);
345
- const hourFrac = dayRemainder * 24;
346
- const hour = Math.floor(hourFrac);
347
- const minute = Math.round((hourFrac - hour) * 60);
348
- return new Date(year, month, day, hour, minute);
349
- }
350
-
351
- /** Convert a Date to a fractional year number. */
352
- function dateToFractionalYear(d: Date): number {
353
- return (
354
- d.getFullYear() +
355
- d.getMonth() / 12 +
356
- (d.getDate() - 1) / 365 +
357
- d.getHours() / 8760 +
358
- d.getMinutes() / 525600
359
- );
360
- }
361
-
362
332
  /**
363
333
  * Adds a duration to a date string and returns the resulting date string.
364
334
  * Supports: d (days), w (weeks), m (months), y (years), h (hours), min (minutes)
@@ -2885,21 +2855,6 @@ function renderMarkers(
2885
2855
  // Timeline Time Scale
2886
2856
  // ============================================================
2887
2857
 
2888
- const MONTH_ABBR = [
2889
- 'Jan',
2890
- 'Feb',
2891
- 'Mar',
2892
- 'Apr',
2893
- 'May',
2894
- 'Jun',
2895
- 'Jul',
2896
- 'Aug',
2897
- 'Sep',
2898
- 'Oct',
2899
- 'Nov',
2900
- 'Dec',
2901
- ];
2902
-
2903
2858
  /**
2904
2859
  * Converts a DSL date string (YYYY, YYYY-MM, YYYY-MM-DD, or YYYY-MM-DD HH:MM) to a human-readable label.
2905
2860
  * '1718' → '1718'
@@ -2947,173 +2902,6 @@ function formatBoundaryLabel(dateStr: string, otherDateStr: string): string {
2947
2902
  return formatDateLabel(dateStr);
2948
2903
  }
2949
2904
 
2950
- /**
2951
- * Computes adaptive tick marks for a timeline scale.
2952
- * - Multi-year spans → year ticks
2953
- * - Within ~1 year → month ticks
2954
- * - Within ~3 months → week ticks (1st, 8th, 15th, 22nd)
2955
- *
2956
- * Optional boundary parameters add ticks at exact data start/end:
2957
- * - boundaryStart/boundaryEnd: numeric date values
2958
- * - boundaryStartLabel/boundaryEndLabel: formatted labels for those dates
2959
- */
2960
- export function computeTimeTicks(
2961
- domainMin: number,
2962
- domainMax: number,
2963
- scale: d3Scale.ScaleLinear<number, number>,
2964
- boundaryStart?: number,
2965
- boundaryEnd?: number,
2966
- boundaryStartLabel?: string,
2967
- boundaryEndLabel?: string
2968
- ): { pos: number; label: string }[] {
2969
- const minYear = Math.floor(domainMin);
2970
- const maxYear = Math.floor(domainMax);
2971
- const span = domainMax - domainMin;
2972
-
2973
- let ticks: { pos: number; label: string }[] = [];
2974
-
2975
- // Year ticks for multi-year spans (need at least 2 boundaries)
2976
- const firstYear = Math.ceil(domainMin);
2977
- const lastYear = Math.floor(domainMax);
2978
- if (lastYear >= firstYear + 1) {
2979
- // Decimate ticks for long spans so labels don't overlap
2980
- const yearSpan = lastYear - firstYear;
2981
- let step = 1;
2982
- if (yearSpan > 80) step = 20;
2983
- else if (yearSpan > 40) step = 10;
2984
- else if (yearSpan > 20) step = 5;
2985
- else if (yearSpan > 10) step = 2;
2986
-
2987
- // Align to step boundary so ticks land on round years (1700, 1710, …)
2988
- const alignedFirst = Math.ceil(firstYear / step) * step;
2989
- for (let y = alignedFirst; y <= lastYear; y += step) {
2990
- ticks.push({ pos: scale(y), label: String(y) });
2991
- }
2992
- } else if (span > 0.25) {
2993
- // Month ticks for spans > ~3 months
2994
- const crossesYear = maxYear > minYear;
2995
- for (let y = minYear; y <= maxYear + 1; y++) {
2996
- for (let m = 1; m <= 12; m++) {
2997
- const val = y + (m - 1) / 12;
2998
- if (val > domainMax) break;
2999
- if (val >= domainMin) {
3000
- ticks.push({
3001
- pos: scale(val),
3002
- label: crossesYear
3003
- ? `${MONTH_ABBR[m - 1]} '${String(y).slice(-2)}`
3004
- : MONTH_ABBR[m - 1],
3005
- });
3006
- }
3007
- }
3008
- }
3009
- } else if (span <= 0.000685) {
3010
- // Minute ticks for spans ≤ ~6 hours
3011
- // Adaptive step: >3h → 30min, >1h → 15min, >30min → 10min, else 5min
3012
- let stepMin = 5;
3013
- const spanHours = span * 8760;
3014
- if (spanHours > 3) stepMin = 30;
3015
- else if (spanHours > 1) stepMin = 15;
3016
- else if (spanHours > 0.5) stepMin = 10;
3017
-
3018
- // Iterate from the start hour boundary
3019
- const startDate = fractionalYearToDate(domainMin);
3020
- // Round down to nearest step boundary
3021
- startDate.setMinutes(
3022
- Math.floor(startDate.getMinutes() / stepMin) * stepMin,
3023
- 0,
3024
- 0
3025
- );
3026
-
3027
- while (true) {
3028
- const val = dateToFractionalYear(startDate);
3029
- if (val > domainMax) break;
3030
- if (val >= domainMin) {
3031
- const hh = String(startDate.getHours()).padStart(2, '0');
3032
- const mm = String(startDate.getMinutes()).padStart(2, '0');
3033
- ticks.push({ pos: scale(val), label: `${hh}:${mm}` });
3034
- }
3035
- startDate.setMinutes(startDate.getMinutes() + stepMin);
3036
- }
3037
- } else if (span <= 0.00822) {
3038
- // Hour ticks for spans ≤ ~3 days
3039
- // Adaptive step: >2d → 6h, >1d → 3h, >12h → 2h, else 1h
3040
- let stepHour = 1;
3041
- const spanHours = span * 8760;
3042
- if (spanHours > 48) stepHour = 6;
3043
- else if (spanHours > 24) stepHour = 3;
3044
- else if (spanHours > 12) stepHour = 2;
3045
-
3046
- // For single-day spans, just show HH:MM without the date prefix
3047
- const singleDay = spanHours <= 24;
3048
-
3049
- const startDate = fractionalYearToDate(domainMin);
3050
- // Round down to nearest step boundary
3051
- startDate.setHours(
3052
- Math.floor(startDate.getHours() / stepHour) * stepHour,
3053
- 0,
3054
- 0,
3055
- 0
3056
- );
3057
-
3058
- while (true) {
3059
- const val = dateToFractionalYear(startDate);
3060
- if (val > domainMax) break;
3061
- if (val >= domainMin) {
3062
- const hh = String(startDate.getHours()).padStart(2, '0');
3063
- const mm = String(startDate.getMinutes()).padStart(2, '0');
3064
- if (singleDay) {
3065
- ticks.push({ pos: scale(val), label: `${hh}:${mm}` });
3066
- } else {
3067
- const mon = MONTH_ABBR[startDate.getMonth()];
3068
- const d = startDate.getDate();
3069
- ticks.push({ pos: scale(val), label: `${mon} ${d} ${hh}:${mm}` });
3070
- }
3071
- }
3072
- startDate.setHours(startDate.getHours() + stepHour);
3073
- }
3074
- } else {
3075
- // Week ticks for spans ≤ ~3 months (1st, 8th, 15th, 22nd of each month)
3076
- for (let y = minYear; y <= maxYear + 1; y++) {
3077
- for (let m = 1; m <= 12; m++) {
3078
- for (const d of [1, 8, 15, 22]) {
3079
- const val = y + (m - 1) / 12 + (d - 1) / 365;
3080
- if (val > domainMax) break;
3081
- if (val >= domainMin) {
3082
- ticks.push({
3083
- pos: scale(val),
3084
- label: `${MONTH_ABBR[m - 1]} ${d}`,
3085
- });
3086
- }
3087
- }
3088
- }
3089
- }
3090
- }
3091
-
3092
- // Add boundary ticks at exact data start/end if provided
3093
- // When a boundary tick collides with a standard tick, replace the standard tick
3094
- const collisionThreshold = 40; // pixels
3095
-
3096
- if (boundaryStart !== undefined && boundaryStartLabel) {
3097
- const boundaryPos = scale(boundaryStart);
3098
- // Remove any standard ticks that would collide with the start boundary
3099
- ticks = ticks.filter(
3100
- (t) => Math.abs(t.pos - boundaryPos) >= collisionThreshold
3101
- );
3102
- ticks.unshift({ pos: boundaryPos, label: boundaryStartLabel });
3103
- }
3104
-
3105
- if (boundaryEnd !== undefined && boundaryEndLabel) {
3106
- const boundaryPos = scale(boundaryEnd);
3107
- // Remove any standard ticks that would collide with the end boundary
3108
- ticks = ticks.filter(
3109
- (t) => Math.abs(t.pos - boundaryPos) >= collisionThreshold
3110
- );
3111
- ticks.push({ pos: boundaryPos, label: boundaryEndLabel });
3112
- }
3113
-
3114
- return ticks;
3115
- }
3116
-
3117
2905
  /**
3118
2906
  * Renders adaptive tick marks along the time axis.
3119
2907
  * Optional boundary parameters add ticks at exact data start/end.
@@ -6678,8 +6466,7 @@ function createExportContainer(width: number, height: number): HTMLDivElement {
6678
6466
  function finalizeSvgExport(
6679
6467
  container: HTMLDivElement,
6680
6468
  theme: string,
6681
- palette: PaletteColors,
6682
- options?: { branding?: boolean }
6469
+ palette: PaletteColors
6683
6470
  ): string {
6684
6471
  const svgEl = container.querySelector('svg');
6685
6472
  if (!svgEl) return '';
@@ -6694,10 +6481,6 @@ function finalizeSvgExport(
6694
6481
  svgEl.querySelectorAll('[data-export-ignore]').forEach((el) => el.remove());
6695
6482
  const svgHtml = svgEl.outerHTML;
6696
6483
  document.body.removeChild(container);
6697
- if (options?.branding !== false) {
6698
- const brandColor = theme === 'transparent' ? '#888' : palette.textMuted;
6699
- return injectBranding(svgHtml, brandColor);
6700
- }
6701
6484
  return svgHtml;
6702
6485
  }
6703
6486
 
@@ -6709,14 +6492,8 @@ export async function renderForExport(
6709
6492
  content: string,
6710
6493
  theme: 'light' | 'dark' | 'transparent',
6711
6494
  palette?: PaletteColors,
6712
- orgExportState?: {
6713
- collapsedNodes?: Set<string>;
6714
- activeTagGroup?: string | null;
6715
- hiddenAttributes?: Set<string>;
6716
- swimlaneTagGroup?: string | null;
6717
- },
6495
+ viewState?: import('./sharing').CompactViewState,
6718
6496
  options?: {
6719
- branding?: boolean;
6720
6497
  c4Level?: 'context' | 'containers' | 'components' | 'deployment';
6721
6498
  c4System?: string;
6722
6499
  c4Container?: string;
@@ -6739,14 +6516,14 @@ export async function renderForExport(
6739
6516
  const orgParsed = parseOrg(content, effectivePalette);
6740
6517
  if (orgParsed.error) return '';
6741
6518
 
6742
- // Apply interactive collapse state when provided
6743
- const collapsedNodes = orgExportState?.collapsedNodes;
6519
+ // Apply interactive collapse state when provided (read from unified viewState)
6520
+ const collapsedNodes = viewState?.cg ? new Set(viewState.cg) : undefined;
6744
6521
  const activeTagGroup = resolveActiveTagGroup(
6745
6522
  orgParsed.tagGroups,
6746
6523
  orgParsed.options['active-tag'],
6747
- orgExportState?.activeTagGroup ?? options?.tagGroup
6524
+ viewState?.tag ?? options?.tagGroup
6748
6525
  );
6749
- const hiddenAttributes = orgExportState?.hiddenAttributes;
6526
+ const hiddenAttributes = viewState?.ha ? new Set(viewState.ha) : undefined;
6750
6527
 
6751
6528
  const { parsed: effectiveParsed, hiddenCounts } =
6752
6529
  collapsedNodes && collapsedNodes.size > 0
@@ -6778,7 +6555,7 @@ export async function renderForExport(
6778
6555
  activeTagGroup,
6779
6556
  hiddenAttributes
6780
6557
  );
6781
- return finalizeSvgExport(container, theme, effectivePalette, options);
6558
+ return finalizeSvgExport(container, theme, effectivePalette);
6782
6559
  }
6783
6560
 
6784
6561
  if (detectedType === 'sitemap') {
@@ -6793,14 +6570,14 @@ export async function renderForExport(
6793
6570
  const sitemapParsed = parseSitemap(content, effectivePalette);
6794
6571
  if (sitemapParsed.error || sitemapParsed.roots.length === 0) return '';
6795
6572
 
6796
- // Apply interactive collapse state when provided
6797
- const collapsedNodes = orgExportState?.collapsedNodes;
6573
+ // Apply interactive collapse state when provided (read from unified viewState)
6574
+ const collapsedNodes = viewState?.cg ? new Set(viewState.cg) : undefined;
6798
6575
  const activeTagGroup = resolveActiveTagGroup(
6799
6576
  sitemapParsed.tagGroups,
6800
6577
  sitemapParsed.options['active-tag'],
6801
- orgExportState?.activeTagGroup ?? options?.tagGroup
6578
+ viewState?.tag ?? options?.tagGroup
6802
6579
  );
6803
- const hiddenAttributes = orgExportState?.hiddenAttributes;
6580
+ const hiddenAttributes = viewState?.ha ? new Set(viewState.ha) : undefined;
6804
6581
 
6805
6582
  const { parsed: effectiveParsed, hiddenCounts } =
6806
6583
  collapsedNodes && collapsedNodes.size > 0
@@ -6832,7 +6609,7 @@ export async function renderForExport(
6832
6609
  activeTagGroup,
6833
6610
  hiddenAttributes
6834
6611
  );
6835
- return finalizeSvgExport(container, theme, effectivePalette, options);
6612
+ return finalizeSvgExport(container, theme, effectivePalette);
6836
6613
  }
6837
6614
 
6838
6615
  if (detectedType === 'kanban') {
@@ -6853,10 +6630,14 @@ export async function renderForExport(
6853
6630
  activeTagGroup: resolveActiveTagGroup(
6854
6631
  kanbanParsed.tagGroups,
6855
6632
  kanbanParsed.options['active-tag'],
6856
- options?.tagGroup
6633
+ viewState?.tag ?? options?.tagGroup
6857
6634
  ),
6635
+ currentSwimlaneGroup: viewState?.swim ?? null,
6636
+ collapsedLanes: viewState?.cl ? new Set(viewState.cl) : undefined,
6637
+ collapsedColumns: viewState?.cc ? new Set(viewState.cc) : undefined,
6638
+ compactMeta: viewState?.cm,
6858
6639
  });
6859
- return finalizeSvgExport(container, theme, effectivePalette, options);
6640
+ return finalizeSvgExport(container, theme, effectivePalette);
6860
6641
  }
6861
6642
 
6862
6643
  if (detectedType === 'class') {
@@ -6884,7 +6665,7 @@ export async function renderForExport(
6884
6665
  undefined,
6885
6666
  { width: exportWidth, height: exportHeight }
6886
6667
  );
6887
- return finalizeSvgExport(container, theme, effectivePalette, options);
6668
+ return finalizeSvgExport(container, theme, effectivePalette);
6888
6669
  }
6889
6670
 
6890
6671
  if (detectedType === 'er') {
@@ -6914,22 +6695,32 @@ export async function renderForExport(
6914
6695
  resolveActiveTagGroup(
6915
6696
  erParsed.tagGroups,
6916
6697
  erParsed.options['active-tag'],
6917
- options?.tagGroup
6918
- )
6698
+ viewState?.tag ?? options?.tagGroup
6699
+ ),
6700
+ viewState?.sem
6919
6701
  );
6920
- return finalizeSvgExport(container, theme, effectivePalette, options);
6702
+ return finalizeSvgExport(container, theme, effectivePalette);
6921
6703
  }
6922
6704
 
6923
6705
  if (detectedType === 'boxes-and-lines') {
6924
6706
  const { parseBoxesAndLines } = await import('./boxes-and-lines/parser');
6925
- const { layoutBoxesAndLines } = await import('./boxes-and-lines/layout');
6926
- const { renderBoxesAndLinesForExport } =
6927
- await import('./boxes-and-lines/renderer');
6928
-
6929
6707
  const effectivePalette = await resolveExportPalette(theme, palette);
6930
6708
  const blParsed = parseBoxesAndLines(content);
6931
6709
  if (blParsed.error || blParsed.nodes.length === 0) return '';
6932
6710
 
6711
+ // Convert viewState.htv (Record<string, string[]>) to Map<string, Set<string>>
6712
+ let blHiddenTagValues: Map<string, Set<string>> | undefined;
6713
+ if (viewState?.htv) {
6714
+ blHiddenTagValues = new Map();
6715
+ for (const [k, v] of Object.entries(viewState.htv)) {
6716
+ blHiddenTagValues.set(k, new Set(v));
6717
+ }
6718
+ }
6719
+
6720
+ const { layoutBoxesAndLines } = await import('./boxes-and-lines/layout');
6721
+ const { renderBoxesAndLinesForExport } =
6722
+ await import('./boxes-and-lines/renderer');
6723
+
6933
6724
  const blLayout = layoutBoxesAndLines(blParsed);
6934
6725
  const PADDING = 20;
6935
6726
  const titleOffset = blParsed.title ? 40 : 0;
@@ -6945,10 +6736,104 @@ export async function renderForExport(
6945
6736
  theme === 'dark',
6946
6737
  {
6947
6738
  exportDims: { width: exportWidth, height: exportHeight },
6948
- activeTagGroup: options?.tagGroup,
6739
+ activeTagGroup: viewState?.tag ?? options?.tagGroup,
6740
+ hiddenTagValues: blHiddenTagValues,
6949
6741
  }
6950
6742
  );
6951
- return finalizeSvgExport(container, theme, effectivePalette, options);
6743
+ return finalizeSvgExport(container, theme, effectivePalette);
6744
+ }
6745
+
6746
+ if (detectedType === 'mindmap') {
6747
+ const { parseMindmap } = await import('./mindmap/parser');
6748
+ const { layoutMindmap } = await import('./mindmap/layout');
6749
+ const { collapseMindmapTree } = await import('./mindmap/collapse');
6750
+ const { renderMindmap } = await import('./mindmap/renderer');
6751
+
6752
+ const isDark = theme === 'dark';
6753
+ const effectivePalette = await resolveExportPalette(theme, palette);
6754
+
6755
+ const mmParsed = parseMindmap(content, effectivePalette);
6756
+ if (mmParsed.error) return '';
6757
+
6758
+ const collapsedNodes = viewState?.cg ? new Set(viewState.cg) : undefined;
6759
+ const activeTagGroup = resolveActiveTagGroup(
6760
+ mmParsed.tagGroups,
6761
+ mmParsed.options['active-tag'],
6762
+ viewState?.tag ?? options?.tagGroup
6763
+ );
6764
+ const hideDescriptions =
6765
+ mmParsed.options['hide-descriptions'] === 'true' ||
6766
+ viewState?.hd === true;
6767
+
6768
+ const { roots: effectiveRoots, hiddenCounts } =
6769
+ collapsedNodes && collapsedNodes.size > 0
6770
+ ? collapseMindmapTree(mmParsed.roots, collapsedNodes)
6771
+ : { roots: mmParsed.roots, hiddenCounts: new Map<string, number>() };
6772
+
6773
+ const effectiveParsed = { ...mmParsed, roots: effectiveRoots };
6774
+
6775
+ const mmLayout = layoutMindmap(effectiveParsed, effectivePalette, {
6776
+ interactive: false,
6777
+ hiddenCounts: hiddenCounts.size > 0 ? hiddenCounts : undefined,
6778
+ activeTagGroup,
6779
+ hideDescriptions,
6780
+ });
6781
+
6782
+ const PADDING = 20;
6783
+ const titleOffset = effectiveParsed.title ? 30 : 0;
6784
+ const exportWidth = mmLayout.width + PADDING * 2;
6785
+ const exportHeight = mmLayout.height + PADDING * 2 + titleOffset;
6786
+ const container = createExportContainer(exportWidth, exportHeight);
6787
+
6788
+ const colorByDepth = viewState?.cbd === true;
6789
+
6790
+ renderMindmap(
6791
+ container,
6792
+ effectiveParsed,
6793
+ mmLayout,
6794
+ effectivePalette,
6795
+ isDark,
6796
+ undefined,
6797
+ { width: exportWidth, height: exportHeight },
6798
+ undefined,
6799
+ hideDescriptions,
6800
+ colorByDepth ? null : activeTagGroup,
6801
+ colorByDepth ? { colorByDepth: true } : undefined
6802
+ );
6803
+ return finalizeSvgExport(container, theme, effectivePalette);
6804
+ }
6805
+
6806
+ if (detectedType === 'wireframe') {
6807
+ const { parseWireframe } = await import('./wireframe/parser');
6808
+ const { layoutWireframe } = await import('./wireframe/layout');
6809
+ const { renderWireframe } = await import('./wireframe/renderer');
6810
+
6811
+ const effectivePalette = await resolveExportPalette(theme, palette);
6812
+ const wireframeParsed = parseWireframe(content);
6813
+ if (
6814
+ wireframeParsed.error ||
6815
+ (wireframeParsed.roots.length === 0 &&
6816
+ wireframeParsed.modals.length === 0)
6817
+ )
6818
+ return '';
6819
+
6820
+ const wireframeLayout = layoutWireframe(wireframeParsed);
6821
+
6822
+ const exportWidth = wireframeLayout.width;
6823
+ const exportHeight = wireframeLayout.height;
6824
+ const container = createExportContainer(exportWidth, exportHeight);
6825
+
6826
+ renderWireframe(
6827
+ container,
6828
+ wireframeParsed,
6829
+ wireframeLayout,
6830
+ effectivePalette,
6831
+ theme === 'dark',
6832
+ undefined,
6833
+ { width: exportWidth, height: exportHeight },
6834
+ theme
6835
+ );
6836
+ return finalizeSvgExport(container, theme, effectivePalette);
6952
6837
  }
6953
6838
 
6954
6839
  if (detectedType === 'c4') {
@@ -6966,10 +6851,18 @@ export async function renderForExport(
6966
6851
  const c4Parsed = parseC4(content, effectivePalette);
6967
6852
  if (c4Parsed.error || c4Parsed.elements.length === 0) return '';
6968
6853
 
6969
- // Container/component-level rendering
6970
- const c4Level = options?.c4Level ?? 'context';
6971
- const c4System = options?.c4System;
6972
- const c4Container = options?.c4Container;
6854
+ // Container/component-level rendering (viewState fallback for share links)
6855
+ const c4Level =
6856
+ options?.c4Level ??
6857
+ (viewState?.c4l as
6858
+ | 'context'
6859
+ | 'containers'
6860
+ | 'components'
6861
+ | 'deployment'
6862
+ | undefined) ??
6863
+ 'context';
6864
+ const c4System = options?.c4System ?? viewState?.c4s;
6865
+ const c4Container = options?.c4Container ?? viewState?.c4c;
6973
6866
 
6974
6867
  const c4Layout =
6975
6868
  c4Level === 'deployment'
@@ -7006,10 +6899,10 @@ export async function renderForExport(
7006
6899
  resolveActiveTagGroup(
7007
6900
  c4Parsed.tagGroups,
7008
6901
  c4Parsed.options['active-tag'],
7009
- options?.tagGroup
6902
+ viewState?.tag ?? options?.tagGroup
7010
6903
  )
7011
6904
  );
7012
- return finalizeSvgExport(container, theme, effectivePalette, options);
6905
+ return finalizeSvgExport(container, theme, effectivePalette);
7013
6906
  }
7014
6907
 
7015
6908
  if (detectedType === 'flowchart') {
@@ -7033,7 +6926,7 @@ export async function renderForExport(
7033
6926
  undefined,
7034
6927
  { width: EXPORT_WIDTH, height: EXPORT_HEIGHT }
7035
6928
  );
7036
- return finalizeSvgExport(container, theme, effectivePalette, options);
6929
+ return finalizeSvgExport(container, theme, effectivePalette);
7037
6930
  }
7038
6931
 
7039
6932
  if (detectedType === 'infra') {
@@ -7052,7 +6945,7 @@ export async function renderForExport(
7052
6945
  const activeTagGroup = resolveActiveTagGroup(
7053
6946
  infraParsed.tagGroups,
7054
6947
  infraParsed.options['active-tag'],
7055
- options?.tagGroup
6948
+ viewState?.tag ?? options?.tagGroup
7056
6949
  );
7057
6950
 
7058
6951
  const titleOffset = infraParsed.title ? 40 : 0;
@@ -7078,7 +6971,8 @@ export async function renderForExport(
7078
6971
  false,
7079
6972
  null,
7080
6973
  null,
7081
- true
6974
+ true,
6975
+ viewState?.cg ? new Set(viewState.cg) : null
7082
6976
  );
7083
6977
  // Restore explicit pixel dimensions for resvg (renderer uses 100%/viewBox for app scaling)
7084
6978
  const infraSvg = container.querySelector('svg');
@@ -7086,7 +6980,7 @@ export async function renderForExport(
7086
6980
  infraSvg.setAttribute('width', String(exportWidth));
7087
6981
  infraSvg.setAttribute('height', String(exportHeight));
7088
6982
  }
7089
- return finalizeSvgExport(container, theme, effectivePalette, options);
6983
+ return finalizeSvgExport(container, theme, effectivePalette);
7090
6984
  }
7091
6985
 
7092
6986
  if (detectedType === 'gantt') {
@@ -7108,10 +7002,19 @@ export async function renderForExport(
7108
7002
  resolved,
7109
7003
  effectivePalette,
7110
7004
  theme === 'dark',
7111
- undefined,
7005
+ {
7006
+ collapsedGroups: viewState?.cg ? new Set(viewState.cg) : undefined,
7007
+ currentSwimlaneGroup: viewState?.swim ?? undefined,
7008
+ collapsedLanes: viewState?.cl ? new Set(viewState.cl) : undefined,
7009
+ currentActiveGroup: resolveActiveTagGroup(
7010
+ resolved.tagGroups,
7011
+ resolved.options.activeTag ?? undefined,
7012
+ viewState?.tag ?? options?.tagGroup
7013
+ ),
7014
+ },
7112
7015
  { width: EXPORT_W, height: EXPORT_H }
7113
7016
  );
7114
- return finalizeSvgExport(container, theme, effectivePalette, options);
7017
+ return finalizeSvgExport(container, theme, effectivePalette);
7115
7018
  }
7116
7019
 
7117
7020
  if (detectedType === 'state') {
@@ -7135,7 +7038,7 @@ export async function renderForExport(
7135
7038
  undefined,
7136
7039
  { width: EXPORT_WIDTH, height: EXPORT_HEIGHT }
7137
7040
  );
7138
- return finalizeSvgExport(container, theme, effectivePalette, options);
7041
+ return finalizeSvgExport(container, theme, effectivePalette);
7139
7042
  }
7140
7043
 
7141
7044
  const parsed = parseVisualization(content, palette);
@@ -7209,9 +7112,9 @@ export async function renderForExport(
7209
7112
  resolveActiveTagGroup(
7210
7113
  parsed.timelineTagGroups,
7211
7114
  undefined,
7212
- orgExportState?.activeTagGroup ?? options?.tagGroup
7115
+ viewState?.tag ?? options?.tagGroup
7213
7116
  ),
7214
- orgExportState?.swimlaneTagGroup
7117
+ viewState?.swim
7215
7118
  );
7216
7119
  } else if (parsed.type === 'venn') {
7217
7120
  renderVenn(container, parsed, effectivePalette, isDark, undefined, dims);
@@ -7235,5 +7138,5 @@ export async function renderForExport(
7235
7138
  );
7236
7139
  }
7237
7140
 
7238
- return finalizeSvgExport(container, theme, effectivePalette, options);
7141
+ return finalizeSvgExport(container, theme, effectivePalette);
7239
7142
  }