From 322b0253504b9cdefdba24e674913385b48d8949 Mon Sep 17 00:00:00 2001 From: cesnimda Date: Thu, 22 Oct 2026 22:11:11 +0200 Subject: [PATCH] fix(ui): prevent wrapped landing headings from overlapping --- docs/redesign/phase-9/p9-06/DEFECTS.md | 6 ++-- .../e2e/landing-presentation.spec.ts | 36 +++++++++++++++++++ job-tracker-ui/src/views/LandingPage.tsx | 8 ++--- 3 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 job-tracker-ui/e2e/landing-presentation.spec.ts diff --git a/docs/redesign/phase-9/p9-06/DEFECTS.md b/docs/redesign/phase-9/p9-06/DEFECTS.md index f22acd6..b4be3c0 100644 --- a/docs/redesign/phase-9/p9-06/DEFECTS.md +++ b/docs/redesign/phase-9/p9-06/DEFECTS.md @@ -5,8 +5,10 @@ Status is evidence-specific. Historic acceptance records remain unchanged. A pas | ID | Severity / impact | Reproduction and disposition | Owner / closure | Phase 9 / production | |---|---|---|---|---| | P9-T01 | Medium test reliability: parser descendant test could time out before it had a descendant | Entry sidecar 55 pass/1 fail (missing PID); isolated run passes; explicit slow startup reproduces the race. Fixture now waits for its child PID before the unchanged 200 ms deadline. Real process-tree kill and HTTP 504 assertions retained. Focused green and full 56/56 successor. No parser product change. | QA; `.artifacts/phase9/sidecar-entry-failed.*`, `sidecar-fixture-red.*`, `sidecar-fixture-green.*`, `sidecar.xml` | Resolved in tested Windows environment; Linux run remains separate. | -| P9-L01 | Low localisation defect: Admin grid vendor controls appear in English in Bokmål | Sole DataGrid owner omitted localeText. New English desktop test passes while Bokmål fails to find “Rader per side:”. Reuse installed `nbNO` locale in AdminUsersPage. Focused 12/12 green; complete regression/native successor required. | Frontend; `admin-users-page.test.tsx`, `AdminUsersPage.tsx`; retained RED/GREEN logs/JSON | Pending integrated successor; no authorisation, pagination or data contract change. | -| RD21 | High data-integrity limitation: generic job/canonical Career stale saves and creation-intent idempotency lack the complete CV-style revision contract | Inherited, not automatically closed by preference/CV conflict tests. Source/previous runtime evidence and any fresh characterization must be read separately. Do not infer server expected-version protection from a component dirty-state guard. | Domain owners; separately scoped contract/design, all callers, server concurrency and recovery tests. No silent architectural expansion in QA. | Retained qualification; blocks unqualified concurrent-write/production claims. | +| P9-L01 | Low localisation defect: Admin grid vendor controls appear in English in Bokmål | Sole DataGrid owner omitted localeText. New English desktop test passes while Bokmål fails to find “Rader per side:”. Reuse installed `nbNO` locale in AdminUsersPage. Focused 12/12 green; first integrated Jest919/104 and native desktop matrix green; final frozen successor recorded separately. | Frontend; `admin-users-page.test.tsx`, `AdminUsersPage.tsx`; retained RED/GREEN logs/JSON | Resolved in observed local integration; no authorisation, pagination or data contract change. | +| P9-V01 | Medium visual defect: wrapped public h2 lines overlap at320px in EN/NB | Four larger h2 owners inherited a fixed body line height. Original native geometry2/2 RED; explicit lineHeight1.4 now2/2 GREEN across16 width/theme/locale vectors; independent EN/light and NB/dark PNG inspection confirms separation. Intermediate1.2 still failed unchanged geometry assertions and was superseded. | Frontend; `LandingPage.tsx`, new `landing-presentation.spec.ts`; `landing-red`, `landing-qualified`, `landing-accepted` raw reports | Bounded fix verified by native geometry/build; complete frozen successor separately required. | +| RD21 | High data-integrity limitation: generic job/canonical Career stale saves and creation-intent idempotency lack the complete CV-style revision contract | Fresh independent two-client reproduction loses the first acknowledged summary/title; stale Career200 and job204 restore older fields. [Observed values](../p9-04/rd21.json). Creation idempotency remains separate and unclosed. | Domain owners; [separate issue](../p9-04/RD21_CONCURRENCY_ISSUE.md), all callers, atomic server concurrency and recovery tests. No silent architectural expansion in QA. | FAIL for observed stale-write safety; blocks unqualified Phase9 and production acceptance. | +| P9-L02 | Low linguistic residual: stored event type `Created` appears in Bokmål job history/audit | Rendered route comparison detects18 occurrences across the matrix; raw stored metadata is not a missing translation key. Do not translate authored/provider content indiscriminately. | UI/localisation owner; separately map known event enums with unknown-value fallback and history/audit tests; human wording review | Retained nonblocking development qualification; no data mutation proposed. | | P9-PERF-01 | Low duplicate report work: shell and Dashboard each initially read reminders | Phase 8 source/network diagnosis retained. Phase 9 request-count evidence must distinguish initial duplicate reads from additional reads during editing. | Shared reminder read owner; measured benefit and independent shell/dashboard failure/loading tests before consolidation. | Accepted existing development debt; no Phase 10 implementation here. | Failed diagnostic fixtures are also retained: initial route matrix mistook “CV unavailable” alert text for a heading; correct page heading is “Public CV”. Initial sustained fixture compared submitted JSON directly with the canonical normalizer and used derived date fields; corrected fixture uses canonical `start`/`end`, distinct bullets and a verified saved readback before checking preservation. The initial drawer assertion inspected immediately after ARIA removal rather than waiting for the closing transition; settled CSS/keyboard proof is required. These are QA harness mistakes, not demonstrated product defects, and failed attempts are never counted as passes. diff --git a/job-tracker-ui/e2e/landing-presentation.spec.ts b/job-tracker-ui/e2e/landing-presentation.spec.ts new file mode 100644 index 0000000..075d130 --- /dev/null +++ b/job-tracker-ui/e2e/landing-presentation.spec.ts @@ -0,0 +1,36 @@ +import { test, expect } from '@playwright/test'; +import { translations } from '../src/i18n/translations'; + +for (const language of ['en', 'nb'] as const) { + test(`[P9 PRES-E01 E08] ${language} wrapped landing headings keep separate text lines`, async ({ page, context }, info) => { + test.setTimeout(90000); + await context.route('**/*', route => { + const url = new URL(route.request().url()); + return ['localhost', '127.0.0.1'].includes(url.hostname) || ['data:', 'blob:', 'about:'].includes(url.protocol) ? route.continue() : route.abort(); + }); + await context.addInitScript(language => localStorage.setItem('uiLanguage', language), language); + const observations = []; + for (const theme of ['light', 'dark']) for (const width of [320, 390, 768, 1440]) { + await page.setViewportSize({ width, height: 900 }); + await page.goto('/'); + await page.evaluate(theme => localStorage.setItem('jobtracker.themeMode', theme), theme); + await page.reload(); + await expect(page.getByRole('heading', { name: translations[language].landingPreviewTitle, exact: true })).toBeVisible(); + await page.evaluate(() => document.fonts.ready); + const headings = await page.locator('h2').evaluateAll(nodes => nodes.map(node => { + const range = document.createRange(); range.selectNodeContents(node); + const lines = [...range.getClientRects()].filter(rect => rect.width > 0).map(rect => ({ top: rect.top, bottom: rect.bottom })); + return { text: node.textContent, lineHeight: getComputedStyle(node).lineHeight, lines }; + })); + expect(headings.length).toBeGreaterThanOrEqual(4); + if (width === 320) expect(headings.some(heading => heading.lines.length > 1)).toBe(true); + for (const heading of headings) for (let index = 1; index < heading.lines.length; index++) { + expect.soft(heading.lines[index].top, `${width}/${theme}: ${heading.text}`).toBeGreaterThanOrEqual(heading.lines[index - 1].bottom - 1); + } + expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(width + 1); + observations.push({ width, theme, language, headings }); + if (width === 320) await info.attach(`landing-${language}-${theme}-${width}`, { body: await page.screenshot({ fullPage: true, animations: 'disabled' }), contentType: 'image/png' }); + } + await info.attach('landing-line-geometry', { body: JSON.stringify(observations, null, 2), contentType: 'application/json' }); + }); +} diff --git a/job-tracker-ui/src/views/LandingPage.tsx b/job-tracker-ui/src/views/LandingPage.tsx index b90ed9a..a58fd16 100644 --- a/job-tracker-ui/src/views/LandingPage.tsx +++ b/job-tracker-ui/src/views/LandingPage.tsx @@ -140,7 +140,7 @@ export default function LandingPage() { {t("landingPreviewEyebrow")} - {t("landingPreviewTitle")} + {t("landingPreviewTitle")} @@ -160,7 +160,7 @@ export default function LandingPage() { {t("landingFeaturesEyebrow")} - {t("landingFeaturesTitle")} + {t("landingFeaturesTitle")} {t("landingFeaturesBody")} @@ -181,7 +181,7 @@ export default function LandingPage() { {t("landingHowEyebrow")} - {t("landingHowTitle")} + {t("landingHowTitle")} {STEPS.map((s) => ( @@ -199,7 +199,7 @@ export default function LandingPage() { {t("landingPlansEyebrow")} - {t("landingPlansTitle")} + {t("landingPlansTitle")} {t("landingPlansBody")}