mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
Canonicalize mobile row disclosure affordances
This commit is contained in:
@@ -1187,9 +1187,12 @@ not a replacement status card, CTA band, or page-local nested card.
|
||||
below the shared mobile breakpoint while preserving the button for screen
|
||||
readers and revealing it on keyboard focus. `PlatformResourceDetailToggleButton`
|
||||
applies that policy to provider-native platform tables, and workload rows
|
||||
opt in only while their compact row remains the disclosure target. Rows
|
||||
whose click action does something other than expand details must keep a
|
||||
visible disclosure control.
|
||||
opt in only while their compact row remains the disclosure target. Every
|
||||
disclosure-button consumer must declare that row-target relationship
|
||||
explicitly through the required `hideWhenRowTappableOnMobile` boolean, so a
|
||||
new table cannot silently restore a redundant mobile chevron. Rows whose
|
||||
click action does something other than expand details must pass `false` and
|
||||
keep a visible disclosure control.
|
||||
They must not rely on a global rule that reveals
|
||||
every hidden column: phone priority remains source-specific because state, capacity,
|
||||
freshness, and recovery posture do not carry equal value for every resource
|
||||
|
||||
@@ -2191,6 +2191,11 @@ forking separate table-only presentation logic. That component now also
|
||||
consumes the shared `frontend-modern/src/utils/resourceChangePresentation.ts`
|
||||
label helper for canonical change kinds, source types, and adapter provenance
|
||||
so the chip wording stays consistent without adding extra hot-path branching.
|
||||
Host, PBS, and PMG summary rows also declare themselves as the compact touch
|
||||
disclosure target through `SummaryRowActionButton`; the shared primitive hides
|
||||
the redundant visible mobile chevron while preserving accessible disclosure
|
||||
semantics, so provider sections do not pay a separate cell-width or tap-target
|
||||
cost for an action already owned by the whole row.
|
||||
The same infrastructure hot path now also depends on the shared
|
||||
`frontend-modern/src/components/shared/ProgressBar.tsx` primitive for metric
|
||||
fill rendering. Performance-sensitive metric bars may vary by value and color,
|
||||
|
||||
@@ -2187,9 +2187,12 @@ files.
|
||||
`frontend-modern/src/components/shared/SummaryRowActionButton.tsx`, but
|
||||
storage group headers should pin through the row itself and must not add a
|
||||
separate scope/pinned pill button beside the disclosure chevron. Touch
|
||||
users still must not inherit synthetic hover branches, and storage must
|
||||
not keep a special trailing expand column once the shared leading action
|
||||
contract exists.
|
||||
pool and physical-disk rows own expansion through the whole row, so they
|
||||
must explicitly suppress the redundant visible mobile chevron; group
|
||||
headers whose row click owns summary focus rather than expansion must keep
|
||||
their disclosure chevron visible. Touch users still must not inherit
|
||||
synthetic hover branches, and storage must not keep a special trailing
|
||||
expand column once the shared leading action contract exists.
|
||||
Static subgroup header emphasis for storage group rows and recovery history
|
||||
day headers must also route through
|
||||
`frontend-modern/src/components/shared/groupedTableRowPresentation.ts` and
|
||||
|
||||
@@ -1585,7 +1585,11 @@ served clones. Proof: `TestClonedResourcesPreservePlatformAdmission` and
|
||||
`frontend-modern/src/components/shared/SummaryRowActionButton.tsx`, so the
|
||||
unified-resource table does not fork mouse-only hover logic, focusable-row
|
||||
button shims, touch-hostile synthetic hover, or provider-specific control
|
||||
handling across host, PBS, and PMG sections.
|
||||
handling across host, PBS, and PMG sections. Those three resource rows are
|
||||
themselves the compact touch disclosure target and must explicitly enable
|
||||
the shared mobile-chevron suppression; their accessible row/button state
|
||||
remains available without spending a visible summary-cell slot on a
|
||||
duplicate control.
|
||||
15. Keep infrastructure search aligned with the governed display label. Shared
|
||||
infrastructure filtering through
|
||||
`frontend-modern/src/components/Infrastructure/infrastructureSelectors.ts`
|
||||
|
||||
@@ -1,25 +1,39 @@
|
||||
{
|
||||
"version": 1,
|
||||
"base_sha": "714b26b69f8a39d906b041dbf1b64d5d623632c1",
|
||||
"verified_at": "2026-08-24T15:30:00Z",
|
||||
"base_sha": "b67ac28f25434cb9e6e393a0787e72d915ba479a",
|
||||
"verified_at": "2026-08-24T15:34:13Z",
|
||||
"result": "passed",
|
||||
"changed_paths": [
|
||||
"frontend-modern/src/components/shared/TooltipPortal.tsx",
|
||||
"frontend-modern/src/components/shared/hoverCapability.ts",
|
||||
"frontend-modern/src/components/shared/useTooltipState.ts",
|
||||
"frontend-modern/src/hooks/useTooltip.ts"
|
||||
"frontend-modern/src/components/Infrastructure/UnifiedResourceHostTableCard.tsx",
|
||||
"frontend-modern/src/components/Infrastructure/UnifiedResourcePBSTableSection.tsx",
|
||||
"frontend-modern/src/components/Infrastructure/UnifiedResourcePMGTableSection.tsx",
|
||||
"frontend-modern/src/components/Storage/DiskList.tsx",
|
||||
"frontend-modern/src/components/Storage/StorageGroupRow.tsx",
|
||||
"frontend-modern/src/components/Storage/StoragePoolRow.tsx",
|
||||
"frontend-modern/src/components/shared/SummaryRowActionButton.tsx"
|
||||
],
|
||||
"content_sha256": {
|
||||
"frontend-modern/src/components/shared/TooltipPortal.tsx": "dafea8136a75491438a9ab45de348a4307ada67799796f38e3e26087339ef445",
|
||||
"frontend-modern/src/components/shared/hoverCapability.ts": "bae00548234580fe72e4fe93833c57ff237d7666d2ada00a0f673c676f9c39dc",
|
||||
"frontend-modern/src/components/shared/useTooltipState.ts": "4959c6840e3c062fa31c2a0a7391f5c0314725ac8ef48deeed37551f9d48194c",
|
||||
"frontend-modern/src/hooks/useTooltip.ts": "fa13e21a922954466a9cb09abaff9cd04b3caf0c43dbb025829e8bf251f8065f"
|
||||
"frontend-modern/src/components/Infrastructure/UnifiedResourceHostTableCard.tsx": "78588725f625e58325c2ddca6133d3e0b0d46a18f9d19e8946f7c0181c094442",
|
||||
"frontend-modern/src/components/Infrastructure/UnifiedResourcePBSTableSection.tsx": "15031095e5910c02bd47038213974de4b3d2aef53b1ff6cd030fee381a98fe91",
|
||||
"frontend-modern/src/components/Infrastructure/UnifiedResourcePMGTableSection.tsx": "d9a5a6f209b97a02dfa40100f783d91e8202b0efc33af73415e05689251f6ec6",
|
||||
"frontend-modern/src/components/Storage/DiskList.tsx": "5d600b9bfa8e34ee9ed13037cdbe87b931c10e6378440d41bf1d1ba68cab9526",
|
||||
"frontend-modern/src/components/Storage/StorageGroupRow.tsx": "5ce92da0c45b4146c51eb52786adb245978b5f6b4adc95b40d2294d949720e99",
|
||||
"frontend-modern/src/components/Storage/StoragePoolRow.tsx": "f389fef94c53a74cf69d6f165ae01f71f02a1e0143dc68c1318033aebe0001a4",
|
||||
"frontend-modern/src/components/shared/SummaryRowActionButton.tsx": "dbd6a62b6dbf11cabfaf01bc7240ab3ba3a8230cef1ee20bc2f0bd8c19e4bb14"
|
||||
},
|
||||
"routes": ["/proxmox"],
|
||||
"routes": [
|
||||
"/proxmox/overview",
|
||||
"/proxmox/storage",
|
||||
"/docker/overview",
|
||||
"/kubernetes/overview",
|
||||
"/truenas/overview",
|
||||
"/vmware/health",
|
||||
"/standalone/machines"
|
||||
],
|
||||
"viewports": [
|
||||
{
|
||||
"width": 1280,
|
||||
"height": 720
|
||||
"height": 800
|
||||
},
|
||||
{
|
||||
"width": 390,
|
||||
@@ -27,13 +41,15 @@
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
"Proxmox workload table with a fine hover-capable desktop pointer",
|
||||
"Proxmox workload table with touch emulation reporting a coarse primary pointer",
|
||||
"expanded artifact-cache-310 inline guest drawer on the narrow viewport"
|
||||
"collapsed storage-pool and physical-disk rows at narrow width without visible disclosure chevrons",
|
||||
"expanded and collapsed storage detail rows activated from the whole summary row",
|
||||
"provider overview rows across Proxmox, Docker, Kubernetes, TrueNAS, vSphere, and standalone machines with accessible but visually suppressed mobile disclosure buttons",
|
||||
"desktop storage rows retaining their explicit disclosure affordance"
|
||||
],
|
||||
"interactions": [
|
||||
"tapped the artifact-cache-310 workload row at 390x844 and confirmed the inline guest drawer opened without a portal or singleton tooltip",
|
||||
"hovered a workload CPU metric at 1280x720 and confirmed the CPU Details tooltip remained available for a fine pointer",
|
||||
"confirmed the touch and desktop media capability queries selected the intended tooltip behavior before restoring the browser viewport"
|
||||
"opened and closed a Proxmox storage-pool detail from the whole row at 390px",
|
||||
"switched from Storage to Physical Disks and confirmed its whole-row disclosure buttons are visually clipped at 390px",
|
||||
"audited seven loaded platform routes and found zero visible Expand or Collapse disclosure buttons at 390px while retaining 232 accessible controls",
|
||||
"inspected the Proxmox storage table at 1280px and confirmed desktop disclosure layout and row density remain intact"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -408,6 +408,7 @@ export const UnifiedResourceHostTableCard: Component<UnifiedResourceHostTableCar
|
||||
subjectLabel={displayName()}
|
||||
expanded={isExpanded()}
|
||||
controlsId={detailControlsId()}
|
||||
hideWhenRowTappableOnMobile
|
||||
onAction={() => table.toggleExpand(resource.id)}
|
||||
onPreviewClear={() => tableProps.onHoverChange?.(null)}
|
||||
/>
|
||||
|
||||
@@ -184,6 +184,7 @@ export const UnifiedResourcePBSTableSection: Component<UnifiedResourcePBSTableSe
|
||||
subjectLabel={displayName()}
|
||||
expanded={isExpanded()}
|
||||
controlsId={detailControlsId()}
|
||||
hideWhenRowTappableOnMobile
|
||||
onAction={() => table.toggleExpand(resource.id)}
|
||||
onPreviewClear={() => tableProps.onHoverChange?.(null)}
|
||||
/>
|
||||
|
||||
@@ -198,6 +198,7 @@ export const UnifiedResourcePMGTableSection: Component<UnifiedResourcePMGTableSe
|
||||
subjectLabel={displayName()}
|
||||
expanded={isExpanded()}
|
||||
controlsId={detailControlsId()}
|
||||
hideWhenRowTappableOnMobile
|
||||
onAction={() => table.toggleExpand(resource.id)}
|
||||
onPreviewClear={() => tableProps.onHoverChange?.(null)}
|
||||
/>
|
||||
|
||||
+1
@@ -821,6 +821,7 @@ describe('UnifiedResourceTable performance contract', () => {
|
||||
expect(source).toContain('data-summary-row-active');
|
||||
expect(source).toContain('createSummaryInteractiveRowPreviewHandlers');
|
||||
expect(source).toContain('SummaryRowActionButton');
|
||||
expect(source).toContain('hideWhenRowTappableOnMobile');
|
||||
expect(source).not.toContain('bg-blue-50 dark:bg-blue-900 ring-1 ring-blue-300');
|
||||
expect(source).not.toContain('bg-blue-100 dark:bg-blue-800');
|
||||
}
|
||||
|
||||
@@ -361,6 +361,7 @@ export const DiskList: Component<DiskListProps> = (props) => {
|
||||
subjectLabel={data.model || 'disk'}
|
||||
expanded={isSelected()}
|
||||
controlsId={detailControlsId}
|
||||
hideWhenRowTappableOnMobile
|
||||
onAction={() => model.toggleSelectedDisk(disk)}
|
||||
onPreviewClear={() => props.onHoverChange?.(null)}
|
||||
/>
|
||||
|
||||
@@ -53,6 +53,7 @@ export const StorageGroupRow: Component<StorageGroupRowProps> = (props) => {
|
||||
kind="disclosure"
|
||||
subjectLabel={row().label}
|
||||
expanded={props.expanded}
|
||||
hideWhenRowTappableOnMobile={false}
|
||||
onAction={props.onToggle}
|
||||
onPreviewClear={() => props.onHoverChange?.(null)}
|
||||
class="inline-flex items-center justify-center"
|
||||
|
||||
@@ -100,6 +100,7 @@ export const StoragePoolRow: Component<StoragePoolRowProps> = (props) => {
|
||||
subjectLabel={props.record.name}
|
||||
expanded={props.expanded}
|
||||
controlsId={detailControlsId()}
|
||||
hideWhenRowTappableOnMobile
|
||||
onAction={props.onToggleExpand}
|
||||
onPreviewClear={() => props.onHoverChange?.(null)}
|
||||
/>
|
||||
|
||||
@@ -1555,7 +1555,7 @@ describe('shared primitive guardrails', () => {
|
||||
expect(summaryRowActionButtonSource).toContain('aria-expanded');
|
||||
expect(summaryRowActionButtonSource).toContain('aria-pressed');
|
||||
expect(summaryRowActionButtonSource).toContain('data-row-action="true"');
|
||||
expect(summaryRowActionButtonSource).toContain('hideWhenRowTappableOnMobile');
|
||||
expect(summaryRowActionButtonSource).toContain('hideWhenRowTappableOnMobile: boolean');
|
||||
expect(summaryRowActionButtonSource).toContain('sm:not-sr-only');
|
||||
expect(platformResourceDetailTableRowSource).toContain('PlatformResourceDetailToggleButton');
|
||||
expect(platformResourceDetailTableRowSource).toContain('SummaryRowActionButton');
|
||||
@@ -1563,7 +1563,6 @@ describe('shared primitive guardrails', () => {
|
||||
|
||||
for (const source of [
|
||||
guestRowSource,
|
||||
storageGroupRowSource,
|
||||
storagePoolRowSource,
|
||||
diskListSource,
|
||||
unifiedResourceHostTableCardSource,
|
||||
@@ -1572,8 +1571,13 @@ describe('shared primitive guardrails', () => {
|
||||
]) {
|
||||
expect(source).toContain('createSummaryInteractiveRowPreviewHandlers');
|
||||
expect(source).toContain('SummaryRowActionButton');
|
||||
expect(source).toContain('hideWhenRowTappableOnMobile');
|
||||
}
|
||||
|
||||
expect(storageGroupRowSource).toContain('createSummaryInteractiveRowPreviewHandlers');
|
||||
expect(storageGroupRowSource).toContain('SummaryRowActionButton');
|
||||
expect(storageGroupRowSource).toContain('hideWhenRowTappableOnMobile={false}');
|
||||
|
||||
expect(workloadPanelSource).toContain('createSummaryInteractiveRowPreviewHandlers');
|
||||
expect(workloadPanelSource).not.toContain('kind="scope"');
|
||||
expect(storageGroupRowSource).not.toContain('kind="scope"');
|
||||
|
||||
@@ -11,7 +11,7 @@ type SummaryRowActionButtonProps =
|
||||
subjectLabel: string;
|
||||
controlsId?: string;
|
||||
class?: string;
|
||||
hideWhenRowTappableOnMobile?: boolean;
|
||||
hideWhenRowTappableOnMobile: boolean;
|
||||
onAction: () => void;
|
||||
onPreviewClear?: () => void;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ describe('SummaryRowActionButton', () => {
|
||||
subjectLabel="alpha"
|
||||
expanded={true}
|
||||
controlsId="summary-row-detail-alpha"
|
||||
hideWhenRowTappableOnMobile={false}
|
||||
onAction={onAction}
|
||||
/>
|
||||
));
|
||||
@@ -78,6 +79,7 @@ describe('SummaryRowActionButton', () => {
|
||||
subjectLabel="alpha"
|
||||
expanded={false}
|
||||
controlsId="summary-row-detail-alpha"
|
||||
hideWhenRowTappableOnMobile={false}
|
||||
onAction={onAction}
|
||||
/>
|
||||
));
|
||||
|
||||
Reference in New Issue
Block a user