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.
- package/README.md +28 -28
- package/node_modules/@mcp-showcase/shared/build/McpServerBase.d.ts +13 -0
- package/node_modules/@mcp-showcase/shared/build/McpServerBase.d.ts.map +1 -1
- package/node_modules/@mcp-showcase/shared/build/McpServerBase.js +40 -0
- package/node_modules/@mcp-showcase/shared/build/McpServerBase.js.map +1 -1
- package/node_modules/@mcp-showcase/shared/build/types.d.ts +11 -0
- package/node_modules/@mcp-showcase/shared/build/types.d.ts.map +1 -1
- package/node_modules/@mcp-showcase/shared/src/McpServerBase.ts +45 -0
- package/node_modules/@mcp-showcase/shared/src/types.ts +12 -0
- package/node_modules/@mcp-showcase/ui-kit/README.md +30 -0
- package/node_modules/@mcp-showcase/ui-kit/build/components.d.ts +10 -0
- package/node_modules/@mcp-showcase/ui-kit/build/components.d.ts.map +1 -0
- package/node_modules/@mcp-showcase/ui-kit/build/components.js +177 -0
- package/node_modules/@mcp-showcase/ui-kit/build/components.js.map +1 -0
- package/node_modules/@mcp-showcase/ui-kit/build/escape.d.ts +3 -0
- package/node_modules/@mcp-showcase/ui-kit/build/escape.d.ts.map +1 -0
- package/node_modules/@mcp-showcase/ui-kit/build/escape.js +10 -0
- package/node_modules/@mcp-showcase/ui-kit/build/escape.js.map +1 -0
- package/node_modules/@mcp-showcase/ui-kit/build/fixture.d.ts +4 -0
- package/node_modules/@mcp-showcase/ui-kit/build/fixture.d.ts.map +1 -0
- package/node_modules/@mcp-showcase/ui-kit/build/fixture.js +52 -0
- package/node_modules/@mcp-showcase/ui-kit/build/fixture.js.map +1 -0
- package/node_modules/@mcp-showcase/ui-kit/build/generate-template.d.ts +2 -0
- package/node_modules/@mcp-showcase/ui-kit/build/generate-template.d.ts.map +1 -0
- package/node_modules/@mcp-showcase/ui-kit/build/generate-template.js +19 -0
- package/node_modules/@mcp-showcase/ui-kit/build/generate-template.js.map +1 -0
- package/node_modules/@mcp-showcase/ui-kit/build/index.d.ts +6 -0
- package/node_modules/@mcp-showcase/ui-kit/build/index.d.ts.map +1 -0
- package/node_modules/@mcp-showcase/ui-kit/build/index.js +7 -0
- package/node_modules/@mcp-showcase/ui-kit/build/index.js.map +1 -0
- package/node_modules/@mcp-showcase/ui-kit/build/render.d.ts +3 -0
- package/node_modules/@mcp-showcase/ui-kit/build/render.d.ts.map +1 -0
- package/node_modules/@mcp-showcase/ui-kit/build/render.js +38 -0
- package/node_modules/@mcp-showcase/ui-kit/build/render.js.map +1 -0
- package/node_modules/@mcp-showcase/ui-kit/build/result-components.d.ts +9 -0
- package/node_modules/@mcp-showcase/ui-kit/build/result-components.d.ts.map +1 -0
- package/node_modules/@mcp-showcase/ui-kit/build/result-components.js +105 -0
- package/node_modules/@mcp-showcase/ui-kit/build/result-components.js.map +1 -0
- package/node_modules/@mcp-showcase/ui-kit/build/result-fixture.d.ts +4 -0
- package/node_modules/@mcp-showcase/ui-kit/build/result-fixture.d.ts.map +1 -0
- package/node_modules/@mcp-showcase/ui-kit/build/result-fixture.js +39 -0
- package/node_modules/@mcp-showcase/ui-kit/build/result-fixture.js.map +1 -0
- package/node_modules/@mcp-showcase/ui-kit/build/result-render.d.ts +3 -0
- package/node_modules/@mcp-showcase/ui-kit/build/result-render.d.ts.map +1 -0
- package/node_modules/@mcp-showcase/ui-kit/build/result-render.js +37 -0
- package/node_modules/@mcp-showcase/ui-kit/build/result-render.js.map +1 -0
- package/node_modules/@mcp-showcase/ui-kit/build/result-runtime.d.ts +2 -0
- package/node_modules/@mcp-showcase/ui-kit/build/result-runtime.d.ts.map +1 -0
- package/node_modules/@mcp-showcase/ui-kit/build/result-runtime.js +72 -0
- package/node_modules/@mcp-showcase/ui-kit/build/result-runtime.js.map +1 -0
- package/node_modules/@mcp-showcase/ui-kit/build/runtime.d.ts +2 -0
- package/node_modules/@mcp-showcase/ui-kit/build/runtime.d.ts.map +1 -0
- package/node_modules/@mcp-showcase/ui-kit/build/runtime.js +221 -0
- package/node_modules/@mcp-showcase/ui-kit/build/runtime.js.map +1 -0
- package/node_modules/@mcp-showcase/ui-kit/build/theme.d.ts +2 -0
- package/node_modules/@mcp-showcase/ui-kit/build/theme.d.ts.map +1 -0
- package/node_modules/@mcp-showcase/ui-kit/build/theme.js +278 -0
- package/node_modules/@mcp-showcase/ui-kit/build/theme.js.map +1 -0
- package/node_modules/@mcp-showcase/ui-kit/build/types.d.ts +113 -0
- package/node_modules/@mcp-showcase/ui-kit/build/types.d.ts.map +1 -0
- package/node_modules/@mcp-showcase/ui-kit/build/types.js +35 -0
- package/node_modules/@mcp-showcase/ui-kit/build/types.js.map +1 -0
- package/node_modules/@mcp-showcase/ui-kit/demo/index.html +653 -0
- package/node_modules/@mcp-showcase/ui-kit/demo/result.html +445 -0
- package/node_modules/@mcp-showcase/ui-kit/package.json +19 -0
- package/node_modules/@mcp-showcase/ui-kit/src/components.ts +191 -0
- package/node_modules/@mcp-showcase/ui-kit/src/escape.ts +9 -0
- package/node_modules/@mcp-showcase/ui-kit/src/fixture.ts +53 -0
- package/node_modules/@mcp-showcase/ui-kit/src/generate-template.ts +21 -0
- package/node_modules/@mcp-showcase/ui-kit/src/index.test.ts +72 -0
- package/node_modules/@mcp-showcase/ui-kit/src/index.ts +6 -0
- package/node_modules/@mcp-showcase/ui-kit/src/render.ts +48 -0
- package/node_modules/@mcp-showcase/ui-kit/src/result-components.ts +112 -0
- package/node_modules/@mcp-showcase/ui-kit/src/result-fixture.ts +40 -0
- package/node_modules/@mcp-showcase/ui-kit/src/result-render.test.ts +47 -0
- package/node_modules/@mcp-showcase/ui-kit/src/result-render.ts +47 -0
- package/node_modules/@mcp-showcase/ui-kit/src/result-runtime.ts +72 -0
- package/node_modules/@mcp-showcase/ui-kit/src/runtime.smoke.test.ts +103 -0
- package/node_modules/@mcp-showcase/ui-kit/src/runtime.ts +221 -0
- package/node_modules/@mcp-showcase/ui-kit/src/theme.ts +278 -0
- package/node_modules/@mcp-showcase/ui-kit/src/types.ts +140 -0
- package/node_modules/@mcp-showcase/ui-kit/tsconfig.json +9 -0
- package/package.json +16 -5
- package/tools/accessibility-checker/build/health-report.d.ts +27 -0
- package/tools/accessibility-checker/build/health-report.d.ts.map +1 -0
- package/tools/accessibility-checker/build/health-report.js +140 -0
- package/tools/accessibility-checker/build/health-report.js.map +1 -0
- package/tools/accessibility-checker/build/index.js +7 -1
- package/tools/accessibility-checker/build/index.js.map +1 -1
- package/tools/accessibility-checker/package.json +1 -0
- package/tools/code-modernizer/build/index.js +60 -44
- package/tools/code-modernizer/build/index.js.map +1 -1
- package/tools/code-modernizer/build/result-report.d.ts +24 -0
- package/tools/code-modernizer/build/result-report.d.ts.map +1 -0
- package/tools/code-modernizer/build/result-report.js +101 -0
- package/tools/code-modernizer/build/result-report.js.map +1 -0
- package/tools/code-modernizer/package.json +2 -0
- package/tools/component-factory/build/index.js +7 -1
- package/tools/component-factory/build/index.js.map +1 -1
- package/tools/component-factory/build/result-report.d.ts +11 -0
- package/tools/component-factory/build/result-report.d.ts.map +1 -0
- package/tools/component-factory/build/result-report.js +104 -0
- package/tools/component-factory/build/result-report.js.map +1 -0
- package/tools/component-factory/package.json +1 -0
- package/tools/component-fixer/build/index.js +6 -6
- package/tools/component-fixer/build/index.js.map +1 -1
- package/tools/component-fixer/build/result-report.d.ts +37 -0
- package/tools/component-fixer/build/result-report.d.ts.map +1 -0
- package/tools/component-fixer/build/result-report.js +106 -0
- package/tools/component-fixer/build/result-report.js.map +1 -0
- package/tools/component-fixer/package.json +1 -0
- package/tools/component-reviewer/build/health-report.d.ts +37 -0
- package/tools/component-reviewer/build/health-report.d.ts.map +1 -0
- package/tools/component-reviewer/build/health-report.js +116 -0
- package/tools/component-reviewer/build/health-report.js.map +1 -0
- package/tools/component-reviewer/build/index.d.ts.map +1 -1
- package/tools/component-reviewer/build/index.js +7 -6
- package/tools/component-reviewer/build/index.js.map +1 -1
- package/tools/component-reviewer/package.json +1 -0
- package/tools/dep-auditor/build/health-report.d.ts +16 -0
- package/tools/dep-auditor/build/health-report.d.ts.map +1 -0
- package/tools/dep-auditor/build/health-report.js +187 -0
- package/tools/dep-auditor/build/health-report.js.map +1 -0
- package/tools/dep-auditor/build/index.d.ts.map +1 -1
- package/tools/dep-auditor/build/index.js +7 -1
- package/tools/dep-auditor/build/index.js.map +1 -1
- package/tools/dep-auditor/package.json +1 -0
- package/tools/generate-tests/build/index.js +8 -1
- package/tools/generate-tests/build/index.js.map +1 -1
- package/tools/generate-tests/build/result-report.d.ts +14 -0
- package/tools/generate-tests/build/result-report.d.ts.map +1 -0
- package/tools/generate-tests/build/result-report.js +124 -0
- package/tools/generate-tests/build/result-report.js.map +1 -0
- package/tools/generate-tests/package.json +1 -0
- package/tools/legacy-analyzer/build/health-report.d.ts +4 -0
- package/tools/legacy-analyzer/build/health-report.d.ts.map +1 -0
- package/tools/legacy-analyzer/build/health-report.js +164 -0
- package/tools/legacy-analyzer/build/health-report.js.map +1 -0
- package/tools/legacy-analyzer/build/index.js +15 -1
- package/tools/legacy-analyzer/build/index.js.map +1 -1
- package/tools/legacy-analyzer/build/tools/01-detect-project-tech.d.ts.map +1 -1
- package/tools/legacy-analyzer/build/tools/01-detect-project-tech.js +3 -8
- package/tools/legacy-analyzer/build/tools/01-detect-project-tech.js.map +1 -1
- package/tools/legacy-analyzer/build/tools/05-analyze-api-layer.d.ts.map +1 -1
- package/tools/legacy-analyzer/build/tools/05-analyze-api-layer.js +25 -3
- package/tools/legacy-analyzer/build/tools/05-analyze-api-layer.js.map +1 -1
- package/tools/legacy-analyzer/package.json +1 -0
- package/tools/lighthouse-runner/build/health-report.d.ts +14 -0
- package/tools/lighthouse-runner/build/health-report.d.ts.map +1 -0
- package/tools/lighthouse-runner/build/health-report.js +138 -0
- package/tools/lighthouse-runner/build/health-report.js.map +1 -0
- package/tools/lighthouse-runner/build/index.d.ts.map +1 -1
- package/tools/lighthouse-runner/build/index.js +7 -1
- package/tools/lighthouse-runner/build/index.js.map +1 -1
- package/tools/lighthouse-runner/package.json +1 -0
- package/tools/monorepo-manager/build/index.js +9 -1
- package/tools/monorepo-manager/build/index.js.map +1 -1
- package/tools/monorepo-manager/build/result-report.d.ts +20 -0
- package/tools/monorepo-manager/build/result-report.d.ts.map +1 -0
- package/tools/monorepo-manager/build/result-report.js +84 -0
- package/tools/monorepo-manager/build/result-report.js.map +1 -0
- package/tools/monorepo-manager/package.json +1 -0
- package/tools/performance-audit/build/health-report.d.ts +30 -0
- package/tools/performance-audit/build/health-report.d.ts.map +1 -0
- package/tools/performance-audit/build/health-report.js +152 -0
- package/tools/performance-audit/build/health-report.js.map +1 -0
- package/tools/performance-audit/build/index.d.ts.map +1 -1
- package/tools/performance-audit/build/index.js +7 -1
- package/tools/performance-audit/build/index.js.map +1 -1
- package/tools/performance-audit/package.json +1 -0
- package/tools/quality-pipeline/build/health-report.d.ts +11 -0
- package/tools/quality-pipeline/build/health-report.d.ts.map +1 -0
- package/tools/quality-pipeline/build/health-report.js +137 -0
- package/tools/quality-pipeline/build/health-report.js.map +1 -0
- package/tools/quality-pipeline/build/index.js +7 -1
- package/tools/quality-pipeline/build/index.js.map +1 -1
- package/tools/quality-pipeline/package.json +1 -0
- package/tools/render-analyzer/build/health-report.d.ts +33 -0
- package/tools/render-analyzer/build/health-report.d.ts.map +1 -0
- package/tools/render-analyzer/build/health-report.js +142 -0
- package/tools/render-analyzer/build/health-report.js.map +1 -0
- package/tools/render-analyzer/build/index.d.ts.map +1 -1
- package/tools/render-analyzer/build/index.js +7 -1
- package/tools/render-analyzer/build/index.js.map +1 -1
- package/tools/render-analyzer/package.json +1 -0
- package/tools/shared/build/McpServerBase.d.ts +13 -0
- package/tools/shared/build/McpServerBase.d.ts.map +1 -1
- package/tools/shared/build/McpServerBase.js +40 -0
- package/tools/shared/build/McpServerBase.js.map +1 -1
- package/tools/shared/build/types.d.ts +11 -0
- package/tools/shared/build/types.d.ts.map +1 -1
- package/tools/storybook-generator/build/index.d.ts.map +1 -1
- package/tools/storybook-generator/build/index.js +9 -1
- package/tools/storybook-generator/build/index.js.map +1 -1
- package/tools/storybook-generator/build/result-report.d.ts +22 -0
- package/tools/storybook-generator/build/result-report.d.ts.map +1 -0
- package/tools/storybook-generator/build/result-report.js +77 -0
- package/tools/storybook-generator/build/result-report.js.map +1 -0
- package/tools/storybook-generator/package.json +1 -0
- package/tools/test-gap-analyzer/build/health-report.d.ts +34 -0
- package/tools/test-gap-analyzer/build/health-report.d.ts.map +1 -0
- package/tools/test-gap-analyzer/build/health-report.js +190 -0
- package/tools/test-gap-analyzer/build/health-report.js.map +1 -0
- package/tools/test-gap-analyzer/build/index.d.ts.map +1 -1
- package/tools/test-gap-analyzer/build/index.js +7 -1
- package/tools/test-gap-analyzer/build/index.js.map +1 -1
- package/tools/test-gap-analyzer/package.json +1 -0
- package/tools/typescript-enforcer/build/health-report.d.ts +33 -0
- package/tools/typescript-enforcer/build/health-report.d.ts.map +1 -0
- package/tools/typescript-enforcer/build/health-report.js +143 -0
- package/tools/typescript-enforcer/build/health-report.js.map +1 -0
- package/tools/typescript-enforcer/build/index.js +6 -1
- package/tools/typescript-enforcer/build/index.js.map +1 -1
- package/tools/typescript-enforcer/package.json +1 -0
- package/tools/ui-kit/README.md +30 -0
- package/tools/ui-kit/build/components.d.ts +10 -0
- package/tools/ui-kit/build/components.d.ts.map +1 -0
- package/tools/ui-kit/build/components.js +177 -0
- package/tools/ui-kit/build/components.js.map +1 -0
- package/tools/ui-kit/build/escape.d.ts +3 -0
- package/tools/ui-kit/build/escape.d.ts.map +1 -0
- package/tools/ui-kit/build/escape.js +10 -0
- package/tools/ui-kit/build/escape.js.map +1 -0
- package/tools/ui-kit/build/fixture.d.ts +4 -0
- package/tools/ui-kit/build/fixture.d.ts.map +1 -0
- package/tools/ui-kit/build/fixture.js +52 -0
- package/tools/ui-kit/build/fixture.js.map +1 -0
- package/tools/ui-kit/build/generate-template.d.ts +2 -0
- package/tools/ui-kit/build/generate-template.d.ts.map +1 -0
- package/tools/ui-kit/build/generate-template.js +19 -0
- package/tools/ui-kit/build/generate-template.js.map +1 -0
- package/tools/ui-kit/build/index.d.ts +6 -0
- package/tools/ui-kit/build/index.d.ts.map +1 -0
- package/tools/ui-kit/build/index.js +7 -0
- package/tools/ui-kit/build/index.js.map +1 -0
- package/tools/ui-kit/build/render.d.ts +3 -0
- package/tools/ui-kit/build/render.d.ts.map +1 -0
- package/tools/ui-kit/build/render.js +38 -0
- package/tools/ui-kit/build/render.js.map +1 -0
- package/tools/ui-kit/build/result-components.d.ts +9 -0
- package/tools/ui-kit/build/result-components.d.ts.map +1 -0
- package/tools/ui-kit/build/result-components.js +105 -0
- package/tools/ui-kit/build/result-components.js.map +1 -0
- package/tools/ui-kit/build/result-fixture.d.ts +4 -0
- package/tools/ui-kit/build/result-fixture.d.ts.map +1 -0
- package/tools/ui-kit/build/result-fixture.js +39 -0
- package/tools/ui-kit/build/result-fixture.js.map +1 -0
- package/tools/ui-kit/build/result-render.d.ts +3 -0
- package/tools/ui-kit/build/result-render.d.ts.map +1 -0
- package/tools/ui-kit/build/result-render.js +37 -0
- package/tools/ui-kit/build/result-render.js.map +1 -0
- package/tools/ui-kit/build/result-runtime.d.ts +2 -0
- package/tools/ui-kit/build/result-runtime.d.ts.map +1 -0
- package/tools/ui-kit/build/result-runtime.js +72 -0
- package/tools/ui-kit/build/result-runtime.js.map +1 -0
- package/tools/ui-kit/build/runtime.d.ts +2 -0
- package/tools/ui-kit/build/runtime.d.ts.map +1 -0
- package/tools/ui-kit/build/runtime.js +221 -0
- package/tools/ui-kit/build/runtime.js.map +1 -0
- package/tools/ui-kit/build/theme.d.ts +2 -0
- package/tools/ui-kit/build/theme.d.ts.map +1 -0
- package/tools/ui-kit/build/theme.js +278 -0
- package/tools/ui-kit/build/theme.js.map +1 -0
- package/tools/ui-kit/build/types.d.ts +113 -0
- package/tools/ui-kit/build/types.d.ts.map +1 -0
- package/tools/ui-kit/build/types.js +35 -0
- package/tools/ui-kit/build/types.js.map +1 -0
- 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,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,'''); }
|
|
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,'&').replace(/</g,'<').replace(/>/g,'>')
|
|
32
|
+
.replace(/"/g,'"').replace(/'/g,'''); }
|
|
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
|
+
})();`;
|