@zenuml/core 3.47.9 → 3.48.0

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 (204) hide show
  1. package/dist/cloud-icons-eHuugVSv.js.map +1 -0
  2. package/dist/zenuml.esm.mjs +2153 -2156
  3. package/dist/zenuml.esm.mjs.map +1 -0
  4. package/dist/zenuml.js +82 -82
  5. package/dist/zenuml.js.map +1 -0
  6. package/package.json +11 -1
  7. package/src/cli/zenuml.ts +1164 -0
  8. package/.agents/skills/babysit-pr/SKILL.md +0 -223
  9. package/.agents/skills/babysit-pr/agents/openai.yaml +0 -7
  10. package/.agents/skills/dia-scoring/SKILL.md +0 -139
  11. package/.agents/skills/dia-scoring/agents/openai.yaml +0 -7
  12. package/.agents/skills/dia-scoring/references/selectors-and-keys.md +0 -253
  13. package/.agents/skills/land-pr/SKILL.md +0 -120
  14. package/.agents/skills/propagate-core-release/SKILL.md +0 -205
  15. package/.agents/skills/propagate-core-release/agents/openai.yaml +0 -7
  16. package/.agents/skills/propagate-core-release/references/downstreams.md +0 -42
  17. package/.agents/skills/ship-branch/SKILL.md +0 -105
  18. package/.agents/skills/submit-branch/SKILL.md +0 -76
  19. package/.agents/skills/validate-branch/SKILL.md +0 -72
  20. package/.claude/commands/README.md +0 -162
  21. package/.claude/commands/analyze.md +0 -101
  22. package/.claude/commands/clarify.md +0 -158
  23. package/.claude/commands/code-review.md +0 -322
  24. package/.claude/commands/constitution.md +0 -73
  25. package/.claude/commands/create-docs.md +0 -309
  26. package/.claude/commands/full-context.md +0 -121
  27. package/.claude/commands/gemini-consult.md +0 -164
  28. package/.claude/commands/handoff.md +0 -146
  29. package/.claude/commands/implement.md +0 -56
  30. package/.claude/commands/plan.md +0 -43
  31. package/.claude/commands/refactor.md +0 -188
  32. package/.claude/commands/specify.md +0 -21
  33. package/.claude/commands/tasks.md +0 -62
  34. package/.claude/commands/update-docs.md +0 -314
  35. package/.claude/hooks/README.md +0 -270
  36. package/.claude/hooks/config/sensitive-patterns.json +0 -86
  37. package/.claude/hooks/gemini-context-injector.sh +0 -129
  38. package/.claude/hooks/mcp-security-scan.sh +0 -147
  39. package/.claude/hooks/notify.sh +0 -103
  40. package/.claude/hooks/setup/hook-setup.md +0 -96
  41. package/.claude/hooks/setup/settings.json.template +0 -63
  42. package/.claude/hooks/sounds/complete.wav +0 -0
  43. package/.claude/hooks/sounds/input-needed.wav +0 -0
  44. package/.claude/hooks/subagent-context-injector.sh +0 -65
  45. package/.claude/skills/babysit-pr/SKILL.md +0 -223
  46. package/.claude/skills/babysit-pr/agents/openai.yaml +0 -7
  47. package/.claude/skills/dia-scoring/SKILL.md +0 -139
  48. package/.claude/skills/dia-scoring/agents/openai.yaml +0 -7
  49. package/.claude/skills/dia-scoring/references/selectors-and-keys.md +0 -253
  50. package/.claude/skills/emoji-eval/SKILL.md +0 -187
  51. package/.claude/skills/land-pr/SKILL.md +0 -120
  52. package/.claude/skills/propagate-core-release/SKILL.md +0 -205
  53. package/.claude/skills/propagate-core-release/agents/openai.yaml +0 -7
  54. package/.claude/skills/propagate-core-release/references/downstreams.md +0 -42
  55. package/.claude/skills/ship-branch/SKILL.md +0 -105
  56. package/.claude/skills/submit-branch/SKILL.md +0 -76
  57. package/.claude/skills/validate-branch/SKILL.md +0 -72
  58. package/.claude/skills/zenuml-ux-research/SKILL.md +0 -183
  59. package/.claude/skills/zenuml-ux-research/references/assertion-catalog.md +0 -261
  60. package/.claude/skills/zenuml-ux-research/references/best-practices-overview.md +0 -56
  61. package/.claude/skills/zenuml-ux-research/references/report-template.md +0 -89
  62. package/.claude/skills/zenuml-ux-research/references/scenarios/edit-message-label.md +0 -37
  63. package/.claude/skills/zenuml-ux-research/references/scenarios/insert-message.md +0 -36
  64. package/.claude/skills/zenuml-ux-research/references/scenarios/insert-participant.md +0 -31
  65. package/.claude/skills/zenuml-ux-research/references/scenarios/rename-participant.md +0 -33
  66. package/.claude/skills/zenuml-ux-research/references/scenarios/undo-insert.md +0 -35
  67. package/.devcontainer/devcontainer.json +0 -21
  68. package/.dockerignore +0 -19
  69. package/.eslintrc.js +0 -39
  70. package/.git-blame-ignore-revs +0 -6
  71. package/.kiro/hooks/README.md +0 -38
  72. package/.kiro/hooks/session-sound-notification.js +0 -44
  73. package/.kiro/hooks/session-sound-notification.json +0 -23
  74. package/.mcp.json.example +0 -17
  75. package/.nvmrc +0 -1
  76. package/.prettierignore +0 -4
  77. package/.prettierrc +0 -1
  78. package/.specify/memory/constitution.md +0 -33
  79. package/.specify/scripts/bash/check-prerequisites.sh +0 -166
  80. package/.specify/scripts/bash/common.sh +0 -113
  81. package/.specify/scripts/bash/create-new-feature.sh +0 -97
  82. package/.specify/scripts/bash/setup-plan.sh +0 -60
  83. package/.specify/scripts/bash/update-agent-context.sh +0 -728
  84. package/.specify/templates/agent-file-template.md +0 -23
  85. package/.specify/templates/plan-template.md +0 -219
  86. package/.specify/templates/spec-template.md +0 -116
  87. package/.specify/templates/tasks-template.md +0 -127
  88. package/.storybook/main.ts +0 -25
  89. package/.storybook/preview.ts +0 -29
  90. package/.watchmanconfig +0 -3
  91. package/AGENTS.md +0 -26
  92. package/CLAUDE.md +0 -124
  93. package/DEPLOYMENT.md +0 -62
  94. package/Dockerfile +0 -36
  95. package/IMPLEMENTATION_PLAN.md +0 -163
  96. package/Integration/vanilla-js/index.html +0 -42
  97. package/MCP-ASSISTANT-RULES.md +0 -85
  98. package/README_CN.md +0 -15
  99. package/TUTORIAL.md +0 -116
  100. package/antlr/antlr-4.11.1-complete.jar +0 -0
  101. package/bun.lock +0 -1544
  102. package/bunfig.toml +0 -52
  103. package/docs/UNICODE_SUPPORT.md +0 -179
  104. package/docs/ai-context/deployment-infrastructure.md +0 -21
  105. package/docs/ai-context/docs-overview.md +0 -89
  106. package/docs/ai-context/handoff.md +0 -174
  107. package/docs/ai-context/project-structure.md +0 -160
  108. package/docs/ai-context/system-integration.md +0 -21
  109. package/docs/asciidoc/contributor.adoc +0 -54
  110. package/docs/asciidoc/create-my-own-theme.adoc +0 -149
  111. package/docs/asciidoc/images/creation-component.png +0 -0
  112. package/docs/asciidoc/images/creation-rtl.png +0 -0
  113. package/docs/asciidoc/images/message-arrow-rtl.png +0 -0
  114. package/docs/asciidoc/images/occurrence.png +0 -0
  115. package/docs/asciidoc/images/return-message-conflict.png +0 -0
  116. package/docs/asciidoc/images/shift-up-half-the-height.png +0 -0
  117. package/docs/asciidoc/images/three-layer-info-arch.png +0 -0
  118. package/docs/asciidoc/images/vertical-alignment.svg +0 -1
  119. package/docs/asciidoc/images/vertically-aligning.png +0 -0
  120. package/docs/asciidoc/index.adoc +0 -277
  121. package/docs/asciidoc/theme-debug-web-app.png +0 -0
  122. package/docs/asciidoc/tutorial.adoc +0 -22
  123. package/docs/asciidoc/user-css.png +0 -0
  124. package/docs/async-vs-sync-parser-rules.md +0 -81
  125. package/docs/divider-parser-allow-spaces.md +0 -38
  126. package/docs/highlighting-messages.md +0 -52
  127. package/docs/images/editor-sample.png +0 -0
  128. package/docs/inherited-vs-provided-from.md +0 -64
  129. package/docs/parser/Assignment.md +0 -8
  130. package/docs/parser/PARSER_IMPROVEMENTS_CC.md +0 -425
  131. package/docs/parser/grammar_review_gemini.md +0 -116
  132. package/docs/participants-function.md +0 -25
  133. package/docs/responsive-participant-margin.md +0 -52
  134. package/docs/starter.md +0 -9
  135. package/docs/superpowers/plans/2026-03-27-e2e-test-reorg.md +0 -698
  136. package/docs/superpowers/plans/2026-03-30-emoji-support.md +0 -1220
  137. package/docs/superpowers/plans/2026-03-30-self-correcting-scoring.md +0 -206
  138. package/docs/superpowers/plans/2026-04-15-keyboard-editing-on-diagram.md +0 -1992
  139. package/docs/superpowers/plans/2026-04-15-zenuml-ux-research-skill.md +0 -1452
  140. package/docs/ux-research/.gitkeep +0 -0
  141. package/docs/ux-research/2026-04-15-rename-participant.md +0 -156
  142. package/docs/ux-research/2026-04-18-insert-participant.md +0 -151
  143. package/docs/width-translate-and-offsets.md +0 -62
  144. package/docs/xss.md +0 -59
  145. package/e2e/data/compare-cases.js +0 -1090
  146. package/e2e/data/diff-algorithm.js +0 -199
  147. package/e2e/fixtures/create-message.html +0 -26
  148. package/e2e/fixtures/editable-label.html +0 -35
  149. package/e2e/fixtures/editable-span.html +0 -122
  150. package/e2e/fixtures/empty-diagram.html +0 -23
  151. package/e2e/fixtures/fixture.html +0 -31
  152. package/e2e/fixtures/insert-participant.html +0 -23
  153. package/e2e/fixtures/reorder-cross-fragment.html +0 -31
  154. package/e2e/fixtures/reorder-fragment.html +0 -29
  155. package/e2e/fixtures/reorder-message.html +0 -27
  156. package/e2e/fixtures/svg-test.html +0 -21
  157. package/e2e/fixtures/type-switch.html +0 -29
  158. package/e2e/tools/canonical-history.html +0 -908
  159. package/e2e/tools/compare-case.html +0 -371
  160. package/e2e/tools/compare.html +0 -35
  161. package/e2e/tools/native-diff-ext/background.js +0 -60
  162. package/e2e/tools/native-diff-ext/bridge.js +0 -26
  163. package/e2e/tools/native-diff-ext/content.js +0 -194
  164. package/e2e/tools/svg-preview.html +0 -56
  165. package/embed.html +0 -193
  166. package/eslint.config.mjs +0 -35
  167. package/firebase-debug.log +0 -108
  168. package/iframe-container-demo/diagram.html +0 -124
  169. package/iframe-container-demo/host.html +0 -817
  170. package/index.html +0 -771
  171. package/mermaid-zenuml-async-spa-auth.png +0 -0
  172. package/mermaid-zenuml-async-spa-auth.snapshot.md +0 -96
  173. package/newsletter/unicode-support-announcement.md +0 -134
  174. package/playground/creation.html +0 -53
  175. package/playground/message.html +0 -63
  176. package/playwright.config.ts +0 -40
  177. package/renderer.html +0 -366
  178. package/scripts/analyze-compare-case/collect-data.mjs +0 -1134
  179. package/scripts/analyze-compare-case/config.mjs +0 -102
  180. package/scripts/analyze-compare-case/geometry.mjs +0 -101
  181. package/scripts/analyze-compare-case/native-diff.mjs +0 -224
  182. package/scripts/analyze-compare-case/output.mjs +0 -74
  183. package/scripts/analyze-compare-case/panel-diff.mjs +0 -114
  184. package/scripts/analyze-compare-case/report.mjs +0 -162
  185. package/scripts/analyze-compare-case/residual-scopes.mjs +0 -347
  186. package/scripts/analyze-compare-case/scoring.mjs +0 -829
  187. package/scripts/analyze-compare-case.mjs +0 -149
  188. package/scripts/bump-version.js +0 -117
  189. package/scripts/snapshot-dual.js +0 -173
  190. package/scripts/update-snapshots.js +0 -70
  191. package/skills/dia-scoring/SKILL.md +0 -129
  192. package/skills/dia-scoring/agents/openai.yaml +0 -7
  193. package/skills/dia-scoring/references/selectors-and-keys.md +0 -253
  194. package/tailwind.config.js +0 -126
  195. package/test-compression.html +0 -274
  196. package/test-mermaid-zenuml.html +0 -57
  197. package/test-setup.ts +0 -124
  198. package/test-url-params.html +0 -192
  199. package/tsconfig.app.json +0 -31
  200. package/tsconfig.node.json +0 -24
  201. package/tsconfig.test.json +0 -9
  202. package/vite.config.lib.ts +0 -93
  203. package/vite.config.ts +0 -84
  204. package/wrangler.toml +0 -18
