From b8b0e3ec9ef3d2e2d1f8c8fc449a85f61216ee14 Mon Sep 17 00:00:00 2001 From: abccodes Date: Tue, 23 Jun 2026 00:02:09 -0700 Subject: [PATCH] [aidan] ui/workflows: dark mode and design-system cohesion --- .../app/pages/Workflows/app/RepeatField.tsx | 7 +- .../src/app/pages/Workflows/app/SaveGuard.tsx | 45 +++--- .../pages/Workflows/app/WorkflowsAppCard.tsx | 24 +-- frontend/src/app/pages/Workflows/app/uiKit.ts | 138 +++++++++++++++--- 4 files changed, 157 insertions(+), 57 deletions(-) diff --git a/frontend/src/app/pages/Workflows/app/RepeatField.tsx b/frontend/src/app/pages/Workflows/app/RepeatField.tsx index 25517e82..66796a82 100644 --- a/frontend/src/app/pages/Workflows/app/RepeatField.tsx +++ b/frontend/src/app/pages/Workflows/app/RepeatField.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef, useState } from 'react'; -import { WC } from './uiKit'; +import { useWC } from './uiKit'; // Combobox for the run limit: pick a preset OR type any count. Self-rendered // (no native {open && ( -
+
{OPTIONS.map((o) => { const active = (o.val == null && value == null) || o.val === value; return ( diff --git a/frontend/src/app/pages/Workflows/app/SaveGuard.tsx b/frontend/src/app/pages/Workflows/app/SaveGuard.tsx index d711cbff..f89cc2b8 100644 --- a/frontend/src/app/pages/Workflows/app/SaveGuard.tsx +++ b/frontend/src/app/pages/Workflows/app/SaveGuard.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { WC } from './uiKit'; +import { useWC } from './uiKit'; // Test-first nudge before scheduling: a test run grants the tool access the // workflow needs, so unattended runs don't stall reaching for them. @@ -8,29 +8,32 @@ const SaveGuard: React.FC<{ onClose: () => void; onSaveAnyway: () => void; onRunTest: () => void; -}> = ({ title, onClose, onSaveAnyway, onRunTest }) => ( -
-
e.stopPropagation()} style={{ width: 430, maxWidth: '100%', background: WC.paper, borderRadius: 16, boxShadow: '0 30px 70px -22px rgba(0,0,0,0.45)', overflow: 'hidden' }}> -
-
-
- +}> = ({ title, onClose, onSaveAnyway, onRunTest }) => { + const WC = useWC(); + return ( +
+
e.stopPropagation()} style={{ width: 430, maxWidth: '100%', background: WC.paper, borderRadius: WC.radius.lg, boxShadow: WC.shadow.lg, overflow: 'hidden' }}> +
+
+
+ +
+

Test run recommended

-

Test run recommended

+

+ You haven’t tested “{title}” yet. A quick test run confirms the steps work and grants the tool access it needs before it goes on a schedule. +

+
+
+ +
-

- You haven’t tested “{title}” yet. A quick test run confirms the steps work and grants the tool access it needs before it goes on a schedule. -

-
-
- -
-
-); + ); +}; export default SaveGuard; diff --git a/frontend/src/app/pages/Workflows/app/WorkflowsAppCard.tsx b/frontend/src/app/pages/Workflows/app/WorkflowsAppCard.tsx index 41ae234c..5a745123 100644 --- a/frontend/src/app/pages/Workflows/app/WorkflowsAppCard.tsx +++ b/frontend/src/app/pages/Workflows/app/WorkflowsAppCard.tsx @@ -1,7 +1,8 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useAppDispatch } from '@/shared/hooks'; import { closeWorkflowsApp, setWorkflowsHubPosition, setWorkflowsHubSize } from '@/shared/state/dashboardLayoutSlice'; -import { WC, FONT_SERIF } from './uiKit'; +import EventRepeatIcon from '@mui/icons-material/EventRepeat'; +import { useWC, FONT_SERIF } from './uiKit'; import WorkflowsAppContent from './WorkflowsAppContent'; type ResizeDir = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw'; @@ -53,6 +54,7 @@ const WorkflowsAppCard: React.FC = ({ isSelected = false, isHighlighted = false, multiDragDelta = null, onCardSelect, onDragStart, onDragMove, onDragEnd, onBringToFront, }) => { + const WC = useWC(); const dispatch = useAppDispatch(); const panRef = useRef({ panX, panY }); @@ -175,7 +177,7 @@ const WorkflowsAppCard: React.FC = ({ const dw = localResize?.w ?? cardWidth; const dh = localResize?.h ?? cardHeight; - const border = isHighlighted ? `2px solid ${WC.accent}` : isSelected ? '2px solid #3b82f6' : '1px solid rgba(33,30,27,0.12)'; + const border = isHighlighted ? `2px solid ${WC.accent}` : isSelected ? '2px solid #3b82f6' : `1px solid ${WC.border.subtle}`; const noTransition = isDragging || isResizing || (isSelected && !!multiDragDelta); return ( @@ -201,8 +203,8 @@ const WorkflowsAppCard: React.FC = ({ left: dx, top: dy, width: dw, height: dh, background: WC.paper, border, - borderRadius: 15, - boxShadow: '0 30px 80px -24px rgba(33,30,27,0.34), 0 8px 24px -12px rgba(33,30,27,0.18)', + borderRadius: WC.radius.lg, + boxShadow: (isDragging || isResizing) ? WC.shadow.lg : WC.shadow.md, overflow: 'hidden', display: 'flex', flexDirection: 'column', @@ -218,11 +220,8 @@ const WorkflowsAppCard: React.FC = ({ style={{ height: 42, flex: 'none', display: 'flex', alignItems: 'center', padding: '0 16px', borderBottom: `1px solid ${WC.line}`, background: WC.panel, gap: 14, cursor: isDragging ? 'grabbing' : 'grab', touchAction: 'none', userSelect: 'none' }} >
- - - - - Workflows + + Workflows
; + +export const WC_LIGHT: WCColors = { accent: '#C25A36', - paper: '#FBFAF7', - panel: '#F4F2EC', - rail: '#F4F2EC', - inset: '#F0EEE7', ink: '#211E1B', ink2: '#2B2722', ink3: '#4B463E', - muted: '#8C857A', - muted2: '#A39C92', - faint: '#B5AEA3', + ink4: '#6B655C', + muted: '#73726C', + muted2: '#8C857A', + faint: '#A39C92', + inkRGB: '33,30,27', line: 'rgba(33,30,27,0.07)', line2: 'rgba(33,30,27,0.12)', hover: 'rgba(33,30,27,0.045)', @@ -24,7 +64,53 @@ export const WC = { danger: '#C2483A', dangerBg: 'rgba(194,72,58,0.10)', warn: '#B98A2E', -} as const; + warnBg: 'rgba(185,138,46,0.14)', + trackOff: '#D5D1C8', +}; + +export const WC_DARK: WCColors = { + accent: '#C25A36', + ink: '#F2EFE9', + ink2: '#E2DDD4', + ink3: '#B3ABA0', + ink4: '#9A9389', + muted: '#938C82', + muted2: '#736D64', + faint: '#6E6860', + inkRGB: '240,236,228', + line: 'rgba(240,236,228,0.07)', + line2: 'rgba(240,236,228,0.12)', + hover: 'rgba(240,236,228,0.045)', + selBg: 'rgba(240,236,228,0.06)', + success: '#2E7D5B', + successBg: 'rgba(46,125,91,0.22)', + danger: '#C2483A', + dangerBg: 'rgba(194,72,58,0.18)', + warn: '#B98A2E', + warnBg: 'rgba(185,138,46,0.18)', + trackOff: 'rgba(240,236,228,0.18)', +}; + +export function useWC(): WCPalette { + const { mode } = useThemeMode(); + const c = useClaudeTokens(); + const base = mode === 'dark' ? WC_DARK : WC_LIGHT; + return { + ...base, + // Surfaces straight from the app tokens, so the window is the same material + // as every other card in both themes (the warmth lives in accent + text). + // Title bar + sidebar stay on bg.surface (not a darker fill) so the whole + // window reads as one card like the chat card; borders separate the regions. + paper: c.bg.surface, + panel: c.bg.surface, + rail: c.bg.surface, + inset: c.bg.page, + raised: c.bg.elevated, + shadow: c.shadow, + radius: c.radius, + border: c.border, + }; +} export const FONT_SERIF = "'Newsreader', Georgia, serif"; export const FONT_SANS = "'Hanken Grotesk', system-ui, sans-serif"; @@ -44,16 +130,22 @@ export function colorForId(id: string): string { return WORKFLOW_PALETTE[h % WORKFLOW_PALETTE.length]; } +// Prefer the user's chosen swatch; fall back to the stable id-hash hue when +// they haven't picked one. Single source of truth for every dot/bar. +export function colorForWorkflow(wf: { id: string; color?: string | null }): string { + return wf.color || colorForId(wf.id); +} + export type RunStatus = 'success' | 'failure' | 'ran_late' | 'running' | 'skipped' | 'paused'; -export function statusChip(status: RunStatus): CSSProperties { +export function statusChip(status: RunStatus, wc: WCPalette): CSSProperties { const map: Record = { - success: [WC.success, WC.successBg], - ran_late: [WC.warn, 'rgba(185,138,46,0.14)'], - failure: [WC.danger, 'rgba(194,72,58,0.12)'], - skipped: [WC.muted, 'rgba(33,30,27,0.07)'], - running: [WC.accent, 'rgba(0,0,0,0.04)'], - paused: [WC.muted, 'rgba(33,30,27,0.07)'], + success: [wc.success, wc.successBg], + ran_late: [wc.warn, wc.warnBg], + failure: [wc.danger, wc.dangerBg], + skipped: [wc.muted, `rgba(${wc.inkRGB},0.07)`], + running: [wc.accent, `rgba(${wc.inkRGB},0.06)`], + paused: [wc.muted, `rgba(${wc.inkRGB},0.07)`], }; const [color, background] = map[status] || map.paused; return { @@ -62,17 +154,17 @@ export function statusChip(status: RunStatus): CSSProperties { }; } -export function statusDot(status: RunStatus): CSSProperties { +export function statusDot(status: RunStatus, wc: WCPalette): CSSProperties { const map: Record = { - success: WC.success, ran_late: WC.warn, failure: WC.danger, - running: WC.accent, skipped: WC.faint, paused: WC.faint, + success: wc.success, ran_late: wc.warn, failure: wc.danger, + running: wc.accent, skipped: wc.faint, paused: wc.faint, }; - return { width: 8, height: 8, borderRadius: '50%', background: map[status] || WC.faint, flex: 'none' }; + return { width: 8, height: 8, borderRadius: '50%', background: map[status] || wc.faint, flex: 'none' }; } -export function track(on: boolean): CSSProperties { +export function track(on: boolean, wc: WCPalette): CSSProperties { return { - width: 34, height: 20, borderRadius: 999, background: on ? WC.accent : '#D5D1C8', + width: 34, height: 20, borderRadius: 999, background: on ? wc.accent : wc.trackOff, position: 'relative', cursor: 'pointer', transition: 'background .15s', flex: 'none', }; }