feat(FN-3397): document add-step chooser flow in workflow-steps docs

Docs(FN-3397): Documented the add-step chooser flow in the workflow steps reference, adding 9 lines to `docs/workflow-steps.md`.

Fusion-Task-Id: FN-3397
This commit is contained in:
Fusion
2026-05-04 21:36:41 -07:00
committed by gsxdsm
parent 7f47bd7f84
commit 95ccf50397
4 changed files with 168 additions and 7 deletions

View File

@@ -228,6 +228,29 @@
gap: 4px;
}
/* Create chooser */
.wfm-create-chooser {
display: flex;
flex-direction: column;
gap: var(--space-md);
padding: var(--space-md);
border: 1px solid var(--border);
border-radius: var(--radius-md);
background: var(--bg-secondary);
}
.wfm-create-chooser-hint {
margin: 0;
color: var(--text-muted);
}
.wfm-create-custom-btn {
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-xs);
}
/* Edit/create form */
.wfm-form {
padding: var(--space-md);
@@ -490,6 +513,10 @@
width: 100%;
}
.wfm-create-custom-btn {
width: 100%;
}
.wfm-footer {
padding: 12px 14px;
}

View File

@@ -129,6 +129,7 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
const [activeTab, setActiveTab] = useState<TabId>("my-steps");
const [editingId, setEditingId] = useState<string | null>(null);
const [isCreating, setIsCreating] = useState(false);
const [showCreateChooser, setShowCreateChooser] = useState(false);
const [form, setForm] = useState<StepFormData>(EMPTY_FORM);
const [saving, setSaving] = useState(false);
const [refining, setRefining] = useState(false);
@@ -192,6 +193,14 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
}, [isOpen, loadSteps, loadTemplates, loadScripts, loadModels]);
const handleCreate = useCallback(() => {
setShowCreateChooser(true);
setIsCreating(false);
setEditingId(null);
setForm(EMPTY_FORM);
}, []);
const handleCreateCustom = useCallback(() => {
setShowCreateChooser(false);
setIsCreating(true);
setEditingId(null);
setForm(EMPTY_FORM);
@@ -217,6 +226,7 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
const handleCancel = useCallback(() => {
setEditingId(null);
setIsCreating(false);
setShowCreateChooser(false);
setForm(EMPTY_FORM);
}, []);
@@ -359,6 +369,7 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
await loadSteps();
// Switch to "My Workflow Steps" tab to show the newly added step
setActiveTab("my-steps");
setShowCreateChooser(false);
} catch (err) {
const msg = getErrorMessage(err);
if (msg?.includes("already exists")) {
@@ -428,7 +439,7 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
)}
{/* My Workflow Steps Tab */}
{activeTab === "my-steps" && !isEditing && (
{activeTab === "my-steps" && !isEditing && !showCreateChooser && (
<>
{steps.length === 0 && (
<div className="wfm-empty" data-testid="empty-state">
@@ -515,7 +526,7 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
)}
{/* Templates Tab */}
{activeTab === "templates" && !isEditing && (
{activeTab === "templates" && !isEditing && !showCreateChooser && (
<>
{templatesLoading ? (
<div className="wfm-loading">
@@ -588,6 +599,84 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
</>
)}
{/* Create chooser */}
{showCreateChooser && !isEditing && (
<div className="wfm-create-chooser" data-testid="workflow-step-create-chooser">
<h3 className="wfm-form-title">How would you like to create this workflow step?</h3>
<p className="wfm-create-chooser-hint">
Start from a built-in template or create a fully custom workflow step.
</p>
<button
className="btn wfm-create-custom-btn"
onClick={handleCreateCustom}
data-testid="create-custom-step"
>
<Plus size={14} />
Custom workflow step
</button>
{templatesLoading ? (
<div className="wfm-loading" data-testid="create-chooser-template-loading">
<Loader2 size={24} className="spin wfm-spinner" />
Loading built-in templates...
</div>
) : templates.length === 0 ? (
<div className="wfm-empty" data-testid="create-chooser-no-templates">
No built-in templates are available right now. You can still create a custom step.
</div>
) : (
<div className="wfm-template-list" data-testid="create-chooser-templates">
{templates.map((template) => {
const IconComponent = getTemplateIcon(template.icon);
const categoryClassName = getCategoryClassName(template.category);
const isAdding = addingTemplateId === template.id;
return (
<div
key={template.id}
className="wfm-template-card"
data-testid={`chooser-template-${template.id}`}
>
<div className="wfm-template-inner">
<div className="wfm-template-icon">
<IconComponent size={20} />
</div>
<div className="wfm-template-content">
<div className="wfm-template-title-row">
<span className="wfm-template-name">{template.name}</span>
<span className={categoryClassName}>{template.category}</span>
</div>
<div className="wfm-template-desc">{template.description}</div>
<button
className="btn btn-primary wfm-template-add-btn"
onClick={() => handleAddTemplate(template)}
disabled={isAdding}
data-testid={`chooser-add-template-${template.id}`}
>
{isAdding ? (
<>
<Loader2 size={12} className="spin" />
Adding...
</>
) : (
<>
<Plus size={12} />
Add template
</>
)}
</button>
</div>
</div>
</div>
);
})}
</div>
)}
</div>
)}
{/* Edit / Create form */}
{isEditing && (
<div className="wfm-form" data-testid="workflow-step-form">
@@ -813,7 +902,7 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
</div>
{/* Footer */}
{!isEditing && (
{!isEditing && !showCreateChooser && (
<div className="wfm-footer">
<button
className="btn btn-primary wfm-footer-add-btn"

View File

@@ -199,7 +199,7 @@ describe("WorkflowStepManager", () => {
});
});
it("opens create form when Add button is clicked", async () => {
it("shows create chooser first when Add button is clicked", async () => {
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([]);
render(<WorkflowStepManager isOpen={true} onClose={onClose} addToast={addToast} />);
@@ -210,9 +210,26 @@ describe("WorkflowStepManager", () => {
fireEvent.click(screen.getByTestId("add-workflow-step"));
expect(screen.getByTestId("workflow-step-form")).toBeInTheDocument();
expect(screen.getByTestId("workflow-step-name")).toBeInTheDocument();
expect(screen.getByTestId("workflow-step-description")).toBeInTheDocument();
expect(screen.getByTestId("workflow-step-create-chooser")).toBeInTheDocument();
expect(screen.getByTestId("create-custom-step")).toBeInTheDocument();
expect(screen.queryByTestId("workflow-step-form")).not.toBeInTheDocument();
});
it("creates a step from chooser template action", async () => {
vi.mocked(fetchWorkflowSteps).mockResolvedValue([]);
render(<WorkflowStepManager isOpen={true} onClose={onClose} addToast={addToast} />);
await screen.findByTestId("add-workflow-step");
fireEvent.click(screen.getByTestId("add-workflow-step"));
const chooserTemplate = await screen.findByTestId("chooser-template-browser-verification");
fireEvent.click(within(chooserTemplate).getByTestId("chooser-add-template-browser-verification"));
await waitFor(() => {
expect(createWorkflowStepFromTemplate).toHaveBeenCalledWith("browser-verification", undefined);
expect(addToast).toHaveBeenCalledWith("Added Browser Verification workflow step", "success");
});
});
it("submits new workflow step", async () => {
@@ -227,6 +244,7 @@ describe("WorkflowStepManager", () => {
});
fireEvent.click(screen.getByTestId("add-workflow-step"));
fireEvent.click(screen.getByTestId("create-custom-step"));
const nameInput = screen.getByTestId("workflow-step-name");
const descInput = screen.getByTestId("workflow-step-description");
@@ -399,6 +417,7 @@ describe("WorkflowStepManager", () => {
});
fireEvent.click(screen.getByTestId("add-workflow-step"));
fireEvent.click(screen.getByTestId("create-custom-step"));
expect(screen.getByTestId("workflow-step-mode-selector")).toBeInTheDocument();
expect(screen.getByTestId("mode-prompt")).toBeInTheDocument();
@@ -415,6 +434,7 @@ describe("WorkflowStepManager", () => {
});
fireEvent.click(screen.getByTestId("add-workflow-step"));
fireEvent.click(screen.getByTestId("create-custom-step"));
// Default is prompt mode — should show prompt field
expect(screen.getByTestId("workflow-step-prompt")).toBeInTheDocument();
@@ -461,6 +481,7 @@ describe("WorkflowStepManager", () => {
});
fireEvent.click(screen.getByTestId("add-workflow-step"));
fireEvent.click(screen.getByTestId("create-custom-step"));
const nameInput = screen.getByTestId("workflow-step-name");
const descInput = screen.getByTestId("workflow-step-description");
@@ -488,6 +509,7 @@ describe("WorkflowStepManager", () => {
});
fireEvent.click(screen.getByTestId("add-workflow-step"));
fireEvent.click(screen.getByTestId("create-custom-step"));
const nameInput = screen.getByTestId("workflow-step-name");
const descInput = screen.getByTestId("workflow-step-description");
@@ -557,6 +579,7 @@ describe("WorkflowStepManager", () => {
});
fireEvent.click(screen.getByTestId("add-workflow-step"));
fireEvent.click(screen.getByTestId("create-custom-step"));
const defaultOnCheckbox = screen.getByTestId("workflow-step-default-on") as HTMLInputElement;
expect(defaultOnCheckbox).toBeInTheDocument();
@@ -575,6 +598,7 @@ describe("WorkflowStepManager", () => {
});
fireEvent.click(screen.getByTestId("add-workflow-step"));
fireEvent.click(screen.getByTestId("create-custom-step"));
const nameInput = screen.getByTestId("workflow-step-name");
const descInput = screen.getByTestId("workflow-step-description");
@@ -847,6 +871,7 @@ describe("WorkflowStepManager theme class structure", () => {
});
fireEvent.click(screen.getByTestId("add-workflow-step"));
fireEvent.click(screen.getByTestId("create-custom-step"));
expect(container.querySelector(".wfm-form")).toBeInTheDocument();
expect(container.querySelector(".wfm-form-title")).toBeInTheDocument();
@@ -866,6 +891,7 @@ describe("WorkflowStepManager theme class structure", () => {
});
fireEvent.click(screen.getByTestId("add-workflow-step"));
fireEvent.click(screen.getByTestId("create-custom-step"));
expect(container.querySelector(".wfm-mode-selector")).toBeInTheDocument();
expect(container.querySelectorAll(".wfm-mode-btn")).toHaveLength(4); // 2 mode + 2 phase
@@ -883,6 +909,7 @@ describe("WorkflowStepManager theme class structure", () => {
});
fireEvent.click(screen.getByTestId("add-workflow-step"));
fireEvent.click(screen.getByTestId("create-custom-step"));
expect(container.querySelector(".wfm-prompt-textarea")).toBeInTheDocument();
expect(container.querySelector(".wfm-prompt-header")).toBeInTheDocument();
@@ -901,6 +928,7 @@ describe("WorkflowStepManager theme class structure", () => {
});
fireEvent.click(screen.getByTestId("add-workflow-step"));
fireEvent.click(screen.getByTestId("create-custom-step"));
expect(container.querySelector(".wfm-checkbox-label")).toBeInTheDocument();
});
@@ -917,6 +945,7 @@ describe("WorkflowStepManager theme class structure", () => {
});
fireEvent.click(screen.getByTestId("add-workflow-step"));
fireEvent.click(screen.getByTestId("create-custom-step"));
expect(container.querySelector(".wfm-form-actions")).toBeInTheDocument();
});
@@ -949,6 +978,7 @@ describe("WorkflowStepManager theme class structure", () => {
});
fireEvent.click(screen.getByTestId("add-workflow-step"));
fireEvent.click(screen.getByTestId("create-custom-step"));
fireEvent.click(screen.getByTestId("mode-script"));
expect(container.querySelector(".wfm-no-scripts")).toBeInTheDocument();
@@ -966,6 +996,7 @@ describe("WorkflowStepManager model override", () => {
});
fireEvent.click(screen.getByTestId("add-workflow-step"));
fireEvent.click(screen.getByTestId("create-custom-step"));
// Model override field should be visible in prompt mode
expect(screen.getByTestId("workflow-step-model-field")).toBeInTheDocument();
@@ -982,6 +1013,7 @@ describe("WorkflowStepManager model override", () => {
});
fireEvent.click(screen.getByTestId("add-workflow-step"));
fireEvent.click(screen.getByTestId("create-custom-step"));
fireEvent.click(screen.getByTestId("mode-script"));
// Model override field should NOT be visible in script mode
@@ -998,6 +1030,7 @@ describe("WorkflowStepManager model override", () => {
});
fireEvent.click(screen.getByTestId("add-workflow-step"));
fireEvent.click(screen.getByTestId("create-custom-step"));
// Select a model via the mock dropdown
const selectBtn = screen.getByTestId("dropdown-select-Model override for this workflow step");
@@ -1030,6 +1063,7 @@ describe("WorkflowStepManager model override", () => {
});
fireEvent.click(screen.getByTestId("add-workflow-step"));
fireEvent.click(screen.getByTestId("create-custom-step"));
const nameInput = screen.getByTestId("workflow-step-name");
const descInput = screen.getByTestId("workflow-step-description");
@@ -1066,6 +1100,7 @@ describe("WorkflowStepManager model override", () => {
});
fireEvent.click(screen.getByTestId("add-workflow-step"));
fireEvent.click(screen.getByTestId("create-custom-step"));
const nameInput = screen.getByTestId("workflow-step-name");
const descInput = screen.getByTestId("workflow-step-description");
@@ -1222,6 +1257,7 @@ describe("WorkflowStepManager model override", () => {
});
fireEvent.click(screen.getByTestId("add-workflow-step"));
fireEvent.click(screen.getByTestId("create-custom-step"));
// No model selected — clear button should NOT be visible
expect(screen.queryByTestId("clear-model-override")).not.toBeInTheDocument();