@@ -1,371 +0,0 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <title>Compare</title>
6
- <style>
7
- body { margin: 0; font-family: sans-serif; }
8
- .compare-bar {
9
- display: flex; align-items: center; gap: 12px;
10
- padding: 8px 16px; background: #1e293b; color: white;
11
- }
12
- .compare-bar a { color: #93c5fd; text-decoration: none; font-size: 13px; }
13
- .compare-bar a:hover { text-decoration: underline; }
14
- .compare-bar h2 { margin: 0; font-size: 15px; font-weight: 500; }
15
- .nav { display: flex; gap: 8px; margin-left: auto; align-items: center; }
16
- .nav a { padding: 4px 10px; background: #334155; border-radius: 4px; font-size: 12px; }
17
- .nav button { padding: 4px 12px; background: #7c3aed; border: none; border-radius: 4px; color: white; font-size: 12px; cursor: pointer; }
18
- .nav button:hover { background: #6d28d9; }
19
- .nav button:disabled { background: #64748b; cursor: wait; }
20
- .nav-btn { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; background: #334155; border: none; border-radius: 4px; color: white; font-size: 16px; cursor: pointer; text-decoration: none; line-height: 1; }
21
- .nav-btn:hover { background: #475569; }
22
- .nav-btn[style*="hidden"] { visibility: hidden; }
23
- .nav .match-badge { font-size: 11px; color: #94a3b8; }
24
- .container { display: flex; gap: 0; }
25
- .container.stacked { flex-direction: column; }
26
- .panel { flex: 1; overflow: auto; }
27
- .panel > h3 { margin: 0; padding: 8px 16px; background: #334155; color: white; font-size: 13px; }
28
- .panel-content { padding: 10px; background: white; }
29
- .diff-panel { display: none; }
30
- .diff-panel.visible { display: block; }
31
- .diff-panel .panel-content canvas { max-width: 100%; }
32
- #html-output .footer { display: none !important; }
33
- #html-output .privacy { visibility: hidden !important; width: 0 !important; overflow: hidden !important; }
34
- </style>
35
- </head>
36
- <body>
37
- <div class="compare-bar">
38
- <a href="/e2e/tools/compare.html">&larr; All cases</a>
39
- <h2 id="case-name"></h2>
40
- <div class="nav">
41
- <a id="prev-link" class="nav-btn" href="#" title="Previous case">&#8249;</a>
42
- <a id="next-link" class="nav-btn" href="#" title="Next case">&#8250;</a>
43
- <span class="match-badge" id="match-badge"></span>
44
- </div>
45
- </div>
46
- <div class="container">
47
- <div class="panel">
48
- <h3>HTML/React Renderer (theme-default)</h3>
49
- <div class="panel-content" id="html-output">
50
- <div id="diagram"><pre class="zenuml" style="margin: 0"></pre></div>
51
- </div>
52
- </div>
53
- <div class="panel">
54
- <h3>Native SVG Renderer</h3>
55
- <div class="panel-content" id="svg-output"></div>
56
- </div>
57
- <div class="panel diff-panel" id="diff-panel">
58
- <h3>Pixel Diff</h3>
59
- <div class="panel-content" id="diff-output"></div>
60
- </div>
61
- </div>
62
- <script type="module">
63
- import ZenUml from "/src/core.tsx";
64
- import { renderToSvg } from "/src/svg/renderToSvg.ts";
65
-
66
- import { CASES } from "../data/compare-cases.js";
67
- import { diffImages, DEFAULTS } from "../data/diff-algorithm.js";
68
-
69
- const caseNames = Object.keys(CASES);
70
- const params = new URLSearchParams(window.location.search);
71
- const caseName = params.get("case") || caseNames[0];
72
- const code = CASES[caseName];
73
-
74
- // Update title and header
75
- document.title = `Compare: ${caseName}`;
76
- document.getElementById("case-name").textContent = caseName;
77
-
78
- // Prev/Next navigation
79
- const idx = caseNames.indexOf(caseName);
80
- const prevLink = document.getElementById("prev-link");
81
- const nextLink = document.getElementById("next-link");
82
- if (idx > 0) {
83
- prevLink.href = `?case=${encodeURIComponent(caseNames[idx - 1])}`;
84
- } else {
85
- prevLink.style.visibility = "hidden";
86
- }
87
- if (idx < caseNames.length - 1) {
88
- nextLink.href = `?case=${encodeURIComponent(caseNames[idx + 1])}`;
89
- } else {
90
- nextLink.style.visibility = "hidden";
91
- }
92
-
93
- // Keyboard navigation
94
- document.addEventListener("keydown", (e) => {
95
- if (e.key === "ArrowLeft" && prevLink.style.visibility !== "hidden") {
96
- window.location.href = prevLink.href;
97
- } else if (e.key === "ArrowRight" && nextLink.style.visibility !== "hidden") {
98
- window.location.href = nextLink.href;
99
- }
100
- });
101
-
102
- // Expose DSL for the extension's batch mode to read
103
- window.__currentDSL = code || "";
104
-
105
- if (code === undefined) {
106
- const errP = document.createElement("p");
107
- errP.style.color = "red";
108
- errP.textContent = "Unknown case: " + caseName;
109
- document.getElementById("svg-output").appendChild(errP);
110
- } else {
111
- // SVG side
112
- const result = renderToSvg(code);
113
- document.getElementById("svg-output").innerHTML = result.svg;
114
-
115
- // HTML side — render with theme-default
116
- const diagram = document.getElementById("diagram");
117
- const pre = diagram.querySelector("pre.zenuml");
118
- pre.textContent = code;
119
- const zenUml = new ZenUml(pre);
120
- await zenUml.render(code, { theme: "theme-default" });
121
-
122
- // Hide footer in HTML output (SVG has no footer equivalent)
123
- requestAnimationFrame(() => {
124
- const footer = document.querySelector("#html-output .footer");
125
- if (footer) footer.style.display = "none";
126
-
127
- // Stack panels vertically when diagram is wider than half the viewport.
128
- // This prevents overflow clipping in CDP screenshots for wide diagrams.
129
- const frame = document.querySelector("#html-output .frame, #html-output .sequence-diagram");
130
- const svgEl = document.querySelector("#svg-output > svg");
131
- const maxW = Math.max(frame?.offsetWidth || 0, svgEl?.offsetWidth || 0);
132
- if (maxW > window.innerWidth / 2 - 40) {
133
- document.querySelector(".container").classList.add("stacked");
134
- }
135
- });
136
- }
137
-
138
- // ——— Element extraction for structural comparison ———
139
-
140
- function extractHtmlElements(container) {
141
- const elements = { participants: [], messages: [], selfCalls: [], creations: [], returns: [], fragments: [] };
142
-
143
- // Participants
144
- container.querySelectorAll("[data-participant-id]").forEach(el => {
145
- const name = el.getAttribute("data-participant-id");
146
- if (name) elements.participants.push(name);
147
- });
148
-
149
- // Messages (non-self, non-return, non-creation interactions)
150
- container.querySelectorAll('.interaction:not(.return):not(.creation)').forEach(el => {
151
- const isSelf = el.classList.contains("self-invocation") ||
152
- el.querySelector(".self-invocation") !== null ||
153
- el.classList.contains("self");
154
- if (isSelf) return;
155
- const sig = el.getAttribute("data-signature") || el.getAttribute("data-to") || "";
156
- if (sig) elements.messages.push(sig);
157
- });
158
-
159
- // Self-calls
160
- container.querySelectorAll('.self-invocation').forEach(el => {
161
- const interaction = el.closest(".interaction");
162
- const sig = interaction?.getAttribute("data-signature") || "";
163
- if (sig) elements.selfCalls.push(sig);
164
- });
165
-
166
- // Creations
167
- container.querySelectorAll('.interaction.creation').forEach(el => {
168
- const sig = el.getAttribute("data-signature") || "";
169
- if (sig) elements.creations.push(sig);
170
- });
171
-
172
- // Returns — match .interaction.return (some have data-type="return", some don't)
173
- container.querySelectorAll('.interaction.return').forEach(el => {
174
- const sig = el.getAttribute("data-signature") || "";
175
- const msgDiv = el.querySelector(".message");
176
- const label = sig || (msgDiv ? msgDiv.textContent.trim().replace(/[\d.]+$/, "").trim() : "");
177
- if (label) elements.returns.push(label);
178
- });
179
-
180
- // Fragments
181
- container.querySelectorAll(".fragment").forEach(el => {
182
- const kind = [...el.classList].find(c => c.startsWith("fragment-"))?.replace("fragment-", "") ||
183
- [...el.classList].find(c => ["alt", "loop", "opt", "par", "tcf", "critical", "section", "ref"].includes(c)) || "unknown";
184
- elements.fragments.push(kind);
185
- });
186
-
187
- return elements;
188
- }
189
-
190
- function extractSvgElements(svgElement) {
191
- const elements = { participants: [], messages: [], selfCalls: [], creations: [], returns: [], fragments: [] };
192
- if (!svgElement) return elements;
193
-
194
- // Participants
195
- svgElement.querySelectorAll("g.participant, g.participant-starter").forEach(g => {
196
- const name = g.getAttribute("data-participant");
197
- if (name) elements.participants.push(name);
198
- });
199
-
200
- // Messages (non-self)
201
- svgElement.querySelectorAll("g.message").forEach(g => {
202
- if (g.classList.contains("self-call")) return;
203
- const text = g.querySelector("text.message-label");
204
- elements.messages.push(text ? text.textContent.trim() : "");
205
- });
206
-
207
- // Self-calls
208
- svgElement.querySelectorAll("g.message.self-call").forEach(g => {
209
- const text = g.querySelector("text.message-label");
210
- elements.selfCalls.push(text ? text.textContent.trim() : "");
211
- });
212
-
213
- // Creations
214
- svgElement.querySelectorAll("g.creation").forEach(g => {
215
- const text = g.querySelector("text.message-label");
216
- elements.creations.push(text ? text.textContent.trim() : "");
217
- });
218
-
219
- // Returns
220
- svgElement.querySelectorAll("g.return").forEach(g => {
221
- const text = g.querySelector("text.return-label");
222
- elements.returns.push(text ? text.textContent.trim() : "");
223
- });
224
-
225
- // Fragments
226
- svgElement.querySelectorAll("g.fragment, g[class*='fragment-']").forEach(g => {
227
- const kind = ([...g.classList].find(c => c.startsWith("fragment-")) || "").replace("fragment-", "") || "unknown";
228
- elements.fragments.push(kind);
229
- });
230
-
231
- return elements;
232
- }
233
-
234
- function computeStructuralMatch(htmlElements, svgElements) {
235
- const types = ["participants", "messages", "selfCalls", "creations", "returns", "fragments"];
236
- let totalScore = 0;
237
- let scoredTypes = 0;
238
- let totalElements = 0;
239
- let matchedElements = 0;
240
- const details = {};
241
-
242
- for (const type of types) {
243
- const htmlList = htmlElements[type] || [];
244
- const svgList = svgElements[type] || [];
245
- const maxLen = Math.max(htmlList.length, svgList.length);
246
-
247
- if (maxLen === 0) continue; // skip types with no elements in either
248
-
249
- // Count matches by order
250
- let matched = 0;
251
- const minLen = Math.min(htmlList.length, svgList.length);
252
- for (let i = 0; i < minLen; i++) {
253
- matched++; // count as matched if present in both at same index
254
- }
255
-
256
- const score = matched / maxLen;
257
- totalScore += score;
258
- scoredTypes++;
259
- totalElements += maxLen;
260
- matchedElements += matched;
261
-
262
- details[type] = { html: htmlList.length, svg: svgList.length, matched, score };
263
- }
264
-
265
- return {
266
- score: scoredTypes > 0 ? totalScore / scoredTypes : 1,
267
- totalElements,
268
- matchedElements,
269
- details,
270
- };
271
- }
272
-
273
- // --- Reusable capture/diff API (also exposed on window for external tools) ---
274
-
275
- // State for hide/restore cycle
276
- let _hiddenEls = [];
277
- let _savedStyles = [];
278
-
279
- function _hideEl(el) {
280
- if (el) { _hiddenEls.push({ el, prev: el.style.display }); el.style.display = "none"; }
281
- }
282
- function _stripMargin(el) {
283
- if (el) { _savedStyles.push({ el, prev: el.style.margin }); el.style.margin = "0"; }
284
- }
285
-
286
- // Prepare HTML panel for screenshot: hide chrome, numbering, icons
287
- window.prepareHtmlForCapture = function() {
288
- _hiddenEls = [];
289
- _savedStyles = [];
290
-
291
- const htmlEl = document.getElementById("html-output");
292
- const htmlFrame = htmlEl.querySelector(".frame");
293
- const seqDiagram = htmlEl.querySelector(".sequence-diagram");
294
- const svgElement = document.getElementById("svg-output").querySelector("svg");
295
-
296
- _hideEl(htmlEl.querySelector(".footer"));
297
- _stripMargin(htmlFrame);
298
-
299
- // Hide bottom participant labels (SVG no longer renders them)
300
- const captureEl = htmlFrame || seqDiagram || htmlEl;
301
- const captureRect = captureEl.getBoundingClientRect();
302
- const captureMidY = captureRect.top + captureRect.height * 0.7;
303
- captureEl.querySelectorAll(".participant").forEach(el => {
304
- const r = el.getBoundingClientRect();
305
- if (r.top > captureMidY) _hideEl(el);
306
- });
307
-
308
- if (htmlFrame) {
309
- const frameRect2 = htmlFrame.getBoundingClientRect();
310
- // Hide SVG icons in the header area (top 35px) — checkbox, etc.
311
- // Skip group outline overlays (data-group-overlay) which are functional rendering elements.
312
- htmlFrame.querySelectorAll("svg").forEach(svg => {
313
- if (svg.hasAttribute("data-group-overlay")) return;
314
- const r = svg.getBoundingClientRect();
315
- if (r.width > 0 && (r.top - frameRect2.top) < 35) _hideEl(svg);
316
- });
317
- }
318
-
319
- return { htmlSelector: "#html-output .frame, #html-output .sequence-diagram", svgSelector: "#svg-output svg" };
320
- };
321
-
322
- // Restore HTML panel after screenshot
323
- window.restoreHtmlAfterCapture = function() {
324
- for (const { el, prev } of _hiddenEls) el.style.display = prev;
325
- for (const { el, prev } of _savedStyles) el.style.margin = prev;
326
- _hiddenEls = [];
327
- _savedStyles = [];
328
- };
329
-
330
- // Run pixel diff from two PNG data URLs and display results on the page.
331
- // Returns { pixelPct, posPct, structPct, matched, total, htmlOnly, svgOnly, colorDiff }
332
- window.diffFromImages = async function(htmlDataUrl, svgDataUrl) {
333
- const htmlEl = document.getElementById("html-output");
334
- const svgEl = document.getElementById("svg-output");
335
- const seqDiagram = htmlEl.querySelector(".sequence-diagram");
336
- const svgElement = svgEl.querySelector("svg");
337
-
338
- // Structural comparison
339
- const htmlElements = extractHtmlElements(seqDiagram || htmlEl);
340
- const svgElements = extractSvgElements(svgElement);
341
- const structural = computeStructuralMatch(htmlElements, svgElements);
342
-
343
- // Allow URL param overrides for debugging
344
- const urlParams = new URLSearchParams(window.location.search);
345
- const opts = {
346
- LUMA_THRESHOLD: parseInt(urlParams.get('luma') || DEFAULTS.LUMA_THRESHOLD, 10),
347
- CHANNEL_TOLERANCE: parseInt(urlParams.get('ctol') || DEFAULTS.CHANNEL_TOLERANCE, 10),
348
- POSITION_TOLERANCE: parseInt(urlParams.get('ptol') || DEFAULTS.POSITION_TOLERANCE, 10),
349
- };
350
-
351
- const { canvas: diffCanvas, stats, badgeHtml } = await diffImages(htmlDataUrl, svgDataUrl, opts);
352
-
353
- const diffOutput = document.getElementById("diff-output");
354
- diffOutput.innerHTML = "";
355
- diffOutput.appendChild(diffCanvas);
356
-
357
- const panel = document.getElementById("diff-panel");
358
- panel.classList.add("visible");
359
-
360
- const structPct = (structural.score * 100).toFixed(0);
361
- document.getElementById("match-badge").innerHTML =
362
- `${structPct}% structural · ${badgeHtml}`;
363
-
364
- return { pixelPct: stats.pixelPct, posPct: stats.posPct, structPct: parseFloat(structPct), ...stats };
365
- };
366
-
367
-
368
-
369
- </script>
370
- </body>
371
- </html>
@@ -1,35 +0,0 @@
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 "../data/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 = `/e2e/tools/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>
@@ -1,60 +0,0 @@
1
- // Background service worker: uses chrome.debugger + CDP for element screenshots
2
-
3
- chrome.action.onClicked.addListener(async (tab) => {
4
- if (!tab.url || !tab.url.includes("compare-case.html")) return;
5
- try {
6
- const response = await chrome.tabs.sendMessage(tab.id, { action: "native-diff" });
7
- console.log("[native-diff-ext bg]", response?.error || `Done: ${response?.pixelPct}%`);
8
- } catch (err) {
9
- console.error("[native-diff-ext bg] Failed:", err);
10
- }
11
- });
12
-
13
- chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
14
- if (msg.action === "cdp-screenshot") {
15
- cdpScreenshot(sender.tab.id, msg.selector)
16
- .then((dataUrl) => sendResponse({ dataUrl }))
17
- .catch((err) => sendResponse({ error: err.message }));
18
- return true;
19
- }
20
- });
21
-
22
- // Take a single element screenshot
23
- async function cdpScreenshot(tabId, selector) {
24
- const target = { tabId };
25
-
26
- try {
27
- await chrome.debugger.attach(target, "1.3");
28
- const { root } = await cdp(target, "DOM.getDocument", {});
29
- return await screenshotNode(target, root.nodeId, selector);
30
- } finally {
31
- try { await chrome.debugger.detach(target); } catch (_) {}
32
- }
33
- }
34
-
35
- async function screenshotNode(target, rootNodeId, selector) {
36
- const { nodeId } = await cdp(target, "DOM.querySelector", {
37
- nodeId: rootNodeId,
38
- selector,
39
- });
40
- if (!nodeId) throw new Error(`Element not found: ${selector}`);
41
-
42
- const { model } = await cdp(target, "DOM.getBoxModel", { nodeId });
43
- const border = model.border;
44
- const x = border[0];
45
- const y = border[1];
46
- const width = Math.ceil(border[2] - border[0]);
47
- const height = Math.ceil(border[5] - border[1]);
48
-
49
- const { data } = await cdp(target, "Page.captureScreenshot", {
50
- format: "png",
51
- clip: { x, y, width, height, scale: 1 },
52
- captureBeyondViewport: true,
53
- });
54
-
55
- return "data:image/png;base64," + data;
56
- }
57
-
58
- function cdp(target, method, params) {
59
- return chrome.debugger.sendCommand(target, method, params);
60
- }
@@ -1,26 +0,0 @@
1
- // Bridge script: runs in ISOLATED world
2
- // Relays messages between content.js (MAIN world) and background.js (service worker)
3
-
4
- // Content.js → bridge → background.js: screenshot requests
5
- window.addEventListener("message", (event) => {
6
- if (event.data && event.data.type === "native-diff-screenshot") {
7
- console.log("[native-diff-ext bridge] Screenshot request:", event.data.selector);
8
- chrome.runtime.sendMessage(
9
- { action: "cdp-screenshot", selector: event.data.selector },
10
- (response) => {
11
- window.postMessage({
12
- type: "native-diff-screenshot-response",
13
- dataUrl: response?.dataUrl,
14
- error: response?.error,
15
- }, "*");
16
- }
17
- );
18
- }
19
- });
20
-
21
- // Icon click → content.js: trigger diff
22
- chrome.runtime.onMessage.addListener((msg) => {
23
- if (msg.action === "native-diff") {
24
- window.postMessage({ type: "native-diff-trigger" }, "*");
25
- }
26
- });
@@ -1,194 +0,0 @@
1
- // Content script: runs in MAIN world (has access to page's window functions)
2
- // Uses CDP element screenshots via bridge.js → background.js
3
- // Hides neighboring panels before each screenshot to prevent overlap.
4
- //
5
- // Diff algorithm lives in e2e/data/diff-algorithm.js, loaded by compare-case.html
6
- // and exposed as window.diffFromImages. This script only handles screenshots
7
- // and orchestration.
8
-
9
- // Auto-run on page load
10
- window.addEventListener("load", () => {
11
- console.log("[native-diff-ext] Page loaded, waiting 1s for renderers...");
12
- setTimeout(() => {
13
- console.log("[native-diff-ext] Starting native diff...");
14
- runNativeDiff();
15
- }, 1000);
16
- });
17
-
18
- // Icon click trigger from bridge.js
19
- window.addEventListener("message", (event) => {
20
- if (event.data && event.data.type === "native-diff-trigger") {
21
- console.log("[native-diff-ext] Triggered by icon click");
22
- runNativeDiff();
23
- }
24
- });
25
-
26
- // Request a single CDP element screenshot via bridge
27
- function screenshotOne(selector) {
28
- return new Promise((resolve) => {
29
- function handler(event) {
30
- if (event.data && event.data.type === "native-diff-screenshot-response") {
31
- window.removeEventListener("message", handler);
32
- resolve(event.data);
33
- }
34
- }
35
- window.addEventListener("message", handler);
36
- window.postMessage({ type: "native-diff-screenshot", selector }, "*");
37
- });
38
- }
39
-
40
- async function runNativeDiff() {
41
- if (typeof window.prepareHtmlForCapture !== "function") {
42
- console.error("[native-diff-ext] prepareHtmlForCapture not found");
43
- return;
44
- }
45
- if (typeof window.diffFromImages !== "function") {
46
- console.error("[native-diff-ext] diffFromImages not found — compare-case.html must load diff-algorithm.js");
47
- return;
48
- }
49
-
50
- // 1. Prepare: hide HTML chrome (same as skill's page.evaluate step)
51
- console.log("[native-diff-ext] Preparing HTML for capture...");
52
- window.prepareHtmlForCapture();
53
-
54
- // 2. Determine selectors
55
- const htmlSelector = document.querySelector("#html-output .frame")
56
- ? "#html-output .frame"
57
- : "#html-output .sequence-diagram";
58
- const svgSelector = "#svg-output > svg";
59
-
60
- // 3. Screenshot HTML (CDP clip isolates the element — no need to hide siblings)
61
- console.log("[native-diff-ext] Taking HTML screenshot...");
62
- const htmlCapture = await screenshotOne(htmlSelector);
63
-
64
- if (htmlCapture.error) {
65
- console.error("[native-diff-ext] HTML screenshot failed:", htmlCapture.error);
66
- window.restoreHtmlAfterCapture();
67
- return;
68
- }
69
-
70
- // 4. Screenshot SVG
71
- console.log("[native-diff-ext] Taking SVG screenshot...");
72
- const svgCapture = await screenshotOne(svgSelector);
73
-
74
- // 5. Restore HTML chrome
75
- window.restoreHtmlAfterCapture();
76
-
77
- if (svgCapture.error) {
78
- console.error("[native-diff-ext] SVG screenshot failed:", svgCapture.error);
79
- return;
80
- }
81
-
82
- console.log("[native-diff-ext] Screenshots captured. Running diff...");
83
-
84
- // 6. Run diff via shared algorithm (exposed by compare-case.html)
85
- const result = await window.diffFromImages(htmlCapture.dataUrl, svgCapture.dataUrl);
86
- console.log("[native-diff-ext] Done!", result.pixelPct + "% pixel match", result.posPct + "% position-only match");
87
-
88
- // Post result back for icon-click flow
89
- window.postMessage({ type: "native-diff-result", result }, "*");
90
-
91
- // Batch mode: if __cr_cases is set in localStorage, save result and auto-advance
92
- try {
93
- const batchCases = localStorage.getItem("__cr_cases");
94
- if (batchCases) {
95
- const cases = JSON.parse(batchCases);
96
- const results = JSON.parse(localStorage.getItem("__cr_results") || "{}");
97
- const currentCase = new URLSearchParams(window.location.search).get("case");
98
- if (currentCase && !results[currentCase]) {
99
- // Get the DSL for this case from the page (exposed by compare-case.html)
100
- const dsl = window.__currentDSL || "";
101
-
102
- results[currentCase] = { score: result.pixelPct, posScore: result.posPct, dsl };
103
- localStorage.setItem("__cr_results", JSON.stringify(results));
104
- const doneCount = Object.keys(results).length;
105
- console.log(`[native-diff-ext] Batch: ${currentCase}=${result.pixelPct}% px / ${result.posPct}% pos (${doneCount}/${cases.length})`);
106
- if (doneCount < cases.length) {
107
- const idx = cases.indexOf(currentCase);
108
- if (idx >= 0 && idx + 1 < cases.length) {
109
- setTimeout(() => {
110
- window.location.href = `/e2e/tools/compare-case.html?case=${cases[idx + 1]}`;
111
- }, 200);
112
- }
113
- } else {
114
- const elapsed = ((Date.now() - parseInt(localStorage.getItem("__cr_start") || "0")) / 1000).toFixed(1);
115
- console.log(`[native-diff-ext] Batch DONE in ${elapsed}s`);
116
- // Save to IndexedDB for history tracking
117
- saveBatchToHistory(results, elapsed);
118
- // Clear batch vars so extension doesn't re-trigger on subsequent page loads
119
- localStorage.removeItem("__cr_cases");
120
- localStorage.removeItem("__cr_results");
121
- localStorage.removeItem("__cr_start");
122
- }
123
- }
124
- }
125
- } catch (e) { /* batch mode is optional, don't break normal flow */ }
126
- }
127
-
128
- // ---- IndexedDB history storage ----
129
- // Database: "canonical-history", store: "runs"
130
- // Each run: { timestamp, elapsed, cases: { name: { score, dsl } }, average }
131
-
132
- function openHistoryDB() {
133
- return new Promise((resolve, reject) => {
134
- const req = indexedDB.open("canonical-history", 1);
135
- req.onupgradeneeded = () => {
136
- const db = req.result;
137
- if (!db.objectStoreNames.contains("runs")) {
138
- db.createObjectStore("runs", { keyPath: "timestamp" });
139
- }
140
- };
141
- req.onsuccess = () => resolve(req.result);
142
- req.onerror = () => reject(req.error);
143
- });
144
- }
145
-
146
- async function saveBatchToHistory(results, elapsed) {
147
- try {
148
- const db = await openHistoryDB();
149
- const scores = Object.values(results).map(r => typeof r === "object" ? r.score : r);
150
- const posScores = Object.values(results).map(r => {
151
- if (typeof r === "object" && typeof r.posScore === "number") return r.posScore;
152
- return typeof r === "object" ? r.score : r;
153
- });
154
- const average = scores.length > 0
155
- ? parseFloat((scores.reduce((a, b) => a + b, 0) / scores.length).toFixed(1))
156
- : 0;
157
- const averagePos = posScores.length > 0
158
- ? parseFloat((posScores.reduce((a, b) => a + b, 0) / posScores.length).toFixed(1))
159
- : average;
160
-
161
- const record = {
162
- timestamp: new Date().toISOString(),
163
- elapsed,
164
- cases: results,
165
- average,
166
- averagePos,
167
- caseCount: Object.keys(results).length,
168
- };
169
-
170
- const tx = db.transaction("runs", "readwrite");
171
- tx.objectStore("runs").add(record);
172
- await new Promise((resolve, reject) => {
173
- tx.oncomplete = resolve;
174
- tx.onerror = () => reject(tx.error);
175
- });
176
-
177
- console.log(`[native-diff-ext] History saved: avg=${average}% px / ${averagePos}% pos, ${Object.keys(results).length} cases`);
178
- db.close();
179
- } catch (e) {
180
- console.error("[native-diff-ext] Failed to save history:", e);
181
- }
182
- }
183
-
184
- // Expose history reader for dashboard pages
185
- window.__getCanonicalHistory = async function() {
186
- const db = await openHistoryDB();
187
- const tx = db.transaction("runs", "readonly");
188
- const store = tx.objectStore("runs");
189
- return new Promise((resolve, reject) => {
190
- const req = store.getAll();
191
- req.onsuccess = () => { db.close(); resolve(req.result); };
192
- req.onerror = () => { db.close(); reject(req.error); };
193
- });
194
- };