From a3d7a90b1bfb14e60432a2a23b0cbd73d6911101 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Mon, 24 Aug 2026 18:40:53 +0100 Subject: [PATCH] Canonicalize platform frontend contracts --- ...tform-canonicalization-audit-2026-08-24.md | 210 ++++++++++++++++++ .../subsystems/frontend-primitives.md | 25 +++ frontend-modern/browser-verification.json | 106 +++++++-- .../scripts/shared-template-audit.mjs | 18 +- .../scripts/shared-template-registry.json | 60 ++++- .../components/Docker/SwarmServicesDrawer.tsx | 104 ++++----- .../ResourceOperatorStateSection.tsx | 118 +++++----- .../ResourceOperatorStateSection.test.ts | 34 +-- .../Kubernetes/K8sDeploymentsDrawer.tsx | 90 ++++---- .../Kubernetes/K8sNamespacesDrawer.tsx | 64 +++--- frontend-modern/src/components/shared/Form.ts | 8 + .../src/components/shared/FormSelect.test.tsx | 16 ++ .../src/components/shared/FormSelect.tsx | 16 +- .../components/shared/FormTextarea.test.tsx | 12 + .../src/components/shared/FormTextarea.tsx | 17 +- .../SharedPrimitives.guardrails.test.ts | 23 +- .../PlatformResourceDetailTableRow.tsx | 45 ++++ .../__tests__/PlatformDetailTable.test.tsx | 32 +++ .../PlatformResourceDetailTableRow.test.tsx | 51 +++++ .../platformOverviewLayout.guardrails.test.ts | 18 +- .../platformPage/sharedPlatformPage.tsx | 45 +++- .../proxmox/ProxmoxBackupServersTable.tsx | 27 ++- .../proxmox/ProxmoxCephClusterDrawer.tsx | 108 ++++----- .../src/features/proxmox/ProxmoxCephTable.tsx | 22 +- .../features/proxmox/ProxmoxCoverageTable.tsx | 129 ++++++----- .../proxmox/ProxmoxMailGatewayDrawer.tsx | 152 ++++++------- .../proxmox/ProxmoxMailGatewayTable.tsx | 30 +-- .../frontendResourceTypeBoundaries.test.ts | 15 +- 28 files changed, 1097 insertions(+), 498 deletions(-) create mode 100644 docs/release-control/v6/internal/records/frontend-platform-canonicalization-audit-2026-08-24.md create mode 100644 frontend-modern/src/features/platformPage/__tests__/PlatformDetailTable.test.tsx create mode 100644 frontend-modern/src/features/platformPage/__tests__/PlatformResourceDetailTableRow.test.tsx diff --git a/docs/release-control/v6/internal/records/frontend-platform-canonicalization-audit-2026-08-24.md b/docs/release-control/v6/internal/records/frontend-platform-canonicalization-audit-2026-08-24.md new file mode 100644 index 000000000..6462039f5 --- /dev/null +++ b/docs/release-control/v6/internal/records/frontend-platform-canonicalization-audit-2026-08-24.md @@ -0,0 +1,210 @@ +# Frontend platform canonicalization audit — 2026-08-24 + +## Executive assessment + +Pulse already rendered as a substantially coherent product before this slice: +the platform section navigation, framed page tables, dark column-header band, +compact single-line summary rows, semantic status treatment, responsive column +priority, drawer navigation, and History placement were genuinely shared. The +remaining drift risk was concentrated at the seams. Several nested drawer and +inline-detail tables independently rebuilt the same table chrome; four legacy +Proxmox inventory tables retained page-local row-disclosure wiring; and the +shared operator-state editor rebuilt compact native controls instead of using +the form primitives. + +No P0 or P1 operator defect was found. The baseline felt like one product, but +the duplicated seams made future density, header, phone-disclosure, and form +changes likely to drift. This slice removes those seams and turns the visual +agreement into shared structure plus static enforcement. + +Rendered evidence is stored under: + +`/Users/rcourtman/.codex/visualizations/2026/08/24/01a03497-2e33-7a81-95ce-f33967bb968d/platform-audit` + +The directory contains a desktop and phone capture for every route in the +coverage matrix, representative open drawers and History tabs, post-change +captures prefixed with `post-`, and independent Computer Use evidence. + +## Canonical design inventory + +| Pattern | Canonical contract | Shared owner | +| --- | --- | --- | +| Platform section navigation | One scrollable section-tab treatment; active state and narrow overflow behavior are shared while section labels remain platform-specific. | `frontend-modern/src/features/platformPage/sharedPlatformPage.tsx` (`PlatformSectionTabs`) and `frontend-modern/src/components/shared/Subtabs.tsx` | +| Framed page table | One card border, title/header band, dark column-header band, overflow boundary, loading/empty/error presentation, and compact row density. | `sharedPlatformPage.tsx` (`PlatformTableShell`) over `TableCard.tsx`, `TableCardHeader.tsx`, and `Table.tsx` | +| Cardless nested table | Drawers and inline details reuse the page table's table/header/body structure without inventing a second card. Column content remains object-specific. | `sharedPlatformPage.tsx` (`PlatformDetailTable`, `PlatformDetailTableHeader`, `PlatformDetailTableBody`) | +| Summary row interaction | The whole row activates detail by pointer and Enter/Space, exposes focus, `aria-expanded`, and `aria-controls`, and ignores embedded interactive controls. | `frontend-modern/src/features/platformPage/PlatformResourceDetailTableRow.tsx` (`createPlatformResourceDetailState`, `getPlatformResourceDetailRowInteractionProps`) | +| Disclosure affordance | Desktop may show the shared disclosure button; phone removes that visual chevron because the row is the touch target. The accessible label remains available. | `PlatformResourceDetailTableRow.tsx` (`PlatformResourceDetailToggleButton`) and `SummaryRowActionButton.tsx` | +| Inline detail placement | Expanded content follows its owning row as a full-width table row and preserves table semantics. | `PlatformResourceDetailTableRow.tsx` and `InlineDetailTableRow.tsx` | +| Drawer hierarchy | Attention/problems and actionable context precede inventory detail; Overview does not repeat low-value row data; Manage owns operator overrides; discovery metadata appears only when enabled. | Platform drawers over the shared resource drawer/detail primitives and `ResourceOperatorStateSection.tsx` | +| History | History is a drawer tab, not duplicated on Overview. Range controls live with the chart and share the history component contract. | Shared drawer tabs and `HistoryChart` | +| Compact operator forms | Low-frequency drawer controls use an explicit compact form density while preserving labels, help relationships, errors, and touch behavior. | `frontend-modern/src/components/shared/Form.ts`, `FormSelect.tsx`, and `FormTextarea.tsx` | +| Loading/empty/error | Page tables use the same presentation and copy slots; domain-specific nouns may vary, but the placement, spacing, iconography, and retry boundary do not. | `sharedPlatformPage.tsx` (`PlatformTableLoading`, `PlatformTableEmpty`, `PlatformTableError`) | +| Status and metrics | Semantic meaning selects the status color; bars share the metric primitive. Different metrics are intentional content, not design variants. | Shared status and metric primitives consumed by platform pages | +| Responsive rows | Rows remain compact and single-line, with truncation and column priority. Tables own overflow; the document does not horizontally scroll. | `Table.tsx` and the exported platform table class constants in `sharedPlatformPage.tsx` | + +## Rendered route coverage + +`D` means 1280×800 inspected and captured. `P` means 390×844 inspected and +captured. All listed routes passed both unless a state note follows. + +| Platform | Route / meaningful section | D | P | Additional states exercised | +| --- | --- | :---: | :---: | --- | +| Proxmox | `/proxmox/overview` | ✓ | ✓ | node, guest, and storage drawers; node Overview and History | +| Proxmox | `/proxmox/storage` | ✓ | ✓ | physical disks, grouped/flat behavior, expanded storage rows | +| Proxmox | `/proxmox/replication` | ✓ | ✓ | grouped inventory and row detail | +| Proxmox | `/proxmox/backups/date` | ✓ | ✓ | backup-server row pointer plus Enter/Space expansion | +| Proxmox | `/proxmox/backups/coverage` | ✓ | ✓ | attention state, expanded restore evidence, nested table | +| Proxmox | `/proxmox/ceph` | ✓ | ✓ | expanded cluster detail and cluster drawer | +| Proxmox | `/proxmox/mail` | ✓ | ✓ | expanded gateway detail and gateway drawer | +| Docker | `/docker/overview` | ✓ | ✓ | host and container drawers; host History | +| Docker | `/docker/images` | ✓ | ✓ | image inventory and drawer | +| Docker | `/docker/storage` | ✓ | ✓ | volume/storage inventory and drawer | +| Docker | `/docker/networks` | ✓ | ✓ | network inventory and drawer | +| Docker | `/docker/swarm` | ✓ | ✓ | services, configs, secrets, nested service table | +| Kubernetes | `/kubernetes/overview` | ✓ | ✓ | cluster drawer and alert context | +| Kubernetes | `/kubernetes/nodes` | ✓ | ✓ | node drawer | +| Kubernetes | `/kubernetes/workloads` | ✓ | ✓ | pods, deployments/controllers, drawers | +| Kubernetes | `/kubernetes/services` | ✓ | ✓ | services/networking and drawers | +| Kubernetes | `/kubernetes/storage` | ✓ | ✓ | storage inventory and drawers | +| Kubernetes | `/kubernetes/configuration` | ✓ | ✓ | config, policy, autoscaling, namespaces/deployments detail | +| Kubernetes | `/kubernetes/events` | ✓ | ✓ | events and attention states | +| TrueNAS | `/truenas/overview` | ✓ | ✓ | system drawer and alerts | +| TrueNAS | `/truenas/storage` | ✓ | ✓ | topology, pools/datasets, drawers | +| TrueNAS | `/truenas/services` | ✓ | ✓ | service state and drawer | +| TrueNAS | `/truenas/apps` | ✓ | ✓ | apps and drawer | +| TrueNAS | `/truenas/vms` | ✓ | ✓ | virtual machines and drawer | +| TrueNAS | `/truenas/shares` | ✓ | ✓ | shares and drawer | +| TrueNAS | `/truenas/protection` | ✓ | ✓ | protection and alert/attention state | +| VMware | `/vmware/overview` | ✓ | ✓ | host drawer and History | +| VMware | `/vmware/storage` | ✓ | ✓ | datastore inventory and drawer | +| VMware | `/vmware/networks` | ✓ | ✓ | network inventory and drawer | +| VMware | `/vmware/health` | ✓ | ✓ | alert/attention state | +| VMware | `/vmware/activity` | ✓ | ✓ | activity/history presentation | +| Standalone | `/machines` | ✓ | ✓ | machine drawer, stale/disconnected examples | +| Standalone | `/availability` | ✓ | ✓ | availability-check drawer; intentionally no History | + +Loading was independently observed in the native browser while the live local +frontend connected to the backend. Empty and error presentation were traced to +and protected by the shared state components and their focused tests; the mock +estate did not expose every empty/error variant on every route, so those states +were not artificially inferred from healthy pixels. + +## Prioritized findings and disposition + +### P2 — nested tables only looked shared + +- **Rendered evidence:** Docker Swarm, Kubernetes configuration drawers, + Proxmox Ceph, Proxmox Mail Gateway, and Proxmox backup coverage matched the + page-table header and density in desktop and phone captures. +- **Baseline source consumers:** `SwarmServicesDrawer.tsx`, + `K8sDeploymentsDrawer.tsx`, `K8sNamespacesDrawer.tsx`, + `ProxmoxCephClusterDrawer.tsx`, `ProxmoxMailGatewayDrawer.tsx`, and + `ProxmoxCoverageTable.tsx` independently composed raw table/header/body + structure and copied canonical class constants. +- **Operator impact:** operators would notice only after the next shared header, + density, border, or responsive change landed unevenly; the defect was + architectural drift risk rather than current pixel breakage. +- **Disposition:** migrated every consumer to `PlatformDetailTable`, + `PlatformDetailTableHeader`, and `PlatformDetailTableBody`. Registry auditing + now requires those owners and consumers. + +### P2 — legacy Proxmox disclosure behavior was page-local + +- **Routes/viewports:** `/proxmox/backups/date`, + `/proxmox/backups/coverage`, `/proxmox/ceph`, and `/proxmox/mail` at both + viewports. Post-change phone evidence includes + `post-proxmox-mail-expanded-390x844.png` and + `post-proxmox-backup-coverage-expanded-390x844.png`. +- **Baseline source consumers:** `ProxmoxBackupServersTable.tsx`, + `ProxmoxCoverageTable.tsx`, `ProxmoxCephTable.tsx`, and + `ProxmoxMailGatewayTable.tsx` each carried local click/keyboard/aria state. +- **Operator impact:** a phone row or keyboard path could diverge while newer + platforms changed centrally; visible duplicate chevrons were a recurring + regression risk. +- **Disposition:** all four now consume the shared interaction owner. Browser + verification confirmed pointer, Enter, and Space activation, focusability, + `aria-expanded`, `aria-controls`, and absence of a visible phone disclosure + button. + +### P2 — compact operator forms bypassed the form system + +- **Rendered surface:** the shared resource drawer Manage tab across platforms. +- **Baseline source owner:** `ResourceOperatorStateSection.tsx` used native + select and textarea shells with page-local classes. +- **Operator impact:** label/help/error behavior and compact density could drift + from other form controls in every resource drawer at once. +- **Disposition:** added documented `compact` density variants to `FormSelect` + and `FormTextarea`, then migrated the shared Manage section. The controls stay + out of Overview because overrides are low-frequency operator actions. + +### P3 — drift detection did not encode these contracts + +- **Baseline:** matching class strings could pass review without proving shared + ownership, while a few guard patterns produced test-only false positives. +- **Disposition:** the shared-template audit now supports required alternative + ownership patterns, requires the nested-table and row-interaction contracts, + excludes tests from runtime chevron/native-control rules, and retains the + canonical-platform lint gate. + +### P3 — unrelated pre-existing shared-template findings + +The complete shared-template audit still reports four consumers outside this +platform canonicalization slice: `ApprovalBanner`/`LoadingSpinner`, +`UpdatesSettingsPanel`/`FormSelect`, `RelayPairingSection`/`ExternalTextLink`, +and `PatrolAttentionWorkbench`/`FormSelect`. None is rendered by or newly +introduced in the audited platform inventory/drawer contract. They remain +explicit exceptions rather than being hidden by this slice. + +## Decisions for previously ambiguous contracts + +1. **Shared structure, object-specific content.** Platform columns, metrics, + health explanations, and technical details remain domain-specific. Table + chrome, density, state presentation, interaction, and responsiveness do not. +2. **Whole row is the phone affordance.** The disclosure button remains in the + accessibility tree and may be visually present on desktop, but phone layouts + do not show a redundant chevron. +3. **History is a drawer destination.** History does not compete with current + operator context on Overview. Availability checks may omit History when the + object has no meaningful historical series. +4. **Operator overrides belong in Manage.** Discovery metadata is conditional; + operator override controls are not promoted into Overview merely to make + them more visible. +5. **Cardless is a supported table variant, not a fork.** Nested tables should + not receive a decorative second card, but they must consume the shared table + anatomy. +6. **State copy may name the object.** Loading/empty/error shells are canonical; + concise domain nouns are supported content rather than inconsistency. + +## Remediation sequence applied + +1. Established rendered baseline and source ownership across all routes. +2. Added the shared cardless table and whole-row interaction owners. +3. Migrated all known repeated nested-table and legacy Proxmox consumers. +4. Added compact form variants and migrated the cross-platform Manage surface. +5. Added focused interaction/structure/form tests and static registry rules. +6. Updated the frontend-primitives contract. +7. Re-ran representative desktop/phone pixels, expanded rows, nested evidence, + alert/attention states, keyboard activation, and independent Computer Use. + +## Patterns already correct and intentionally not churned + +- Platform section tabs and narrow horizontal overflow. +- `PlatformTableShell` page-level card/table anatomy. +- Compact single-line summary-row density, truncation, and column priority. +- Shared status colors and metric bars; different provider metrics remain. +- Docker, Kubernetes, TrueNAS, VMware, Machines, and availability whole-row + disclosure consumers already using the shared detail-state contract. +- Drawer tabs, Overview/History placement, range controls, alert-first + hierarchy, and conditional discovery metadata. +- Loading/empty/error component ownership and healthy/stale/disconnected status + semantics. +- Availability check drawers intentionally omitting History when no useful + historical series exists. + +## Remaining exceptions + +There are no known platform-page exceptions to the canonical table or summary +row interaction contracts after this slice. The four unrelated static-audit +findings listed above remain outside this task. Route-specific cells, metrics, +drawer sections, and technical details are intentional object content inside +the shared contract, not forks. diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index 4b00ba65c..aaa676f93 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -107,6 +107,27 @@ on Overview or reintroduce object-local tab or range-selector chrome. The metrics and chart groups may remain object-specific without changing this navigation contract. +Platform inventory presentation has one structural owner across provider pages, +drawers, and inline detail rows. `PlatformTableShell` owns framed page tables; +`PlatformDetailTable`, `PlatformDetailTableHeader`, and +`PlatformDetailTableBody` own cardless nested tables while reusing the same +header band, borders, single-line density, responsive table class, and overflow +boundary. Provider-specific columns and cell contents are intentional variants, +but a drawer or expanded row must not rebuild raw `table` / `thead` / `tbody` +chrome or duplicate the shared header class strings. + +Expandable platform summary rows use +`getPlatformResourceDetailRowInteractionProps` (or +`createPlatformResourceDetailState`, which owns the same state contract) for +whole-row pointer activation, Enter/Space keyboard activation, focus treatment, +`aria-expanded` / `aria-controls`, and exclusion of embedded links and controls. +`PlatformResourceDetailToggleButton` is the desktop disclosure affordance and +is visually removed on phone layouts where the complete row is the touch target; +provider tables must not add a second mobile chevron. Operator overrides remain +in the shared `Manage` tab and use the explicit compact density of `FormSelect` +and `FormTextarea`, keeping form labels, help relationships, touch targets, and +control chrome canonical without expanding the low-frequency management surface. + Presentation helpers that mirror a server-side classification must name the predicate they mirror and expose it as a single exported function rather than @@ -270,6 +291,10 @@ puts the same machine on two surfaces that do not share an identity. 131c. `frontend-modern/src/features/platformPage/PlatformOutdatedAgentNotice.tsx` 131d. `frontend-modern/src/features/platformPage/PlatformOutdatedSensorSetupNotice.tsx` 131e. `frontend-modern/src/features/platformPage/platformEstateOverviewModel.ts` + 131f. `frontend-modern/src/components/shared/Form.ts` + 131g. `frontend-modern/src/components/shared/FormSelect.tsx` + 131h. `frontend-modern/src/components/shared/FormTextarea.tsx` + 131i. `frontend-modern/src/components/Infrastructure/ResourceOperatorStateSection.tsx` 132. `frontend-modern/src/utils/platformSupportManifest.generated.ts` 133. `frontend-modern/src/utils/platformSupportManifest.ts` 134. `frontend-modern/src/utils/sourcePlatformOptions.ts` diff --git a/frontend-modern/browser-verification.json b/frontend-modern/browser-verification.json index 34689725b..16df1a464 100644 --- a/frontend-modern/browser-verification.json +++ b/frontend-modern/browser-verification.json @@ -1,23 +1,81 @@ { "version": 1, - "base_sha": "baabed91a61acc7b4262c5170e3f5f5a6b457ab1", - "verified_at": "2026-08-24T16:27:42Z", + "base_sha": "164e03ad3c77a50574df782409ee7a417efe8955", + "verified_at": "2026-08-24T17:44:49Z", "result": "passed", "changed_paths": [ - "frontend-modern/src/components/Storage/Storage.tsx", - "frontend-modern/src/components/Storage/StorageContentCard.tsx", - "frontend-modern/src/components/Storage/StoragePoolsTable.tsx" + "frontend-modern/src/components/Docker/SwarmServicesDrawer.tsx", + "frontend-modern/src/components/Infrastructure/ResourceOperatorStateSection.tsx", + "frontend-modern/src/components/Kubernetes/K8sDeploymentsDrawer.tsx", + "frontend-modern/src/components/Kubernetes/K8sNamespacesDrawer.tsx", + "frontend-modern/src/components/shared/Form.ts", + "frontend-modern/src/components/shared/FormSelect.tsx", + "frontend-modern/src/components/shared/FormTextarea.tsx", + "frontend-modern/src/features/platformPage/PlatformResourceDetailTableRow.tsx", + "frontend-modern/src/features/platformPage/sharedPlatformPage.tsx", + "frontend-modern/src/features/proxmox/ProxmoxBackupServersTable.tsx", + "frontend-modern/src/features/proxmox/ProxmoxCephClusterDrawer.tsx", + "frontend-modern/src/features/proxmox/ProxmoxCephTable.tsx", + "frontend-modern/src/features/proxmox/ProxmoxCoverageTable.tsx", + "frontend-modern/src/features/proxmox/ProxmoxMailGatewayDrawer.tsx", + "frontend-modern/src/features/proxmox/ProxmoxMailGatewayTable.tsx" ], "content_sha256": { - "frontend-modern/src/components/Storage/Storage.tsx": "68ec929f7daf54b809fb9fa508e35ed73613666e242df41cd2c147bdcdb761af", - "frontend-modern/src/components/Storage/StorageContentCard.tsx": "4ec6d2d81673e02e8937695cfe59773f548b5c052554245e44deffc0d44f03af", - "frontend-modern/src/components/Storage/StoragePoolsTable.tsx": "d23551a00a460075996ac78eed48d7cf190dcd40adace7fcfdaf2fa2034b2797" + "frontend-modern/src/components/Docker/SwarmServicesDrawer.tsx": "cfe0382ab9ac0b73ac3d1c0a5c7fdd61e43d1851164cf38071b0d8d99a48fb7e", + "frontend-modern/src/components/Infrastructure/ResourceOperatorStateSection.tsx": "80334e63a0a5560063cf8e3b26e0fd5316989f146f6f014242193c66c51d20a0", + "frontend-modern/src/components/Kubernetes/K8sDeploymentsDrawer.tsx": "1806e0ca765a2fe73f456760f1e4a6506befcbba9e56bbd5249c7ee685b4b225", + "frontend-modern/src/components/Kubernetes/K8sNamespacesDrawer.tsx": "d5d09d77eb54ba91e5e2539c31b5104473ecc73a7e891c50cca4e662975cd294", + "frontend-modern/src/components/shared/Form.ts": "0186bf1fe8d10378d8d603dec8f6b9796302b418e02444a8d18044692815c2b5", + "frontend-modern/src/components/shared/FormSelect.tsx": "38b64e18d93bf7f374e18e29c2f633028c52c4e8b1207a63578f22e0e65c3ec2", + "frontend-modern/src/components/shared/FormTextarea.tsx": "cccbd6e027aabf61eddebab7c9d153737e3b607f64568751cd03c7c138b483f6", + "frontend-modern/src/features/platformPage/PlatformResourceDetailTableRow.tsx": "3c07555f296ab58dec752bd2ce8f8ac058257d0f1efd4d3823c1b5220525fd1b", + "frontend-modern/src/features/platformPage/sharedPlatformPage.tsx": "97a23102f8aff06e4e271248244d9bc63d9fd61ef8d6afe7705353189a153671", + "frontend-modern/src/features/proxmox/ProxmoxBackupServersTable.tsx": "b9a2da5b1191687ffcbbb1ebf95a766c303fbe5717dc30a43f80908481d6b02e", + "frontend-modern/src/features/proxmox/ProxmoxCephClusterDrawer.tsx": "aa8df0cef4a602ab81b8d53251d3c1f7b9b4334f18a2caaa0ba71c70f079c73c", + "frontend-modern/src/features/proxmox/ProxmoxCephTable.tsx": "663ffc8917075275617d1b0ca40e5739429553a855c0e130aa162b2421dca89b", + "frontend-modern/src/features/proxmox/ProxmoxCoverageTable.tsx": "276ee693542e06b116cda0b8b6b9771fba1e416bc11f3645a4d265883d6353f2", + "frontend-modern/src/features/proxmox/ProxmoxMailGatewayDrawer.tsx": "b9119d1611e87d8115c4f8e378b169b0db5750a077fc433f57969877bd6d4b8e", + "frontend-modern/src/features/proxmox/ProxmoxMailGatewayTable.tsx": "f9e3185ca23b59bdfc7f77a2738c2cd9e7d5d768ad10846b5c9b3680750bc17a" }, - "routes": ["/proxmox/storage"], + "routes": [ + "/proxmox/overview", + "/proxmox/storage", + "/proxmox/replication", + "/proxmox/backups/date", + "/proxmox/backups/coverage", + "/proxmox/ceph", + "/proxmox/mail", + "/docker/overview", + "/docker/images", + "/docker/storage", + "/docker/networks", + "/docker/swarm", + "/kubernetes/overview", + "/kubernetes/nodes", + "/kubernetes/workloads", + "/kubernetes/services", + "/kubernetes/storage", + "/kubernetes/configuration", + "/kubernetes/events", + "/truenas/overview", + "/truenas/storage", + "/truenas/services", + "/truenas/apps", + "/truenas/vms", + "/truenas/shares", + "/truenas/protection", + "/vmware/overview", + "/vmware/storage", + "/vmware/networks", + "/vmware/health", + "/vmware/activity", + "/machines", + "/availability" + ], "viewports": [ { "width": 1280, - "height": 720 + "height": 800 }, { "width": 390, @@ -25,15 +83,27 @@ } ], "states": [ - "Proxmox Storage with plain desktop table headings", - "Proxmox Storage after sorting by usage and changing direction through View preferences", - "Proxmox Storage compact phone table with Filters and View preferences open" + "default populated inventories for every Proxmox, Docker, Kubernetes, TrueNAS, vSphere, Machines, and availability route", + "live loading state while platform resources connected", + "healthy, attention, alert, stale, and disconnected resource presentations", + "grouped and flat inventory views where available", + "expanded Proxmox backup server, backup coverage evidence, Ceph cluster, and Mail Gateway rows", + "node, guest, storage, host, cluster, system, machine, and availability drawers", + "drawer Overview, Manage, Discovery when enabled, and History with range controls", + "cardless Docker Swarm, Kubernetes namespace and deployment, Ceph, Mail Gateway, and restore-evidence tables", + "rebased Proxmox Storage table with the newer mainline plain-header contract" ], "interactions": [ - "confirmed every desktop Storage table heading contains no button, aria-sort state, or visible direction marker", - "selected Usage in View -> Order and changed the sort direction, confirming the rendered row order changed while headings remained plain", - "confirmed compact phone headings render Storage, State, Type, Host, Prot, and Used without direction markers", - "opened phone Filters -> View and confirmed Sort by and Sort direction remain available", - "confirmed the page has no horizontal overflow at 390 pixels" + "activated legacy Proxmox rows by whole-row pointer click and confirmed aria-expanded and aria-controls", + "activated Mail Gateway detail with Enter and Space and confirmed both keyboard paths toggle the same row", + "confirmed embedded controls do not trigger the owning summary row", + "confirmed desktop disclosure controls remain available and phone disclosure buttons have no visible footprint", + "opened backup coverage restore evidence and confirmed the nested canonical table header and body", + "opened representative platform drawers, switched to History, and inspected chart range placement", + "inspected Manage controls using the compact shared select and textarea presentation", + "confirmed no document-level horizontal overflow at 1280 by 800 or 390 by 844", + "independently inspected the fully rendered post-change backup coverage route with Computer Use in Safari", + "rechecked the rebased Storage route at exactly 1280 by 800 and backup coverage at exactly 390 by 844", + "confirmed phone disclosure controls remain accessibility-exposed while their clipped visual footprint leaves the whole row as the activation affordance" ] } diff --git a/frontend-modern/scripts/shared-template-audit.mjs b/frontend-modern/scripts/shared-template-audit.mjs index 9faeb99f1..70272224b 100644 --- a/frontend-modern/scripts/shared-template-audit.mjs +++ b/frontend-modern/scripts/shared-template-audit.mjs @@ -197,8 +197,11 @@ for (const guard of requiredPatternGuards) { } const requiredPatterns = Array.isArray(guard.requiredPatterns) ? guard.requiredPatterns : []; - if (requiredPatterns.length === 0) { - failures.push(`${guard.id}: missing requiredPatterns`); + const requiredAnyPatterns = Array.isArray(guard.requiredAnyPatterns) + ? guard.requiredAnyPatterns + : []; + if (requiredPatterns.length === 0 && requiredAnyPatterns.length === 0) { + failures.push(`${guard.id}: missing requiredPatterns or requiredAnyPatterns`); continue; } @@ -238,6 +241,17 @@ for (const guard of requiredPatternGuards) { break; } } + + if ( + requiredAnyPatterns.length > 0 && + !requiredAnyPatterns.some((pattern) => source.includes(pattern)) + ) { + failures.push( + `${guard.id}: ${file} matches ${JSON.stringify( + triggerPatterns, + )} but does not compose one of ${JSON.stringify(requiredAnyPatterns)}`, + ); + } } for (const allowedPath of allowedPaths) { diff --git a/frontend-modern/scripts/shared-template-registry.json b/frontend-modern/scripts/shared-template-registry.json index fe72b03f7..25f5dd824 100644 --- a/frontend-modern/scripts/shared-template-registry.json +++ b/frontend-modern/scripts/shared-template-registry.json @@ -2166,6 +2166,28 @@ "scripts/shared-template-audit.mjs" ] }, + { + "id": "platform-detail-table-shell", + "category": "platform-table-shell", + "summary": "Cardless tables inside platform drawers and inline detail rows compose the canonical platform table structure instead of duplicating header, body, density, and overflow chrome.", + "canonical": { + "path": "src/features/platformPage/sharedPlatformPage.tsx", + "export": "PlatformDetailTable" + }, + "requiredConsumers": [ + { "path": "src/components/Docker/SwarmServicesDrawer.tsx" }, + { "path": "src/components/Kubernetes/K8sDeploymentsDrawer.tsx" }, + { "path": "src/components/Kubernetes/K8sNamespacesDrawer.tsx" }, + { "path": "src/features/proxmox/ProxmoxCephClusterDrawer.tsx" }, + { "path": "src/features/proxmox/ProxmoxCoverageTable.tsx" }, + { "path": "src/features/proxmox/ProxmoxMailGatewayDrawer.tsx" } + ], + "proof": [ + "src/components/shared/SharedPrimitives.guardrails.test.ts", + "src/features/platformPage/__tests__/sharedPlatformPage.test.ts", + "scripts/shared-template-audit.mjs" + ] + }, { "id": "platform-table-loading-state", "category": "platform-table-loading-state", @@ -4728,7 +4750,10 @@ "src/features/vmware" ], "extensions": [".tsx"], - "allPatterns": ["flex justify-center", "text-xs text-muted", "aria-hidden=\"true\""], + "allPatterns": [ + "class=\"flex justify-center\"", + "class=\"text-xs text-muted\" aria-hidden=\"true\"" + ], "legacyReason": "Retired migration debt. Platform metric cells must compose PlatformTableMetricFallback instead of carrying inline centered dash fallback markup.", "allowedPaths": [], "ignoredPaths": [], @@ -5256,6 +5281,7 @@ }, "scopes": ["src/components", "src/features"], "extensions": [".tsx"], + "pathExcludes": [".test.", "__tests__"], "allPatterns": [" - - - - - {drawerPresentation.serviceColumnLabel} - - - - {drawerPresentation.imageColumnLabel} - - - {drawerPresentation.modeColumnLabel} - - - {drawerPresentation.desiredColumnLabel} - - - {drawerPresentation.runningColumnLabel} - - - - {drawerPresentation.portsColumnLabel} - - - - + + + + {drawerPresentation.serviceColumnLabel} + + + + {drawerPresentation.imageColumnLabel} + + + {drawerPresentation.modeColumnLabel} + + + {drawerPresentation.desiredColumnLabel} + + + {drawerPresentation.runningColumnLabel} + + + + {drawerPresentation.portsColumnLabel} + + + {(svc) => { const name = () => asTrimmedString(svc.name) || svc.id; @@ -387,8 +379,8 @@ export const SwarmServicesDrawer: Component<{ cluster: string; swarm?: SwarmInfo ); }} - -
+ +
} diff --git a/frontend-modern/src/components/Infrastructure/ResourceOperatorStateSection.tsx b/frontend-modern/src/components/Infrastructure/ResourceOperatorStateSection.tsx index 130384f4e..ad9bd894b 100644 --- a/frontend-modern/src/components/Infrastructure/ResourceOperatorStateSection.tsx +++ b/frontend-modern/src/components/Infrastructure/ResourceOperatorStateSection.tsx @@ -1,5 +1,7 @@ import { Component, For, Show, createEffect, createMemo, createSignal } from 'solid-js'; import { Toggle } from '@/components/shared/Toggle'; +import { FormSelect } from '@/components/shared/FormSelect'; +import { FormTextarea } from '@/components/shared/FormTextarea'; import { notificationStore } from '@/stores/notifications'; import { type ResourceCriticality, @@ -527,35 +529,30 @@ export const ResourceOperatorStateSection: Component
- + setCriticality(e.currentTarget.value as ResourceCriticality)} + disabled={saving()} + help="Orders this resource among same-severity Patrol findings." + helpClass="text-[11px] leading-tight" + > + + + + + -