@zenuml/core 3.46.0 → 3.46.2

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 (60) hide show
  1. package/.claude/skills/dia-scoring/SKILL.md +139 -0
  2. package/.claude/skills/dia-scoring/agents/openai.yaml +7 -0
  3. package/.claude/skills/dia-scoring/references/selectors-and-keys.md +253 -0
  4. package/.claude/skills/land-pr/SKILL.md +98 -0
  5. package/.claude/skills/ship-branch/SKILL.md +81 -0
  6. package/.claude/skills/submit-branch/SKILL.md +76 -0
  7. package/.claude/skills/validate-branch/SKILL.md +54 -0
  8. package/CLAUDE.md +1 -1
  9. package/bun.lock +25 -11
  10. package/cy/canonical-history.html +908 -0
  11. package/cy/compare-case.html +357 -0
  12. package/cy/compare-cases.js +824 -0
  13. package/cy/compare.html +35 -0
  14. package/cy/diff-algorithm.js +199 -0
  15. package/cy/element-report.html +705 -0
  16. package/cy/icons-test.html +29 -0
  17. package/cy/legacy-vs-html.html +291 -0
  18. package/cy/native-diff-ext/background.js +60 -0
  19. package/cy/native-diff-ext/bridge.js +26 -0
  20. package/cy/native-diff-ext/content.js +194 -0
  21. package/cy/parity-test.html +122 -0
  22. package/cy/return-in-nested-if.html +29 -0
  23. package/cy/svg-preview.html +56 -0
  24. package/cy/svg-test.html +21 -0
  25. package/cy/theme-default-test.html +28 -0
  26. package/dist/stats.html +1 -1
  27. package/dist/zenuml.esm.mjs +16352 -15223
  28. package/dist/zenuml.js +701 -575
  29. package/docs/ship-branch-skill-plan.md +134 -0
  30. package/docs/superpowers/plans/2026-03-23-svg-parity-features.md +283 -0
  31. package/index.html +568 -73
  32. package/package.json +15 -4
  33. package/scripts/analyze-compare-case/collect-data.mjs +991 -0
  34. package/scripts/analyze-compare-case/config.mjs +102 -0
  35. package/scripts/analyze-compare-case/geometry.mjs +101 -0
  36. package/scripts/analyze-compare-case/native-diff.mjs +224 -0
  37. package/scripts/analyze-compare-case/output.mjs +74 -0
  38. package/scripts/analyze-compare-case/panel-diff.mjs +114 -0
  39. package/scripts/analyze-compare-case/report.mjs +157 -0
  40. package/scripts/analyze-compare-case/residual-scopes.mjs +325 -0
  41. package/scripts/analyze-compare-case/scoring.mjs +816 -0
  42. package/scripts/analyze-compare-case.mjs +149 -0
  43. package/scripts/snapshot-dual.js +34 -34
  44. package/skills/dia-scoring/SKILL.md +129 -0
  45. package/skills/dia-scoring/agents/openai.yaml +7 -0
  46. package/skills/dia-scoring/references/selectors-and-keys.md +253 -0
  47. package/test-setup.ts +8 -0
  48. package/types/index.d.ts +56 -0
  49. package/vite.config.ts +4 -0
  50. package/dist/10029-icon-service-Function-Apps-ObflOLuF.js +0 -5
  51. package/dist/Res_AWS-Identity-Access-Management_IAM-Access-Analyzer_48-BPq60XMY.js +0 -11
  52. package/dist/Res_AWS-Lambda_Lambda-Function_48-Co38UB_2.js +0 -12
  53. package/dist/Res_Amazon-EC2_Instance_48-CRaqbNUl.js +0 -12
  54. package/dist/Res_Amazon-Simple-Notification-Service_Topic_48-q13mxUeM.js +0 -11
  55. package/dist/Res_Amazon-Simple-Queue-Service_Queue_48-D2-8gbFw.js +0 -11
  56. package/dist/Robustness_Diagram_Boundary-nYnmTPs8.js +0 -10
  57. package/dist/Robustness_Diagram_Control-DLNLoMxd.js +0 -11
  58. package/dist/Robustness_Diagram_Entity-Be3kcbIE.js +0 -11
  59. package/dist/actor-BMj_HFpo.js +0 -11
  60. package/dist/database-BKHQQWQK.js +0 -8
