diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index 982f490fb..6ef081c68 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -3716,7 +3716,15 @@ compact `DetailSectionTable` rows as Overview; those facts must not switch to a provider-local card mosaic or require another tap. Only genuinely large or interactive provider-support content belongs behind `TechnicalDetailsDisclosure`, which owns its collapsed, lazily mounted -boundary. Monitoring mode, lifecycle, notes, maintenance, +boundary. `DetailSectionTable` keeps the single bordered table at narrow widths +where density matters, then presents those same canonical rows as bounded +section cards on desktop. Desktop cards share the available row width, stretch +to the same row height, balance five- and six-section drawers across three-card +rows, and use a bounded local label column with left-aligned values so the +layout has no ragged fixed-width island, stranded full-width final card, or +full-drawer scan distance. The responsive presentation stays owned by the shared primitive; +provider drawers must not fork their own desktop card renderers. Monitoring +mode, lifecycle, notes, maintenance, automatic-action policy, saved access configuration, and action audit belong to a dedicated `Manage` tab and must not render inside Overview. Guest, node, Docker-host, and unified-resource drawers must compose these shared primitives diff --git a/frontend-modern/browser-verification.json b/frontend-modern/browser-verification.json index 0b3d1acc3..945aea446 100644 --- a/frontend-modern/browser-verification.json +++ b/frontend-modern/browser-verification.json @@ -1,44 +1,37 @@ { "version": 1, - "base_sha": "3b866458f665ff7be80e611cc60286d622a66e92", - "verified_at": "2026-08-26T09:20:54Z", + "base_sha": "a8e84b4011afec482def5775b776b0f24a30d412", + "verified_at": "2026-08-26T09:27:37Z", "result": "passed", - "changed_paths": [ - "frontend-modern/src/components/Workloads/guestRowModel.tsx", - "frontend-modern/src/components/Workloads/useGuestRowState.ts", - "frontend-modern/src/components/Workloads/workloadSelectors.ts", - "frontend-modern/src/components/Workloads/workloadsFilterModel.ts" - ], + "changed_paths": ["frontend-modern/src/components/shared/DetailSectionTable.tsx"], "content_sha256": { - "frontend-modern/src/components/Workloads/guestRowModel.tsx": "e5bdf0f4bcb070227c7c40d512863eaff1bcb80db53c19d59a9b9085b648d344", - "frontend-modern/src/components/Workloads/useGuestRowState.ts": "591c98dbdb3213636ae5465f65fff4df03882ef428b57589c33eec36dc22fb85", - "frontend-modern/src/components/Workloads/workloadSelectors.ts": "98fe0f042d5e114f5b3b99080cc894e7da99d490f208ce6cd94867aa5a8aaff3", - "frontend-modern/src/components/Workloads/workloadsFilterModel.ts": "085dbf2b61d3cd87aec04b54aa3fcc15f0a12d3d70c4014830c45a807788cb79" + "frontend-modern/src/components/shared/DetailSectionTable.tsx": "07a1cd5c6489fe055842417468014bdfee01aa79785b531f15cea69c0532796a" }, - "routes": [ - "/proxmox/overview" - ], + "routes": ["/proxmox/overview", "/docker/overview"], "viewports": [ { "width": 1920, - "height": 1200 + "height": 600 }, { - "width": 768, - "height": 1000 + "width": 1280, + "height": 800 + }, + { + "width": 390, + "height": 844 } ], "states": [ - "loaded grouped guest table with running and stopped VM and LXC rows", - "wide table with the shared Info/ID column visible", - "narrow table with the responsive Info/ID column visible", - "active ascending and descending ID sort indicators" + "Proxmox guest drawer with four detail sections filling one equal-height desktop row", + "Docker host drawer with five detail sections balanced across three- and two-card desktop rows", + "phone detail sections retained the compact single-table presentation" ], "interactions": [ - "loaded the Proxmox overview from the final source at desktop and narrow widths", - "clicked the shared ID header to sort ascending and confirmed the first group ordered IDs from 663 upward", - "clicked the shared ID header again to sort descending and confirmed the first group ordered IDs from 680 downward", - "confirmed the ID sort indicator and centered header-cell alignment remain coherent at 1920px and 768px", - "confirmed the responsive table remained readable without clipping the changed header and restored the viewport override" + "opened the backup-orchestrator-311 Proxmox guest drawer at 1920 by 600 and measured four 432-pixel cards filling the 1752-pixel detail row with equal 112-pixel heights", + "rechecked the Proxmox guest drawer at 1280 by 800 and confirmed four 273-pixel cards, a 124-pixel label-to-value distance, no truncation of Node agent connected, and no horizontal overflow", + "opened the auth-service-01 Docker host drawer at 1280 by 800 and confirmed five sections balance into three 367-pixel cards and two 555-pixel cards without a stranded full-width final card", + "measured the shared detail renderer at 390 by 844 and confirmed native table, table-row-group, and table-row display with no horizontal overflow", + "restored the browser viewport override to 1280 by 800" ] } diff --git a/frontend-modern/src/components/shared/DetailSectionTable.tsx b/frontend-modern/src/components/shared/DetailSectionTable.tsx index 71384a991..07a233c8a 100644 --- a/frontend-modern/src/components/shared/DetailSectionTable.tsx +++ b/frontend-modern/src/components/shared/DetailSectionTable.tsx @@ -24,54 +24,64 @@ const detailValueToneClass = (tone: DetailValueTone | undefined): string => { return 'text-base-content'; }; +const detailSectionDesktopBasisClass = (sectionCount: number): string => + sectionCount === 5 || sectionCount === 6 + ? 'lg:basis-[calc(33.333%-0.5rem)]' + : 'lg:basis-[calc(25%-0.5rem)]'; + export const DetailSectionTable: Component<{ sections: DetailSection[]; class?: string; }> = (props) => ( -
- - - - {(section) => ( - <> - - - {section.label} - - - - {(row) => ( - - - {row.label} - - +
+ + {(section) => ( + + + + {section.label} + + + + {(row) => ( + + + {row.label} + + + - - {row.value} - - - - )} - - - )} - - + {row.value} + + + + )} + + + )} +
); diff --git a/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts b/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts index e950907a0..e69258aa8 100644 --- a/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts +++ b/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts @@ -7926,6 +7926,11 @@ describe('shared primitive guardrails', () => { expect(detailSectionTableSource).toContain('DetailSectionTable'); expect(detailSectionTableSource).toContain('InlineDetailPanel'); expect(detailSectionTableSource).toContain('ObjectDrawerHeader'); + expect(detailSectionTableSource).toContain('lg:basis-[calc(25%-0.5rem)]'); + expect(detailSectionTableSource).toContain('lg:basis-[calc(33.333%-0.5rem)]'); + expect(detailSectionTableSource).toContain('lg:grid-cols-[7rem_minmax(0,1fr)]'); + expect(detailSectionTableSource).toContain('lg:text-left'); + expect(detailSectionTableSource).toContain('lg:divide-y-0'); for (const source of [ resourceDetailDrawerKubernetesModelSource, diff --git a/frontend-modern/src/components/shared/__tests__/DetailSectionTable.test.tsx b/frontend-modern/src/components/shared/__tests__/DetailSectionTable.test.tsx index a0f5247b8..47919e6ce 100644 --- a/frontend-modern/src/components/shared/__tests__/DetailSectionTable.test.tsx +++ b/frontend-modern/src/components/shared/__tests__/DetailSectionTable.test.tsx @@ -57,8 +57,8 @@ describe('DetailSectionTable', () => { expect(formatDetailCountValue(undefined, 'disk')).toBeNull(); }); - it('renders section tables with shared value tone classes', () => { - render(() => ( + it('keeps compact table rows on narrow screens and bounded section cards on desktop', () => { + const { container } = render(() => ( { { label: 'Resource', value: 'tower', title: 'tower.example.test' }, ], }, + { + label: 'Runtime', + rows: [{ label: 'Kernel', value: '6.8.0' }], + }, ]} /> )); @@ -76,6 +80,40 @@ describe('DetailSectionTable', () => { expect(screen.getByText('Severity')).toBeInTheDocument(); expect(screen.getByText('Warning').closest('td')).toHaveClass('text-amber-700'); expect(screen.getByText('tower').closest('td')).toHaveAttribute('title', 'tower.example.test'); + + const table = container.querySelector('table'); + expect(table).toHaveClass('table-fixed', 'lg:flex', 'lg:flex-wrap', 'lg:items-stretch'); + const sections = container.querySelectorAll('tbody'); + expect(sections).toHaveLength(2); + expect(sections[0]).toHaveClass( + 'lg:flex', + 'lg:flex-1', + 'lg:basis-[calc(25%-0.5rem)]', + 'lg:rounded', + 'lg:border', + 'lg:p-3', + ); + expect(screen.getByText('Severity').closest('tr')).toHaveClass( + 'lg:grid', + 'lg:grid-cols-[7rem_minmax(0,1fr)]', + 'lg:gap-3', + ); + expect(screen.getByText('Warning').closest('td')).toHaveClass('lg:text-left'); + }); + + it('balances five desktop sections across three- and two-card rows', () => { + const { container } = render(() => ( + ({ + label: `Section ${index + 1}`, + rows: [{ label: 'Value', value: String(index + 1) }], + }))} + /> + )); + + const sections = container.querySelectorAll('tbody'); + expect(sections).toHaveLength(5); + sections.forEach((section) => expect(section).toHaveClass('lg:basis-[calc(33.333%-0.5rem)]')); }); it('lazily renders technical details with the same compact section rows', () => {