From be7c1639e0b7882e7bbdf88fcd061ee2aa60a074 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Thu, 14 May 2026 12:10:23 +0100 Subject: [PATCH] Add metric fill motion to infrastructure views --- .../subsystems/frontend-primitives.md | 7 ++++ .../subsystems/performance-and-scalability.md | 7 ++++ .../src/__tests__/App.architecture.test.ts | 3 ++ .../components/Workloads/EnhancedCPUBar.tsx | 1 + .../components/Workloads/StackedDiskBar.tsx | 22 ++++++++---- .../components/Workloads/StackedMemoryBar.tsx | 8 ++++- ...loadsSurface.performance.contract.test.tsx | 5 +++ .../src/components/shared/ProgressBar.tsx | 9 ++++- .../SharedPrimitives.guardrails.test.ts | 3 ++ frontend-modern/src/index.css | 34 ++++++++++++++++++- 10 files changed, 89 insertions(+), 10 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index c8bf557df..e1e8a18ad 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -288,6 +288,13 @@ prompt explain the same operator-facing priority. alert threshold tables, and Infrastructure Settings source-manager tables; feature owners may own group content and behavior, but not duplicate the subgroup band styling. + Shared progress and metric-fill motion belongs to the frontend primitive + CSS contract in `frontend-modern/src/index.css`. Generic progress bars must + keep the CSP-safe `ProgressBar` / `foreignObject` shape and use the shared + `.progress-fill-frame` and `.progress-fill` classes for width and color + transitions instead of inline styles or page-local animation wrappers. The + same global CSS owner must provide the `prefers-reduced-motion` disable path + for these fills so feature surfaces inherit one accessibility policy. Shared primitives must not reintroduce app-shell monitored-system capacity banners. Monitored-system grouping and ledger presentation belongs in the owned settings surfaces, while commercial plan explanation belongs in diff --git a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md index 96d99cc8b..e9ccc2789 100644 --- a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md +++ b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md @@ -262,6 +262,13 @@ regression protection. override resolution belongs to the alerts-owned activation store and `frontend-modern/src/utils/metricThresholds.ts`, while the hot-path bar models remain presentation-only consumers. + Metric-fill motion for these hot-path bars must stay CSS-owned and + transform-free for layout stability: `StackedDiskBar`, `StackedMemoryBar`, + and `EnhancedCPUBar` may attach the shared `.metric-fill-geometry` and + `.metric-fill-divider` classes to existing SVG geometry, but they must not + add per-row timers, animation signals, measurement loops, or page-local + motion state. The global frontend primitive CSS owns the easing, + color/geometry transition timing, and `prefers-reduced-motion` fallback. 23. Extend grouped workload row windowing, reveal-index clamping, overscan math, and per-group visible-slice derivation through `frontend-modern/src/components/Workloads/useGroupedTableWindowing.ts`, and extend viewport event wiring through `frontend-modern/src/components/Workloads/useWorkloadViewportSync.ts` rather than rebuilding scroll handlers, mounted-row budgets, viewport listeners, or group-slice math inside `frontend-modern/src/components/Workloads/useWorkloadsDerivedState.ts` 24. Extend Workloads shell rendering through `frontend-modern/src/components/Workloads/WorkloadsStateCards.tsx`, `frontend-modern/src/components/Workloads/WorkloadsTable.tsx`, and `frontend-modern/src/components/Workloads/WorkloadsStatsStrip.tsx` rather than accreting loading cards, workload table markup, or stats-strip presentation back into `frontend-modern/src/components/Workloads/WorkloadsSurface.tsx` 25. Extend workload table shell ownership through `frontend-modern/src/components/Workloads/WorkloadTableHeader.tsx` and `frontend-modern/src/components/Workloads/WorkloadPanel.tsx` rather than rebuilding sortable header markup, grouped node rows, row expansion, or guest-drawer rendering inside `frontend-modern/src/components/Workloads/WorkloadsTable.tsx` diff --git a/frontend-modern/src/__tests__/App.architecture.test.ts b/frontend-modern/src/__tests__/App.architecture.test.ts index 04e89020c..4df87a7e3 100644 --- a/frontend-modern/src/__tests__/App.architecture.test.ts +++ b/frontend-modern/src/__tests__/App.architecture.test.ts @@ -144,6 +144,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('.progress-fill-frame'); + expect(appStylesSource).toContain('.metric-fill-geometry'); + expect(appStylesSource).toContain('@media (prefers-reduced-motion: reduce)'); expect(appStylesSource).not.toContain('--color-grouped-table-row-bg: theme('); expect(appStylesSource).not.toContain('@keyframes pulse-brand-wordmark'); expect(appStylesSource).not.toContain('text-shadow'); diff --git a/frontend-modern/src/components/Workloads/EnhancedCPUBar.tsx b/frontend-modern/src/components/Workloads/EnhancedCPUBar.tsx index 1151c9f5f..a8c58e8bb 100644 --- a/frontend-modern/src/components/Workloads/EnhancedCPUBar.tsx +++ b/frontend-modern/src/components/Workloads/EnhancedCPUBar.tsx @@ -27,6 +27,7 @@ export function EnhancedCPUBar(props: EnhancedCPUBarProps) { > {presentation().displayLabel} - + {' '} {presentation().maxLabelShort} @@ -130,7 +136,11 @@ export function StackedDiskBar(props: StackedDiskBarProps) { } > -
+
{(disk) => ( @@ -147,6 +157,7 @@ export function StackedDiskBar(props: StackedDiskBarProps) { > {/* Tooltip for disk breakdown */} - +
{presentation().tooltipTitle} @@ -199,6 +206,7 @@ export function StackedDiskBar(props: StackedDiskBarProps) { > +
{ it('keeps stacked disk bar runtime and derivations in canonical owners', () => { expect(stackedDiskBarSource).toContain('useStackedDiskBarState'); + expect(stackedDiskBarSource).toContain('metric-fill-geometry'); + expect(stackedDiskBarSource).toContain('metric-fill-divider'); expect(stackedDiskBarSource).not.toContain('style={{'); expect(stackedDiskBarSource).not.toContain('style={'); expect(stackedDiskBarSource).not.toContain('const [containerWidth, setContainerWidth] ='); @@ -947,6 +949,8 @@ describe('Workloads performance contract', () => { it('keeps stacked memory bar runtime and derivations in canonical owners', () => { expect(stackedMemoryBarSource).toContain('useStackedMemoryBarState'); + expect(stackedMemoryBarSource).toContain('metric-fill-geometry'); + expect(stackedMemoryBarSource).toContain('metric-fill-divider'); expect(stackedMemoryBarSource).not.toContain('style={{'); expect(stackedMemoryBarSource).not.toContain('style={'); expect(stackedMemoryBarSource).not.toContain('const [containerWidth, setContainerWidth] ='); @@ -974,6 +978,7 @@ describe('Workloads performance contract', () => { it('keeps enhanced CPU bar runtime and derivations in canonical owners', () => { expect(enhancedCpuBarSource).toContain('useEnhancedCPUBarState'); + expect(enhancedCpuBarSource).toContain('metric-fill-geometry'); expect(enhancedCpuBarSource).not.toContain('style={{'); expect(enhancedCpuBarSource).not.toContain('style={'); expect(enhancedCpuBarSource).not.toContain('const tip = useTooltip()'); diff --git a/frontend-modern/src/components/shared/ProgressBar.tsx b/frontend-modern/src/components/shared/ProgressBar.tsx index 183483c70..76c027b5e 100644 --- a/frontend-modern/src/components/shared/ProgressBar.tsx +++ b/frontend-modern/src/components/shared/ProgressBar.tsx @@ -30,7 +30,14 @@ export const ProgressBar: Component = (props) => { preserveAspectRatio="none" aria-hidden="true" > - +
diff --git a/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts b/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts index 3a0f889be..d8ed41f90 100644 --- a/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts +++ b/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts @@ -1222,8 +1222,11 @@ describe('shared primitive guardrails', () => { it('keeps progress bars CSP-safe in the shared primitive owner', () => { expect(progressBarSource).toContain('data-progress-fill'); expect(progressBarSource).toContain('foreignObject'); + expect(progressBarSource).toContain('progress-fill-frame'); expect(progressBarSource).not.toContain('style={{'); expect(progressBarSource).not.toContain('style={'); + expect(frontendIndexCssSource).toContain('.progress-fill-frame'); + expect(frontendIndexCssSource).toContain('@media (prefers-reduced-motion: reduce)'); }); it('keeps search field on shell, runtime, and model owners', () => { diff --git a/frontend-modern/src/index.css b/frontend-modern/src/index.css index 090cbaf85..d4213c85c 100644 --- a/frontend-modern/src/index.css +++ b/frontend-modern/src/index.css @@ -300,9 +300,41 @@ } /* Progress bars */ + .progress-fill-frame { + transition: width 320ms cubic-bezier(0.22, 1, 0.36, 1); + will-change: width; + } + .progress-fill { @apply h-full; - transition: width 300ms ease-out; + transition: + width 320ms cubic-bezier(0.22, 1, 0.36, 1), + background-color 180ms ease-out; + } + + .metric-fill-geometry { + transition: + x 320ms cubic-bezier(0.22, 1, 0.36, 1), + width 320ms cubic-bezier(0.22, 1, 0.36, 1), + fill 180ms ease-out; + will-change: x, width, fill; + } + + .metric-fill-divider { + transition: + x1 320ms cubic-bezier(0.22, 1, 0.36, 1), + x2 320ms cubic-bezier(0.22, 1, 0.36, 1); + will-change: x1, x2; + } + + @media (prefers-reduced-motion: reduce) { + .progress-fill-frame, + .progress-fill, + .metric-fill-geometry, + .metric-fill-divider { + transition: none; + will-change: auto; + } } /* Custom scrollbar - subtle styling */