From d1d0281bdd58c2d26f06b405df4b45e95e01d21e Mon Sep 17 00:00:00 2001 From: rcourtman Date: Mon, 3 Aug 2026 22:07:32 +0100 Subject: [PATCH] Contain table scrolling on narrow screens --- .../subsystems/frontend-primitives.md | 10 ++++--- frontend-modern/browser-verification.json | 29 ++++++++++--------- .../src/__tests__/App.architecture.test.ts | 3 ++ .../SharedPrimitives.guardrails.test.ts | 7 ++++- .../src/components/shared/Table.tsx | 2 +- .../shared/__tests__/PulseDataGrid.test.tsx | 3 ++ .../shared/__tests__/Table.test.tsx | 2 ++ frontend-modern/src/index.css | 5 ++++ 8 files changed, 42 insertions(+), 19 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index f40bcf39d..17ebae2b4 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -1004,10 +1004,12 @@ not a replacement status card, CTA band, or page-local nested card. the shared `Table` already fills the available width and that second minimum would override the phone-width floor, squeezing names and metrics into unreadable slivers. - Narrow viewports keep document-level overflow - contained by `Table` and scroll the table itself; feature tables must not - squeeze every declared column into the viewport, override the shared floor, - or add a second page-local scroll wrapper. + Narrow viewports keep document-level overflow contained by `Table` and + scroll the table itself. The shared scroll shell owns inline-size and paint + containment plus horizontal overscroll containment so a readable table + floor cannot widen or horizontally pan the application document. Feature + tables must not squeeze every declared column into the viewport, override + the shared floor, or add a second page-local scroll wrapper. Product-table subgroup/header rows must likewise consume the shared `frontend-modern/src/components/shared/groupedTableRowPresentation.ts` helper and `.grouped-table-row` CSS token contract instead of local diff --git a/frontend-modern/browser-verification.json b/frontend-modern/browser-verification.json index b92a96abe..c5ecd518a 100644 --- a/frontend-modern/browser-verification.json +++ b/frontend-modern/browser-verification.json @@ -1,16 +1,19 @@ { "version": 1, - "base_sha": "d00249fd7ddcc6cee0f5f35911dd6c0127cf1f33", - "verified_at": "2026-08-03T18:47:21Z", + "base_sha": "32d373395cee894628757f49cde858a66093531f", + "verified_at": "2026-08-03T21:06:07Z", "result": "passed", "changed_paths": [ - "frontend-modern/src/features/patrol/PatrolAttentionWorkbench.tsx" + "frontend-modern/src/components/shared/Table.tsx", + "frontend-modern/src/index.css" ], "content_sha256": { - "frontend-modern/src/features/patrol/PatrolAttentionWorkbench.tsx": "87e314940a667c8ff49e2aaf63b45f8d3f6278c3c01747fcaf9f850295d3e413" + "frontend-modern/src/components/shared/Table.tsx": "d4c1fdc6bf387a2d37bb143e414c4b2dfc188769884d7738e109a6061852305d", + "frontend-modern/src/index.css": "061b892fa8260365adfdaa9f58d4b00d2e67b1b072ac109a75e958f14a36332a" }, "routes": [ - "/patrol" + "/standalone/machines", + "/docker/overview" ], "viewports": [ { @@ -23,15 +26,15 @@ } ], "states": [ - "Desktop Patrol attention workbench with all six counted lifecycle choices visible in the segmented control", - "Desktop Recent resolved selection with the resolved queue item loaded", - "Narrow Patrol attention workbench with the segmented control hidden and the labeled native select visible", - "Narrow Recent resolved selection with the resolved queue item loaded" + "Desktop Machines table fully loaded with its standard toolbar and all visible columns", + "Phone-width Machines table loaded at the left edge with the document contained to 390 pixels", + "Phone-width Machines table after a 300-pixel table-local horizontal scroll", + "Phone-width Docker Overview with both shared table shells contained to the viewport" ], "interactions": [ - "Selected Recent resolved from the desktop segmented control and confirmed its pressed state and resolved queue result", - "Reloaded /patrol at 390 by 844 and confirmed the Attention state select and every counted option remained available without horizontal overflow", - "Selected Recent resolved from the narrow native select and confirmed its selected state and resolved queue result", - "Measured the desktop segmented group inside the viewport and the narrow document at exactly 390 pixels wide with the wide group display set to none" + "Applied a 300-pixel horizontal gesture outside the Machines table and confirmed document scrollX remained 0", + "Applied a 300-pixel horizontal gesture inside the Machines table and confirmed its scrollLeft changed while document scrollX remained 0", + "Repeated the outside-page and inside-table horizontal gestures on Docker Overview and confirmed only the targeted table shell scrolled", + "Resized Machines to 1280 by 720 and confirmed the table filled its container without document overflow or an unnecessary local scroll range" ] } diff --git a/frontend-modern/src/__tests__/App.architecture.test.ts b/frontend-modern/src/__tests__/App.architecture.test.ts index 0297007fa..2586b45be 100644 --- a/frontend-modern/src/__tests__/App.architecture.test.ts +++ b/frontend-modern/src/__tests__/App.architecture.test.ts @@ -408,6 +408,9 @@ describe('App architecture', () => { expect(appStylesSource).toContain('--color-grouped-table-row-bg'); expect(appStylesSource).toContain('--color-grouped-table-row-bg: rgba(226, 232, 240, 0.72);'); expect(appStylesSource).toContain('--color-grouped-table-row-bg: rgba(51, 65, 85, 0.58);'); + expect(appStylesSource).toContain('.table-scroll-shell'); + expect(appStylesSource).toContain('contain: inline-size paint'); + expect(appStylesSource).toContain('overscroll-behavior-x: contain'); expect(appStylesSource).toContain('.progress-fill-frame'); expect(appStylesSource).toContain('.metric-fill-geometry'); expect(appStylesSource).toContain('.animated-number'); diff --git a/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts b/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts index 03415a7b9..4c223921a 100644 --- a/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts +++ b/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts @@ -1293,7 +1293,12 @@ describe('shared primitive guardrails', () => { it('keeps product table scroll frames on the shared table shell', () => { expect(tableSource).toContain('wrapperClass'); - expect(tableSource).toContain('w-full overflow-x-auto touch-scroll'); + expect(tableSource).toContain( + 'table-scroll-shell w-full min-w-0 max-w-full overflow-x-auto touch-scroll', + ); + expect(frontendIndexCssSource).toContain('.table-scroll-shell'); + expect(frontendIndexCssSource).toContain('contain: inline-size paint'); + expect(frontendIndexCssSource).toContain('overscroll-behavior-x: contain'); expect(tableSource).toContain('w-full border-collapse text-left whitespace-nowrap'); expect(tableCardSource).toContain('TABLE_CARD_FRAME_CLASS'); expect(tableCardSource).toContain('overflow-hidden'); diff --git a/frontend-modern/src/components/shared/Table.tsx b/frontend-modern/src/components/shared/Table.tsx index 91ae8ff9b..2d4a7ad96 100644 --- a/frontend-modern/src/components/shared/Table.tsx +++ b/frontend-modern/src/components/shared/Table.tsx @@ -19,7 +19,7 @@ export function Table(props: TableProps) {
{ expect(tableSource).toContain('customDividePattern'); expect(tableSource).toContain('customBorderPattern'); expect(tableSource).toContain('touch-scroll'); + expect(tableSource).toContain( + 'table-scroll-shell w-full min-w-0 max-w-full overflow-x-auto touch-scroll', + ); expect(tableSource).toContain('height?: string | number;'); expect(tableSource).not.toContain('style={{'); expect(tableSource).not.toContain('style={'); diff --git a/frontend-modern/src/components/shared/__tests__/Table.test.tsx b/frontend-modern/src/components/shared/__tests__/Table.test.tsx index 3983b4193..4c1116be8 100644 --- a/frontend-modern/src/components/shared/__tests__/Table.test.tsx +++ b/frontend-modern/src/components/shared/__tests__/Table.test.tsx @@ -14,6 +14,8 @@ import { describe('TableBody', () => { it('keeps the shared table wrapper CSP-safe', () => { expect(tableSource).toContain('touch-scroll'); + expect(tableSource).toContain('table-scroll-shell'); + expect(tableSource).toContain('min-w-0 max-w-full'); expect(tableSource).not.toContain('style={{'); expect(tableSource).not.toContain('style={'); }); diff --git a/frontend-modern/src/index.css b/frontend-modern/src/index.css index a372be158..a960a34c8 100644 --- a/frontend-modern/src/index.css +++ b/frontend-modern/src/index.css @@ -185,6 +185,11 @@ -webkit-overflow-scrolling: touch; } + .table-scroll-shell { + contain: inline-size paint; + overscroll-behavior-x: contain; + } + .app-scroll-shell { scrollbar-gutter: stable; }