package/index.html CHANGED
@@ -26,21 +26,269 @@
26
26
  rel="stylesheet"
27
27
  href="/vendor/codemirror/material-darker.min.css"
28
28
  />
29
- <title>ZenUML - Local Development</title>
29
+ <title>ZenUML Dev Workbench</title>
30
30
  <style>
31
+ :root {
32
+ --bg: #0f172a;
33
+ --bg-alt: #111827;
34
+ --panel: #ffffff;
35
+ --panel-alt: #f8fafc;
36
+ --panel-muted: #eef2f7;
37
+ --ink: #0f172a;
38
+ --muted: #526072;
39
+ --line: #d7deea;
40
+ --line-strong: #c4cedd;
41
+ --accent: #0f766e;
42
+ --accent-strong: #115e59;
43
+ --danger: #991b1b;
44
+ --shadow: 0 18px 48px rgba(15, 23, 42, 0.14);
45
+ --radius-lg: 14px;
46
+ --radius-md: 10px;
47
+ --editor-width: 420px;
48
+ }
49
+
31
50
  * {
51
+ box-sizing: border-box;
52
+ }
53
+
54
+ html,
55
+ body {
56
+ margin: 0;
57
+ min-height: 100%;
58
+ }
59
+
60
+ body {
32
61
  font-family:
33
- "Inter",
34
- -apple-system,
35
- BlinkMacSystemFont,
62
+ "SF Pro Text",
36
63
  "Segoe UI",
37
- "Roboto",
64
+ system-ui,
38
65
  sans-serif;
66
+ color: var(--ink);
67
+ background: linear-gradient(180deg, var(--bg) 0%, var(--bg-alt) 100%);
68
+ }
69
+
70
+ a {
71
+ color: inherit;
39
72
  }
40
73
 
41
74
  code,
