diff --git a/src/components/FooterProvenance.tsx b/src/components/FooterProvenance.tsx
index 5e13cf11..56fa37e5 100644
--- a/src/components/FooterProvenance.tsx
+++ b/src/components/FooterProvenance.tsx
@@ -5,35 +5,37 @@ const REPO_URL = 'https://github.com/bradsaucier/opsnormal';
export function FooterProvenance() {
return (
Provenance
-
-
-
- Build
- -
- v{__APP_VERSION__}
-
-
-
-
- License
- - MIT
-
-
-
-
- Source
-
+
+
+
+
- Build
+ -
+ v{__APP_VERSION__}
+
+
+
+
- License
+ - MIT
+
+
+
+
+ Source
+
+
);
}
diff --git a/src/styles/index.css b/src/styles/index.css
index d7f0c226..b83a3d5c 100644
--- a/src/styles/index.css
+++ b/src/styles/index.css
@@ -379,7 +379,7 @@ h3,
.ops-headline-h1 {
font-size: 2.5rem;
line-height: 1.1;
- letter-spacing: 0;
+ letter-spacing: -0.015em;
}
.ops-headline-h2 {
@@ -1176,6 +1176,7 @@ h3,
.ops-telemetry-horizon-label {
border-left: 2px solid var(--color-ops-accent-deep);
+ box-shadow: -4px 0 12px rgba(46, 143, 106, 0.18);
}
.ops-surface-fault-card {
@@ -1510,6 +1511,7 @@ h3,
bottom: -1px;
width: 2px;
height: 16px;
+ border-radius: 1px;
background: var(--color-ops-accent);
box-shadow: 0 0 10px rgba(110, 231, 183, 0.2);
}
@@ -1707,7 +1709,8 @@ h3,
box-shadow:
inset 0 2px 0 var(--ops-status-nominal-border),
inset 1px 0 0 var(--ops-today-rail),
- inset -1px 0 0 var(--ops-today-rail);
+ inset -1px 0 0 var(--ops-today-rail),
+ 0 0 10px rgba(110, 231, 183, 0.07);
}
.ops-history-today-cell {
@@ -1729,7 +1732,8 @@ h3,
box-shadow:
inset 2px 0 0 var(--ops-today-rail),
inset -1px 0 0 var(--ops-today-rail),
- inset 0 2px 0 var(--ops-status-nominal-border);
+ inset 0 2px 0 var(--ops-status-nominal-border),
+ 0 0 10px rgba(110, 231, 183, 0.07);
}
.ops-history-today-cell.ops-history-week-boundary {
@@ -2318,6 +2322,10 @@ h3,
box-shadow: none;
}
+ .ops-telemetry-horizon-label {
+ box-shadow: none;
+ }
+
.ops-section-frame,
.ops-frame-emphasis-primary,
.ops-frame-emphasis-standard,
@@ -2431,7 +2439,8 @@ h3,
--notch: var(--ops-notch-chip);
min-height: 28px;
min-width: 2rem;
- width: 100%;
+ width: auto;
+ flex-shrink: 0;
justify-content: center;
padding: 0 0.75rem;
gap: 0.5rem;
@@ -2439,34 +2448,20 @@ h3,
letter-spacing: 0.2em;
text-transform: uppercase;
color: var(--color-ops-text-secondary);
+ white-space: nowrap;
}
@media (min-width: 1024px) {
.ops-provenance-facts {
grid-auto-flow: column;
grid-auto-columns: max-content;
- justify-content: end;
+ justify-content: start;
column-gap: 1.5rem;
}
.ops-provenance-facts > div {
grid-template-columns: auto auto;
}
-
- .ops-provenance-facts dd {
- text-align: right;
- }
-
- .ops-provenance-source {
- justify-self: end;
- }
-}
-
-@media (min-width: 1024px) and (max-width: 1535px) {
- .ops-provenance-source {
- width: 2rem;
- padding: 0;
- }
}
@media (max-width: 640px) {
@@ -2481,10 +2476,3 @@ h3,
.ops-provenance-source:hover {
color: var(--color-ops-text-primary);
}
-
-@media (min-width: 640px) {
- .ops-provenance-source {
- width: auto;
- align-self: center;
- }
-}
diff --git a/tests/e2e/footer-provenance.spec.ts b/tests/e2e/footer-provenance.spec.ts
index b334b956..356eaed7 100644
--- a/tests/e2e/footer-provenance.spec.ts
+++ b/tests/e2e/footer-provenance.spec.ts
@@ -1,5 +1,25 @@
import { expect, test } from '@playwright/test';
+interface Box {
+ height: number;
+ width: number;
+ x: number;
+ y: number;
+}
+
+function assertBox(box: Box | null, label: string): asserts box is Box {
+ expect(box, `${label} should have a layout box`).not.toBeNull();
+}
+
+function boxesOverlap(first: Box, second: Box) {
+ const horizontalOverlap =
+ first.x < second.x + second.width && first.x + first.width > second.x;
+ const verticalOverlap =
+ first.y < second.y + second.height && first.y + first.height > second.y;
+
+ return horizontalOverlap && verticalOverlap;
+}
+
test('footer exposes build metadata and the public source link', async ({
page,
}) => {
@@ -23,3 +43,35 @@ test('footer exposes build metadata and the public source link', async ({
page.getByText('OpsNormal is a personal status tracking tool.'),
).toBeVisible();
});
+
+for (const width of [1024, 1280, 1536, 1920]) {
+ test(`footer provenance does not overflow or overlap at ${width}px`, async ({
+ page,
+ }) => {
+ await page.setViewportSize({ width, height: 900 });
+ await page.goto('/');
+
+ const provenance = page.getByTestId('footer-provenance');
+ const label = provenance.getByText('Provenance');
+ const facts = provenance.locator('dl');
+ const source = page.getByTestId('footer-provenance-source');
+
+ const provenanceBox = await provenance.boundingBox();
+ const labelBox = await label.boundingBox();
+ const factsBox = await facts.boundingBox();
+ const sourceBox = await source.boundingBox();
+
+ assertBox(provenanceBox, 'footer provenance');
+ assertBox(labelBox, 'footer provenance label');
+ assertBox(factsBox, 'footer provenance facts');
+ assertBox(sourceBox, 'footer provenance source');
+
+ expect(sourceBox.x + sourceBox.width).toBeLessThanOrEqual(
+ provenanceBox.x + provenanceBox.width + 1,
+ );
+ expect(factsBox.y).toBeGreaterThanOrEqual(labelBox.y + labelBox.height - 1);
+ expect(boxesOverlap(labelBox, factsBox)).toBe(false);
+ expect(boxesOverlap(labelBox, sourceBox)).toBe(false);
+ expect(boxesOverlap(factsBox, sourceBox)).toBe(false);
+ });
+}
diff --git a/tests/unit/footerProvenance.test.tsx b/tests/unit/footerProvenance.test.tsx
index dbae34ae..6794edf5 100644
--- a/tests/unit/footerProvenance.test.tsx
+++ b/tests/unit/footerProvenance.test.tsx
@@ -10,6 +10,7 @@ describe('FooterProvenance', () => {
expect(screen.getByText('Build')).toBeInTheDocument();
expect(screen.getByText('License')).toBeInTheDocument();
expect(screen.getByText('MIT')).toBeInTheDocument();
+ expect(screen.getByText('Source')).toBeInTheDocument();
expect(screen.getByText(/^v\d+\.\d+\.\d+/)).toBeInTheDocument();
const link = screen.getByTestId('footer-provenance-source');
diff --git a/tests/unit/historyGrid.test.tsx b/tests/unit/historyGrid.test.tsx
index fab73e01..778e670b 100644
--- a/tests/unit/historyGrid.test.tsx
+++ b/tests/unit/historyGrid.test.tsx
@@ -249,7 +249,9 @@ describe('history helpers and grid behavior', () => {
const previousWeekButton = screen.getByRole('button', {
name: /previous week/i,
});
- const nextWeekButton = screen.getByRole('button', { name: /next week/i });
+ const nextWeekButton = screen.getByRole('button', {
+ name: /next week/i,
+ });
const weekStatus = screen.getByTestId('mobile-history-week-status');
const dailyBriefHeading = screen.getByRole('heading', {
level: 3,
@@ -282,7 +284,7 @@ describe('history helpers and grid behavior', () => {
expect(
screen.getByRole('heading', { level: 3, name: /sat, mar 28, 2026/i }),
).toBeVisible();
- });
+ }, 30000);
it('renders a first-run empty state before any history entries exist', () => {
const dateKeys = getTrailingDateKeys(30, new Date(2026, 2, 28));
@@ -329,7 +331,7 @@ describe('history helpers and grid behavior', () => {
await waitFor(() => {
expect(selectedCellSurface).toHaveClass('ops-sector-spine-degraded');
});
- });
+ }, 30000);
it('applies the state spine to the mobile daily brief surface', async () => {
const user = userEvent.setup({ delay: null });
@@ -365,7 +367,7 @@ describe('history helpers and grid behavior', () => {
);
expect((await axe(container)).violations).toEqual([]);
- }, 15000);
+ }, 30000);
it('has no accessibility violations in the desktop history view', async () => {
const dateKeys = getTrailingDateKeys(30, new Date(2026, 2, 28));
@@ -376,7 +378,7 @@ describe('history helpers and grid behavior', () => {
);
expect((await axe(container)).violations).toEqual([]);
- }, 15000);
+ }, 30000);
it('keeps a single tabbable desktop gridcell and updates the selected-cell brief during keyboard traversal', async () => {
const user = userEvent.setup({ delay: null });
diff --git a/tests/unit/todayPanel.test.tsx b/tests/unit/todayPanel.test.tsx
index 68557de4..4bc211ce 100644
--- a/tests/unit/todayPanel.test.tsx
+++ b/tests/unit/todayPanel.test.tsx
@@ -43,7 +43,7 @@ describe('TodayPanel', () => {
expect((await axe(container)).violations).toEqual([]);
vi.useFakeTimers();
- });
+ }, 15000);
it('renders direct-select radio controls for each sector', () => {
render();