From 90a98739f6fbf667bf9878face53b2c2e6d1209c Mon Sep 17 00:00:00 2001 From: abccodes Date: Fri, 19 Jun 2026 18:20:21 -0700 Subject: [PATCH] [aidan] refactor/workflow-cards: use radius and status design tokens, polish card chrome --- .../src/app/pages/Workflows/EditAgentView.tsx | 2 +- .../app/pages/Workflows/SchedulePopover.tsx | 2 +- .../pages/Workflows/ScheduleThisPopover.tsx | 4 +- .../src/app/pages/Workflows/WorkflowCard.tsx | 30 ++++++------- .../pages/Workflows/WorkflowCardSubviews.tsx | 26 +++++------ .../app/pages/Workflows/WorkflowsHubCard.tsx | 45 ++++++++++--------- .../pages/Workflows/workflowEditCommon.tsx | 2 +- .../app/pages/Workflows/workflowVisuals.tsx | 18 ++++---- 8 files changed, 63 insertions(+), 66 deletions(-) diff --git a/frontend/src/app/pages/Workflows/EditAgentView.tsx b/frontend/src/app/pages/Workflows/EditAgentView.tsx index b3bfce8e..4148baf5 100644 --- a/frontend/src/app/pages/Workflows/EditAgentView.tsx +++ b/frontend/src/app/pages/Workflows/EditAgentView.tsx @@ -213,7 +213,7 @@ export default function EditAgentView({ workflow, steps, isFixMode = false, onEd title={canSave ? undefined : 'Add at least one step before saving'} sx={{ fontSize: '0.8rem', fontWeight: 700, color: '#fff', bgcolor: c.accent.primary, - px: 1.2, py: 0.35, borderRadius: 999, cursor: canSave ? 'pointer' : 'not-allowed', + px: 1.2, py: 0.35, borderRadius: c.radius.full, cursor: canSave ? 'pointer' : 'not-allowed', opacity: canSave ? 1 : 0.45, '&:hover': { filter: 'brightness(1.05)' }, }}> diff --git a/frontend/src/app/pages/Workflows/SchedulePopover.tsx b/frontend/src/app/pages/Workflows/SchedulePopover.tsx index b0cd2fa7..2183a931 100644 --- a/frontend/src/app/pages/Workflows/SchedulePopover.tsx +++ b/frontend/src/app/pages/Workflows/SchedulePopover.tsx @@ -139,7 +139,7 @@ export default function SchedulePopover({ initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} - transition={{ duration: 0.12, ease: 'easeOut' }} + transition={{ duration: 0.14, ease: 'easeOut' }} style={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column' }}> {mode === 'search' && ( diff --git a/frontend/src/app/pages/Workflows/ScheduleThisPopover.tsx b/frontend/src/app/pages/Workflows/ScheduleThisPopover.tsx index 81edead4..49fe6c71 100644 --- a/frontend/src/app/pages/Workflows/ScheduleThisPopover.tsx +++ b/frontend/src/app/pages/Workflows/ScheduleThisPopover.tsx @@ -179,8 +179,8 @@ export default function ScheduleThisPopover({ anchorEl, onClose, sessionId, sess display: 'flex', flexDirection: 'column', gap: 0.4, px: 1, py: 0.75, mb: 0.75, borderRadius: `${c.radius.md}px`, - bgcolor: c.status.warningBg || c.bg.elevated, - border: `1px solid ${(c.status.warning || c.text.muted) + '60'}`, + bgcolor: c.status.warningBg, + border: `1px solid ${c.status.warning + '60'}`, }}> This chat is already scheduled. diff --git a/frontend/src/app/pages/Workflows/WorkflowCard.tsx b/frontend/src/app/pages/Workflows/WorkflowCard.tsx index c255289c..6f63c3cc 100644 --- a/frontend/src/app/pages/Workflows/WorkflowCard.tsx +++ b/frontend/src/app/pages/Workflows/WorkflowCard.tsx @@ -472,7 +472,7 @@ const WorkflowCard: React.FC = ({ ? '2px solid #3b82f6' : isRunning ? `1px solid ${c.accent.primary}80` - : `1px solid ${c.border.subtle}`; + : `1px solid ${c.border.strong}`; const shadow = isHighlighted ? `0 0 0 3px ${c.accent.primary}50, 0 0 20px ${c.accent.primary}35, 0 0 40px ${c.accent.primary}15` @@ -480,7 +480,7 @@ const WorkflowCard: React.FC = ({ ? c.shadow.lg : isSelected ? `0 0 0 1px #3b82f6, ${c.shadow.md}` - : c.shadow.md; + : c.shadow.sm; return ( = ({ width: displayW, height: autoHeight ? 'auto' : displayH, maxHeight: autoHeight ? 'min(82vh, 760px)' : undefined, - borderRadius: '14px', + borderRadius: 3, border, bgcolor: c.bg.surface, boxShadow: shadow, @@ -541,7 +541,7 @@ const WorkflowCard: React.FC = ({ onPointerUp={handleDragPointerUp} sx={{ display: 'flex', alignItems: 'center', gap: 1, - px: 2, py: 1.4, + px: 2, pt: 2, pb: 1.4, cursor: isDragging ? 'grabbing' : 'grab', touchAction: 'none', userSelect: 'none', flexShrink: 0, @@ -549,7 +549,7 @@ const WorkflowCard: React.FC = ({ position: 'relative', }} > - + {isDraft ? ( // Draft state: title is inline-editable. Patches the openCard's // draft.title so PreviewView picks it up on Save. Saved cards @@ -610,9 +610,9 @@ const WorkflowCard: React.FC = ({ data-no-drag onClick={(e) => { e.stopPropagation(); onClose(); }} onPointerDown={(e) => e.stopPropagation()} - sx={{ p: 0.5, color: c.text.secondary, '&:hover': { color: c.status.error, bgcolor: c.status.errorBg } }} + sx={{ p: 0.5, color: c.text.ghost, '&:hover': { color: c.status.error, bgcolor: c.status.errorBg } }} > - + @@ -633,7 +633,6 @@ const WorkflowCard: React.FC = ({ workflow={null} runs={null} fallbackModel={card?.draft?.model} - fallbackMode={card?.draft?.mode} fallbackSourceSessionId={card?.draft?.source_session_id} /> @@ -842,7 +841,7 @@ const WorkflowCard: React.FC = ({ fallbackSourceSessionId ? s.agents.sessions[fallbackSourceSessionId] : undefined); - // Match Image #34/#35/#36/#38/#40: "Claude Opus 4.6 agent 28s". - // Spaces between fields, all in muted text. + // Reads "Claude Opus 4.6 28s": model then work time, spaces between, + // all muted. Mode is omitted to match the chat card's subtitle. const effModel = workflow?.model || fallbackModel || ''; const modelLabel = React.useMemo(() => { if (!effModel) return ''; @@ -969,7 +967,6 @@ function SubtitleRow({ workflow, runs, fallbackModel, fallbackMode, fallbackSour } return effModel; }, [effModel, modelsByProvider]); - const modeLabel = workflow?.mode || fallbackMode || ''; const duration = React.useMemo(() => { const finished = (runs || []).find((r) => r.finished_at); if (finished && finished.finished_at) { @@ -989,7 +986,6 @@ function SubtitleRow({ workflow, runs, fallbackModel, fallbackMode, fallbackSour return ( {modelLabel && {modelLabel}} - {modeLabel && {modeLabel}} {duration && {duration}} ); @@ -1030,7 +1026,7 @@ function RunningHeader({ workflowId }: { workflowId: string }) { onClick={controlsDisabled ? undefined : onStop} role="button" aria-disabled={controlsDisabled} - sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.35, fontSize: '0.82rem', fontWeight: 600, px: 1, py: 0.4, color: c.text.secondary, cursor: controlsDisabled ? 'default' : 'pointer', borderRadius: 999, opacity: controlsDisabled && !stopPending ? 0.55 : 1, '&:hover': controlsDisabled ? {} : { color: c.text.primary, bgcolor: c.bg.elevated } }}> + sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.35, fontSize: '0.82rem', fontWeight: 600, px: 1, py: 0.4, color: c.text.secondary, cursor: controlsDisabled ? 'default' : 'pointer', borderRadius: c.radius.full, opacity: controlsDisabled && !stopPending ? 0.55 : 1, '&:hover': controlsDisabled ? {} : { color: c.text.primary, bgcolor: c.bg.elevated } }}> {stopPending ? : } Stop @@ -1042,7 +1038,7 @@ function RunningHeader({ workflowId }: { workflowId: string }) { sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.35, fontSize: '0.82rem', fontWeight: 700, - px: 1.1, py: 0.4, borderRadius: 999, + px: 1.1, py: 0.4, borderRadius: c.radius.full, bgcolor: c.accent.primary, color: '#fff', cursor: controlsDisabled ? 'default' : 'pointer', opacity: controlsDisabled && !pausePending ? 0.55 : 1, '&:hover': controlsDisabled ? {} : { filter: 'brightness(1.05)' }, diff --git a/frontend/src/app/pages/Workflows/WorkflowCardSubviews.tsx b/frontend/src/app/pages/Workflows/WorkflowCardSubviews.tsx index 2536678f..01ae2147 100644 --- a/frontend/src/app/pages/Workflows/WorkflowCardSubviews.tsx +++ b/frontend/src/app/pages/Workflows/WorkflowCardSubviews.tsx @@ -85,7 +85,7 @@ export function ActionBtn({ label, tone, disabled, onClick, icon }: { label: str display: 'inline-flex', alignItems: 'center', gap: 0.4, fontSize: '0.78rem', fontWeight: 600, px: 1, py: 0.35, - borderRadius: 999, + borderRadius: c.radius.full, cursor: disabled ? 'not-allowed' : 'pointer', color: palette.color, bgcolor: palette.bg, @@ -267,7 +267,7 @@ export function PreviewView({ workflowId, steps, sourceSessionId, initialDraft, sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.5, fontSize: '0.88rem', fontWeight: 700, - px: 1.75, py: 0.6, borderRadius: 999, + px: 1.75, py: 0.6, borderRadius: c.radius.full, color: '#fff', bgcolor: c.accent.primary, cursor: busy ? 'wait' : canSave ? 'pointer' : 'not-allowed', opacity: busy || !canSave ? 0.6 : 1, @@ -300,7 +300,7 @@ export function PreviewView({ workflowId, steps, sourceSessionId, initialDraft, role="button" onClick={canSave ? onSaveDraft : undefined} title={canSave ? undefined : 'Add at least one step before saving'} - sx={{ fontSize: '0.84rem', fontWeight: 700, color: '#fff', bgcolor: c.accent.primary, borderRadius: 999, cursor: busy ? 'wait' : canSave ? 'pointer' : 'not-allowed', px: 1.5, py: 0.6, opacity: busy || !canSave ? 0.6 : 1, '&:hover': { filter: 'brightness(1.06)' } }}> + sx={{ fontSize: '0.84rem', fontWeight: 700, color: '#fff', bgcolor: c.accent.primary, borderRadius: c.radius.full, cursor: busy ? 'wait' : canSave ? 'pointer' : 'not-allowed', px: 1.5, py: 0.6, opacity: busy || !canSave ? 0.6 : 1, '&:hover': { filter: 'brightness(1.06)' } }}> Save @@ -466,7 +466,7 @@ export function SavedView({ workflow, steps, runs, activeRunId }: { workflow: Wo sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.5, fontSize: '0.88rem', fontWeight: 700, - px: 1.75, py: 0.6, borderRadius: 999, + px: 1.75, py: 0.6, borderRadius: c.radius.full, color: '#fff', bgcolor: c.accent.primary, cursor: 'pointer', '&:hover': { bgcolor: c.accent.primary, filter: 'brightness(1.06)' }, @@ -499,12 +499,12 @@ export function SavedView({ workflow, steps, runs, activeRunId }: { workflow: Wo display: 'inline-flex', alignItems: 'center', gap: 0.45, fontSize: '0.82rem', fontWeight: 600, px: 1.25, py: 0.5, - borderRadius: 999, + borderRadius: c.radius.full, cursor: 'pointer', color: c.text.secondary, bgcolor: 'transparent', border: `1px solid ${c.border.medium}`, - '&:hover': { bgcolor: c.bg.elevated, borderColor: c.border.strong || c.border.medium, color: c.text.primary }, + '&:hover': { bgcolor: c.bg.elevated, borderColor: c.border.strong, color: c.text.primary }, }}> Edit @@ -596,7 +596,7 @@ function AuditTraceLink({ workflowId }: { workflowId: string }) { @@ -712,7 +712,7 @@ export function HistoryList({ runs, onOpen, showWorkflow = false, workflowTitleF color: filter === k ? c.accent.primary : c.text.muted, bgcolor: filter === k ? c.accent.primary + '14' : 'transparent', border: `1px solid ${filter === k ? c.accent.primary + '40' : c.border.subtle}`, - px: 0.7, py: 0.2, borderRadius: 999, cursor: 'pointer', + px: 0.75, py: 0.3, borderRadius: c.radius.full, cursor: 'pointer', '&:hover': { color: c.accent.primary }, }}> {k === 'all' ? 'All' : k === 'failure' ? 'Failures only' : 'Ran late only'} @@ -733,8 +733,8 @@ export function HistoryList({ runs, onOpen, showWorkflow = false, workflowTitleF setExpandedId(expanded ? null : r.id)} - sx={{ display: 'flex', alignItems: 'center', gap: 1.25, py: 0.6, px: 0.5, cursor: 'pointer', borderRadius: 0.75, '&:hover': { bgcolor: c.bg.elevated } }}> - + sx={{ display: 'flex', alignItems: 'center', gap: 1.25, py: 0.6, px: 0.5, cursor: 'pointer', borderRadius: c.radius.sm, '&:hover': { bgcolor: c.bg.elevated } }}> + {labelForStatus(r.status)} {showWorkflow && workflowTitleFor ? ( @@ -753,7 +753,7 @@ export function HistoryList({ runs, onOpen, showWorkflow = false, workflowTitleF {expanded && ( - + {r.error ? ( {r.error} ) : ( @@ -784,11 +784,11 @@ export function HistoryDetail({ run, onBack }: { run: WorkflowRun | null; onBack ← back - {labelForStatus(run.status)} + {labelForStatus(run.status)} {formatRunDate(run.started_at)} {run.error && ( - {run.error} + {run.error} )} Started {formatRunDate(run.started_at)}, finished {run.finished_at ? formatRunDate(run.finished_at) : 'in progress'}. {run.session_id && ( diff --git a/frontend/src/app/pages/Workflows/WorkflowsHubCard.tsx b/frontend/src/app/pages/Workflows/WorkflowsHubCard.tsx index 853f4043..e2bef8dd 100644 --- a/frontend/src/app/pages/Workflows/WorkflowsHubCard.tsx +++ b/frontend/src/app/pages/Workflows/WorkflowsHubCard.tsx @@ -3,6 +3,7 @@ import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; import IconButton from '@mui/material/IconButton'; import InputBase from '@mui/material/InputBase'; +import Fade from '@mui/material/Fade'; import CloseIcon from '@mui/icons-material/Close'; import AddIcon from '@mui/icons-material/Add'; import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; @@ -108,14 +109,14 @@ function TimeSavedBadge() { - + {count} · {timeLabel} back @@ -344,14 +345,14 @@ const WorkflowsHubCard: React.FC = ({ ? `2px solid ${c.accent.primary}` : isSelected ? '2px solid #3b82f6' - : `1px solid ${c.border.medium}`; + : `1px solid ${c.border.strong}`; const shadow = isHighlighted ? `0 0 0 3px ${c.accent.primary}50, 0 0 20px ${c.accent.primary}35, 0 0 40px ${c.accent.primary}15` : (isDragging || isResizing) ? c.shadow.lg : isSelected ? `0 0 0 1px #3b82f6, ${c.shadow.md}` - : c.shadow.md; + : c.shadow.sm; const noTransition = isDragging || isResizing || (isSelected && !!multiDragDelta); return ( @@ -380,7 +381,7 @@ const WorkflowsHubCard: React.FC = ({ height: dh, bgcolor: c.bg.surface, border, - borderRadius: `${c.radius.lg}px`, + borderRadius: 3, boxShadow: shadow, display: 'flex', flexDirection: 'column', @@ -408,15 +409,15 @@ const WorkflowsHubCard: React.FC = ({ points right, matching the Workflows brand mark. */} - Workflows + Workflows { e.stopPropagation(); dispatch(closeWorkflowsHub()); }} onPointerDown={(e) => e.stopPropagation()} - sx={{ p: 0.35, color: c.text.ghost, '&:hover': { color: c.status.error, bgcolor: c.status.errorBg } }} + sx={{ p: 0.5, color: c.text.ghost, '&:hover': { color: c.status.error, bgcolor: c.status.errorBg } }} > - + @@ -433,9 +434,9 @@ const WorkflowsHubCard: React.FC = ({ data-no-drag sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.4, - fontSize: '0.85rem', fontWeight: 600, color: c.text.primary, + fontSize: '0.82rem', fontWeight: 600, color: c.text.primary, bgcolor: c.bg.elevated, border: `1px solid ${c.border.subtle}`, - px: 1, py: 0.4, borderRadius: `${c.radius.md}px`, cursor: 'pointer', + px: 1, py: 0.35, borderRadius: `${c.radius.md}px`, cursor: 'pointer', '&:hover': { borderColor: c.accent.primary, color: c.accent.primary }, }} > @@ -448,11 +449,11 @@ const WorkflowsHubCard: React.FC = ({ data-no-drag sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.4, ml: 0.5, - fontSize: '0.8rem', fontWeight: 600, - color: paused ? c.status.warning || c.accent.primary : c.text.secondary, - bgcolor: paused ? (c.status.warningBg || c.bg.elevated) : 'transparent', - border: `1px solid ${paused ? (c.status.warning || c.accent.primary) + '60' : c.border.subtle}`, - px: 0.85, py: 0.3, borderRadius: `${c.radius.md}px`, cursor: 'pointer', + fontSize: '0.82rem', fontWeight: 600, + color: c.text.secondary, + bgcolor: paused ? c.bg.elevated : 'transparent', + border: `1px solid ${paused ? c.border.medium : c.border.subtle}`, + px: 1, py: 0.35, borderRadius: `${c.radius.md}px`, cursor: 'pointer', '&:hover': { color: c.text.primary, borderColor: c.border.medium }, }}> @@ -468,7 +469,7 @@ const WorkflowsHubCard: React.FC = ({ sx={{ fontSize: '0.82rem', fontWeight: 500, color: c.text.secondary, border: `1px solid ${c.border.subtle}`, - px: 1.1, py: 0.35, borderRadius: `${c.radius.md}px`, cursor: 'pointer', + px: 1, py: 0.35, borderRadius: `${c.radius.md}px`, cursor: 'pointer', '&:hover': { color: c.text.primary, borderColor: c.border.medium }, }}>Today setRefDate(addDays(refDate, view === 'Month' ? -28 : -7))} sx={{ p: 0.3 }}> @@ -495,7 +496,7 @@ const WorkflowsHubCard: React.FC = ({ {view} - {viewOpen && ( + {(['List', 'Week', 'Month'] as const).map((v) => ( = ({ ))} - )} + @@ -686,7 +687,7 @@ function SidebarSection({ title, items, onPick, scheduled, onContext, onSchedule toggleEnabled(w, e)} sx={{ - width: 14, height: 14, borderRadius: '3px', flexShrink: 0, + width: 14, height: 14, borderRadius: c.radius.sm, flexShrink: 0, border: `1.5px solid ${w.schedule.enabled ? c.accent.primary : c.border.medium}`, bgcolor: w.schedule.enabled ? c.accent.primary : 'transparent', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', @@ -702,7 +703,7 @@ function SidebarSection({ title, items, onPick, scheduled, onContext, onSchedule { e.stopPropagation(); onSchedule?.(w, e.currentTarget); }} sx={{ - width: 16, height: 16, borderRadius: '4px', flexShrink: 0, + width: 16, height: 16, borderRadius: c.radius.sm, flexShrink: 0, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: c.text.muted, cursor: 'pointer', '&:hover': { color: c.accent.primary, bgcolor: c.bg.elevated }, @@ -720,7 +721,7 @@ function SidebarSection({ title, items, onPick, scheduled, onContext, onSchedule )} {scheduled && (!w.schedule.enabled || allPaused) && ( - Paused + Paused )} ))} diff --git a/frontend/src/app/pages/Workflows/workflowEditCommon.tsx b/frontend/src/app/pages/Workflows/workflowEditCommon.tsx index 285e8c04..41404094 100644 --- a/frontend/src/app/pages/Workflows/workflowEditCommon.tsx +++ b/frontend/src/app/pages/Workflows/workflowEditCommon.tsx @@ -36,7 +36,7 @@ export function ActionBtn({ label, tone, disabled, onClick, icon }: { label: str sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.45, fontSize: LABEL_FS, fontWeight: 600, px: 1.25, py: 0.5, - borderRadius: 999, + borderRadius: c.radius.full, cursor: disabled ? 'not-allowed' : 'pointer', color: palette.color, bgcolor: palette.bg, diff --git a/frontend/src/app/pages/Workflows/workflowVisuals.tsx b/frontend/src/app/pages/Workflows/workflowVisuals.tsx index acfcbeeb..27871ae9 100644 --- a/frontend/src/app/pages/Workflows/workflowVisuals.tsx +++ b/frontend/src/app/pages/Workflows/workflowVisuals.tsx @@ -48,7 +48,7 @@ export type LastRunStatus = NonNullable; export function statusDotColor(status: LastRunStatus | null | undefined, c: ReturnType) { switch (status) { case 'success': return c.status.success; - case 'ran_late': return c.status.warning || '#f59e0b'; + case 'ran_late': return c.status.warning; case 'failure': return c.status.error; case 'running': return c.accent.primary; case 'skipped': return c.text.muted; @@ -75,7 +75,7 @@ export function StatusDot({ status }: { status: LastRunStatus | null | undefined {tiers.map((t, i) => ( @@ -204,7 +204,7 @@ export function ScheduleChip({ workflow }: { workflow: Workflow }) { color: enabled ? c.accent.primary : c.text.muted, bgcolor: enabled ? c.accent.primary + '14' : c.bg.elevated, border: `1px solid ${enabled ? c.accent.primary + '40' : c.border.subtle}`, - px: 0.85, py: 0.3, borderRadius: 999, + px: 0.75, py: 0.3, borderRadius: c.radius.full, cursor: enabled ? 'pointer' : 'default', '&:hover': enabled ? { bgcolor: c.accent.primary + '22' } : undefined, }}> @@ -354,7 +354,7 @@ function chipSx(c: ReturnType) { color: c.text.secondary, bgcolor: c.bg.elevated, border: `1px solid ${c.border.subtle}`, - px: 0.75, py: 0.3, borderRadius: 999, + px: 0.75, py: 0.3, borderRadius: c.radius.full, } as const; } @@ -433,10 +433,10 @@ export function StreakBadge({ runs }: { runs: WorkflowRun[] | undefined }) { 🔥 {n}