From a418b8da27fda581db5368022210e74d1b99464c Mon Sep 17 00:00:00 2001
From: rcourtman
Date: Sat, 13 Jun 2026 22:20:03 +0100
Subject: [PATCH] Converge security auth actions on shared Button primitive
---
.../subsystems/frontend-primitives.md | 4 +++
.../internal/subsystems/security-privacy.md | 5 ++++
.../scripts/shared-template-registry.json | 9 ++++++
.../components/Settings/SecurityAuthPanel.tsx | 28 +++++++++++--------
.../__tests__/settingsArchitecture.test.ts | 16 +++++++++++
.../SharedPrimitives.guardrails.test.ts | 24 ++++++++++++++++
6 files changed, 74 insertions(+), 12 deletions(-)
diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md
index 52707ccf2..ce3982370 100644
--- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md
+++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md
@@ -572,6 +572,10 @@ not a replacement status card, CTA band, or page-local nested card.
cloud-paid surfaces own Suspend, Activate, Reload, tenant state, and mutation
semantics, while `Button` owns the row-action chrome through the secondary
`sm` and `xs` sizes.
+ Security authentication settings actions follow the same boundary:
+ security/privacy owns auth setup, password-change, credential-rotation, and
+ read-only semantics, while `Button` owns the warning, primary, secondary,
+ and settings-action chrome.
If a new surface needs a variant that the shared primitive does not expose,
extend the primitive and registry guard rather than adding a page-local
class string.
diff --git a/docs/release-control/v6/internal/subsystems/security-privacy.md b/docs/release-control/v6/internal/subsystems/security-privacy.md
index adb257b88..f86af8be2 100644
--- a/docs/release-control/v6/internal/subsystems/security-privacy.md
+++ b/docs/release-control/v6/internal/subsystems/security-privacy.md
@@ -221,6 +221,11 @@ controls as normal product settings.
primitive. Security/privacy owns scope authority, wildcard behavior, preset
membership, and custom scope toggles; frontend-primitives owns active and
inactive pill tone, focus, disabled treatment, and pressed-state wiring.
+6c. Keep authentication setup, password-change, and credential-rotation actions
+ on the shared `Button` primitive. Security/privacy owns the auth authority,
+ setup/rotation semantics, and read-only capability state;
+ frontend-primitives owns warning, primary, secondary, focus, disabled, and
+ settings-action chrome.
6. Keep the shared storage-directory and secure storage-file hardening helper aligned with the crypto manager plus control-plane magic-link key and store handling whenever runtime data-root ownership assumptions change.
7. Keep auth-env ingestion, hosted commercial base URL validation, and shared
fingerprint-verifier TLS defaults aligned whenever runtime auth loading,
diff --git a/frontend-modern/scripts/shared-template-registry.json b/frontend-modern/scripts/shared-template-registry.json
index 292663312..aaeef0af0 100644
--- a/frontend-modern/scripts/shared-template-registry.json
+++ b/frontend-modern/scripts/shared-template-registry.json
@@ -970,6 +970,7 @@
{ "path": "src/components/Settings/ReportingPanel.tsx" },
{ "path": "src/components/Settings/ResourcePicker.tsx" },
{ "path": "src/components/Settings/SelfHostedCommercialRecoverySection.tsx" },
+ { "path": "src/components/Settings/SecurityAuthPanel.tsx" },
{ "path": "src/components/Settings/SSOProvidersPanel.tsx" },
{ "path": "src/components/UpdateConfirmationModal.tsx" },
{ "path": "src/components/UpdateProgressModal.tsx" },
@@ -1038,6 +1039,14 @@
"min-h-10 sm:min-h-9 px-4 py-2.5 text-sm font-medium rounded-md border border-border text-base-content hover:bg-surface-hover transition-colors disabled:opacity-60 disabled:cursor-not-allowed"
]
},
+ {
+ "path": "src/components/Settings/SecurityAuthPanel.tsx",
+ "patterns": [
+ "w-full sm:w-auto px-3 py-2 text-xs font-medium rounded-md border border-amber-300 text-amber-800 bg-amber-100 hover:bg-amber-200 transition-colors dark:border-amber-700 dark:text-amber-200 dark:bg-amber-900 dark:hover:bg-amber-800",
+ "w-full sm:w-auto min-h-10 sm:min-h-10 px-4 py-2.5 text-sm font-medium bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors",
+ "w-full sm:w-auto min-h-10 sm:min-h-10 px-4 py-2.5 text-sm font-medium border border-border text-base-content rounded-md hover:bg-surface-hover transition-colors"
+ ]
+ },
{
"path": "src/components/Settings/SSOProvidersPanel.tsx",
"patterns": [
diff --git a/frontend-modern/src/components/Settings/SecurityAuthPanel.tsx b/frontend-modern/src/components/Settings/SecurityAuthPanel.tsx
index b45c68d5f..7552b8aaa 100644
--- a/frontend-modern/src/components/Settings/SecurityAuthPanel.tsx
+++ b/frontend-modern/src/components/Settings/SecurityAuthPanel.tsx
@@ -1,4 +1,5 @@
import { Component, Show, Accessor, Setter } from 'solid-js';
+import { Button } from '@/components/shared/Button';
import { CalloutCard } from '@/components/shared/CalloutCard';
import SettingsPanel from '@/components/shared/SettingsPanel';
import { Toggle } from '@/components/shared/Toggle';
@@ -75,14 +76,15 @@ export const SecurityAuthPanel: Component = (props) => {
{SECURITY_AUTH_DISABLED_READ_ONLY_MESSAGE}
-
+
@@ -107,26 +109,28 @@ export const SecurityAuthPanel: Component = (props) => {
-
-
User:{' '}
diff --git a/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts b/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts
index 9792ac58f..17aff7a82 100644
--- a/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts
+++ b/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts
@@ -327,6 +327,22 @@ describe('settings architecture guardrails', () => {
expect(ssoProvidersPanelSource).not.toContain(
'text-blue-600 hover:underline flex items-center gap-1',
);
+
+ expect(securityAuthPanelSource).toContain(
+ "import { Button } from '@/components/shared/Button';",
+ );
+ expect(securityAuthPanelSource).toContain('variant="warning"');
+ expect(securityAuthPanelSource).toContain('variant="primary"');
+ expect(securityAuthPanelSource).toContain('size="settingsAction"');
+ expect(securityAuthPanelSource).not.toContain(
+ 'w-full sm:w-auto px-3 py-2 text-xs font-medium rounded-md border border-amber-300',
+ );
+ expect(securityAuthPanelSource).not.toContain(
+ 'w-full sm:w-auto min-h-10 sm:min-h-10 px-4 py-2.5 text-sm font-medium bg-blue-600',
+ );
+ expect(securityAuthPanelSource).not.toContain(
+ 'w-full sm:w-auto min-h-10 sm:min-h-10 px-4 py-2.5 text-sm font-medium border border-border',
+ );
});
it('keeps settings callouts on the shared CalloutCard primitive', () => {
diff --git a/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts b/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts
index a874fba54..4427b87d4 100644
--- a/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts
+++ b/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts
@@ -3098,6 +3098,7 @@ describe('shared primitive guardrails', () => {
'src/components/Settings/ReportingPanel.tsx',
'src/components/Settings/ResourcePicker.tsx',
'src/components/Settings/SelfHostedCommercialRecoverySection.tsx',
+ 'src/components/Settings/SecurityAuthPanel.tsx',
'src/components/Settings/SSOProvidersPanel.tsx',
'src/components/UpdateConfirmationModal.tsx',
'src/components/UpdateProgressModal.tsx',
@@ -3167,6 +3168,14 @@ describe('shared primitive guardrails', () => {
'min-h-10 sm:min-h-9 px-4 py-2.5 text-sm font-medium rounded-md border border-border text-base-content hover:bg-surface-hover transition-colors disabled:opacity-60 disabled:cursor-not-allowed',
]),
}),
+ expect.objectContaining({
+ path: 'src/components/Settings/SecurityAuthPanel.tsx',
+ patterns: expect.arrayContaining([
+ 'w-full sm:w-auto px-3 py-2 text-xs font-medium rounded-md border border-amber-300 text-amber-800 bg-amber-100 hover:bg-amber-200 transition-colors dark:border-amber-700 dark:text-amber-200 dark:bg-amber-900 dark:hover:bg-amber-800',
+ 'w-full sm:w-auto min-h-10 sm:min-h-10 px-4 py-2.5 text-sm font-medium bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors',
+ 'w-full sm:w-auto min-h-10 sm:min-h-10 px-4 py-2.5 text-sm font-medium border border-border text-base-content rounded-md hover:bg-surface-hover transition-colors',
+ ]),
+ }),
expect.objectContaining({
path: 'src/components/Settings/SSOProvidersPanel.tsx',
patterns: expect.arrayContaining([
@@ -3955,6 +3964,21 @@ describe('shared primitive guardrails', () => {
expect(selfHostedCommercialRecoverySectionSource).not.toContain(
'min-h-10 sm:min-h-9 px-4 py-2.5 text-sm font-medium rounded-md border border-border text-base-content hover:bg-surface-hover transition-colors disabled:opacity-60 disabled:cursor-not-allowed',
);
+ expect(securityAuthPanelSource).toContain('@/components/shared/Button');
+ expect(securityAuthPanelSource).toContain('