MAESTRO: improve standalone HTML export

This commit is contained in:
Mariusz Banach
2026-02-18 03:17:03 +01:00
parent 65f294de59
commit 98ec96356d
3 changed files with 24 additions and 6 deletions

View File

@@ -54,7 +54,7 @@ ReportContainer
- [x] Security appliances show as badges; empty state handled gracefully - [x] Security appliances show as badges; empty state handled gracefully
- [x] Search filters results in real-time across test name, header name, and analysis text - [x] Search filters results in real-time across test name, header name, and analysis text
- [x] Export JSON produces a valid JSON file containing all results - [x] Export JSON produces a valid JSON file containing all results
- [ ] Export HTML produces a styled standalone page viewable in any browser - [x] Export HTML produces a styled standalone page viewable in any browser
- [ ] All report components are keyboard accessible - [ ] All report components are keyboard accessible
- [ ] Linting passes (`npx eslint src/`, `npx prettier --check src/`) - [ ] Linting passes (`npx eslint src/`, `npx prettier --check src/`)
- [ ] Run `/speckit.analyze` to verify consistency - [ ] Run `/speckit.analyze` to verify consistency

View File

@@ -136,7 +136,7 @@ describe("ReportExport", () => {
expect(createObjectUrlSpy).toHaveBeenCalled(); expect(createObjectUrlSpy).toHaveBeenCalled();
expect(clickSpy).toHaveBeenCalled(); expect(clickSpy).toHaveBeenCalled();
expect(lastBlob).not.toBeNull(); expect(lastBlob).not.toBeNull();
expect(lastBlob?.type).toBe("application/json"); expect(lastBlob?.type).toBe("application/json;charset=utf-8");
const text = await lastBlob?.text(); const text = await lastBlob?.text();
const parsed = JSON.parse(text ?? "{}") as AnalysisReport; const parsed = JSON.parse(text ?? "{}") as AnalysisReport;
@@ -144,7 +144,7 @@ describe("ReportExport", () => {
expect(parsed.results[0]?.testId).toBe(101); expect(parsed.results[0]?.testId).toBe(101);
}); });
it("triggers an HTML download", () => { it("triggers an HTML download with styled markup", async () => {
const { container } = render(<ReportExport report={report} />); const { container } = render(<ReportExport report={report} />);
const htmlButton = getByTestId(container, "report-export-html"); const htmlButton = getByTestId(container, "report-export-html");
@@ -155,5 +155,13 @@ describe("ReportExport", () => {
expect(createObjectUrlSpy).toHaveBeenCalled(); expect(createObjectUrlSpy).toHaveBeenCalled();
expect(clickSpy).toHaveBeenCalled(); expect(clickSpy).toHaveBeenCalled();
expect(lastBlob?.type).toBe("text/html;charset=utf-8");
const text = await lastBlob?.text();
expect(text).toContain("<!doctype html>");
expect(text).toContain("<style>");
expect(text).toContain("Email Header Analysis Report");
expect(text).toContain("Summary");
expect(text).toContain("SpamAssassin Rule Hits");
}); });
}); });

View File

@@ -147,6 +147,7 @@ const buildHtmlReport = (report: AnalysisReport): string => {
background: var(--background); background: var(--background);
color: var(--text); color: var(--text);
font-family: "Geist", "Segoe UI", system-ui, sans-serif; font-family: "Geist", "Segoe UI", system-ui, sans-serif;
line-height: 1.5;
} }
main { main {
@@ -162,6 +163,11 @@ const buildHtmlReport = (report: AnalysisReport): string => {
margin: 0 0 4px; margin: 0 0 4px;
} }
h2 {
font-size: 18px;
margin: 0 0 12px;
}
.muted { .muted {
color: var(--muted); color: var(--muted);
font-size: 13px; font-size: 13px;
@@ -427,16 +433,20 @@ const downloadBlob = (content: string, mimeType: string, fileName: string) => {
document.body.appendChild(anchor); document.body.appendChild(anchor);
anchor.click(); anchor.click();
anchor.remove(); anchor.remove();
URL.revokeObjectURL(url); window.setTimeout(() => URL.revokeObjectURL(url), 1000);
}; };
export default function ReportExport({ report }: ReportExportProps) { export default function ReportExport({ report }: ReportExportProps) {
const handleJsonExport = () => { const handleJsonExport = () => {
downloadBlob(JSON.stringify(report, null, 2), "application/json", "analysis-report.json"); downloadBlob(
JSON.stringify(report, null, 2),
"application/json;charset=utf-8",
"analysis-report.json",
);
}; };
const handleHtmlExport = () => { const handleHtmlExport = () => {
downloadBlob(buildHtmlReport(report), "text/html", "analysis-report.html"); downloadBlob(buildHtmlReport(report), "text/html;charset=utf-8", "analysis-report.html");
}; };
return ( return (