@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
@@ -0,0 +1,35 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>SVG vs HTML Comparison</title>
6
+ <style>
7
+ body { margin: 0; font-family: sans-serif; background: #0f172a; color: #e2e8f0; }
8
+ h1 { margin: 0; padding: 16px 24px; font-size: 20px; font-weight: 600; border-bottom: 1px solid #334155; }
9
+ .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; padding: 16px 24px; }
10
+ .card {
11
+ display: block; padding: 14px 18px; background: #1e293b; border-radius: 8px;
12
+ text-decoration: none; color: #e2e8f0; border: 1px solid #334155;
13
+ transition: border-color 0.15s, background 0.15s;
14
+ }
15
+ .card:hover { border-color: #60a5fa; background: #1e3a5f; }
16
+ .card .name { font-size: 15px; font-weight: 500; }
17
+ </style>
18
+ </head>
19
+ <body>
20
+ <h1>SVG vs HTML Comparison</h1>
21
+ <div class="grid" id="grid"></div>
22
+ <script type="module">
23
+ import { CASES } from "./compare-cases.js";
24
+
25
+ const grid = document.getElementById("grid");
26
+ for (const name of Object.keys(CASES)) {
27
+ const a = document.createElement("a");
28
+ a.href = `/cy/compare-case.html?case=${encodeURIComponent(name)}`;
29
+ a.className = "card";
30
+ a.innerHTML = `<div class="name">${name}</div>`;
31
+ grid.appendChild(a);
32
+ }
33
+ </script>
34
+ </body>
35
+ </html>
@@ -0,0 +1,199 @@
1
+ /*
2
+ * Shared pixel-diff algorithm used by:
3
+ * - compare-case.html (via window.diffFromImages)
4
+ * - native-diff-ext/content.js (extension)
5
+ * - analyze-compare-case.mjs (CLI, via window.diffFromImages on the page)
6
+ *
7
+ * Single source of truth for parameters and pixel classification.
8
+ */
9
+
10
+ // Default parameters — callers may override via `opts`
11
+ export const DEFAULTS = {
12
+ LUMA_THRESHOLD: 240,
13
+ CHANNEL_TOLERANCE: 12,
14
+ POSITION_TOLERANCE: 0,
15
+ };
16
+
17
+ // Diff output colors (RGBA)
18
+ export const COLORS = {
19
+ background: [240, 240, 240, 255],
20
+ match: [0, 100, 0, 255],
21
+ htmlOnly: [255, 0, 0, 255],
22
+ svgOnly: [0, 0, 255, 255],
23
+ colorDiff: [255, 0, 255, 255],
24
+ };
25
+
26
+ function luma(r, g, b) {
27
+ return 0.3 * r + 0.59 * g + 0.11 * b;
28
+ }
29
+
30
+ function getPixel(data, stride, x, y) {
31
+ const i = (y * stride + x) * 4;
32
+ return [data[i], data[i + 1], data[i + 2]];
33
+ }
34
+
35
+ /**
36
+ * Run pixel diff on two same-sized RGBA pixel arrays.
37
+ *
38
+ * @param {Uint8ClampedArray} data1 - HTML screenshot pixel data
39
+ * @param {Uint8ClampedArray} data2 - SVG screenshot pixel data
40
+ * @param {number} w - width of both images (use max if sizes differ)
41
+ * @param {number} h - height of both images
42
+ * @param {number} stride1 - row stride for data1 (usually same as w)
43
+ * @param {number} stride2 - row stride for data2
44
+ * @param {object} [opts] - optional parameter overrides
45
+ * @returns {{ diff: Uint8ClampedArray, stats: object }}
46
+ */
47
+ export function pixelDiff(data1, data2, w, h, stride1, stride2, opts) {
48
+ const LUMA_THRESHOLD = opts?.LUMA_THRESHOLD ?? DEFAULTS.LUMA_THRESHOLD;
49
+ const CHANNEL_TOLERANCE = opts?.CHANNEL_TOLERANCE ?? DEFAULTS.CHANNEL_TOLERANCE;
50
+ const POSITION_TOLERANCE = opts?.POSITION_TOLERANCE ?? DEFAULTS.POSITION_TOLERANCE;
51
+
52
+ const diff = new Uint8ClampedArray(w * h * 4);
53
+ let total = 0, matched = 0, htmlOnly = 0, svgOnly = 0, colorDiff = 0;
54
+
55
+ function pixelsClose(a, b) {
56
+ return Math.abs(a[0] - b[0]) <= CHANNEL_TOLERANCE &&
57
+ Math.abs(a[1] - b[1]) <= CHANNEL_TOLERANCE &&
58
+ Math.abs(a[2] - b[2]) <= CHANNEL_TOLERANCE;
59
+ }
60
+
61
+ function hasNearbyMatch(srcData, srcStride, dstData, dstStride, x, y) {
62
+ const p1 = getPixel(srcData, srcStride, x, y);
63
+ for (let dy = -POSITION_TOLERANCE; dy <= POSITION_TOLERANCE; dy++) {
64
+ for (let dx = -POSITION_TOLERANCE; dx <= POSITION_TOLERANCE; dx++) {
65
+ const nx = x + dx, ny = y + dy;
66
+ if (nx < 0 || nx >= w || ny < 0 || ny >= h) continue;
67
+ const p2 = getPixel(dstData, dstStride, nx, ny);
68
+ if (luma(...p2) < LUMA_THRESHOLD && pixelsClose(p1, p2)) return true;
69
+ }
70
+ }
71
+ return false;
72
+ }
73
+
74
+ for (let y = 0; y < h; y++) {
75
+ for (let x = 0; x < w; x++) {
76
+ const pA = getPixel(data1, stride1, x, y);
77
+ const pB = getPixel(data2, stride2, x, y);
78
+ const isA = luma(...pA) < LUMA_THRESHOLD;
79
+ const isB = luma(...pB) < LUMA_THRESHOLD;
80
+ const di = (y * w + x) * 4;
81
+
82
+ if (!isA && !isB) {
83
+ diff[di] = COLORS.background[0];
84
+ diff[di + 1] = COLORS.background[1];
85
+ diff[di + 2] = COLORS.background[2];
86
+ diff[di + 3] = COLORS.background[3];
87
+ continue;
88
+ }
89
+
90
+ total++;
91
+ const matchAB = hasNearbyMatch(data1, stride1, data2, stride2, x, y);
92
+ const matchBA = hasNearbyMatch(data2, stride2, data1, stride1, x, y);
93
+
94
+ let color;
95
+ if (isA && isB) {
96
+ if (matchAB || matchBA) {
97
+ matched++;
98
+ color = COLORS.match;
99
+ } else {
100
+ colorDiff++;
101
+ color = COLORS.colorDiff;
102
+ }
103
+ } else if (isA) {
104
+ if (matchAB) {
105
+ matched++;
106
+ color = COLORS.match;
107
+ } else {
108
+ htmlOnly++;
109
+ color = COLORS.htmlOnly;
110
+ }
111
+ } else {
112
+ if (matchBA) {
113
+ matched++;
114
+ color = COLORS.match;
115
+ } else {
116
+ svgOnly++;
117
+ color = COLORS.svgOnly;
118
+ }
119
+ }
120
+
121
+ diff[di] = color[0];
122
+ diff[di + 1] = color[1];
123
+ diff[di + 2] = color[2];
124
+ diff[di + 3] = color[3];
125
+ }
126
+ }
127
+
128
+ const posMatched = matched + colorDiff;
129
+ const pixelPct = total > 0 ? parseFloat((matched / total * 100).toFixed(1)) : 0.0;
130
+ const posPct = total > 0 ? parseFloat((posMatched / total * 100).toFixed(1)) : 0.0;
131
+
132
+ return {
133
+ diff,
134
+ stats: { matched, posMatched, total, htmlOnly, svgOnly, colorDiff, pixelPct, posPct },
135
+ };
136
+ }
137
+
138
+ /**
139
+ * Load an image from a data URL into an ImageData-compatible object.
140
+ * Works in any browser context (page or extension content script).
141
+ */
142
+ export function loadImage(dataUrl) {
143
+ return new Promise((resolve, reject) => {
144
+ const img = new Image();
145
+ img.onload = () => resolve(img);
146
+ img.onerror = () => reject(new Error("Failed to load image"));
147
+ img.src = dataUrl;
148
+ });
149
+ }
150
+
151
+ /**
152
+ * Draw an image onto a white-filled canvas and return its pixel data.
153
+ */
154
+ export function getImageData(img, w, h) {
155
+ const canvas = document.createElement("canvas");
156
+ canvas.width = w;
157
+ canvas.height = h;
158
+ const ctx = canvas.getContext("2d");
159
+ ctx.fillStyle = "white";
160
+ ctx.fillRect(0, 0, w, h);
161
+ ctx.drawImage(img, 0, 0);
162
+ return ctx.getImageData(0, 0, w, h).data;
163
+ }
164
+
165
+ /**
166
+ * High-level: load two data-URL images, run diff, render to a canvas.
167
+ * Returns { canvas, stats, badgeHtml }.
168
+ */
169
+ export async function diffImages(htmlDataUrl, svgDataUrl, opts) {
170
+ const [htmlImg, svgImg] = await Promise.all([
171
+ loadImage(htmlDataUrl),
172
+ loadImage(svgDataUrl),
173
+ ]);
174
+
175
+ const w = Math.max(htmlImg.width, svgImg.width);
176
+ const h = Math.max(htmlImg.height, svgImg.height);
177
+
178
+ const data1 = getImageData(htmlImg, w, h);
179
+ const data2 = getImageData(svgImg, w, h);
180
+
181
+ const { diff, stats } = pixelDiff(data1, data2, w, h, w, w, opts);
182
+
183
+ const canvas = document.createElement("canvas");
184
+ canvas.width = w;
185
+ canvas.height = h;
186
+ const ctx = canvas.getContext("2d");
187
+ const imageData = ctx.createImageData(w, h);
188
+ imageData.data.set(diff);
189
+ ctx.putImageData(imageData, 0, 0);
190
+
191
+ const badgeHtml =
192
+ `<b>${stats.pixelPct}%</b> native pixel match / <b>${stats.posPct}%</b> pos-only (${stats.matched}/${stats.total} px) ` +
193
+ `<span style="color:#006400">\u25a0</span> match ` +
194
+ `<span style="color:#ff0000">\u25a0</span> HTML-only (${stats.htmlOnly}) ` +
195
+ `<span style="color:#0000ff">\u25a0</span> SVG-only (${stats.svgOnly}) ` +
196
+ `<span style="color:#ff00ff">\u25a0</span> color diff (${stats.colorDiff})`;
197
+
198
+ return { canvas, stats, badgeHtml };
199
+ }