From b883ac557b7e72522ec0c51aa072a215d7d14485 Mon Sep 17 00:00:00 2001 From: Taylor Ho Date: Wed, 12 Aug 2026 01:20:27 -0700 Subject: [PATCH] Redesign workflow editor Signed-off-by: Taylor Ho --- .../features/workflows/ui/WorkflowDialog.tsx | 20 +- .../workflows/ui/WorkflowFormBuilder.tsx | 575 ++++++++++++------ .../workflows/ui/WorkflowStepCard.tsx | 46 +- desktop/tests/e2e/navigation.spec.ts | 1 + desktop/tests/e2e/workflows.spec.ts | 32 + 5 files changed, 467 insertions(+), 207 deletions(-) diff --git a/desktop/src/features/workflows/ui/WorkflowDialog.tsx b/desktop/src/features/workflows/ui/WorkflowDialog.tsx index dc6793792..89e13c1d7 100644 --- a/desktop/src/features/workflows/ui/WorkflowDialog.tsx +++ b/desktop/src/features/workflows/ui/WorkflowDialog.tsx @@ -153,8 +153,8 @@ export function WorkflowDialog({ return ( <> - - + + {TITLES[mode]} {mode === "edit" @@ -165,7 +165,7 @@ export function WorkflowDialog({ -
+
- - {mutation.error instanceof Error ? ( -

- {mutation.error.message} -

- ) : null}
-
+ {mutation.error instanceof Error ? ( +

+ {mutation.error.message} +

+ ) : null} + +
-function FlowNode({ - children, - marker, -}: { - children: React.ReactNode; - marker: React.ReactNode; -}) { - return ( -
  • -
    - {marker} -
    -
    {children}
    + + + + + + + + Add action + + {ACTION_TYPES.map((action) => ( + onAddAfter(action)} + > + {ACTION_LABELS[action]} + + ))} + + + {connectsToNext ? ( + + ) : null} +
  • ); } @@ -191,6 +280,8 @@ export function WorkflowFormBuilder({ ? initialParseRef.current.error : null, ); + const [selectedNode, setSelectedNode] = React.useState(null); + const shouldReduceMotion = useReducedMotion(); const updateFormState = React.useCallback( (next: WorkflowFormState) => { @@ -207,6 +298,7 @@ export function WorkflowFormBuilder({ if (nextMode === "yaml") { setMode("yaml"); setParseError(null); + setSelectedNode(null); return; } @@ -224,15 +316,25 @@ export function WorkflowFormBuilder({ [mode, yaml], ); - const addStep = React.useCallback(() => { - updateFormState({ - ...formState, - steps: [ - ...formState.steps, - { id: nextStepId(formState.steps), action: "delay" }, - ], - }); - }, [formState, updateFormState]); + const insertStep = React.useCallback( + (index: number, action: ActionType) => { + const nextSteps = [...formState.steps]; + const newStep: StepFormState = { + id: nextStepId(formState.steps), + action, + }; + if (action === "call_webhook") { + newStep.method = "POST"; + } + nextSteps.splice(index, 0, newStep); + updateFormState({ + ...formState, + steps: nextSteps, + }); + setSelectedNode({ type: "step", index }); + }, + [formState, updateFormState], + ); const removeStep = React.useCallback( (index: number) => { @@ -240,6 +342,14 @@ export function WorkflowFormBuilder({ ...formState, steps: formState.steps.filter((_, i) => i !== index), }); + setSelectedNode((current) => { + if (current?.type !== "step") return current; + if (current.index === index) return null; + if (current.index > index) { + return { type: "step", index: current.index - 1 }; + } + return current; + }); }, [formState, updateFormState], ); @@ -253,9 +363,17 @@ export function WorkflowFormBuilder({ [formState, updateFormState], ); + const selectedStep = + selectedNode?.type === "step" + ? formState.steps[selectedNode.index] + : undefined; + return ( -
    -
    +
    +
    +

    + Build the sequence, then select a node to configure it. +

    {parseError ? ( -

    +

    Cannot switch to form view: {parseError}

    ) : null} {mode === "yaml" ? ( -
    - {scopeField} -
    +
    +
    {scopeField}
    +