mcp-react-toolkit 1.3.0 → 1.4.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 (268) hide show
  1. package/README.md +28 -28
  2. package/node_modules/@mcp-showcase/shared/build/McpServerBase.d.ts +13 -0
  3. package/node_modules/@mcp-showcase/shared/build/McpServerBase.d.ts.map +1 -1
  4. package/node_modules/@mcp-showcase/shared/build/McpServerBase.js +40 -0
  5. package/node_modules/@mcp-showcase/shared/build/McpServerBase.js.map +1 -1
  6. package/node_modules/@mcp-showcase/shared/build/types.d.ts +11 -0
  7. package/node_modules/@mcp-showcase/shared/build/types.d.ts.map +1 -1
  8. package/node_modules/@mcp-showcase/shared/src/McpServerBase.ts +45 -0
  9. package/node_modules/@mcp-showcase/shared/src/types.ts +12 -0
  10. package/node_modules/@mcp-showcase/ui-kit/README.md +30 -0
  11. package/node_modules/@mcp-showcase/ui-kit/build/components.d.ts +10 -0
  12. package/node_modules/@mcp-showcase/ui-kit/build/components.d.ts.map +1 -0
  13. package/node_modules/@mcp-showcase/ui-kit/build/components.js +177 -0
  14. package/node_modules/@mcp-showcase/ui-kit/build/components.js.map +1 -0
  15. package/node_modules/@mcp-showcase/ui-kit/build/escape.d.ts +3 -0
  16. package/node_modules/@mcp-showcase/ui-kit/build/escape.d.ts.map +1 -0
  17. package/node_modules/@mcp-showcase/ui-kit/build/escape.js +10 -0
  18. package/node_modules/@mcp-showcase/ui-kit/build/escape.js.map +1 -0
  19. package/node_modules/@mcp-showcase/ui-kit/build/fixture.d.ts +4 -0
  20. package/node_modules/@mcp-showcase/ui-kit/build/fixture.d.ts.map +1 -0
  21. package/node_modules/@mcp-showcase/ui-kit/build/fixture.js +52 -0
  22. package/node_modules/@mcp-showcase/ui-kit/build/fixture.js.map +1 -0
  23. package/node_modules/@mcp-showcase/ui-kit/build/generate-template.d.ts +2 -0
  24. package/node_modules/@mcp-showcase/ui-kit/build/generate-template.d.ts.map +1 -0
  25. package/node_modules/@mcp-showcase/ui-kit/build/generate-template.js +19 -0
  26. package/node_modules/@mcp-showcase/ui-kit/build/generate-template.js.map +1 -0
  27. package/node_modules/@mcp-showcase/ui-kit/build/index.d.ts +6 -0
  28. package/node_modules/@mcp-showcase/ui-kit/build/index.d.ts.map +1 -0
  29. package/node_modules/@mcp-showcase/ui-kit/build/index.js +7 -0
  30. package/node_modules/@mcp-showcase/ui-kit/build/index.js.map +1 -0
  31. package/node_modules/@mcp-showcase/ui-kit/build/render.d.ts +3 -0
  32. package/node_modules/@mcp-showcase/ui-kit/build/render.d.ts.map +1 -0
  33. package/node_modules/@mcp-showcase/ui-kit/build/render.js +38 -0
  34. package/node_modules/@mcp-showcase/ui-kit/build/render.js.map +1 -0
  35. package/node_modules/@mcp-showcase/ui-kit/build/result-components.d.ts +9 -0
  36. package/node_modules/@mcp-showcase/ui-kit/build/result-components.d.ts.map +1 -0
  37. package/node_modules/@mcp-showcase/ui-kit/build/result-components.js +105 -0
  38. package/node_modules/@mcp-showcase/ui-kit/build/result-components.js.map +1 -0
  39. package/node_modules/@mcp-showcase/ui-kit/build/result-fixture.d.ts +4 -0
  40. package/node_modules/@mcp-showcase/ui-kit/build/result-fixture.d.ts.map +1 -0
  41. package/node_modules/@mcp-showcase/ui-kit/build/result-fixture.js +39 -0
  42. package/node_modules/@mcp-showcase/ui-kit/build/result-fixture.js.map +1 -0
  43. package/node_modules/@mcp-showcase/ui-kit/build/result-render.d.ts +3 -0
  44. package/node_modules/@mcp-showcase/ui-kit/build/result-render.d.ts.map +1 -0
  45. package/node_modules/@mcp-showcase/ui-kit/build/result-render.js +37 -0
  46. package/node_modules/@mcp-showcase/ui-kit/build/result-render.js.map +1 -0
  47. package/node_modules/@mcp-showcase/ui-kit/build/result-runtime.d.ts +2 -0
  48. package/node_modules/@mcp-showcase/ui-kit/build/result-runtime.d.ts.map +1 -0
  49. package/node_modules/@mcp-showcase/ui-kit/build/result-runtime.js +72 -0
  50. package/node_modules/@mcp-showcase/ui-kit/build/result-runtime.js.map +1 -0
  51. package/node_modules/@mcp-showcase/ui-kit/build/runtime.d.ts +2 -0
  52. package/node_modules/@mcp-showcase/ui-kit/build/runtime.d.ts.map +1 -0
  53. package/node_modules/@mcp-showcase/ui-kit/build/runtime.js +221 -0
  54. package/node_modules/@mcp-showcase/ui-kit/build/runtime.js.map +1 -0
  55. package/node_modules/@mcp-showcase/ui-kit/build/theme.d.ts +2 -0
  56. package/node_modules/@mcp-showcase/ui-kit/build/theme.d.ts.map +1 -0
  57. package/node_modules/@mcp-showcase/ui-kit/build/theme.js +278 -0
  58. package/node_modules/@mcp-showcase/ui-kit/build/theme.js.map +1 -0
  59. package/node_modules/@mcp-showcase/ui-kit/build/types.d.ts +113 -0
  60. package/node_modules/@mcp-showcase/ui-kit/build/types.d.ts.map +1 -0
  61. package/node_modules/@mcp-showcase/ui-kit/build/types.js +35 -0
  62. package/node_modules/@mcp-showcase/ui-kit/build/types.js.map +1 -0
  63. package/node_modules/@mcp-showcase/ui-kit/demo/index.html +653 -0
  64. package/node_modules/@mcp-showcase/ui-kit/demo/result.html +445 -0
  65. package/node_modules/@mcp-showcase/ui-kit/package.json +19 -0
  66. package/node_modules/@mcp-showcase/ui-kit/src/components.ts +191 -0
  67. package/node_modules/@mcp-showcase/ui-kit/src/escape.ts +9 -0
  68. package/node_modules/@mcp-showcase/ui-kit/src/fixture.ts +53 -0
  69. package/node_modules/@mcp-showcase/ui-kit/src/generate-template.ts +21 -0
  70. package/node_modules/@mcp-showcase/ui-kit/src/index.test.ts +72 -0
  71. package/node_modules/@mcp-showcase/ui-kit/src/index.ts +6 -0
  72. package/node_modules/@mcp-showcase/ui-kit/src/render.ts +48 -0
  73. package/node_modules/@mcp-showcase/ui-kit/src/result-components.ts +112 -0
  74. package/node_modules/@mcp-showcase/ui-kit/src/result-fixture.ts +40 -0
  75. package/node_modules/@mcp-showcase/ui-kit/src/result-render.test.ts +47 -0
  76. package/node_modules/@mcp-showcase/ui-kit/src/result-render.ts +47 -0
  77. package/node_modules/@mcp-showcase/ui-kit/src/result-runtime.ts +72 -0
  78. package/node_modules/@mcp-showcase/ui-kit/src/runtime.smoke.test.ts +103 -0
  79. package/node_modules/@mcp-showcase/ui-kit/src/runtime.ts +221 -0
  80. package/node_modules/@mcp-showcase/ui-kit/src/theme.ts +278 -0
  81. package/node_modules/@mcp-showcase/ui-kit/src/types.ts +140 -0
  82. package/node_modules/@mcp-showcase/ui-kit/tsconfig.json +9 -0
  83. package/package.json +16 -5
  84. package/tools/accessibility-checker/build/health-report.d.ts +27 -0
  85. package/tools/accessibility-checker/build/health-report.d.ts.map +1 -0
  86. package/tools/accessibility-checker/build/health-report.js +140 -0
  87. package/tools/accessibility-checker/build/health-report.js.map +1 -0
  88. package/tools/accessibility-checker/build/index.js +7 -1
  89. package/tools/accessibility-checker/build/index.js.map +1 -1
  90. package/tools/accessibility-checker/package.json +1 -0
  91. package/tools/code-modernizer/build/index.js +60 -44
  92. package/tools/code-modernizer/build/index.js.map +1 -1
  93. package/tools/code-modernizer/build/result-report.d.ts +24 -0
  94. package/tools/code-modernizer/build/result-report.d.ts.map +1 -0
  95. package/tools/code-modernizer/build/result-report.js +101 -0
  96. package/tools/code-modernizer/build/result-report.js.map +1 -0
  97. package/tools/code-modernizer/package.json +2 -0
  98. package/tools/component-factory/build/index.js +7 -1
  99. package/tools/component-factory/build/index.js.map +1 -1
  100. package/tools/component-factory/build/result-report.d.ts +11 -0
  101. package/tools/component-factory/build/result-report.d.ts.map +1 -0
  102. package/tools/component-factory/build/result-report.js +104 -0
  103. package/tools/component-factory/build/result-report.js.map +1 -0
  104. package/tools/component-factory/package.json +1 -0
  105. package/tools/component-fixer/build/index.js +6 -6
  106. package/tools/component-fixer/build/index.js.map +1 -1
  107. package/tools/component-fixer/build/result-report.d.ts +37 -0
  108. package/tools/component-fixer/build/result-report.d.ts.map +1 -0
  109. package/tools/component-fixer/build/result-report.js +106 -0
  110. package/tools/component-fixer/build/result-report.js.map +1 -0
  111. package/tools/component-fixer/package.json +1 -0
  112. package/tools/component-reviewer/build/health-report.d.ts +37 -0
  113. package/tools/component-reviewer/build/health-report.d.ts.map +1 -0
  114. package/tools/component-reviewer/build/health-report.js +116 -0
  115. package/tools/component-reviewer/build/health-report.js.map +1 -0
  116. package/tools/component-reviewer/build/index.d.ts.map +1 -1
  117. package/tools/component-reviewer/build/index.js +7 -6
  118. package/tools/component-reviewer/build/index.js.map +1 -1
  119. package/tools/component-reviewer/package.json +1 -0
  120. package/tools/dep-auditor/build/health-report.d.ts +16 -0
  121. package/tools/dep-auditor/build/health-report.d.ts.map +1 -0
  122. package/tools/dep-auditor/build/health-report.js +187 -0
  123. package/tools/dep-auditor/build/health-report.js.map +1 -0
  124. package/tools/dep-auditor/build/index.d.ts.map +1 -1
  125. package/tools/dep-auditor/build/index.js +7 -1
  126. package/tools/dep-auditor/build/index.js.map +1 -1
  127. package/tools/dep-auditor/package.json +1 -0
  128. package/tools/generate-tests/build/index.js +8 -1
  129. package/tools/generate-tests/build/index.js.map +1 -1
  130. package/tools/generate-tests/build/result-report.d.ts +14 -0
  131. package/tools/generate-tests/build/result-report.d.ts.map +1 -0
  132. package/tools/generate-tests/build/result-report.js +124 -0
  133. package/tools/generate-tests/build/result-report.js.map +1 -0
  134. package/tools/generate-tests/package.json +1 -0
  135. package/tools/legacy-analyzer/build/health-report.d.ts +4 -0
  136. package/tools/legacy-analyzer/build/health-report.d.ts.map +1 -0
  137. package/tools/legacy-analyzer/build/health-report.js +164 -0
  138. package/tools/legacy-analyzer/build/health-report.js.map +1 -0
  139. package/tools/legacy-analyzer/build/index.js +15 -1
  140. package/tools/legacy-analyzer/build/index.js.map +1 -1
  141. package/tools/legacy-analyzer/build/tools/01-detect-project-tech.d.ts.map +1 -1
  142. package/tools/legacy-analyzer/build/tools/01-detect-project-tech.js +3 -8
  143. package/tools/legacy-analyzer/build/tools/01-detect-project-tech.js.map +1 -1
  144. package/tools/legacy-analyzer/build/tools/05-analyze-api-layer.d.ts.map +1 -1
  145. package/tools/legacy-analyzer/build/tools/05-analyze-api-layer.js +25 -3
  146. package/tools/legacy-analyzer/build/tools/05-analyze-api-layer.js.map +1 -1
  147. package/tools/legacy-analyzer/package.json +1 -0
  148. package/tools/lighthouse-runner/build/health-report.d.ts +14 -0
  149. package/tools/lighthouse-runner/build/health-report.d.ts.map +1 -0
  150. package/tools/lighthouse-runner/build/health-report.js +138 -0
  151. package/tools/lighthouse-runner/build/health-report.js.map +1 -0
  152. package/tools/lighthouse-runner/build/index.d.ts.map +1 -1
  153. package/tools/lighthouse-runner/build/index.js +7 -1
  154. package/tools/lighthouse-runner/build/index.js.map +1 -1
  155. package/tools/lighthouse-runner/package.json +1 -0
  156. package/tools/monorepo-manager/build/index.js +9 -1
  157. package/tools/monorepo-manager/build/index.js.map +1 -1
  158. package/tools/monorepo-manager/build/result-report.d.ts +20 -0
  159. package/tools/monorepo-manager/build/result-report.d.ts.map +1 -0
  160. package/tools/monorepo-manager/build/result-report.js +84 -0
  161. package/tools/monorepo-manager/build/result-report.js.map +1 -0
  162. package/tools/monorepo-manager/package.json +1 -0
  163. package/tools/performance-audit/build/health-report.d.ts +30 -0
  164. package/tools/performance-audit/build/health-report.d.ts.map +1 -0
  165. package/tools/performance-audit/build/health-report.js +152 -0
  166. package/tools/performance-audit/build/health-report.js.map +1 -0
  167. package/tools/performance-audit/build/index.d.ts.map +1 -1
  168. package/tools/performance-audit/build/index.js +7 -1
  169. package/tools/performance-audit/build/index.js.map +1 -1
  170. package/tools/performance-audit/package.json +1 -0
  171. package/tools/quality-pipeline/build/health-report.d.ts +11 -0
  172. package/tools/quality-pipeline/build/health-report.d.ts.map +1 -0
  173. package/tools/quality-pipeline/build/health-report.js +137 -0
  174. package/tools/quality-pipeline/build/health-report.js.map +1 -0
  175. package/tools/quality-pipeline/build/index.js +7 -1
  176. package/tools/quality-pipeline/build/index.js.map +1 -1
  177. package/tools/quality-pipeline/package.json +1 -0
  178. package/tools/render-analyzer/build/health-report.d.ts +33 -0
  179. package/tools/render-analyzer/build/health-report.d.ts.map +1 -0
  180. package/tools/render-analyzer/build/health-report.js +142 -0
  181. package/tools/render-analyzer/build/health-report.js.map +1 -0
  182. package/tools/render-analyzer/build/index.d.ts.map +1 -1
  183. package/tools/render-analyzer/build/index.js +7 -1
  184. package/tools/render-analyzer/build/index.js.map +1 -1
  185. package/tools/render-analyzer/package.json +1 -0
  186. package/tools/shared/build/McpServerBase.d.ts +13 -0
  187. package/tools/shared/build/McpServerBase.d.ts.map +1 -1
  188. package/tools/shared/build/McpServerBase.js +40 -0
  189. package/tools/shared/build/McpServerBase.js.map +1 -1
  190. package/tools/shared/build/types.d.ts +11 -0
  191. package/tools/shared/build/types.d.ts.map +1 -1
  192. package/tools/storybook-generator/build/index.d.ts.map +1 -1
  193. package/tools/storybook-generator/build/index.js +9 -1
  194. package/tools/storybook-generator/build/index.js.map +1 -1
  195. package/tools/storybook-generator/build/result-report.d.ts +22 -0
  196. package/tools/storybook-generator/build/result-report.d.ts.map +1 -0
  197. package/tools/storybook-generator/build/result-report.js +77 -0
  198. package/tools/storybook-generator/build/result-report.js.map +1 -0
  199. package/tools/storybook-generator/package.json +1 -0
  200. package/tools/test-gap-analyzer/build/health-report.d.ts +34 -0
  201. package/tools/test-gap-analyzer/build/health-report.d.ts.map +1 -0
  202. package/tools/test-gap-analyzer/build/health-report.js +190 -0
  203. package/tools/test-gap-analyzer/build/health-report.js.map +1 -0
  204. package/tools/test-gap-analyzer/build/index.d.ts.map +1 -1
  205. package/tools/test-gap-analyzer/build/index.js +7 -1
  206. package/tools/test-gap-analyzer/build/index.js.map +1 -1
  207. package/tools/test-gap-analyzer/package.json +1 -0
  208. package/tools/typescript-enforcer/build/health-report.d.ts +33 -0
  209. package/tools/typescript-enforcer/build/health-report.d.ts.map +1 -0
  210. package/tools/typescript-enforcer/build/health-report.js +143 -0
  211. package/tools/typescript-enforcer/build/health-report.js.map +1 -0
  212. package/tools/typescript-enforcer/build/index.js +6 -1
  213. package/tools/typescript-enforcer/build/index.js.map +1 -1
  214. package/tools/typescript-enforcer/package.json +1 -0
  215. package/tools/ui-kit/README.md +30 -0
  216. package/tools/ui-kit/build/components.d.ts +10 -0
  217. package/tools/ui-kit/build/components.d.ts.map +1 -0
  218. package/tools/ui-kit/build/components.js +177 -0
  219. package/tools/ui-kit/build/components.js.map +1 -0
  220. package/tools/ui-kit/build/escape.d.ts +3 -0
  221. package/tools/ui-kit/build/escape.d.ts.map +1 -0
  222. package/tools/ui-kit/build/escape.js +10 -0
  223. package/tools/ui-kit/build/escape.js.map +1 -0
  224. package/tools/ui-kit/build/fixture.d.ts +4 -0
  225. package/tools/ui-kit/build/fixture.d.ts.map +1 -0
  226. package/tools/ui-kit/build/fixture.js +52 -0
  227. package/tools/ui-kit/build/fixture.js.map +1 -0
  228. package/tools/ui-kit/build/generate-template.d.ts +2 -0
  229. package/tools/ui-kit/build/generate-template.d.ts.map +1 -0
  230. package/tools/ui-kit/build/generate-template.js +19 -0
  231. package/tools/ui-kit/build/generate-template.js.map +1 -0
  232. package/tools/ui-kit/build/index.d.ts +6 -0
  233. package/tools/ui-kit/build/index.d.ts.map +1 -0
  234. package/tools/ui-kit/build/index.js +7 -0
  235. package/tools/ui-kit/build/index.js.map +1 -0
  236. package/tools/ui-kit/build/render.d.ts +3 -0
  237. package/tools/ui-kit/build/render.d.ts.map +1 -0
  238. package/tools/ui-kit/build/render.js +38 -0
  239. package/tools/ui-kit/build/render.js.map +1 -0
  240. package/tools/ui-kit/build/result-components.d.ts +9 -0
  241. package/tools/ui-kit/build/result-components.d.ts.map +1 -0
  242. package/tools/ui-kit/build/result-components.js +105 -0
  243. package/tools/ui-kit/build/result-components.js.map +1 -0
  244. package/tools/ui-kit/build/result-fixture.d.ts +4 -0
  245. package/tools/ui-kit/build/result-fixture.d.ts.map +1 -0
  246. package/tools/ui-kit/build/result-fixture.js +39 -0
  247. package/tools/ui-kit/build/result-fixture.js.map +1 -0
  248. package/tools/ui-kit/build/result-render.d.ts +3 -0
  249. package/tools/ui-kit/build/result-render.d.ts.map +1 -0
  250. package/tools/ui-kit/build/result-render.js +37 -0
  251. package/tools/ui-kit/build/result-render.js.map +1 -0
  252. package/tools/ui-kit/build/result-runtime.d.ts +2 -0
  253. package/tools/ui-kit/build/result-runtime.d.ts.map +1 -0
  254. package/tools/ui-kit/build/result-runtime.js +72 -0
  255. package/tools/ui-kit/build/result-runtime.js.map +1 -0
  256. package/tools/ui-kit/build/runtime.d.ts +2 -0
  257. package/tools/ui-kit/build/runtime.d.ts.map +1 -0
  258. package/tools/ui-kit/build/runtime.js +221 -0
  259. package/tools/ui-kit/build/runtime.js.map +1 -0
  260. package/tools/ui-kit/build/theme.d.ts +2 -0
  261. package/tools/ui-kit/build/theme.d.ts.map +1 -0
  262. package/tools/ui-kit/build/theme.js +278 -0
  263. package/tools/ui-kit/build/theme.js.map +1 -0
  264. package/tools/ui-kit/build/types.d.ts +113 -0
  265. package/tools/ui-kit/build/types.d.ts.map +1 -0
  266. package/tools/ui-kit/build/types.js +35 -0
  267. package/tools/ui-kit/build/types.js.map +1 -0
  268. package/tools/ui-kit/package.json +19 -0
