mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
Converge standalone machine actions on ActionIconButton
This commit is contained in:
@@ -557,6 +557,9 @@ not a replacement status card, CTA band, or page-local nested card.
|
||||
disabled treatment, title fallback, and accessible name wiring must come
|
||||
from that shared primitive rather than page-local `<button>` plus inline SVG
|
||||
shells.
|
||||
Standalone machine row action triggers follow the same rule: the Machines
|
||||
table owns remove-agent semantics and menu placement, while
|
||||
`ActionIconButton` owns the compact muted trigger chrome.
|
||||
AI Chat follows that same boundary for drawer header controls, session row
|
||||
actions, transcript fallback close/download actions, activity-dock queued
|
||||
follow-up controls, composer send, footer help/route actions, and compact
|
||||
|
||||
@@ -913,6 +913,11 @@ AI-only summary payloads, or page-local heuristics.
|
||||
provider-owned nodes must not become machine-page members through
|
||||
hostname, `agent` platform scope, or agent telemetry alone; those facts
|
||||
surface as facets on the owning provider page.
|
||||
`AgentsMachinesTable.tsx` may own row membership, resource-derived menu
|
||||
eligibility, and remove-agent semantics for these projected rows, but the
|
||||
compact row action trigger chrome stays under the frontend-primitives
|
||||
`ActionIconButton` boundary rather than becoming a unified-resource-local
|
||||
button shell.
|
||||
The default tab for each platform path must point at a sub-tab whose
|
||||
canonical unified-resource projection actually populates, and visible
|
||||
workflow subtabs must stay evidence-gated by the same canonical row or
|
||||
|
||||
@@ -1259,7 +1259,8 @@
|
||||
{ "path": "src/components/Alerts/ResourceTable.tsx" },
|
||||
{ "path": "src/components/AI/Chat/index.tsx" },
|
||||
{ "path": "src/components/Settings/RolesPanel.tsx" },
|
||||
{ "path": "src/components/Settings/SSOProvidersPanel.tsx" }
|
||||
{ "path": "src/components/Settings/SSOProvidersPanel.tsx" },
|
||||
{ "path": "src/features/standalone/AgentsMachinesTable.tsx" }
|
||||
],
|
||||
"forbiddenPatterns": [
|
||||
{
|
||||
@@ -1306,6 +1307,12 @@
|
||||
"flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-border bg-surface text-muted transition-colors hover:border-border hover:bg-surface-hover hover:text-base-content focus:outline-none focus:ring-2 focus:ring-blue-500/30"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/features/standalone/AgentsMachinesTable.tsx",
|
||||
"patterns": [
|
||||
"inline-flex h-7 w-7 items-center justify-center rounded-md text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/60"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "src/components/Settings/SSOProvidersPanel.tsx",
|
||||
"patterns": [
|
||||
@@ -3186,6 +3193,29 @@
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "standalone-agent-machine-action-icon-local-shell",
|
||||
"category": "action-button",
|
||||
"summary": "Standalone machine row action triggers must not recreate local muted h-7 icon-button shells; compose ActionIconButton.",
|
||||
"canonical": {
|
||||
"path": "src/components/shared/Button.tsx",
|
||||
"export": "ActionIconButton"
|
||||
},
|
||||
"scopes": ["src/features/standalone/AgentsMachinesTable.tsx"],
|
||||
"extensions": [".tsx"],
|
||||
"allPatterns": [
|
||||
"inline-flex h-7 w-7 items-center justify-center rounded-md text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/60"
|
||||
],
|
||||
"legacyReason": "Retired migration debt. Standalone machine action triggers compose ActionIconButton for shared icon-action size, tone, focus, title, and accessible-label behavior.",
|
||||
"allowedPaths": [],
|
||||
"ignoredPaths": ["src/components/shared/Button.test.tsx"],
|
||||
"proof": [
|
||||
"src/components/shared/SharedPrimitives.guardrails.test.ts",
|
||||
"src/components/shared/Button.test.tsx",
|
||||
"src/features/standalone/__tests__/AgentsMachinesTable.test.tsx",
|
||||
"scripts/shared-template-audit.mjs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ai-chat-action-icon-header-local-shell",
|
||||
"category": "action-button",
|
||||
|
||||
@@ -2982,6 +2982,9 @@ describe('shared primitive guardrails', () => {
|
||||
const alertResourceActionGuard = registry.patternGuards?.find(
|
||||
(guard) => guard.id === 'alert-resource-action-local-svg-button-shell',
|
||||
);
|
||||
const standaloneAgentMachineActionGuard = registry.patternGuards?.find(
|
||||
(guard) => guard.id === 'standalone-agent-machine-action-icon-local-shell',
|
||||
);
|
||||
const aiChatActionIconHeaderGuard = registry.patternGuards?.find(
|
||||
(guard) => guard.id === 'ai-chat-action-icon-header-local-shell',
|
||||
);
|
||||
@@ -3835,6 +3838,7 @@ describe('shared primitive guardrails', () => {
|
||||
'src/components/AI/Chat/index.tsx',
|
||||
'src/components/Settings/RolesPanel.tsx',
|
||||
'src/components/Settings/SSOProvidersPanel.tsx',
|
||||
'src/features/standalone/AgentsMachinesTable.tsx',
|
||||
]);
|
||||
expect(actionIconRule?.forbiddenPatterns).toEqual(
|
||||
expect.arrayContaining([
|
||||
@@ -3886,6 +3890,12 @@ describe('shared primitive guardrails', () => {
|
||||
'flex h-7 w-7 shrink-0 items-center justify-center rounded-md border border-border bg-surface text-muted transition-colors hover:border-border hover:bg-surface-hover hover:text-base-content focus:outline-none focus:ring-2 focus:ring-blue-500/30',
|
||||
]),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
path: 'src/features/standalone/AgentsMachinesTable.tsx',
|
||||
patterns: expect.arrayContaining([
|
||||
'inline-flex h-7 w-7 items-center justify-center rounded-md text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/60',
|
||||
]),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
path: 'src/components/Settings/SSOProvidersPanel.tsx',
|
||||
patterns: expect.arrayContaining([
|
||||
@@ -3913,6 +3923,20 @@ describe('shared primitive guardrails', () => {
|
||||
'src/components/shared/Button.test.tsx',
|
||||
'src/components/shared/SharedPrimitives.guardrails.test.ts',
|
||||
]);
|
||||
expect(standaloneAgentMachineActionGuard?.canonical?.path).toBe(
|
||||
'src/components/shared/Button.tsx',
|
||||
);
|
||||
expect(standaloneAgentMachineActionGuard?.canonical?.export).toBe('ActionIconButton');
|
||||
expect(standaloneAgentMachineActionGuard?.allPatterns).toEqual([
|
||||
'inline-flex h-7 w-7 items-center justify-center rounded-md text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/60',
|
||||
]);
|
||||
expect(standaloneAgentMachineActionGuard?.scopes).toEqual([
|
||||
'src/features/standalone/AgentsMachinesTable.tsx',
|
||||
]);
|
||||
expect(standaloneAgentMachineActionGuard?.allowedPaths ?? []).toHaveLength(0);
|
||||
expect(standaloneAgentMachineActionGuard?.ignoredPaths).toEqual([
|
||||
'src/components/shared/Button.test.tsx',
|
||||
]);
|
||||
for (const guard of [
|
||||
aiChatActionIconHeaderGuard,
|
||||
aiChatActionIconAccentGuard,
|
||||
@@ -4079,6 +4103,11 @@ describe('shared primitive guardrails', () => {
|
||||
]) {
|
||||
expect(aiChatSource).not.toContain(retiredActionIconShell);
|
||||
}
|
||||
expect(agentsMachinesTableSource).toContain('@/components/shared/Button');
|
||||
expect(agentsMachinesTableSource).toContain('ActionIconButton');
|
||||
expect(agentsMachinesTableSource).not.toContain(
|
||||
'inline-flex h-7 w-7 items-center justify-center rounded-md text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/60',
|
||||
);
|
||||
expect(resourceDetailDrawerDebugTabSource).toContain('@/components/shared/Button');
|
||||
expect(resourceDetailDrawerDebugTabSource).not.toContain(
|
||||
'rounded-md border border-border bg-surface px-3 py-1.5 text-xs font-medium text-base-content',
|
||||
|
||||
@@ -17,6 +17,7 @@ import { MonitoringAPI } from '@/api/monitoring';
|
||||
import { EnhancedCPUBar } from '@/components/Workloads/EnhancedCPUBar';
|
||||
import { StackedDiskBar } from '@/components/Workloads/StackedDiskBar';
|
||||
import { StackedMemoryBar } from '@/components/Workloads/StackedMemoryBar';
|
||||
import { ActionIconButton } from '@/components/shared/Button';
|
||||
import { ColumnPicker } from '@/components/shared/ColumnPicker';
|
||||
import { StatusDot } from '@/components/shared/StatusDot';
|
||||
import { TemperatureGauge } from '@/components/shared/TemperatureGauge';
|
||||
@@ -720,12 +721,11 @@ const AgentMachineActionsCell: Component<{
|
||||
if (event.key === 'Escape' && props.menuOpen) props.onToggleMenu();
|
||||
}}
|
||||
>
|
||||
<button
|
||||
<ActionIconButton
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
class="inline-flex h-7 w-7 items-center justify-center rounded-md text-muted transition-colors hover:bg-surface-hover hover:text-base-content focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/60"
|
||||
title="Machine actions"
|
||||
aria-label={`Machine actions for ${props.name}`}
|
||||
label={`Machine actions for ${props.name}`}
|
||||
tone="muted"
|
||||
size="sm"
|
||||
aria-haspopup="menu"
|
||||
aria-expanded={props.menuOpen ? 'true' : 'false'}
|
||||
onClick={(event) => {
|
||||
@@ -735,7 +735,7 @@ const AgentMachineActionsCell: Component<{
|
||||
}}
|
||||
>
|
||||
<MoreHorizontalIcon class="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</ActionIconButton>
|
||||
|
||||
<Show when={props.menuOpen}>
|
||||
<Portal mount={document.body}>
|
||||
|
||||
Reference in New Issue
Block a user