diff --git a/frontend/src/app/pages/Workflows/ScheduleCalendar.tsx b/frontend/src/app/pages/Workflows/ScheduleCalendar.tsx index dcfeb12b..5a220063 100644 --- a/frontend/src/app/pages/Workflows/ScheduleCalendar.tsx +++ b/frontend/src/app/pages/Workflows/ScheduleCalendar.tsx @@ -34,6 +34,7 @@ export default function ScheduleCalendar({ view, density, onSelectWorkflow, refD const c = useClaudeTokens(); const dispatch = useAppDispatch(); const workflows = useAppSelector((s) => Object.values(s.workflows.items)); + const allPaused = useAppSelector((s) => s.workflows.paused); // Right-click menu: pinned position + the workflow whose pill was // clicked. Same anchor pattern as MUI's menu examples. const [ctxMenu, setCtxMenu] = useState<{ x: number; y: number; workflow: Workflow } | null>(null); @@ -236,6 +237,7 @@ export default function ScheduleCalendar({ view, density, onSelectWorkflow, refD sx={{ height: SLOT_H, borderLeft: `1px solid ${c.border.subtle}`, borderTop: hourIdx === 0 ? 'none' : `1px solid ${c.border.subtle}`, position: 'relative' }}> { ev.preventDefault(); setCtxMenu({ x: ev.clientX, y: ev.clientY, workflow: wf }); }} @@ -374,8 +376,9 @@ export default function ScheduleCalendar({ view, density, onSelectWorkflow, refD // Apple Calendar style event chip: 3px colored left-bar + faintly-tinted // background + readable text. One chip per cell with a "+N" badge for // overflow; clicking it opens a popover listing all events that hour. -function EventStack({ events, onSelectWorkflow, eventFontSize, onContextWorkflow }: { +function EventStack({ events, paused, onSelectWorkflow, eventFontSize, onContextWorkflow }: { events: { workflow: Workflow; date: Date }[]; + paused?: boolean; onSelectWorkflow?: (id: string) => void; eventFontSize: string; onContextWorkflow?: (workflow: Workflow, e: React.MouseEvent) => void; @@ -414,6 +417,7 @@ function EventStack({ events, onSelectWorkflow, eventFontSize, onContextWorkflow fontSize: eventFontSize, fontWeight: 500, overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 0.5, + opacity: paused ? 0.45 : 1, '&:hover': { bgcolor: accent + '22' }, }}> {first.workflow.title} diff --git a/frontend/src/app/pages/Workflows/WorkflowCard.tsx b/frontend/src/app/pages/Workflows/WorkflowCard.tsx index 5b03aca6..0c2c8c84 100644 --- a/frontend/src/app/pages/Workflows/WorkflowCard.tsx +++ b/frontend/src/app/pages/Workflows/WorkflowCard.tsx @@ -47,7 +47,7 @@ import { Typewriter } from '@/app/components/feedback/Animated'; import StopRounded from '@mui/icons-material/StopRounded'; import PauseRounded from '@mui/icons-material/PauseRounded'; import { StatusDot, RunSparkline, LastFiredHint, isStaleSinceLastRun, isRealTitle } from './workflowVisuals'; -import { stepsSignature } from './scheduleUtils'; +import { stepsSignature, isWorkflowSchedulable } from './scheduleUtils'; import { store } from '@/shared/state/store'; import { getAgentWorkTime, fmtSeconds } from '@/shared/agentWorkTime'; @@ -118,6 +118,7 @@ const WorkflowCard: React.FC = ({ const expandedSessionIds = useAppSelector((s) => s.agents.expandedSessionIds); const defaultModel = useAppSelector((s) => s.settings.data.default_model); const defaultMode = useAppSelector((s) => s.settings.data.default_mode); + const allPaused = useAppSelector((s) => s.workflows.paused); const cardBoxRef = useRef(null); useEffect(() => { @@ -591,6 +592,15 @@ const WorkflowCard: React.FC = ({ )} + {workflow && isWorkflowSchedulable(workflow) && (!workflow.schedule.enabled || allPaused) && ( + Paused + )} )} diff --git a/frontend/src/app/pages/Workflows/WorkflowCardSubviews.tsx b/frontend/src/app/pages/Workflows/WorkflowCardSubviews.tsx index 6ebc2d03..2536678f 100644 --- a/frontend/src/app/pages/Workflows/WorkflowCardSubviews.tsx +++ b/frontend/src/app/pages/Workflows/WorkflowCardSubviews.tsx @@ -694,20 +694,17 @@ export function HistoryList({ runs, onOpen, showWorkflow = false, workflowTitleF } return out; }, [filtered]); - // Header sparkline summarising recent successes/failures so users can - // see "lately broken" before scrolling. - const recent = (runs || []).slice(0, 30); if (!runs || runs.length === 0) { return No runs yet; } return ( - - {recent.map((r) => ( - - ))} - + {groups.length > 0 && ( + + {groups[0].key.toUpperCase()} + + )} {(['all', 'failure', 'ran_late'] as const).map((k) => ( setFilter(k)} role="button" sx={{ @@ -722,11 +719,13 @@ export function HistoryList({ runs, onOpen, showWorkflow = false, workflowTitleF ))} - {groups.map(({ key, runs: gRuns }) => ( + {groups.map(({ key, runs: gRuns }, gi) => ( - - {key.toUpperCase()} - + {gi > 0 && ( + + {key.toUpperCase()} + + )} {gRuns.map((r) => { const expanded = expandedId === r.id; const dur = runDuration(r); @@ -759,7 +758,7 @@ export function HistoryList({ runs, onOpen, showWorkflow = false, workflowTitleF {r.error} ) : ( - {r.session_id ? `Saved as session ${r.session_id.slice(0, 8)}.` : 'No session was recorded for this run.'} Click below to see the full conversation. + {r.session_id ? 'Click below to see the full conversation.' : 'No session was recorded for this run. Click below to see the full conversation.'} )} diff --git a/frontend/src/app/pages/Workflows/WorkflowsHubCard.tsx b/frontend/src/app/pages/Workflows/WorkflowsHubCard.tsx index 149599d7..afafcdc2 100644 --- a/frontend/src/app/pages/Workflows/WorkflowsHubCard.tsx +++ b/frontend/src/app/pages/Workflows/WorkflowsHubCard.tsx @@ -641,6 +641,7 @@ function SidebarSection({ title, items, onPick, scheduled, onContext, onSchedule }) { const c = useClaudeTokens(); const dispatch = useAppDispatch(); + const allPaused = useAppSelector((s) => s.workflows.paused); const [open, setOpen] = useState(true); const toggleEnabled = useCallback((wf: Workflow, e: React.MouseEvent) => { @@ -707,10 +708,10 @@ function SidebarSection({ title, items, onPick, scheduled, onContext, onSchedule never animates on first appearance or for already-named rows. */} {(t) => ( - {t} + {t} )} - {scheduled && !w.schedule.enabled && ( + {scheduled && (!w.schedule.enabled || allPaused) && ( Paused )}