42
- .CodeMirror {
43
- font-family: "JetBrains Mono", "Monaco", "Consolas", monospace;
75
+ .CodeMirror,
76
+ .tool-chip,
77
+ .tool-button,
78
+ .tool-link,
79
+ .stats-text,
80
+ .toolbar-label {
81
+ font-family:
82
+ "IBM Plex Mono",
83
+ "SFMono-Regular",
84
+ "Menlo",
85
+ "Monaco",
86
+ "Consolas",
87
+ monospace;
88
+ }
89
+
90
+ .app-shell {
91
+ min-height: 100vh;
92
+ display: grid;
93
+ grid-template-rows: auto 1fr;
94
+ gap: 12px;
95
+ padding: 14px;
96
+ }
97
+
98
+ .toolbar {
99
+ display: grid;
100
+ grid-template-columns: minmax(0, 1fr) auto;
101
+ align-items: center;
102
+ gap: 16px;
103
+ padding: 12px 14px;
104
+ border: 1px solid rgba(255, 255, 255, 0.08);
105
+ border-radius: var(--radius-lg);
106
+ background: rgba(15, 23, 42, 0.9);
107
+ color: #e5edf8;
108
+ box-shadow: var(--shadow);
109
+ }
110
+
111
+ .toolbar-title {
112
+ display: flex;
113
+ flex-wrap: wrap;
114
+ align-items: baseline;
115
+ gap: 12px;
116
+ }
117
+
118
+ .toolbar-title strong {
119
+ font-size: 15px;
120
+ font-weight: 700;
121
+ letter-spacing: 0.01em;
122
+ }
123
+
124
+ .toolbar-title span {
125
+ color: #9fb1c7;
126
+ font-size: 12px;
127
+ }
128
+
129
+ .toolbar-actions {
130
+ display: flex;
131
+ align-items: center;
132
+ justify-content: flex-end;
133
+ flex-wrap: wrap;
134
+ gap: 8px;
135
+ }
136
+
137
+ .toolbar-label {
138
+ color: #9fb1c7;
139
+ font-size: 11px;
140
+ letter-spacing: 0.08em;
141
+ text-transform: uppercase;
142
+ }
143
+
144
+ .workspace {
145
+ min-height: 0;
146
+ display: grid;
147
+ grid-template-columns: minmax(280px, var(--editor-width)) 10px minmax(320px, 1fr) minmax(320px, 1fr);
148
+ gap: 12px;
149
+ align-items: stretch;
150
+ }
151
+
152
+ .panel {
153
+ display: flex;
154
+ flex-direction: column;
155
+ min-height: 0;
156
+ min-width: 0;
157
+ overflow: hidden;
158
+ border-radius: var(--radius-lg);
159
+ border: 1px solid var(--line);
160
+ background: var(--panel);
161
+ box-shadow: var(--shadow);
162
+ }
163
+
164
+ .workspace-resizer {
165
+ position: relative;
166
+ min-height: 0;
167
+ border-radius: 999px;
168
+ background: rgba(148, 163, 184, 0.28);
169
+ cursor: col-resize;
170
+ touch-action: none;
171
+ }
172
+
173
+ .workspace-resizer::before {
174
+ content: "";
175
+ position: absolute;
176
+ inset: 0;
177
+ margin: auto;
178
+ width: 4px;
179
+ height: 56px;
180
+ border-radius: 999px;
181
+ background: rgba(15, 118, 110, 0.22);
182
+ }
183
+
184
+ .workspace-resizer:hover,
185
+ .workspace.is-resizing .workspace-resizer {
186
+ background: rgba(15, 118, 110, 0.2);
187
+ }
188
+
189
+ .workspace.is-resizing {
190
+ user-select: none;
191
+ }
192
+
193
+ .panel-header {
194
+ display: flex;
195
+ align-items: center;
196
+ justify-content: space-between;
197
+ gap: 14px;
198
+ padding: 12px 14px;
199
+ border-bottom: 1px solid var(--line);
200
+ background: var(--panel-alt);
201
+ }
202
+
203
+ .panel-title {
204
+ display: flex;
205
+ flex-direction: column;
206
+ gap: 4px;
207
+ }
208
+
209
+ .panel-title h2,
210
+ .panel-title h3 {
211
+ margin: 0;
212
+ font-size: 14px;
213
+ letter-spacing: 0.01em;
214
+ }
215
+
216
+ .panel-title p {
217
+ margin: 0;
218
+ color: var(--muted);
219
+ font-size: 12px;
220
+ line-height: 1.3;
221
+ }
222
+
223
+ .panel-actions {
224
+ display: flex;
225
+ align-items: center;
226
+ flex-wrap: wrap;
227
+ justify-content: flex-end;
228
+ gap: 8px;
229
+ }
230
+
231
+ .tool-button,
232
+ .tool-chip,
233
+ .tool-link {
234
+ display: inline-flex;
235
+ align-items: center;
236
+ justify-content: center;
237
+ appearance: none;
238
+ border: 1px solid var(--line);
239
+ border-radius: 8px;
240
+ padding: 8px 10px;
241
+ background: #fff;
242
+ color: var(--ink);
243
+ cursor: pointer;
244
+ text-decoration: none;
245
+ transition:
246
+ border-color 160ms ease,
247
+ background 160ms ease;
248
+ font-size: 11px;
249
+ letter-spacing: 0.03em;
250
+ white-space: nowrap;
251
+ }
252
+
253
+ .tool-button:hover,
254
+ .tool-chip:hover,
255
+ .tool-link:hover {
256
+ border-color: var(--line-strong);
257
+ background: var(--panel-muted);
258
+ }
259
+
260
+ .toolbar .tool-chip,
261
+ .toolbar .tool-link {
262
+ background: rgba(15, 23, 42, 0.55);
263
+ border-color: rgba(159, 177, 199, 0.25);
264
+ color: #e5edf8;
265
+ }
266
+
267
+ .toolbar .tool-chip:hover,
268
+ .toolbar .tool-link:hover {
269
+ background: rgba(30, 41, 59, 0.95);
270
+ border-color: rgba(191, 209, 230, 0.4);
271
+ }
272
+
273
+ .tool-chip.is-active {
274
+ background: var(--accent);
275
+ border-color: var(--accent-strong);
276
+ color: #f8fffe;
277
+ }
278
+
279
+ .tool-button.primary {
280
+ background: var(--accent);
281
+ border-color: var(--accent-strong);
282
+ color: #f8fffe;
283
+ }
284
+
285
+ .panel-body {
286
+ flex: 1;
287
+ min-height: 0;
288
+ }
289
+
290
+ .editor-body {
291
+ background: #1d1d1b;
44
292
  }
45
293
 
46
294
  .CodeMirror {
@@ -58,83 +306,207 @@
58
306
  border-left-width: 2px;
59
307
  }
60
308
 
61
- .gradient-text {
62
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
63
- -webkit-background-clip: text;
64
- -webkit-text-fill-color: transparent;
65
- background-clip: text;
309
+ .preview-surface {
310
+ height: 100%;
311
+ overflow: auto;
312
+ padding: 12px;
313
+ background: var(--panel-muted);
314
+ }
315
+
316
+ .html-surface {
317
+ position: relative;
66
318
  }
67
319
 
68
- .editor-shadow {
69
- box-shadow:
70
- 0 20px 25px -5px rgba(0, 0, 0, 0.1),
71
- 0 10px 10px -5px rgba(0, 0, 0, 0.04);
320
+ .preview-stage {
321
+ min-height: 100%;
322
+ padding: 10px;
323
+ border-radius: var(--radius-md);
324
+ border: 1px solid var(--line);
325
+ background: #fff;
326
+ }
327
+
328
+ .preview-stage pre {
329
+ margin: 0;
330
+ }
331
+
332
+ .preview-stage svg {
333
+ display: block;
334
+ }
335
+
336
+ .svg-stage {
337
+ display: block;
338
+ }
339
+
340
+ #svg-container {
341
+ width: max-content;
342
+ margin: 0 auto;
343
+ min-width: 100%;
344
+ min-height: 100%;
345
+ display: grid;
346
+ place-items: center;
347
+ }
348
+
349
+ #svg-container svg {
350
+ display: block;
351
+ box-shadow: 0 10px 28px rgba(15, 23, 42, 0.08);
352
+ }
353
+
354
+ .stats-text {
355
+ color: #9fb1c7;
356
+ font-size: 11px;
357
+ }
358
+
359
+ .svg-error {
360
+ display: grid;
361
+ place-items: center;
362
+ min-height: 240px;
363
+ padding: 20px;
364
+ border-radius: var(--radius-md);
365
+ border: 1px dashed #d4b4b4;
366
+ color: var(--danger);
367
+ background: #fff7f7;
368
+ text-align: center;
369
+ line-height: 1.6;
370
+ }
371
+
372
+ .workspace.mode-svg,
373
+ .workspace.mode-dom {
374
+ grid-template-columns: minmax(280px, var(--editor-width)) 10px minmax(720px, 1fr);
375
+ }
376
+
377
+ .workspace.mode-svg .html-panel,
378
+ .workspace.mode-dom .svg-panel {
379
+ display: none;
380
+ }
381
+
382
+ @media (max-width: 1440px) {
383
+ .workspace,
384
+ .workspace.mode-svg,
385
+ .workspace.mode-dom {
386
+ grid-template-columns: minmax(280px, var(--editor-width)) 10px minmax(320px, 1fr);
387
+ }
388
+
389
+ .workspace.mode-split .svg-panel {
390
+ grid-column: 3;
391
+ }
392
+ }
393
+
394
+ @media (max-width: 1024px) {
395
+ .app-shell {
396
+ padding: 10px;
397
+ }
398
+
399
+ .toolbar {
400
+ grid-template-columns: 1fr;
401
+ align-items: start;
402
+ }
403
+
404
+ .workspace,
405
+ .workspace.mode-svg,
406
+ .workspace.mode-dom {
407
+ grid-template-columns: 1fr;
408
+ }
409
+
410
+ .workspace-resizer {
411
+ display: none;
412
+ }
413
+
414
+ .workspace.mode-split .svg-panel {
415
+ grid-column: auto;
416
+ }
417
+
418
+ .panel {
419
+ min-height: 360px;
420
+ }
72
421
  }
73
422
  </style>
74
423
  <script src="/vendor/tailwindcss/tailwindcss.js"></script>
75
424
  </head>
76
- <body class="bg-gray-50">
77
- <!-- Main Editor Section -->
78
- <div class="w-full h-screen p-4">
79
- <div class="grid grid-cols-3 gap-4 h-full" id="diagram1">
80
- <!-- Editor Panel -->
81
- <div
82
- class="bg-white rounded-lg shadow-lg editor-shadow overflow-hidden h-full"
83
- >
425
+ <body>
426
+ <div class="app-shell">
427
+ <header class="toolbar">
428
+ <div class="toolbar-title">
429
+ <strong>ZenUML Dev Workbench</strong>
430
+ <span>default to native SVG, switch to DOM or split only when needed</span>
431
+ </div>
432
+ <div class="toolbar-actions">
433
+ <span class="toolbar-label">View</span>
434
+ <button id="view-svg" class="tool-chip">SVG</button>
435
+ <button id="view-dom" class="tool-chip">DOM</button>
436
+ <button id="view-split" class="tool-chip">Split</button>
437
+ <button id="width-provider-toggle" class="tool-chip"></button>
438
+ <span id="svg-stats" class="stats-text">SVG: waiting</span>
439
+ <a class="tool-link" href="/cy/compare.html" target="_blank" rel="noreferrer">
440
+ Compare Cases
441
+ </a>
442
+ <a class="tool-link" href="/cy/svg-preview.html" target="_blank" rel="noreferrer">
443
+ SVG Only
444
+ </a>
445
+ </div>
446
+ </header>
447
+
448
+ <main class="workspace mode-svg" id="workspace">
449
+ <article class="panel editor-panel">
450
+ <div class="panel-header">
451
+ <div class="panel-title">
452
+ <h2>Editor</h2>
453
+ <p>Persisted in <code>localStorage</code>.</p>
454
+ </div>
455
+ <div class="panel-actions">
456
+ <button onclick="loadExample('basic')" class="tool-button">Basic</button>
457
+ <button onclick="loadExample('advanced')" class="tool-button">Advanced</button>
458
+ <button onclick="clearEditor()" class="tool-button">Clear</button>
459
+ <button onclick="exportDiagram()" class="tool-button primary">Export PNG</button>
460
+ </div>
461
+ </div>
462
+ <div class="panel-body editor-body">
463
+ <textarea id="text" style="display: none"></textarea>
464
+ </div>
465
+ </article>
466
+
84
467
  <div
85
- class="bg-gray-800 text-white px-4 py-3 flex items-center justify-between"
86
- >
87
- <h3 class="font-medium">Editor</h3>
88
- <div class="flex space-x-2">
89
- <button
90
- onclick="loadExample('basic')"
91
- class="text-sm bg-gray-700 hover:bg-gray-600 px-3 py-1 rounded transition-colors"
92
- >
93
- Basic
94
- </button>
95
- <button
96
- onclick="loadExample('advanced')"
97
- class="text-sm bg-gray-700 hover:bg-gray-600 px-3 py-1 rounded transition-colors"
98
- >
99
- Advanced
100
- </button>
101
- <button
102
- onclick="clearEditor()"
103
- class="text-sm bg-gray-700 hover:bg-gray-600 px-3 py-1 rounded transition-colors"
104
- >
105
- Clear
106
- </button>
107
- <button
108
- onclick="exportDiagram()"
109
- class="text-sm bg-blue-600 hover:bg-blue-700 px-3 py-1 rounded transition-colors"
110
- >
111
- Export PNG
112
- </button>
468
+ id="editor-resizer"
469
+ class="workspace-resizer"
470
+ role="separator"
471
+ aria-orientation="vertical"
472
+ aria-label="Resize editor"
473
+ ></div>
474
+
475
+ <article class="panel html-panel">
476
+ <div class="panel-header">
477
+ <div class="panel-title">
478
+ <h3>DOM preview</h3>
479
+ <p><code>window.zenUml.render()</code> output.</p>
480
+ </div>
113
481
  </div>
114
- </div>
115
- <div style="height: calc(100% - 52px)">
116
- <textarea id="text" style="display: none"></textarea>
117
- </div>
118
- </div>
482
+ <div class="panel-body preview-surface html-surface">
483
+ <div class="preview-stage">
484
+ <pre class="zenuml"></pre>
485
+ </div>
486
+ </div>
487
+ </article>
119
488
 
120
- <!-- Preview Panel -->
121
- <div
122
- class="bg-white rounded-lg shadow-lg editor-shadow overflow-hidden h-full col-span-2"
123
- >
124
- <div class="bg-gray-800 text-white px-4 py-3">
125
- <h3 class="font-medium">Preview</h3>
126
- </div>
127
- <div style="height: calc(100% - 52px); overflow: auto" class="p-4">
128
- <pre class="zenuml" style="margin: 0"></pre>
129
- </div>
130
- </div>
131
- </div>
489
+ <article class="panel svg-panel">
490
+ <div class="panel-header">
491
+ <div class="panel-title">
492
+ <h3>Native SVG preview</h3>
493
+ <p><code>renderToSvg()</code> output.</p>
494
+ </div>
495
+ </div>
496
+ <div class="panel-body preview-surface svg-surface">
497
+ <div class="preview-stage svg-stage">
498
+ <div id="svg-container"></div>
499
+ </div>
500
+ </div>
501
+ </article>
502
+ </main>
132
503
  </div>
133
504
 
134
505
  <script type="module">
135
506
  import { waitUntil, debounce } from "./src/utils.ts";
136
507
  import { createConfig } from "./src/config.ts";
137
508
  import { toPng } from "html-to-image";
509
+ import { renderToSvg } from "./src/svg/renderToSvg.ts";
138
510
 
139
511
  const editor = CodeMirror.fromTextArea(document.getElementById("text"), {
140
512
  lineNumbers: true,
@@ -144,7 +516,41 @@
144
516
  autofocus: true,
145
517
  });
146
518
 
147
- const updateDiagram = debounce((content) => {
519
+ const svgContainer = document.getElementById("svg-container");
520
+ const svgStats = document.getElementById("svg-stats");
521
+ const workspace = document.getElementById("workspace");
522
+ const editorResizer = document.getElementById("editor-resizer");
523
+ const viewButtons = {
524
+ svg: document.getElementById("view-svg"),
525
+ dom: document.getElementById("view-dom"),
526
+ split: document.getElementById("view-split"),
527
+ };
528
+ const minEditorWidth = 280;
529
+
530
+ function clampEditorWidth(nextWidth) {
531
+ const maxEditorWidth = Math.max(minEditorWidth, Math.floor(window.innerWidth * 0.72));
532
+ return Math.min(Math.max(nextWidth, minEditorWidth), maxEditorWidth);
533
+ }
534
+
535
+ function setEditorWidth(nextWidth) {
536
+ const clampedWidth = clampEditorWidth(nextWidth);
537
+ document.documentElement.style.setProperty("--editor-width", `${clampedWidth}px`);
538
+ localStorage.setItem("zenuml-workbench-editor-width", String(clampedWidth));
539
+ }
540
+
541
+ function setViewMode(mode) {
542
+ const validMode = ["svg", "dom", "split"].includes(mode) ? mode : "svg";
543
+ workspace.classList.remove("mode-svg", "mode-dom", "mode-split");
544
+ workspace.classList.add(`mode-${validMode}`);
545
+
546
+ Object.entries(viewButtons).forEach(([key, button]) => {
547
+ button.classList.toggle("is-active", key === validMode);
548
+ });
549
+
550
+ localStorage.setItem("zenuml-workbench-view", validMode);
551
+ }
552
+
553
+ const updateHtmlPreview = debounce((content) => {
148
554
  const config = createConfig({
149
555
  onContentChange: (code) => editor.setValue(code),
150
556
  });
@@ -154,15 +560,83 @@
154
560
  .child({ name: "index.html" })
155
561
  .debug("render resolved", r);
156
562
  });
157
- }, 500);
563
+ }, 400);
564
+
565
+ const updateSvgPreview = debounce((content) => {
566
+ try {
567
+ const t0 = performance.now();
568
+ const result = renderToSvg(content);
569
+ const dt = (performance.now() - t0).toFixed(1);
570
+
571
+ if (!result.svg || result.width === 0 || result.height === 0) {
572
+ svgContainer.innerHTML = `
573
+ <div class="svg-error">
574
+ Native SVG output is empty for the current input.
575
+ </div>
576
+ `;
577
+ svgStats.textContent = "SVG: empty";
578
+ return;
579
+ }
580
+
581
+ svgContainer.innerHTML = result.svg;
582
+ svgStats.textContent = `SVG ${result.width}x${result.height} | ${dt}ms`;
583
+ } catch (error) {
584
+ console.error("Failed to render native SVG preview:", error);
585
+ svgContainer.innerHTML = `
586
+ <div class="svg-error">
587
+ Native SVG preview failed. Check the console for details.
588
+ </div>
589
+ `;
590
+ svgStats.textContent = "SVG: error";
591
+ }
592
+ }, 180);
593
+
594
+ Object.entries(viewButtons).forEach(([mode, button]) => {
595
+ button.addEventListener("click", () => setViewMode(mode));
596
+ });
597
+
598
+ editorResizer.addEventListener("pointerdown", (event) => {
599
+ if (window.innerWidth <= 1024) {
600
+ return;
601
+ }
602
+
603
+ event.preventDefault();
604
+ workspace.classList.add("is-resizing");
605
+ editorResizer.setPointerCapture(event.pointerId);
606
+
607
+ const onPointerMove = (moveEvent) => {
608
+ setEditorWidth(moveEvent.clientX - workspace.getBoundingClientRect().left);
609
+ };
610
+
611
+ const stopResize = () => {
612
+ workspace.classList.remove("is-resizing");
613
+ editorResizer.removeEventListener("pointermove", onPointerMove);
614
+ editorResizer.removeEventListener("pointerup", stopResize);
615
+ editorResizer.removeEventListener("pointercancel", stopResize);
616
+ };
617
+
618
+ editorResizer.addEventListener("pointermove", onPointerMove);
619
+ editorResizer.addEventListener("pointerup", stopResize);
620
+ editorResizer.addEventListener("pointercancel", stopResize);
621
+ });
622
+
623
+ window.addEventListener("resize", () => {
624
+ const savedWidth = Number(localStorage.getItem("zenuml-workbench-editor-width"));
625
+ if (savedWidth) {
626
+ setEditorWidth(savedWidth);
627
+ }
628
+ });
158
629
 
