diff --git a/Auto Run Docs/SpecKit-web-header-analyzer-Phase-09-Polish.md b/Auto Run Docs/SpecKit-web-header-analyzer-Phase-09-Polish.md
index a964997..d814bb0 100644
--- a/Auto Run Docs/SpecKit-web-header-analyzer-Phase-09-Polish.md
+++ b/Auto Run Docs/SpecKit-web-header-analyzer-Phase-09-Polish.md
@@ -19,7 +19,7 @@ This phase performs final integration, accessibility audit, responsive testing,
- [x] T046 Wire all components together in `frontend/src/app/page.tsx` — integrate HeaderInput, FileDropZone, AnalysisControls, AnalyseButton, ProgressIndicator, ReportContainer, CaptchaChallenge into the single-view application with correct data flow. Ensure: input feeds to analysis hook, progress hook drives progress indicator, result feeds to report container, 429 errors trigger CAPTCHA modal, cache hook restores state on mount. Notes: added AnalysisControls + CAPTCHA retry flow, extended analysis hook for bypass token handling, confirmed cache restore.
- [x] T047 Verify WCAG 2.1 AA compliance across all components (NFR-03) — ARIA labels, keyboard nav order, focus indicators, colour contrast ratios (dark theme). Fix violations. Test with screen reader simulation. Ensure all interactive elements have visible focus states. Notes: added keyboard-accessible file picker with ARIA descriptions, focus-visible outlines on drop zone/summary/search fields, boosted low-contrast text from 40% to 60%, linked CAPTCHA dialog description, added file picker tests; ran `npx vitest run src/__tests__/FileDropZone.test.tsx`.
-- [ ] T048 [P] Verify responsive layout 320px–2560px (NFR-04) at breakpoints: 320px, 768px, 1024px, 1440px, 2560px. No horizontal scroll, no overlapping elements, readable text. Fix any layout issues discovered
+- [x] T048 [P] Verify responsive layout 320px–2560px (NFR-04) at breakpoints: 320px, 768px, 1024px, 1440px, 2560px. No horizontal scroll, no overlapping elements, readable text. Fix any layout issues discovered. Notes: stacked control cards on small screens, added min-w-0 + flex-wrap on report UI, and break-words handling for long header values, hop chain hostnames/IPs, and search pills to prevent overflow.
- [ ] T049 [P] Run full linting pass — `ruff check backend/` and `ruff format backend/` zero errors; `npx eslint src/` and `npx prettier --check src/` zero errors; no `any` types in TypeScript. Fix all violations
- [ ] T050 [P] Run full test suites and verify coverage — `pytest backend/tests/ --cov` ≥80% new modules (NFR-06); `npx vitest run --coverage` ≥80% new components (NFR-07). Add missing tests if coverage is below threshold
- [ ] T051 [P] Verify initial page load <3s on simulated 4G (constitution P7). Use Lighthouse with Slow 4G preset. Target score ≥90. Fix blocking resources or missing lazy-loading if score is below target
diff --git a/frontend/src/__tests__/report/HopChainVisualisation.test.tsx b/frontend/src/__tests__/report/HopChainVisualisation.test.tsx
index 90c10a1..1ead9b1 100644
--- a/frontend/src/__tests__/report/HopChainVisualisation.test.tsx
+++ b/frontend/src/__tests__/report/HopChainVisualisation.test.tsx
@@ -88,4 +88,16 @@ describe("HopChainVisualisation", () => {
expect(connectors.length).toBe(hopChain.length - 1);
});
+
+ it("adds wrapping classes for long hostnames and IPs", () => {
+ const { container } = render();
+
+ const firstHop = getByTestId(container, "hop-chain-node-0");
+ const spanNodes = Array.from(firstHop.querySelectorAll("span"));
+ const hostnameNode = spanNodes.find((node) => node.textContent === "mail.sender.example");
+ const ipNode = spanNodes.find((node) => node.textContent === "192.0.2.10");
+
+ expect(hostnameNode?.className ?? "").toContain("break-words");
+ expect(ipNode?.className ?? "").toContain("break-all");
+ });
});
diff --git a/frontend/src/__tests__/report/TestResultCard.test.tsx b/frontend/src/__tests__/report/TestResultCard.test.tsx
index 7137047..d7f34c0 100644
--- a/frontend/src/__tests__/report/TestResultCard.test.tsx
+++ b/frontend/src/__tests__/report/TestResultCard.test.tsx
@@ -127,4 +127,25 @@ describe("TestResultCard", () => {
const errorIndicator = getByTestId(container, `test-result-error-${result.testId}`);
expect(errorIndicator.textContent ?? "").toMatch(/SpamAssassin database timeout/);
});
+
+ it("adds wrapping classes for long header values", () => {
+ const result = buildResult({
+ testId: 505,
+ headerValue: "X".repeat(200),
+ });
+ const { container } = render();
+
+ const details = container.querySelector(`#test-result-details-${result.testId}`);
+ if (!details) {
+ throw new Error("Expected test details container to be rendered.");
+ }
+
+ const headerValue = details.querySelector("span.font-mono");
+ if (!headerValue) {
+ throw new Error("Expected header value to be rendered.");
+ }
+
+ expect(headerValue.className).toContain("break-words");
+ expect(headerValue.className).toContain("whitespace-pre-wrap");
+ });
});
diff --git a/frontend/src/components/AnalysisControls.tsx b/frontend/src/components/AnalysisControls.tsx
index c5ceef0..cb69a90 100644
--- a/frontend/src/components/AnalysisControls.tsx
+++ b/frontend/src/components/AnalysisControls.tsx
@@ -59,7 +59,7 @@ export default function AnalysisControls({ config, onChange }: AnalysisControlsP
US2
-
+
@@ -83,7 +83,7 @@ export default function AnalysisControls({ config, onChange }: AnalysisControlsP
{resolvedConfig.resolve ? "On" : "Off"}
-
+
diff --git a/frontend/src/components/AnalysisResults.tsx b/frontend/src/components/AnalysisResults.tsx
index c7790f4..57344d9 100644
--- a/frontend/src/components/AnalysisResults.tsx
+++ b/frontend/src/components/AnalysisResults.tsx
@@ -52,9 +52,11 @@ export default function AnalysisResults({ report }: AnalysisResultsProps) {
className="rounded-2xl border border-info/10 bg-background/40 p-4"
>
-
-
{result.testName}
-
+
+
+ {result.testName}
+
+
Test #{result.testId} · {result.headerName}
@@ -69,10 +71,10 @@ export default function AnalysisResults({ report }: AnalysisResultsProps) {
{result.analysis ? (
-
{result.analysis}
+
{result.analysis}
) : null}
{result.description ? (
-
{result.description}
+
{result.description}
) : null}
{result.status === "error" ? (
diff --git a/frontend/src/components/ProgressIndicator.tsx b/frontend/src/components/ProgressIndicator.tsx
index 5711c0b..16482a3 100644
--- a/frontend/src/components/ProgressIndicator.tsx
+++ b/frontend/src/components/ProgressIndicator.tsx
@@ -142,7 +142,10 @@ export default function ProgressIndicator({
-
+
{progress?.currentTest ?? "Preparing analysis"}
@@ -170,7 +173,7 @@ export default function ProgressIndicator({
>
Timeout reached at {timeoutSeconds} seconds.
Incomplete tests:
-
+
{incompleteTests.length > 0 ? incompleteTests.join(", ") : "None"}
diff --git a/frontend/src/components/report/HopChainVisualisation.tsx b/frontend/src/components/report/HopChainVisualisation.tsx
index 879f584..0094106 100644
--- a/frontend/src/components/report/HopChainVisualisation.tsx
+++ b/frontend/src/components/report/HopChainVisualisation.tsx
@@ -47,22 +47,24 @@ export default function HopChainVisualisation({ hopChain }: HopChainVisualisatio
-
+
- {node.hostname}
+
+ {node.hostname}
+
{node.ip ? (
- {node.ip}
+ {node.ip}
) : null}
{node.timestamp ? (
-
+
{node.timestamp}
) : null}
{node.serverInfo ? (
-
+
{node.serverInfo}
diff --git a/frontend/src/components/report/ReportSearchBar.tsx b/frontend/src/components/report/ReportSearchBar.tsx
index 7b8bc3e..f9c48b6 100644
--- a/frontend/src/components/report/ReportSearchBar.tsx
+++ b/frontend/src/components/report/ReportSearchBar.tsx
@@ -40,14 +40,14 @@ export default function ReportSearchBar({
className="rounded-2xl border border-info/10 bg-surface/50 p-4 shadow-[0_0_30px_rgba(15,23,42,0.18)]"
>
diff --git a/frontend/src/components/report/TestResultCard.tsx b/frontend/src/components/report/TestResultCard.tsx
index 9f972e5..3099378 100644
--- a/frontend/src/components/report/TestResultCard.tsx
+++ b/frontend/src/components/report/TestResultCard.tsx
@@ -101,10 +101,10 @@ export default function TestResultCard({ result, highlightQuery = "" }: TestResu
aria-controls={detailsId}
onClick={toggle}
onKeyDown={handleKeyDown}
- className="flex w-full items-center justify-between gap-4 text-left focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-info"
+ className="flex w-full flex-wrap items-center justify-between gap-4 text-left focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-info"
>
-
-
+
+
{highlightText(result.testName, highlightQuery)}
Test #{result.testId}
@@ -136,19 +136,21 @@ export default function TestResultCard({ result, highlightQuery = "" }: TestResu
Header
-
+
{highlightText(result.headerName, highlightQuery)}
- {result.headerValue}
+
+ {result.headerValue}
+
{result.analysis ? (
-
+
{highlightText(result.analysis, highlightQuery)}
) : null}
{result.description ? (
-
{result.description}
+
{result.description}
) : null}
{result.status === "error" ? (