From 6ff930e2f2610285a6e44011bce99a5dc7a4e942 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 19 Jun 2026 05:31:47 -0700 Subject: [PATCH] FN-6701: theme workflow editor controls Theme workflow editor controls and checkbox affordances with Fusion tokens. - Add scoped React Flow zoom control and mini-map theme overrides for the workflow editor canvas. - Apply the workflow accent token to workflow settings, field, and column-trait checkboxes. - Cover the CSS contract with assertions against hardcoded white backgrounds and missing token usage. Files changed: .../app/components/WorkflowFieldsPanel.css | 8 +++ .../app/components/WorkflowNodeEditor.css | 55 +++++++++++++++++++ .../app/components/WorkflowSettingsPanel.css | 8 +++ .../__tests__/WorkflowNodeEditor.css.test.ts | 64 ++++++++++++++++++++++ 4 files changed, 135 insertions(+) Fusion-Task-Id: FN-6701 Fusion-Task-Lineage: e2e510ca-e1f7-408b-b113-ec30c9a3763a --- .../app/components/WorkflowFieldsPanel.css | 8 +++ .../app/components/WorkflowNodeEditor.css | 55 ++++++++++++++++ .../app/components/WorkflowSettingsPanel.css | 8 +++ .../__tests__/WorkflowNodeEditor.css.test.ts | 64 +++++++++++++++++++ 4 files changed, 135 insertions(+) diff --git a/packages/dashboard/app/components/WorkflowFieldsPanel.css b/packages/dashboard/app/components/WorkflowFieldsPanel.css index d49352b1ed..4c0909289e 100644 --- a/packages/dashboard/app/components/WorkflowFieldsPanel.css +++ b/packages/dashboard/app/components/WorkflowFieldsPanel.css @@ -124,6 +124,14 @@ color: var(--text-muted); } +/* +FNXC:WorkflowEditorTheme 2026-06-19-05:22: +Field-list checkboxes share the editor sidebar surface and must stay theme-accented across editable and built-in read-only workflow states. +*/ +.wf-field--checkbox input[type="checkbox"] { + accent-color: var(--todo); +} + .wf-field-required { flex: 0 0 auto; white-space: nowrap; diff --git a/packages/dashboard/app/components/WorkflowNodeEditor.css b/packages/dashboard/app/components/WorkflowNodeEditor.css index c2ba523966..e94ada1125 100644 --- a/packages/dashboard/app/components/WorkflowNodeEditor.css +++ b/packages/dashboard/app/components/WorkflowNodeEditor.css @@ -568,6 +568,53 @@ position: relative; } +/* +FNXC:WorkflowEditorTheme 2026-06-19-05:22: +React Flow ships white default controls and mini-map chrome, but the workflow editor must inherit Fusion theme tokens in both dark and light modes. Scope overrides to the canvas so other React Flow consumers keep their local contracts. +*/ +.wf-editor-canvas .react-flow__controls { + background: var(--surface); + border: var(--btn-border-width) solid var(--border); + border-radius: var(--radius-sm); + box-shadow: var(--shadow-sm); + color: var(--text); + overflow: hidden; +} + +.wf-editor-canvas .react-flow__controls-button { + background: var(--surface); + border: 0; + border-bottom: var(--btn-border-width) solid var(--border); + color: var(--text); + fill: currentColor; +} + +.wf-editor-canvas .react-flow__controls-button:hover { + background: var(--surface-hover); + color: var(--text); +} + +.wf-editor-canvas .react-flow__controls-button svg { + fill: currentColor; + stroke: currentColor; +} + +.wf-editor-canvas .react-flow__minimap { + background: var(--surface); + border: var(--btn-border-width) solid var(--border); + border-radius: var(--radius-sm); + box-shadow: var(--shadow-sm); +} + +.wf-editor-canvas .react-flow__minimap-node { + fill: var(--bg-secondary); + stroke: var(--border); +} + +.wf-editor-canvas .react-flow__minimap-mask { + fill: color-mix(in srgb, var(--surface) 70%, transparent); +} + .wf-mobile-shell { display: none; } @@ -1483,6 +1530,14 @@ color: var(--text-muted); } +/* +FNXC:WorkflowEditorTheme 2026-06-19-05:22: +Column trait toggles are left-sidebar workflow controls; keep their enabled and disabled checkbox chrome on the workflow accent token instead of React/browser defaults. +*/ +.wf-column-trait input[type="checkbox"] { + accent-color: var(--todo); +} + .wf-column-traits-label, .wf-column-agent-label { font-size: 0.65rem; diff --git a/packages/dashboard/app/components/WorkflowSettingsPanel.css b/packages/dashboard/app/components/WorkflowSettingsPanel.css index 13663ecfa9..65f87cbed9 100644 --- a/packages/dashboard/app/components/WorkflowSettingsPanel.css +++ b/packages/dashboard/app/components/WorkflowSettingsPanel.css @@ -193,6 +193,14 @@ color: var(--text-muted); } +/* +FNXC:WorkflowEditorTheme 2026-06-19-05:22: +Boolean workflow settings live in the editor's left sidebar, so their checkboxes must use the same accent token as inspector checkboxes instead of browser-default white controls. +*/ +.wf-setting--checkbox input[type="checkbox"] { + accent-color: var(--todo); +} + .wf-setting-options { display: flex; flex-direction: column; diff --git a/packages/dashboard/app/components/__tests__/WorkflowNodeEditor.css.test.ts b/packages/dashboard/app/components/__tests__/WorkflowNodeEditor.css.test.ts index 9a9cb36b1f..2e761da5ef 100644 --- a/packages/dashboard/app/components/__tests__/WorkflowNodeEditor.css.test.ts +++ b/packages/dashboard/app/components/__tests__/WorkflowNodeEditor.css.test.ts @@ -37,6 +37,70 @@ function findRule(blocks: string[], selector: RegExp): string { return rule; } +function expectNoHardcodedWhiteBackground(rule: string): void { + expect(rule).not.toMatch(/background(?:-color)?\s*:[^;]*(?:#fff|#ffffff|\bwhite\b)/i); +} + +describe("WorkflowNodeEditor themed React Flow CSS contract", () => { + it("FN-6701 themes zoom controls, mini-map, and sidebar checkboxes with tokens", () => { + const baseCss = loadAllAppCssBaseOnly(); + + // Surface Enumeration: WorkflowNodeEditor.tsx is the only React Flow / mount; WorkflowResultsTab and MobileWorkflowGraphView do not mount those affordances. Left-sidebar checkbox surfaces are WorkflowSettingsPanel, WorkflowFieldsPanel, and WorkflowColumnPanel trait toggles; inspector .wf-field--checkbox stays covered by WorkflowNodeEditor.css. + const controlsRule = findRule([baseCss], /\.wf-editor-canvas \.react-flow__controls\s*\{[^}]*\}/); + expect(controlsRule).toMatch(/background\s*:\s*var\(--surface\)\s*;/); + expect(controlsRule).toMatch(/border\s*:\s*var\(--btn-border-width\) solid var\(--border\)\s*;/); + expect(controlsRule).toMatch(/color\s*:\s*var\(--text\)\s*;/); + expectNoHardcodedWhiteBackground(controlsRule); + + const controlsButtonRule = findRule([baseCss], /\.wf-editor-canvas \.react-flow__controls-button\s*\{[^}]*\}/); + expect(controlsButtonRule).toMatch(/background\s*:\s*var\(--surface\)\s*;/); + expect(controlsButtonRule).toMatch(/border-bottom\s*:\s*var\(--btn-border-width\) solid var\(--border\)\s*;/); + expect(controlsButtonRule).toMatch(/color\s*:\s*var\(--text\)\s*;/); + expect(controlsButtonRule).toMatch(/fill\s*:\s*currentColor\s*;/); + expectNoHardcodedWhiteBackground(controlsButtonRule); + + const controlsButtonHoverRule = findRule( + [baseCss], + /\.wf-editor-canvas \.react-flow__controls-button:hover\s*\{[^}]*\}/, + ); + expect(controlsButtonHoverRule).toMatch(/background\s*:\s*var\(--surface-hover\)\s*;/); + expect(controlsButtonHoverRule).toMatch(/color\s*:\s*var\(--text\)\s*;/); + expectNoHardcodedWhiteBackground(controlsButtonHoverRule); + + const controlsSvgRule = findRule([baseCss], /\.wf-editor-canvas \.react-flow__controls-button svg\s*\{[^}]*\}/); + expect(controlsSvgRule).toMatch(/fill\s*:\s*currentColor\s*;/); + expect(controlsSvgRule).toMatch(/stroke\s*:\s*currentColor\s*;/); + + const minimapRule = findRule([baseCss], /\.wf-editor-canvas \.react-flow__minimap\s*\{[^}]*\}/); + expect(minimapRule).toMatch(/background\s*:\s*var\(--surface\)\s*;/); + expect(minimapRule).toMatch(/border\s*:\s*var\(--btn-border-width\) solid var\(--border\)\s*;/); + expectNoHardcodedWhiteBackground(minimapRule); + + const minimapNodeRule = findRule([baseCss], /\.wf-editor-canvas \.react-flow__minimap-node\s*\{[^}]*\}/); + expect(minimapNodeRule).toMatch(/fill\s*:\s*var\(--bg-secondary\)\s*;/); + expect(minimapNodeRule).toMatch(/stroke\s*:\s*var\(--border\)\s*;/); + + const minimapMaskRule = findRule([baseCss], /\.wf-editor-canvas \.react-flow__minimap-mask\s*\{[^}]*\}/); + expect(minimapMaskRule).toMatch(/fill\s*:\s*color-mix\(in srgb, var\(--surface\) 70%, transparent\)\s*;/); + + for (const selector of [ + /\.wf-setting--checkbox input\[type="checkbox"\]\s*\{[^}]*\}/, + /\.wf-field--checkbox input\[type="checkbox"\]\s*\{[^}]*\}/, + /\.wf-column-trait input\[type="checkbox"\]\s*\{[^}]*\}/, + ]) { + const checkboxRule = findRule([baseCss], selector); + expect(checkboxRule).toMatch(/accent-color\s*:\s*var\(--todo\)\s*;/); + expectNoHardcodedWhiteBackground(checkboxRule); + } + + const lightThemeOverrides = [...baseCss.matchAll(/\[data-theme="light"\][^{]*\{[^}]*\}/g)].map((match) => match[0]); + for (const overrideRule of lightThemeOverrides.filter((rule) => /react-flow__|wf-(?:setting|field|column-trait)/.test(rule))) { + expect(overrideRule).toMatch(/var\(--/); + expectNoHardcodedWhiteBackground(overrideRule); + } + }); +}); + describe("WorkflowNodeEditor edge visibility CSS contract", () => { it("keeps swimlane bands translucent so built-in workflow edges remain visible", () => { const editorCss = readComponentCss("WorkflowNodeEditor.css");