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;
+});