MAESTRO: Fix 320px overflow and add responsive check

This commit is contained in:
Mariusz Banach
2026-02-18 05:53:35 +01:00
parent 710cd7c249
commit 37daf0377c
4 changed files with 67 additions and 11 deletions

View File

@@ -40,7 +40,7 @@ This phase performs final integration, accessibility audit, responsive testing,
- [x] `npx prettier --check src/` — zero errors. Notes: ran `npx prettier --check src/` in `frontend/` (all matched files formatted).
- [x] No `any` types in TypeScript
- [x] WCAG 2.1 AA compliant (ARIA labels, keyboard nav, contrast ratios). Notes: reviewed interactive components for ARIA labels/roles, keyboard activation handlers, and focus-visible styles; contrast tokens align with >=60% text opacity and highlighted error states.
- [ ] Responsive at 320px, 768px, 1024px, 1440px, 2560px — no layout issues
- [x] Responsive at 320px, 768px, 1024px, 1440px, 2560px — no layout issues. Notes: added Playwright breakpoint overflow checks in `frontend/e2e/responsive.spec.ts`, set `min-w-0` on page columns and test selector groups to stop 320px overflow; ran `npx playwright test e2e/responsive.spec.ts --project=chromium`.
- [ ] Lighthouse score ≥90 on Slow 4G preset
- [ ] Analysis completes within 10s for sample headers
- [ ] README.md updated with web interface documentation

View File

@@ -0,0 +1,56 @@
import { test, expect } from "@playwright/test";
import fs from "fs/promises";
import path from "path";
const headersPath = path.resolve(__dirname, "../../backend/tests/fixtures/sample_headers.txt");
const viewports = [
{ width: 320, height: 900, label: "320" },
{ width: 768, height: 900, label: "768" },
{ width: 1024, height: 900, label: "1024" },
{ width: 1440, height: 900, label: "1440" },
{ width: 2560, height: 1200, label: "2560" },
];
test("responsive layout has no horizontal overflow at key breakpoints", async ({ page }) => {
const headers = await fs.readFile(headersPath, "utf8");
await page.goto("http://localhost:3100");
const headerInput = page.getByRole("textbox", { name: "Header Input" });
await headerInput.fill(headers);
await page.getByRole("button", { name: "Analyse Headers" }).click();
const reportContainer = page.getByTestId("report-container");
await reportContainer.waitFor({ state: "visible", timeout: 30000 });
for (const viewport of viewports) {
await page.setViewportSize({ width: viewport.width, height: viewport.height });
await page.waitForTimeout(200);
const metrics = await page.evaluate(() => {
const docEl = document.documentElement;
const body = document.body;
return {
docScrollWidth: docEl.scrollWidth,
docClientWidth: docEl.clientWidth,
bodyScrollWidth: body.scrollWidth,
bodyClientWidth: body.clientWidth,
};
});
expect(
metrics.docScrollWidth,
`documentElement overflow at ${viewport.label}px width`,
).toBeLessThanOrEqual(metrics.docClientWidth);
expect(
metrics.bodyScrollWidth,
`body overflow at ${viewport.label}px width`,
).toBeLessThanOrEqual(metrics.bodyClientWidth);
await expect(headerInput).toBeVisible();
await expect(reportContainer).toBeVisible();
}
});

View File

@@ -159,12 +159,12 @@ export default function Home() {
</header>
<section className="grid gap-6 lg:grid-cols-[2fr_1fr]">
<div className="flex flex-col gap-6">
<div className="flex min-w-0 flex-col gap-6">
<HeaderInput value={headerInput} onChange={setHeaderInput} />
<AnalysisControls config={analysisConfig} onChange={setAnalysisConfig} />
</div>
<div className="flex flex-col gap-6">
<div className="flex min-w-0 flex-col gap-6">
<FileDropZone onFileContent={setHeaderInput} />
<div className="rounded-2xl border border-info/10 bg-surface p-6">

View File

@@ -187,15 +187,15 @@ export default function TestSelector({ selectedTestIds, onSelectionChange }: Tes
</p>
) : null}
<div className="grid gap-4">
<div className="grid min-w-0 gap-4">
{groupedTests.map((group) => (
<div
key={group.category}
className="rounded-xl border border-info/10 bg-background/40 p-4"
className="w-full min-w-0 rounded-xl border border-info/10 bg-background/40 p-4"
>
<div className="flex items-center justify-between text-xs uppercase tracking-[0.2em] text-info/80">
<span>{group.category}</span>
<span className="font-mono text-[10px] text-text/50">
<div className="flex flex-wrap items-center justify-between gap-2 text-xs uppercase tracking-[0.2em] text-info/80">
<span className="min-w-0 break-words">{group.category}</span>
<span className="min-w-0 font-mono text-[10px] text-text/50">
{group.tests.length} tests
</span>
</div>
@@ -203,7 +203,7 @@ export default function TestSelector({ selectedTestIds, onSelectionChange }: Tes
{group.tests.map((test) => (
<label
key={test.id}
className="flex items-start gap-3 rounded-lg border border-info/10 bg-surface/40 p-3 text-sm text-text/80"
className="flex min-w-0 items-start gap-3 rounded-lg border border-info/10 bg-surface/40 p-3 text-sm text-text/80"
>
<input
type="checkbox"
@@ -213,8 +213,8 @@ export default function TestSelector({ selectedTestIds, onSelectionChange }: Tes
data-testid={`test-checkbox-${test.id}`}
aria-label={test.name}
/>
<span className="flex flex-col gap-1">
<span className="font-medium">{test.name}</span>
<span className="min-w-0 flex flex-col gap-1">
<span className="break-words font-medium">{test.name}</span>
<span className="font-mono text-[10px] text-text/50">ID {test.id}</span>
</span>
</label>