diff --git a/frontend/src/app/pages/Workflows/GeneralFacet.tsx b/frontend/src/app/pages/Workflows/GeneralFacet.tsx
index 72a4e038..d1fbee8f 100644
--- a/frontend/src/app/pages/Workflows/GeneralFacet.tsx
+++ b/frontend/src/app/pages/Workflows/GeneralFacet.tsx
@@ -1,9 +1,12 @@
import React from 'react';
import Box from '@mui/material/Box';
+import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography';
import InputBase from '@mui/material/InputBase';
import Select from '@mui/material/Select';
import MenuItem from '@mui/material/MenuItem';
+import Tooltip from '@mui/material/Tooltip';
+import DeleteOutlineRounded from '@mui/icons-material/DeleteOutlineRounded';
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import { useAppDispatch } from '@/shared/hooks';
@@ -124,6 +127,33 @@ export default function GeneralFacet({ draft, setDraft }: { draft: Workflow; set
}}
sx={{ flex: 1, fontSize: INPUT_FS, color: c.text.primary, border: `1px solid ${c.border.subtle}`, borderRadius: `${c.radius.md}px`, px: 1.25, py: 0.6, lineHeight: 1.4 }}
/>
+ 1 ? 'Remove step' : 'Workflow needs at least one step'}>
+
+ {
+ if (draft.steps.length <= 1) return;
+ setDraft({ ...draft, steps: draft.steps.filter((_, i) => i !== idx) });
+ }}
+ sx={{
+ width: 26,
+ height: 26,
+ p: 0,
+ mt: 0.3,
+ color: c.text.muted,
+ flexShrink: 0,
+ '&:hover': {
+ color: c.status.error,
+ bgcolor: c.status.errorBg,
+ },
+ }}
+ >
+
+
+
+
))}
diff --git a/frontend/src/app/pages/Workflows/StepList.tsx b/frontend/src/app/pages/Workflows/StepList.tsx
index 7d3338e6..9e8caf24 100644
--- a/frontend/src/app/pages/Workflows/StepList.tsx
+++ b/frontend/src/app/pages/Workflows/StepList.tsx
@@ -11,8 +11,11 @@
import React from 'react';
import Box from '@mui/material/Box';
+import IconButton from '@mui/material/IconButton';
import TextareaAutosize from '@mui/material/TextareaAutosize';
+import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography';
+import DeleteOutlineRounded from '@mui/icons-material/DeleteOutlineRounded';
import KeyboardArrowDownRounded from '@mui/icons-material/KeyboardArrowDownRounded';
import CheckRounded from '@mui/icons-material/CheckRounded';
import CloseRounded from '@mui/icons-material/CloseRounded';
@@ -29,6 +32,7 @@ interface Props {
framed?: boolean;
// Edit mode
onChangeStep?: (idx: number, text: string) => void;
+ onDeleteStep?: (idx: number, id: string) => void;
// Expand mode
expandable?: boolean;
expandedIds?: string[];
@@ -46,7 +50,7 @@ const CONNECTOR_X = CIRCLE_SIZE / 2;
export default function StepList(props: Props) {
const {
- steps, framed, onChangeStep,
+ steps, framed, onChangeStep, onDeleteStep,
expandable, expandedIds, onToggleExpand,
stepStatuses, activeStepSubtitle, activeStepDuration,
maxVisible = 4,
@@ -86,6 +90,42 @@ export default function StepList(props: Props) {
const label = (s.label || '').trim() || firstWords(s.text, 6);
const rawBody = (s.text || '').trim();
const hasExpandableBody = Boolean(expandable && rawBody);
+ const canDelete = Boolean(onDeleteStep && steps.length > 1);
+ const deleteButton = onDeleteStep ? (
+
+ e.stopPropagation()}
+ onPointerDown={(e) => e.stopPropagation()}
+ style={{ display: 'inline-flex' }}
+ >
+ {
+ e.stopPropagation();
+ if (canDelete) onDeleteStep(idx, s.id);
+ }}
+ onPointerDown={(e) => e.stopPropagation()}
+ sx={{
+ width: 24,
+ height: 24,
+ p: 0,
+ color: c.text.muted,
+ flexShrink: 0,
+ opacity: canDelete ? 0.72 : 0.35,
+ '&:hover': {
+ color: c.status.error,
+ bgcolor: c.status.errorBg,
+ opacity: 1,
+ },
+ }}
+ >
+
+
+
+
+ ) : null;
return (
@@ -151,6 +191,7 @@ export default function StepList(props: Props) {
{activeStepDuration}
)}
+ {deleteButton}
{hasExpandableBody && !isActive && (
{rawBody}
diff --git a/frontend/src/app/pages/Workflows/WorkflowCardSubviews.tsx b/frontend/src/app/pages/Workflows/WorkflowCardSubviews.tsx
index 457d3dfb..b4aee099 100644
--- a/frontend/src/app/pages/Workflows/WorkflowCardSubviews.tsx
+++ b/frontend/src/app/pages/Workflows/WorkflowCardSubviews.tsx
@@ -126,6 +126,21 @@ export function PreviewView({ workflowId, steps, sourceSessionId, initialDraft,
dispatch(toggleExpandedStep({ workflowId, stepId }));
}, [dispatch, workflowId]);
+ const onDeleteStep = useCallback((idx: number, stepId: string) => {
+ if (steps.length <= 1) return;
+ const nextSteps = steps.filter((_, i) => i !== idx);
+ dispatch(updateWorkflowCard({
+ workflowId,
+ patch: {
+ draft: {
+ ...liveDraft,
+ steps: nextSteps,
+ },
+ expandedStepIds: (card?.expandedStepIds || []).filter((id) => id !== stepId),
+ },
+ }));
+ }, [card?.expandedStepIds, dispatch, liveDraft, steps, workflowId]);
+
const onChangeDescription = useCallback((value: string) => {
dispatch(updateWorkflowCard({ workflowId, patch: { draft: { ...liveDraft, description: value } } }));
}, [dispatch, workflowId, liveDraft]);
@@ -171,7 +186,7 @@ export function PreviewView({ workflowId, steps, sourceSessionId, initialDraft,
void onChangeDescription;
return (
-
+
{/* Schedule prompt card. Soft accent tint + calendar icon. Accent is the
same color the human-intervention (AskUserQuestion) popup uses. */}
@@ -267,6 +282,7 @@ export function SavedView({ workflow, steps, runs, activeRunId }: { workflow: Wo
void runs; void activeRunId;
const card = useAppSelector((s) => s.workflows.openCards[workflow.id]);
const expandedIds = card?.expandedStepIds || [];
+ const [deletingStepId, setDeletingStepId] = useState(null);
const openEditAgent = useCallback(() => {
dispatch(updateWorkflowCard({ workflowId: workflow.id, patch: { view: 'edit_agent' } }));
}, [dispatch, workflow.id]);
@@ -276,6 +292,19 @@ export function SavedView({ workflow, steps, runs, activeRunId }: { workflow: Wo
const onToggleStep = useCallback((stepId: string) => {
dispatch(toggleExpandedStep({ workflowId: workflow.id, stepId }));
}, [dispatch, workflow.id]);
+ const onDeleteStep = useCallback(async (idx: number, stepId: string) => {
+ if (workflow.steps.length <= 1 || deletingStepId) return;
+ setDeletingStepId(stepId);
+ try {
+ await dispatch(updateWorkflow({
+ id: workflow.id,
+ patch: { steps: workflow.steps.filter((_, i) => i !== idx) },
+ ifMatch: workflow.updated_at || null,
+ }));
+ } finally {
+ setDeletingStepId(null);
+ }
+ }, [deletingStepId, dispatch, workflow.id, workflow.steps, workflow.updated_at]);
const scheduleLine = workflow.schedule.enabled ? describeSchedule(workflow) : 'Schedule this workflow';
const scheduleClickable = !workflow.schedule.enabled;
@@ -288,6 +317,7 @@ export function SavedView({ workflow, steps, runs, activeRunId }: { workflow: Wo
expandable
expandedIds={expandedIds}
onToggleExpand={onToggleStep}
+ onDeleteStep={onDeleteStep}
/>