159
630
  editor.on("change", function (cm) {
631
+ const content = cm.getValue();
632
+ updateSvgPreview(content);
633
+
160
634
  waitUntil(
161
635
  () => window.zenUml,
162
636
  () => {
163
- updateDiagram(cm.getValue());
637
+ updateHtmlPreview(content);
164
638
  // Save to localStorage
165
- localStorage.setItem("zenuml-cm-code", cm.getValue());
639
+ localStorage.setItem("zenuml-cm-code", content);
166
640
  },
167
641
  );
168
642
  });
@@ -226,6 +700,27 @@ WebApp --> Customer: Order confirmation`,
226
700
  } else {
227
701
  editor.setValue(examples.basic);
228
702
  }
703
+
704
+ const savedEditorWidth = Number(localStorage.getItem("zenuml-workbench-editor-width"));
705
+ if (savedEditorWidth) {
706
+ setEditorWidth(savedEditorWidth);
707
+ }
708
+
709
+ setViewMode(localStorage.getItem("zenuml-workbench-view") || "svg");
710
+ </script>
711
+ <script type="module">
712
+ const params = new URLSearchParams(location.search);
713
+ const wp = params.get("WIDTH_PROVIDER") || import.meta.env.VITE_WIDTH_PROVIDER || "browser";
714
+ const toggle = document.getElementById("width-provider-toggle");
715
+ const isCanvas = wp === "canvas";
716
+ toggle.textContent = "WIDTH: " + wp;
717
+ toggle.classList.toggle("is-active", isCanvas);
718
+ toggle.title = "Click to switch to " + (isCanvas ? "browser" : "canvas");
719
+ toggle.addEventListener("click", () => {
720
+ const next = isCanvas ? "browser" : "canvas";
721
+ params.set("WIDTH_PROVIDER", next);
722
+ location.search = params.toString();
723
+ });
229
724
  </script>
230
725
  <script type="module" src="/src/main.tsx"></script>
231
726
  </body>