14 KiB
Frontend Primitives Contract
Contract Metadata
{
"subsystem_id": "frontend-primitives",
"lane": "L8",
"contract_file": "docs/release-control/v6/internal/subsystems/frontend-primitives.md",
"status_file": "docs/release-control/v6/internal/status.json",
"registry_file": "docs/release-control/v6/internal/subsystems/registry.json",
"dependency_subsystem_ids": []
}
Purpose
Own reusable frontend primitives and canonical page-shell patterns so feature work extends shared components instead of creating new local variants.
Canonical Files
frontend-modern/src/components/shared/frontend-modern/src/components/Settings/Settings.tsxfrontend-modern/src/components/Settings/SettingsPageShell.tsxfrontend-modern/src/components/Settings/settingsPanelRegistry.tsfrontend-modern/src/components/Settings/APIAccessPanel.tsxfrontend-modern/src/components/Settings/AISettings.tsxfrontend-modern/src/components/Settings/AuditLogPanel.tsxfrontend-modern/src/components/Settings/AuditWebhookPanel.tsxfrontend-modern/src/components/Settings/GeneralSettingsPanel.tsxfrontend-modern/src/components/Settings/NetworkSettingsPanel.tsxfrontend-modern/src/components/Settings/RecoverySettingsPanel.tsxfrontend-modern/src/components/Settings/SecurityAuthPanel.tsxfrontend-modern/src/components/Settings/SecurityOverviewPanel.tsxfrontend-modern/src/components/Settings/settingsHeaderMeta.tsfrontend-modern/src/components/Settings/SettingsPageShell.tsxfrontend-modern/src/components/Settings/settingsPanelRegistry.tsfrontend-modern/src/components/Settings/SSOProvidersPanel.tsxfrontend-modern/src/components/Settings/UpdatesSettingsPanel.tsxfrontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.tstests/integration/tests/15-settings-shell-consistency.spec.tsfrontend-modern/src/components/shared/PageControls.guardrails.test.tsfrontend-modern/src/components/shared/TypeColumn.guardrails.test.tsfrontend-modern/src/features/frontend-modern/src/components/SetupWizard/SetupWizard.tsxfrontend-modern/src/components/SetupWizard/SetupCompletionPreview.tsxfrontend-modern/src/components/SetupWizard/__tests__/SetupWizard.test.tsxfrontend-modern/src/components/SetupWizard/__tests__/SetupCompletionPreview.test.tsxfrontend-modern/src/components/shared/MonitoredSystemLimitWarningBanner.tsx
Shared Boundaries
frontend-modern/src/components/Settings/GeneralSettingsPanel.tsxshared withsecurity-privacy: the general settings privacy panel is both a security/privacy control surface and a canonical settings-shell presentation boundary.frontend-modern/src/components/Settings/SecurityAuthPanel.tsxshared withsecurity-privacy: the authentication settings surface is both a security/privacy control surface and a canonical settings-shell presentation boundary.frontend-modern/src/components/Settings/SecurityOverviewPanel.tsxshared withsecurity-privacy: the security overview settings surface is both a security/privacy control surface and a canonical settings-shell presentation boundary.
Extension Points
- Add shared primitives in
frontend-modern/src/components/shared/ - Route new top-level settings surfaces through the canonical settings shell instead of introducing page-local framing
- Add feature-specific presentation only when no shared primitive should own it
- Add guardrail tests when a new shared pattern is introduced
Forbidden Paths
- Reinventing table/filter/toggle primitives when a shared version exists
- Feature-local styling forks of canonical shared components without explicit justification
- Direct imports that bypass shared presentation helpers where guardrails exist
- Top-level settings panels introducing bespoke page-level headers or outer
framing instead of the canonical settings shell and
SettingsPanelcontract
Completion Obligations
- Update guardrail tests when new shared primitives are added
- Keep top-level settings surfaces routed through the canonical settings shell
and maintain both
frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.tsplustests/integration/tests/15-settings-shell-consistency.spec.ts - Update this contract when a new canonical UI pattern is adopted
- Remove local forks after the shared primitive is introduced
Current State
The frontend already has several guardrail tests. The next step is to keep turning repeated local patterns into explicit shared primitives with hard usage bounds.
The subsystem registry now also requires explicit proof-policy coverage for all
shared runtime files, and shared-component guardrails fail if raw table
composition is reintroduced in new shared components outside the canonical
allowlist.
Infrastructure summary and detail surfaces now also use the shared normalized
identity lookup helper from frontend-modern/src/utils/resourceIdentity.ts
so dotted hostnames and alias variants stay consistent between the shared
table, drawer, and detail views instead of each component carrying its own
identifier-variant logic.
Those same surfaces also share the trimmed-string helper from
frontend-modern/src/utils/stringUtils.ts so shared components do not keep
their own copy of the same whitespace-trimming identity logic.
General settings segmented selectors for theme preference and temperature unit
must now also route through the shared FilterButtonGroup primitive instead of
maintaining local button-group styling forks inside
frontend-modern/src/components/Settings/GeneralSettingsPanel.tsx.
Reporting time-range/export selectors and General settings Proxmox VE polling
presets must now also route through the shared FilterButtonGroup prominent
variant instead of maintaining local blue segmented-control styling forks in
feature components.
That same shared FilterButtonGroup primitive must stay CSP-safe: touch-scroll
overflow behavior must come from canonical CSS classes rather than inline
style attributes so settings and reporting selectors do not reintroduce
browser console CSP violations under the release build policy.
Selectable settings cards for compact provider pickers and detail choice panels
must now route through the shared SelectionCardGroup primitive instead of
duplicating border-2 active-card styling in feature components.
Settings informational callouts with icon-plus-copy layouts must now route
through the shared CalloutCard primitive instead of maintaining feature-local
blue bordered wrappers.
Alert incident-event filter containers, labels, and chips must now route
through the shared presentation helpers in
frontend-modern/src/utils/alertIncidentPresentation.ts instead of allowing
frontend-modern/src/pages/Alerts.tsx and
frontend-modern/src/features/alerts/OverviewTab.tsx to fork their own filter
button styling.
Alert incident acknowledged badges, event cards, and note-editor controls must
also route through frontend-modern/src/utils/alertIncidentPresentation.ts
instead of letting the alerts page and overview timeline maintain duplicate
inline incident-detail styling.
Alert incident meta-row and detail-text presentation must also route through
frontend-modern/src/utils/alertIncidentPresentation.ts instead of letting
the alerts page and overview timeline maintain duplicated inline incident
typography rules.
Alert incident timeline event card structure must also route through
frontend-modern/src/components/Alerts/IncidentTimelineEventCard.tsx so the
alerts page and overview timeline share one canonical event-card renderer
instead of reimplementing the same summary/detail/output block twice.
Resource incident panel card and summary-row presentation must also route
through frontend-modern/src/utils/alertIncidentPresentation.ts instead of
maintaining page-local incident panel styling inside
frontend-modern/src/pages/Alerts.tsx.
Shared primitive consumers that split status-dot tone and status-text tone must now keep both values routed through the same exported presentation helper. Feature cards such as RAID status may not call shadow local aliases that drift from the canonical shared class/variant helpers.
Active alert card state and action-button presentation must also route through
frontend-modern/src/utils/alertOverviewPresentation.ts instead of leaving
feature-local alert card styling inside
frontend-modern/src/features/alerts/OverviewTab.tsx.
Alert resource display labels used by the thresholds editor and alerts page
must now route through the shared helper in
frontend-modern/src/features/alerts/helpers.ts instead of rebuilding
resource display-name fallback chains inline. Governed resources must preserve
their canonical policy-aware label across grouped node headers, docker host
grouping, and saved override rows rather than collapsing back to raw names or
friendly-name truncation.
Shared search inputs must now keep their forwarded keyboard, blur, and clear handlers as explicit callable functions instead of relying on loose Solid event-handler unions. Shared search primitives still need to accept the real input/button event targets, but direct invocation inside the primitive must stay type-safe so consumers do not reintroduce union-call regressions while adding history, shortcut, or trailing-control behavior.
Shared shared-shell primitives that expose semantic title or value-level
onChange props must now explicitly omit the conflicting DOM attribute names
from their inherited HTML props. CalloutCard, FilterSegmentedControl, and
Subtabs may still forward ordinary div attributes, but their canonical API
must preserve JSX element titles and value-callback handlers instead of
widening back to raw DOM attribute unions.
Shared entitlement/migration warning banners that live under
frontend-modern/src/components/shared/ must also keep their counted fleet
surface on the Pulse Unified Agent term. Shared primitive copy may describe
legacy/API-connected resources separately, but it may not regress the primary
banner label or CTA text back to host-agent product language.
The self-hosted commercial paywall copy on those shared warning surfaces is
now also explicitly locked to monitored systems rather than agents. When a
shared banner or shared settings shell is explaining self-hosted plan caps,
the operator-facing commercial term must follow the monitored-system model even
if explicit legacy-v5 compatibility helpers still decode older alias fields at
import boundaries.
That banner boundary now also owns the canonical monitored-system naming
surface directly: the shared warning component path and exported symbol are
MonitoredSystemLimitWarningBanner, and future work may not reintroduce an
agent-era banner filename or component name as the primary primitive.
First-session educational surfaces must also stay brief, flat, and model-led.
When Pulse needs to teach a user how a flow works, the primary on-screen
guidance should collapse to a few short descriptions of the real product
mental model instead of a logo wall, feature brochure, or verbose internal
mechanics dump. The runtime wizard itself now stays on the two-step
Welcome -> Security path, while the separate setup-completion preview owns
the brief three-step explanation: install the Unified Agent, get the first
Pulse resource, then layer on additional context.
The settings shell is now also a governed frontend primitive boundary.
Top-level settings surfaces must route through Settings.tsx,
SettingsPageShell.tsx, and
frontend-modern/src/components/shared/SettingsPanel.tsx instead of
reintroducing bespoke outer page headers or one-off top-level panel framing.
The shell metadata driving those surfaces is part of the same boundary as
well: frontend-modern/src/components/Settings/settingsHeaderMeta.ts and
representative top-level panels such as
frontend-modern/src/components/Settings/APIAccessPanel.tsx,
frontend-modern/src/components/Settings/AISettings.tsx,
frontend-modern/src/components/Settings/AuditLogPanel.tsx,
frontend-modern/src/components/Settings/AuditWebhookPanel.tsx,
frontend-modern/src/components/Settings/GeneralSettingsPanel.tsx,
frontend-modern/src/components/Settings/NetworkSettingsPanel.tsx must keep
frontend-modern/src/components/Settings/SecurityAuthPanel.tsx must keep
frontend-modern/src/components/Settings/SecurityOverviewPanel.tsx,
frontend-modern/src/components/Settings/RecoverySettingsPanel.tsx,
frontend-modern/src/components/Settings/SSOProvidersPanel.tsx, and
frontend-modern/src/components/Settings/UpdatesSettingsPanel.tsx must keep
page-shell titles, descriptions, and lead panel framing aligned instead of
letting navigation/header labels drift away from the actual settings surface.
First-session runtime framing is now part of that same owned primitive story.
frontend-modern/src/components/SetupWizard/SetupWizard.tsx must stay on the
real two-step runtime shape (Welcome, then Security) and hand successful
setup directly into the canonical Infrastructure Operations install route
instead of regrowing a second setup-only completion step. The standalone
preview surface in
frontend-modern/src/components/SetupWizard/SetupCompletionPreview.tsx may
still exist for guarded preview coverage, but it must remain explicitly
separate from the runtime wizard so first-session UX does not split into two
competing product flows again.
The release-ready shell proof now also includes a representative desktop
Playwright rehearsal in
tests/integration/tests/15-settings-shell-consistency.spec.ts so general,
organization, billing, relay, security, AI, updates, and recovery panels are
all exercised through the built app shell under a seeded multi-tenant runtime.
The security-facing settings panels within that shell now also follow an
explicit shared boundary with security-privacy so shell framing stays here
while auth posture, token controls, and privacy semantics remain governed as a
trust surface instead of generic UX copy.
Single-surface settings pages that only render one canonical SettingsPanel
must stay rooted directly at that panel instead of wrapping it in an extra
page-level space-y-* container. frontend-modern/src/components/Settings/UpdatesSettingsPanel.tsx
frontend-modern/src/components/Settings/RecoverySettingsPanel.tsx, and
frontend-modern/src/components/Settings/AuditLogPanel.tsx are the current
reference cases, and
frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts
locks that direct-root contract so single-surface pages do not quietly regain
redundant outer spacing chrome.