Contain table scrolling on narrow screens

This commit is contained in:
rcourtman
2026-08-03 22:07:32 +01:00
parent 32d373395c
commit d1d0281bdd
8 changed files with 42 additions and 19 deletions
@@ -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
+16 -13
View File
@@ -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"
]
}
@@ -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');
@@ -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');
@@ -19,7 +19,7 @@ export function Table(props: TableProps) {
<div
{...local.wrapperProps}
ref={local.wrapperRef}
class={`w-full overflow-x-auto touch-scroll ${local.wrapperClass || ''}`}
class={`table-scroll-shell w-full min-w-0 max-w-full overflow-x-auto touch-scroll ${local.wrapperClass || ''}`}
>
<table
class={`w-full border-collapse text-left whitespace-nowrap ${local.class || ''}`}
@@ -60,6 +60,9 @@ describe('PulseDataGrid', () => {
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={');
@@ -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={');
});
+5
View File
@@ -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;
}