From 41fa79d52b362e3c3b8b6c333f0fe6bc55f3ec5f Mon Sep 17 00:00:00 2001 From: "pulse-triage[bot]" <249995291+pulse-triage[bot]@users.noreply.github.com> Date: Mon, 21 Sep 2026 14:31:27 +0100 Subject: [PATCH] fix(alerts): align the Started run with the alert-card footer (#2119) The Alerts overview alert-card footer is an items-center flex row whose first child, the Started timestamp, carried its own mt-1. Under items-center that margin shifted the Started run 2px below the adjacent delivery-status run, which is the small text misalignment the reporter underlined in #2119. Remove the child margin; the footer row already carries the top margin, so the two runs share a baseline. Add a presentation regression test and an offline browser proof that measures the text rects of both runs at desktop and narrow widths. Contract-Neutral: Behavioral footer-alignment fix; no public-contract or subsystem-shape delta. Change-source: pulse-maintainer --- .../browser-tests/alert-card-footer.html | 10 ++ .../browser-tests/alert-card-footer.tsx | 43 +++++++ frontend-modern/browser-verification.json | 46 +++---- .../alertOverviewPresentation.test.ts | 12 +- .../src/utils/alertOverviewPresentation.ts | 4 +- scripts/check-alert-card-footer.cjs | 120 ++++++++++++++++++ 6 files changed, 205 insertions(+), 30 deletions(-) create mode 100644 frontend-modern/browser-tests/alert-card-footer.html create mode 100644 frontend-modern/browser-tests/alert-card-footer.tsx create mode 100644 scripts/check-alert-card-footer.cjs diff --git a/frontend-modern/browser-tests/alert-card-footer.html b/frontend-modern/browser-tests/alert-card-footer.html new file mode 100644 index 000000000..ac1d35ab2 --- /dev/null +++ b/frontend-modern/browser-tests/alert-card-footer.html @@ -0,0 +1,10 @@ + + + + + + +
+ + + diff --git a/frontend-modern/browser-tests/alert-card-footer.tsx b/frontend-modern/browser-tests/alert-card-footer.tsx new file mode 100644 index 000000000..4d115741c --- /dev/null +++ b/frontend-modern/browser-tests/alert-card-footer.tsx @@ -0,0 +1,43 @@ +// Browser fixture for issue #2119: the Alerts overview alert-card footer. +// +// It reproduces the production footer from AlertOverviewAlertCard.tsx: a flex +// row (items-center) whose first child is the "Started" line classed by +// getAlertOverviewStartedAtClass(), followed by the delivery-status span. The +// reporter's screenshot underlines the Started timestamp and the status text at +// different heights, so the fixture exists to measure whether the two runs share +// a baseline. Synthetic props only; no backend, API or WebSocket path. +import { render } from 'solid-js/web'; + +import { getAlertOverviewStartedAtClass } from '../src/utils/alertOverviewPresentation'; +import '../src/index.css'; + +function AlertCardFooter() { + return ( +
+

+ Alert notifications are not reaching their destinations. +

+ +
+ ); +} + +render(() => , document.getElementById('root')!); diff --git a/frontend-modern/browser-verification.json b/frontend-modern/browser-verification.json index f7e826e09..4d84052c9 100644 --- a/frontend-modern/browser-verification.json +++ b/frontend-modern/browser-verification.json @@ -1,48 +1,38 @@ { "version": 1, - "base_sha": "fdb2be15befb4fbcd34b4e0399b147bfa098b355", - "verified_at": "2026-09-21T05:50:47Z", + "base_sha": "eb47a4e653581661d4b267710d9c1bb60a904733", + "verified_at": "2026-09-21T14:31:00Z", "result": "passed", "changed_paths": [ - "frontend-modern/src/features/docs/docMarkdown.ts", - "frontend-modern/src/pages/Docs.tsx" + "frontend-modern/src/utils/alertOverviewPresentation.ts" ], "content_sha256": { - "frontend-modern/src/features/docs/docMarkdown.ts": "e114a4c2e49a8dc0f2549a704af6ceac54d4eca0cfcae28c8ab8cb6cc1b0718c", - "frontend-modern/src/pages/Docs.tsx": "c6c4b066c6af8a4eb8f01a9e03c2f939ee1592867dea2bf0f328bf111f99f2eb" + "frontend-modern/src/utils/alertOverviewPresentation.ts": "7f68a70800deb0cb8214d4fd25c7454c5b0fde1084e08365eb93bc361b80846a" }, "routes": [ - "/docs/API", - "/docs/API#resource-maintenance-and-operator-state", - "/docs/README" + "/browser-tests/alert-card-footer.html" ], "viewports": [ { - "width": 1440, - "height": 900 + "width": 2010, + "height": 1072 }, { - "width": 390, - "height": 844 + "width": 480, + "height": 800 } ], "states": [ - "API reference renders 149 headings, every one with a unique generated id; the maintenance section id is resource-maintenance-and-operator-state", - "The in-document link to #resource-maintenance-and-operator-state resolves to the generated heading", - "Direct fragment load focuses and scrolls the maintenance heading after the asynchronous Markdown fetch", - "Reload on the same fragment restores focus and target", - "Malformed (#%invalid) and missing (#does-not-exist) fragments leave the document rendered and do not move focus", - "Keyboard activation of the All documentation link routes to /docs/README inside the SPA shell", - "Wide API tables scroll inside their own container; neither viewport overflows horizontally" + "Alerts overview alert-card footer with the Started timestamp and the adjacent delivery-status run", + "The Started run classed by getAlertOverviewStartedAtClass() inside the items-center footer flex row", + "Desktop 2010x1072 and narrow 480x800 layouts" ], "interactions": [ - "Mount the production Docs page under the production router and serve the real public/docs/API.md asset from the Vite dev server", - "Load /docs/API, /docs/API#resource-maintenance-and-operator-state, a malformed fragment and a missing fragment, and reload the direct fragment", - "Assert document.activeElement, tabIndex, scrollIntoView position and window.location.hash", - "Focus the first documentation link and activate it with Enter, confirming router navigation without a full reload", - "Resize to 390x844 and check page overflow and table scroll wrappers", - "Record Chromium 141.0.7390.37 with no uncaught page errors and no failed responses" + "Mount the production footer markup from AlertOverviewAlertCard.tsx with the class returned by getAlertOverviewStartedAtClass() and the project CSS", + "Measure the text bounding rects of the Started run and the status run and assert their top and bottom edges match within 1px", + "Before the fix the same run reported the Started run 2px below the status run (started top 70 / bottom 86 vs status top 68 / bottom 84); after the fix both report top 66 / bottom 82 at both viewports", + "Inspect the desktop and narrow screenshots" ], - "command": "pulse-worker-browser scripts/check-docs-fragment-navigation.cjs", - "notes": "Offline Playwright Chromium run of the production Docs page. Synthetic backend: /api/security/status is answered with hasAuthentication=false; the shipped API markdown asset is served by the Vite dev server, not a live Pulse. Heading ids follow GitHub's algorithm (lower-case, punctuation removed, spaces to hyphens, per-document duplicate numbering) so repository fragment links resolve. The renderer keeps the documentation route dependency-free. Chromium 141.0.7390.37 matches the offline runtime. This receipt is content-addressed to the changed frontend sources at this candidate base." + "command": "pulse-worker-browser scripts/check-alert-card-footer.cjs", + "notes": "Offline Playwright Chromium 141.0.7390.37 run of the production Alerts overview alert-card footer markup with the class returned by getAlertOverviewStartedAtClass() and the project CSS; no backend, API or WebSocket path. The footer is an items-center flex row that already carries the top margin, so the child 'mt-1' pushed the Started run 2px below the delivery-status span; this matches the two runs the reporter underlined at different heights in #2119. Removing the child margin aligns both runs (top 66 / bottom 82 at desktop and narrow, delta 0). No page errors and no failed responses. This receipt is content-addressed to alertOverviewPresentation.ts sha256 7f68a708…." } diff --git a/frontend-modern/src/utils/__tests__/alertOverviewPresentation.test.ts b/frontend-modern/src/utils/__tests__/alertOverviewPresentation.test.ts index df01b043d..7228c40c3 100644 --- a/frontend-modern/src/utils/__tests__/alertOverviewPresentation.test.ts +++ b/frontend-modern/src/utils/__tests__/alertOverviewPresentation.test.ts @@ -324,7 +324,7 @@ describe('alertOverviewPresentation', () => { expect(getAlertOverviewAcknowledgedBadgeClass()).toBe( 'px-2 py-0.5 text-xs bg-yellow-200 dark:bg-yellow-800 text-yellow-800 dark:text-yellow-200 rounded', ); - expect(getAlertOverviewStartedAtClass()).toBe('mt-1 text-xs text-muted'); + expect(getAlertOverviewStartedAtClass()).toBe('text-xs text-muted'); expect(getAlertOverviewPrimaryActionClass(true)).toBe( 'px-3 py-1.5 text-xs font-medium border rounded-md transition-all disabled:opacity-50 disabled:cursor-not-allowed text-base-content border-border hover:bg-surface-hover', ); @@ -336,6 +336,16 @@ describe('alertOverviewPresentation', () => { ); }); + it('keeps the Started run on the footer baseline (#2119)', () => { + // The alert-card footer is an items-center flex row that already carries the + // top margin. A vertical margin on this child shifted the Started run below + // the adjacent delivery-status span, which the reporter underlined. + const startedClass = getAlertOverviewStartedAtClass(); + expect(startedClass.split(/\s+/)).not.toContain('mt-1'); + expect(startedClass).toContain('text-xs'); + expect(startedClass).toContain('text-muted'); + }); + it('localizes alert overview helper copy through the active locale', () => { setActiveLocale('es'); diff --git a/frontend-modern/src/utils/alertOverviewPresentation.ts b/frontend-modern/src/utils/alertOverviewPresentation.ts index 5825a629c..53f5b1fd7 100644 --- a/frontend-modern/src/utils/alertOverviewPresentation.ts +++ b/frontend-modern/src/utils/alertOverviewPresentation.ts @@ -432,7 +432,9 @@ export function getAlertOverviewAcknowledgedBadgeClass(): string { } export function getAlertOverviewStartedAtClass(): string { - return 'mt-1 text-xs text-muted'; + // The footer flex row already carries the top margin; a margin on this child + // shifts the Started run below its siblings under items-center (#2119). + return 'text-xs text-muted'; } export function getAlertOverviewPrimaryActionClass(acknowledged: boolean): string { diff --git a/scripts/check-alert-card-footer.cjs b/scripts/check-alert-card-footer.cjs new file mode 100644 index 000000000..87ffbd4e7 --- /dev/null +++ b/scripts/check-alert-card-footer.cjs @@ -0,0 +1,120 @@ +// Offline real-browser regression for issue #2119 (Alerts overview card footer). +// +// Mounts the production footer markup with the class returned by +// getAlertOverviewStartedAtClass() and measures whether the "Started" run and +// the adjacent delivery-status run share a vertical baseline. Run with: +// pulse-worker-browser scripts/check-alert-card-footer.cjs +const path = require("node:path"); +const { chromium } = require("playwright"); + +const ROOT = path.resolve(process.cwd(), "frontend-modern"); + +const launchOptions = { + headless: true, + channel: "chromium", + args: ["--no-sandbox"], +}; + +const measure = () => + (() => { + const started = document.querySelector('[data-testid="started"]'); + const status = document.querySelector('[data-testid="status"]'); + if (!started || !status) return null; + const textRect = (el) => { + const range = document.createRange(); + range.selectNodeContents(el); + const rect = range.getBoundingClientRect(); + return { top: rect.top, bottom: rect.bottom }; + }; + const startedText = textRect(started); + const statusText = textRect(status); + return { + startedClass: started.getAttribute("class"), + statusClass: status.getAttribute("class"), + startedText, + statusText, + baselineDelta: Math.abs(startedText.bottom - statusText.bottom), + topDelta: Math.abs(startedText.top - statusText.top), + }; + })(); + +(async () => { + process.chdir(ROOT); + const { createServer } = await import( + path.join(ROOT, "node_modules", "vite", "dist", "node", "index.js") + ); + const server = await createServer({ + root: ROOT, + configFile: path.join(ROOT, "vite.config.ts"), + server: { host: "127.0.0.1", port: 5198, strictPort: true }, + }); + let browser; + const errors = []; + const httpErrors = []; + try { + await server.listen(); + browser = await chromium.launch(launchOptions); + const page = await browser.newPage({ + viewport: { width: 2010, height: 1072 }, + }); + page.on("pageerror", (error) => errors.push(error.message)); + page.on("console", (message) => { + if (message.type() !== "error") return; + if (message.text().includes("Failed to load resource")) return; + errors.push(`console: ${message.text()}`); + }); + page.on("response", (response) => { + if (response.status() >= 400) + httpErrors.push(`${response.status()} ${response.url()}`); + }); + + await page.goto( + "http://127.0.0.1:5198/browser-tests/alert-card-footer.html", + ); + await page.waitForSelector('[data-testid="started"]'); + await page.waitForSelector('[data-testid="status"]'); + + const desktop = await page.evaluate(measure); + if (!desktop) throw new Error("footer runs not rendered"); + await page.screenshot({ + path: path.join(ROOT, "browser-tests", "alert-card-footer-desktop.png"), + }); + + await page.setViewportSize({ width: 480, height: 800 }); + await page.waitForTimeout(150); + const narrow = await page.evaluate(measure); + await page.screenshot({ + path: path.join(ROOT, "browser-tests", "alert-card-footer-narrow.png"), + }); + + if (errors.length > 0) + throw new Error(`page errors: ${errors.join(" | ")}`); + + const worst = Math.max( + desktop.baselineDelta, + desktop.topDelta, + narrow.baselineDelta, + narrow.topDelta, + ); + if (worst > 1) { + throw new Error( + `Started and status runs are vertically misaligned: desktop ${JSON.stringify(desktop)} ` + + `narrow ${JSON.stringify(narrow)}`, + ); + } + + console.log( + JSON.stringify( + { result: "passed", desktop, narrow, httpErrors }, + null, + 2, + ), + ); + } finally { + if (browser) await browser.close(); + await server.close(); + } +})().catch((error) => { + console.error("FAILED:", error && error.stack ? error.stack : error); + process.exitCode = 1; +});