@@ -0,0 +1,47 @@
1
+ // ============================================================================
2
+ // renderResultHTML — one self-contained HTML document for an action/result
3
+ // report (scaffold, fix, convert, generate). Shares styles + chrome with the
4
+ // health report; uses the result-specific runtime.
5
+ // ============================================================================
6
+
7
+ import { STYLES } from "./theme.js";
8
+ import { RESULT_RUNTIME } from "./result-runtime.js";
9
+ import { esc } from "./escape.js";
10
+ import {
11
+ buildResultHeader,
12
+ buildResultHero,
13
+ buildChanges,
14
+ buildSections,
15
+ buildNextSteps,
16
+ buildChrome,
17
+ } from "./result-components.js";
18
+ import { ResultReport } from "./types.js";
19
+
20
+ function embedJson(data: unknown): string {
21
+ return JSON.stringify(data).replace(/</g, "\\u003c");
22
+ }
23
+
24
+ export function renderResultHTML(report: ResultReport): string {
25
+ return `<!doctype html>
26
+ <html lang="en" data-theme="light">
27
+ <head>
28
+ <meta charset="utf-8"/>
29
+ <meta name="viewport" content="width=device-width,initial-scale=1"/>
30
+ <title>${esc(report.meta.title)} — ${esc(report.meta.target)}</title>
31
+ <style>${STYLES}</style>
32
+ </head>
33
+ <body>
34
+ <div class="wrap">
35
+ ${buildResultHeader(report)}
36
+ ${buildResultHero(report)}
37
+ ${buildNextSteps(report)}
38
+ ${buildChanges(report)}
39
+ ${buildSections(report)}
40
+ <div class="foot-note">${esc(report.meta.tool)} · generated ${esc(report.meta.generatedAt)} · mcp-react-toolkit</div>
41
+ </div>
42
+ ${buildChrome()}
43
+ <script id="report-data" type="application/json">${embedJson(report)}</script>
44
+ <script>${RESULT_RUNTIME}</script>
45
+ </body>
46
+ </html>`;
47
+ }
@@ -0,0 +1,72 @@
1
+ // ============================================================================
2
+ // CLIENT RUNTIME for the RESULT view. Dependency-free browser JS.
3
+ // Theme toggle, change-diff drawer, next-step actions (host postMessage /
4
+ // browser clipboard fallback), markdown export. Mirrors the health runtime's
5
+ // DataAdapter behaviour.
6
+ // ============================================================================
7
+
8
+ export const RESULT_RUNTIME = String.raw`(function(){
9
+ "use strict";
10
+ function readEmbedded(){ var el=document.getElementById('report-data'); if(!el) return null;
11
+ try{ return JSON.parse(el.textContent||'null'); }catch(e){ return null; } }
12
+ function readUrl(){ try{ var p=new URLSearchParams(location.search).get('data'); if(!p) return null;
13
+ var bytes=Uint8Array.from(atob(p), function(c){ return c.charCodeAt(0); });
14
+ return JSON.parse(new TextDecoder().decode(bytes)); }catch(e){ return null; } }
15
+ var DATA = window.__REPORT__ || readEmbedded() || readUrl();
16
+ if(!DATA){ return; }
17
+ var EMBEDDED=false; try{ EMBEDDED = window.self !== window.top; }catch(e){ EMBEDDED=true; }
18
+ var HOST_ORIGIN = window.__MCP_HOST_ORIGIN__ || '*';
19
+ var root=document.documentElement, lastFocus=null;
20
+ function E(s){ return String(s==null?'':s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;'); }
21
+ function $(id){ return document.getElementById(id); }
22
+
23
+ // theme
24
+ function store(k,v){ try{ if(v==null) return localStorage.getItem(k); localStorage.setItem(k,v); }catch(e){ return null; } }
25
+ function applyTheme(t){ root.setAttribute('data-theme',t);
26
+ var sun=document.querySelector('.theme-sun'), moon=document.querySelector('.theme-moon');
27
+ if(sun&&moon){ sun.hidden=t!=='dark'; moon.hidden=t==='dark'; } }
28
+ var prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
29
+ applyTheme(store('mcp-ui-theme') || (prefersDark?'dark':'light'));
30
+ var tt=$('theme-toggle'); if(tt) tt.addEventListener('click', function(){
31
+ var n=root.getAttribute('data-theme')==='dark'?'light':'dark'; applyTheme(n); store('mcp-ui-theme',n); });
32
+
33
+ root.setAttribute('data-band', DATA.status==='partial'?'warn':'good');
34
+
35
+ // actions
36
+ var actionsById={}; (DATA.nextActions||[]).forEach(function(a){ actionsById[a.id]=a; });
37
+ function copy(text){ try{ if(navigator.clipboard&&navigator.clipboard.writeText) return navigator.clipboard.writeText(text); }catch(e){}
38
+ try{ var t=document.createElement('textarea'); t.value=text; document.body.appendChild(t); t.select(); document.execCommand('copy'); document.body.removeChild(t); }catch(e){} }
39
+ var toastTimer; function toast(msg){ var el=$('toast'); if(!el) return; el.textContent=msg; el.classList.add('show');
40
+ clearTimeout(toastTimer); toastTimer=setTimeout(function(){ el.classList.remove('show'); },2600); }
41
+ function runAction(id){ var a=actionsById[id]; if(!a) return;
42
+ if(a.kind==='link'){ if(!/^https?:\/\//i.test(a.href||'')){ toast('Unsafe link blocked'); return; } window.open(a.href,'_blank','noopener'); return; }
43
+ if(EMBEDDED){ var msg = a.kind==='tool' ? { type:'tool', messageId:'mcp-ui-'+Date.now(), payload:{ toolName:a.tool, params:a.params||{} } } : { type:'prompt', messageId:'mcp-ui-'+Date.now(), payload:{ prompt:a.prompt } };
44
+ try{ window.parent.postMessage(msg,HOST_ORIGIN); toast(a.kind==='tool'?('Running '+a.tool+' …'):'Sent to agent ✓'); return; }catch(e){} }
45
+ var text = a.kind==='tool' ? (a.fallback||('Run '+a.tool)) : (a.prompt||a.fallback||a.label); copy(text); toast('Copied — paste into your agent'); }
46
+ document.addEventListener('click', function(e){ var el=e.target.closest && e.target.closest('[data-action]');
47
+ if(el){ e.preventDefault(); runAction(el.getAttribute('data-action')); } });
48
+
49
+ // change diff drawer
50
+ var drawer=$('drawer'), scrim=$('scrim');
51
+ function renderDiff(diff){ return diff.split('\n').map(function(l){
52
+ var c = l.charAt(0)==='+' ? 'dl-add' : l.charAt(0)==='-' ? 'dl-del' : 'dl-ctx'; return '<span class="'+c+'">'+E(l)+'</span>'; }).join('\n'); }
53
+ function openChange(i){ var c=(DATA.changes||[])[i]; if(!c||!c.diff) return; lastFocus=document.activeElement;
54
+ $('d-title').textContent=c.path;
55
+ $('d-body').innerHTML='<div class="kv" style="margin-bottom:14px"><span class="pair"><b>'+E(c.kind)+'</b></span>'+(c.language?'<span class="pair">'+E(c.language)+'</span>':'')+'</div><div class="diff">'+renderDiff(c.diff)+'</div>';
56
+ $('d-acts').innerHTML='';
57
+ drawer.classList.add('open'); scrim.classList.add('open'); var dc=$('d-close'); if(dc) dc.focus(); }
58
+ function closeDrawer(){ drawer.classList.remove('open'); scrim.classList.remove('open'); if(lastFocus&&lastFocus.focus){ lastFocus.focus(); lastFocus=null; } }
59
+ document.addEventListener('click', function(e){ var r=e.target.closest && e.target.closest('.change-row[data-change]'); if(r) openChange(parseInt(r.getAttribute('data-change'),10)); });
60
+ document.addEventListener('keydown', function(e){ if((e.key==='Enter'||e.key===' ')){ var r=e.target.closest && e.target.closest('.change-row[data-change]'); if(r){ e.preventDefault(); openChange(parseInt(r.getAttribute('data-change'),10)); } } });
61
+ if(scrim) scrim.addEventListener('click', closeDrawer);
62
+ var dcl=$('d-close'); if(dcl) dcl.addEventListener('click', closeDrawer);
63
+ document.addEventListener('keydown', function(e){ if(e.key==='Escape') closeDrawer(); });
64
+
65
+ // export
66
+ var ex=$('export-btn'); function md(s){ return String(s==null?'':s).replace(/[\r\n]+/g,' '); }
67
+ if(ex) ex.addEventListener('click', function(){ var L=[]; L.push('# '+md(DATA.meta.title)); L.push('');
68
+ L.push('**'+md(DATA.headline)+'** ('+DATA.status+') '); L.push('**Target:** '+md(DATA.meta.target)); L.push('');
69
+ if((DATA.changes||[]).length){ L.push('## Changes'); (DATA.changes||[]).forEach(function(c){ L.push('- ['+c.kind.toUpperCase()+'] '+md(c.path)+(c.summary?(' — '+md(c.summary)):'')); }); L.push(''); }
70
+ (DATA.sections||[]).forEach(function(s){ L.push('## '+md(s.title)); s.items.forEach(function(it){ L.push('- '+md(it.title)+(it.detail?(' — '+md(it.detail)):'')); }); L.push(''); });
71
+ copy(L.join('\n')); toast('Summary copied as Markdown ✓'); });
72
+ })();`;
@@ -0,0 +1,103 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import { JSDOM } from "jsdom";
3
+ import { renderReportHTML } from "./render.js";
4
+ import { SAMPLE_REPORT } from "./fixture.js";
5
+ import type { HealthReport } from "./types.js";
6
+
7
+ function bootHtml(html: string): { window: Window & typeof globalThis; doc: Document } {
8
+ const dom = new JSDOM(html, {
9
+ url: "https://example.com/",
10
+ runScripts: "dangerously",
11
+ pretendToBeVisual: true,
12
+ });
13
+ // @ts-expect-error jsdom window is a structural match for the test
14
+ return { window: dom.window, doc: dom.window.document };
15
+ }
16
+ function boot(): { window: Window & typeof globalThis; doc: Document } {
17
+ return bootHtml(renderReportHTML(SAMPLE_REPORT));
18
+ }
19
+
20
+ describe("runtime (jsdom)", () => {
21
+ let doc: Document;
22
+ let window: Window & typeof globalThis;
23
+ beforeEach(() => {
24
+ const b = boot();
25
+ doc = b.doc;
26
+ window = b.window;
27
+ });
28
+
29
+ it("hydrates the triage table from embedded data", () => {
30
+ const rows = doc.querySelectorAll("#rows tr");
31
+ expect(rows.length).toBe(SAMPLE_REPORT.issues.length);
32
+ });
33
+
34
+ it("defaults to light theme and toggles to dark", () => {
35
+ expect(doc.documentElement.getAttribute("data-theme")).toBe("light");
36
+ (doc.getElementById("theme-toggle") as HTMLButtonElement).click();
37
+ expect(doc.documentElement.getAttribute("data-theme")).toBe("dark");
38
+ });
39
+
40
+ it("sets the accent band from the score", () => {
41
+ // score 62 -> warn band
42
+ expect(doc.documentElement.getAttribute("data-band")).toBe("warn");
43
+ });
44
+
45
+ it("filters rows by severity", () => {
46
+ const critical = SAMPLE_REPORT.issues.filter((i) => i.severity === "critical").length;
47
+ const btn = doc.querySelector('.filter button[data-sev="critical"]') as HTMLButtonElement;
48
+ btn.click();
49
+ expect(doc.querySelectorAll("#rows tr").length).toBe(critical);
50
+ });
51
+
52
+ it("searches across title and file", () => {
53
+ const search = doc.getElementById("search") as HTMLInputElement;
54
+ search.value = "Dashboard.jsx";
55
+ search.dispatchEvent(new window.Event("input"));
56
+ const rows = doc.querySelectorAll("#rows tr");
57
+ expect(rows.length).toBe(1);
58
+ });
59
+
60
+ it("opens the drawer when a row is clicked", () => {
61
+ const row = doc.querySelector("#rows tr") as HTMLTableRowElement;
62
+ row.click();
63
+ expect(doc.getElementById("drawer")?.classList.contains("open")).toBe(true);
64
+ expect(doc.getElementById("d-title")?.textContent).toBeTruthy();
65
+ });
66
+
67
+ it("dispatches an MCP tool message when embedded in a host", () => {
68
+ // simulate iframe embedding by making self !== top is not possible post-boot;
69
+ // instead assert the action handler runs and surfaces a toast (fallback path).
70
+ const action = doc.querySelector('[data-action="fix-god"]') as HTMLButtonElement;
71
+ action.click();
72
+ const toast = doc.getElementById("toast");
73
+ expect(toast?.classList.contains("show")).toBe(true);
74
+ });
75
+
76
+ it("restores focus to the triggering row when the drawer closes", () => {
77
+ const row = doc.querySelector("#rows tr") as HTMLTableRowElement;
78
+ row.focus();
79
+ row.click();
80
+ (doc.getElementById("d-close") as HTMLButtonElement).click();
81
+ expect(doc.activeElement).toBe(row);
82
+ });
83
+
84
+ it("escapes malicious issue data injected into the runtime-built table (no XSS)", () => {
85
+ const evil: HealthReport = {
86
+ ...SAMPLE_REPORT,
87
+ issues: [
88
+ {
89
+ id: "x1",
90
+ category: "components",
91
+ severity: "high",
92
+ title: `<img src=x onerror="window.__xss=1">`,
93
+ file: `src/<script>window.__xss=1</script>.tsx`,
94
+ },
95
+ ],
96
+ };
97
+ const { window, doc } = bootHtml(renderReportHTML(evil));
98
+ expect((window as unknown as { __xss?: number }).__xss).toBeUndefined();
99
+ // the malicious markup must be inert text, not real elements
100
+ expect(doc.querySelectorAll("#rows img").length).toBe(0);
101
+ expect(doc.querySelector("#rows .cell-title")?.textContent).toContain("<img");
102
+ });
103
+ });
@@ -0,0 +1,221 @@
1
+ // ============================================================================
2
+ // CLIENT RUNTIME — plain browser JS emitted as a string into the single-file
3
+ // HTML. No build step, no framework. Handles: theme, gauge/counter animation,
4
+ // triage table (sort/filter/search), drawer, agentic actions and export.
5
+ //
6
+ // DataAdapter: detects surface at runtime.
7
+ // • Embedded in an iframe (MCP host) -> postMessage MCP-UI actions to parent.
8
+ // • Standalone browser tab -> graceful fallback (copy prompt).
9
+ // ============================================================================
10
+
11
+ export const RUNTIME = String.raw`(function(){
12
+ "use strict";
13
+ function readEmbedded(){
14
+ var el=document.getElementById('report-data');
15
+ if(!el) return null; try{ return JSON.parse(el.textContent||'null'); }catch(e){ return null; }
16
+ }
17
+ function readUrl(){
18
+ try{ var p=new URLSearchParams(location.search).get('data'); if(!p) return null;
19
+ var bytes=Uint8Array.from(atob(p), function(c){ return c.charCodeAt(0); });
20
+ return JSON.parse(new TextDecoder().decode(bytes)); }catch(e){ return null; }
21
+ }
22
+ var DATA = window.__REPORT__ || readEmbedded() || readUrl();
23
+ if(!DATA){ return; }
24
+ var EMBEDDED = false; try{ EMBEDDED = window.self !== window.top; }catch(e){ EMBEDDED = true; }
25
+ var HOST_ORIGIN = window.__MCP_HOST_ORIGIN__ || '*'; // host may inject its origin to harden postMessage
26
+ var lastFocus = null;
27
+ var root = document.documentElement;
28
+ var SEV_RANK = { critical:4, high:3, medium:2, low:1 };
29
+
30
+ function E(s){ return String(s==null?'':s)
31
+ .replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
32
+ .replace(/"/g,'&quot;').replace(/'/g,'&#39;'); }
33
+ function $(id){ return document.getElementById(id); }
34
+
35
+ // ---------- theme ----------
36
+ function store(k,v){ try{ if(v==null) return localStorage.getItem(k); localStorage.setItem(k,v); }catch(e){ return null; } }
37
+ function applyTheme(t){
38
+ root.setAttribute('data-theme', t);
39
+ var sun=document.querySelector('.theme-sun'), moon=document.querySelector('.theme-moon');
40
+ if(sun&&moon){ sun.hidden = t!=='dark'; moon.hidden = t==='dark'; }
41
+ }
42
+ var prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
43
+ applyTheme(store('mcp-ui-theme') || (prefersDark?'dark':'light'));
44
+ var tt=$('theme-toggle');
45
+ if(tt) tt.addEventListener('click', function(){
46
+ var next = root.getAttribute('data-theme')==='dark'?'light':'dark';
47
+ applyTheme(next); store('mcp-ui-theme', next);
48
+ });
49
+
50
+ // ---------- band + intro animation ----------
51
+ root.setAttribute('data-band', DATA.score>=70?'good':(DATA.score>=45?'warn':'bad'));
52
+ var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
53
+ requestAnimationFrame(function(){
54
+ var arc=document.querySelector('.gauge .arc');
55
+ if(arc) arc.style.strokeDashoffset = arc.getAttribute('data-target');
56
+ Array.prototype.forEach.call(document.querySelectorAll('.bar > i'), function(i){ i.style.width=i.getAttribute('data-w')+'%'; });
57
+ var el=document.querySelector('.score'); if(!el) return;
58
+ var target=parseInt(el.getAttribute('data-count'),10)||0;
59
+ if(reduce){ el.textContent=target; return; }
60
+ var start=null;
61
+ requestAnimationFrame(function step(ts){ if(!start)start=ts; var p=Math.min(1,(ts-start)/1000);
62
+ el.textContent=Math.round((1-Math.pow(1-p,3))*target); if(p<1) requestAnimationFrame(step); });
63
+ });
64
+
65
+ // ---------- actions (DataAdapter) ----------
66
+ var actionsById={};
67
+ (DATA.topActions||[]).forEach(function(a){ actionsById[a.id]=a; });
68
+ (DATA.issues||[]).forEach(function(it){ (it.actions||[]).forEach(function(a){ actionsById[a.id]=a; }); });
69
+ function copy(text){
70
+ try{ if(navigator.clipboard&&navigator.clipboard.writeText) return navigator.clipboard.writeText(text); }catch(e){}
71
+ try{ var t=document.createElement('textarea'); t.value=text; document.body.appendChild(t); t.select();
72
+ document.execCommand('copy'); document.body.removeChild(t); }catch(e){}
73
+ }
74
+ var toastTimer;
75
+ function toast(msg){
76
+ var el=$('toast'); if(!el) return; el.textContent=msg; el.classList.add('show');
77
+ clearTimeout(toastTimer); toastTimer=setTimeout(function(){ el.classList.remove('show'); }, 2600);
78
+ }
79
+ function runAction(id){
80
+ var a=actionsById[id]; if(!a) return;
81
+ if(a.kind==='link'){
82
+ if(!/^https?:\/\//i.test(a.href||'')){ toast('Unsafe link blocked'); return; }
83
+ window.open(a.href,'_blank','noopener'); return;
84
+ }
85
+ if(EMBEDDED){
86
+ var msg = a.kind==='tool'
87
+ ? { type:'tool', messageId:'mcp-ui-'+Date.now(), payload:{ toolName:a.tool, params:a.params||{} } }
88
+ : { type:'prompt', messageId:'mcp-ui-'+Date.now(), payload:{ prompt:a.prompt } };
89
+ try{ window.parent.postMessage(msg,HOST_ORIGIN); toast(a.kind==='tool'?('Running '+a.tool+' …'):'Sent to agent ✓'); return; }catch(e){}
90
+ }
91
+ var text = a.kind==='tool' ? (a.fallback||('Run '+a.tool)) : (a.prompt||a.fallback||a.label);
92
+ copy(text); toast('Copied — paste into your agent');
93
+ }
94
+ document.addEventListener('click', function(e){
95
+ var el=e.target.closest && e.target.closest('[data-action]');
96
+ if(el){ e.preventDefault(); runAction(el.getAttribute('data-action')); }
97
+ });
98
+ document.addEventListener('keydown', function(e){
99
+ if((e.key==='Enter'||e.key===' ')){ var el=e.target.closest && e.target.closest('.qitem[data-action]');
100
+ if(el){ e.preventDefault(); runAction(el.getAttribute('data-action')); } }
101
+ });
102
+
103
+ // ---------- triage table ----------
104
+ var state={ sev:'all', q:'', cat:null, key:'severity', dir:'desc' };
105
+ var rowsEl=$('rows'), emptyEl=$('empty'), countEl=$('issue-count');
106
+ function passes(it){
107
+ if(state.sev!=='all' && it.severity!==state.sev) return false;
108
+ if(state.cat && it.category!==state.cat) return false;
109
+ if(state.q){ var q=state.q.toLowerCase();
110
+ var hay=(it.title+' '+(it.file||'')+' '+(it.category||'')+' '+(it.description||'')).toLowerCase();
111
+ if(hay.indexOf(q)<0) return false; }
112
+ return true;
113
+ }
114
+ function cmp(a,b){
115
+ var k=state.key, av, bv;
116
+ if(k==='severity'){ av=SEV_RANK[a.severity]||0; bv=SEV_RANK[b.severity]||0; }
117
+ else { av=(a[k]||'').toString().toLowerCase(); bv=(b[k]||'').toString().toLowerCase(); }
118
+ if(av<bv) return state.dir==='asc'?-1:1;
119
+ if(av>bv) return state.dir==='asc'?1:-1;
120
+ return 0;
121
+ }
122
+ function catName(id){ var c=(DATA.categories||[]).filter(function(x){return x.id===id;})[0]; return c?c.name:id; }
123
+ function render(){
124
+ var list=(DATA.issues||[]).filter(passes).slice().sort(cmp);
125
+ rowsEl.innerHTML = list.map(function(it){
126
+ return '<tr data-id="'+E(it.id)+'" tabindex="0" role="button" aria-label="Open issue: '+E(it.title)+'">'+
127
+ '<td><span class="sev '+E(it.severity)+'"><span class="pip"></span>'+E(it.severity)+'</span></td>'+
128
+ '<td class="cell-title">'+E(it.title)+'</td>'+
129
+ '<td><span class="tag">'+E(catName(it.category))+'</span></td>'+
130
+ '<td class="cell-file">'+E(it.file||'—')+'</td></tr>';
131
+ }).join('');
132
+ emptyEl.hidden = list.length>0;
133
+ if(countEl) countEl.textContent = list.length+(state.cat?(' in '+catName(state.cat)):(' of '+(DATA.issues||[]).length));
134
+ }
135
+ if(rowsEl){
136
+ rowsEl.addEventListener('click', function(e){
137
+ var tr=e.target.closest('tr[data-id]'); if(tr) openDrawer(tr.getAttribute('data-id'));
138
+ });
139
+ rowsEl.addEventListener('keydown', function(e){
140
+ if(e.key==='Enter'||e.key===' '){ var tr=e.target.closest('tr[data-id]');
141
+ if(tr){ e.preventDefault(); openDrawer(tr.getAttribute('data-id')); } }
142
+ });
143
+ Array.prototype.forEach.call(document.querySelectorAll('.filter button'), function(b){
144
+ b.addEventListener('click', function(){
145
+ state.sev=b.getAttribute('data-sev');
146
+ Array.prototype.forEach.call(document.querySelectorAll('.filter button'), function(x){
147
+ x.setAttribute('aria-pressed', x===b?'true':'false'); });
148
+ render();
149
+ });
150
+ });
151
+ var search=$('search');
152
+ if(search) search.addEventListener('input', function(){ state.q=search.value; render(); });
153
+ Array.prototype.forEach.call(document.querySelectorAll('thead th[data-sort]'), function(th){
154
+ th.addEventListener('click', function(){
155
+ var k=th.getAttribute('data-sort');
156
+ if(state.key===k){ state.dir=state.dir==='asc'?'desc':'asc'; } else { state.key=k; state.dir=k==='severity'?'desc':'asc'; }
157
+ Array.prototype.forEach.call(document.querySelectorAll('thead th[data-sort]'), function(x){ x.removeAttribute('aria-sort'); });
158
+ th.setAttribute('aria-sort', state.dir==='asc'?'ascending':'descending');
159
+ render();
160
+ });
161
+ });
162
+ Array.prototype.forEach.call(document.querySelectorAll('.card[data-category]'), function(card){
163
+ function trigger(){
164
+ var id=card.getAttribute('data-category');
165
+ state.cat = state.cat===id ? null : id;
166
+ Array.prototype.forEach.call(document.querySelectorAll('.card[data-category]'), function(c){
167
+ c.style.borderColor = (c===card && state.cat) ? 'var(--accent)' : ''; });
168
+ render();
169
+ var sec=document.querySelector('.table-card'); if(sec&&state.cat) sec.scrollIntoView({behavior:reduce?'auto':'smooth', block:'start'});
170
+ }
171
+ card.addEventListener('click', trigger);
172
+ card.addEventListener('keydown', function(e){ if(e.key==='Enter'||e.key===' '){ e.preventDefault(); trigger(); } });
173
+ });
174
+ render();
175
+ }
176
+
177
+ // ---------- drawer ----------
178
+ var drawer=$('drawer'), scrim=$('scrim');
179
+ function field(k,v,mono){ return '<div class="field"><div class="k">'+E(k)+'</div><div class="v'+(mono?' mono':'')+'">'+E(v)+'</div></div>'; }
180
+ function openDrawer(id){
181
+ var it=(DATA.issues||[]).filter(function(x){return x.id===id;})[0]; if(!it) return;
182
+ lastFocus=document.activeElement;
183
+ $('d-title').textContent=it.title;
184
+ var body='';
185
+ body+='<div class="kv" style="margin-bottom:16px">'+
186
+ '<span class="pair"><b style="color:var(--sev-'+E(it.severity)+')">'+E(it.severity)+'</b></span>'+
187
+ '<span class="pair">'+E(catName(it.category))+'</span></div>';
188
+ if(it.description) body+=field('What it means', it.description);
189
+ if(it.file) body+=field('File', it.file, true);
190
+ (it.meta||[]).forEach(function(m){ body+=field(m.label, m.value); });
191
+ $('d-body').innerHTML=body;
192
+ $('d-acts').innerHTML=(it.actions||[]).map(function(a,i){
193
+ return '<button class="btn'+(i===0?' primary':'')+'" type="button" data-action="'+E(a.id)+'">'+E(a.label)+'</button>';
194
+ }).join('') || '<span style="color:var(--faint);font-size:12px">No automated fix available</span>';
195
+ drawer.classList.add('open'); scrim.classList.add('open');
196
+ var dcEl=$('d-close'); if(dcEl) dcEl.focus();
197
+ }
198
+ function closeDrawer(){
199
+ drawer.classList.remove('open'); scrim.classList.remove('open');
200
+ if(lastFocus&&lastFocus.focus){ lastFocus.focus(); lastFocus=null; }
201
+ }
202
+ if(scrim) scrim.addEventListener('click', closeDrawer);
203
+ var dc=$('d-close'); if(dc) dc.addEventListener('click', closeDrawer);
204
+ document.addEventListener('keydown', function(e){ if(e.key==='Escape') closeDrawer(); });
205
+
206
+ // ---------- export ----------
207
+ var ex=$('export-btn');
208
+ function md(s){ return String(s==null?'':s).replace(/[\r\n]+/g,' '); }
209
+ if(ex) ex.addEventListener('click', function(){
210
+ var L=[]; L.push('# '+md(DATA.meta.title)); L.push('');
211
+ L.push('**Target:** '+md(DATA.meta.target)+' ');
212
+ L.push('**Health score:** '+DATA.score+'/100 ');
213
+ L.push('**Issues:** '+DATA.totalIssues); L.push('');
214
+ L.push('## Areas');
215
+ (DATA.categories||[]).forEach(function(c){ L.push('- **'+md(c.name)+'** ('+c.status+(typeof c.score==='number'?', '+c.score:'')+') — '+md(c.summary)); });
216
+ L.push(''); L.push('## Issues');
217
+ (DATA.issues||[]).slice().sort(function(a,b){return (SEV_RANK[b.severity]||0)-(SEV_RANK[a.severity]||0);}).forEach(function(it){
218
+ L.push('- ['+it.severity.toUpperCase()+'] '+md(it.title)+(it.file?(' — '+md(it.file)):'')); });
219
+ copy(L.join('\n')); toast('Report copied as Markdown ✓');
220
+ });
221
+ })();`;