feat(FN-1112): remove browser-verification special-case UI

- Remove the hardcoded Browser Verification checkbox from TaskForm and rely on configured workflow steps
- Remove the InlineCreateCard Browser toggle and stop injecting browser-verification into create payloads
- Align TaskForm and InlineCreateCard tests with template-only workflow-step selection behavior
- Delete obsolete NewTaskModal and TaskDetailModal assertions for browser-verification controls
This commit is contained in:
gsxdsm
2026-04-07 22:37:19 -07:00
parent 4e41eb0e7a
commit a0cf687a81
6 changed files with 30 additions and 340 deletions

View File

@@ -1,6 +1,6 @@
import { useState, useCallback, useEffect, useRef } from "react";
import { createPortal } from "react-dom";
import { Brain, Link, Lightbulb, ListTree, Zap, ChevronDown, ChevronUp, Globe } from "lucide-react";
import { Brain, Link, Lightbulb, ListTree, Zap, ChevronDown, ChevronUp } from "lucide-react";
import type { Task, TaskCreateInput, Settings } from "@fusion/core";
import type { ToastType } from "../hooks/useToast";
import { fetchModels, uploadAttachment, fetchSettings, updateGlobalSettings } from "../api";
@@ -93,7 +93,6 @@ export function InlineCreateCard({
const [submitting, setSubmitting] = useState(false);
const [pendingImages, setPendingImages] = useState<PendingImage[]>([]);
const [isExpanded, setIsExpanded] = useState(false);
const [browserVerification, setBrowserVerification] = useState(false);
const justResetRef = useRef(false);
const inputRef = useRef<HTMLTextAreaElement>(null);
const cardRef = useRef<HTMLDivElement>(null);
@@ -282,7 +281,7 @@ export function InlineCreateCard({
modelId: hasExecutorOverride ? executorModelId : undefined,
validatorModelProvider: hasValidatorOverride ? validatorProvider : undefined,
validatorModelId: hasValidatorOverride ? validatorModelId : undefined,
enabledWorkflowSteps: browserVerification ? ["browser-verification"] : undefined,
enabledWorkflowSteps: undefined,
});
// Upload pending images as attachments
@@ -317,7 +316,6 @@ export function InlineCreateCard({
setShowDeps(false);
setIsModelModalOpen(false);
setShowPresets(false);
setBrowserVerification(false);
addToast(`Created ${task.id}`, "success");
// Collapse and clear localStorage after successful task creation
@@ -345,7 +343,6 @@ export function InlineCreateCard({
onSubmit,
addToast,
selectedPresetId,
browserVerification,
]);
const handleKeyDown = useCallback(
@@ -478,7 +475,6 @@ export function InlineCreateCard({
setShowDeps(false);
setIsModelModalOpen(false);
setShowPresets(false);
setBrowserVerification(false);
setIsExpanded(false);
}, [description, onPlanningMode, addToast]);
@@ -500,7 +496,6 @@ export function InlineCreateCard({
setShowDeps(false);
setIsModelModalOpen(false);
setShowPresets(false);
setBrowserVerification(false);
setIsExpanded(false);
}, [description, onSubtaskBreakdown, addToast]);
@@ -729,19 +724,6 @@ export function InlineCreateCard({
</div>
{!submitting && (
<button
type="button"
className={`btn btn-sm ${browserVerification ? "btn-active" : ""}`}
onClick={() => setBrowserVerification((v) => !v)}
disabled={submitting}
data-testid="browser-verification-toggle"
title="Verify with agent-browser"
>
<Globe size={12} style={{ verticalAlign: "middle" }} />
Browser
</button>
)}
</div>
<div className="inline-create-actions">
<span className="inline-create-hint">Enter to create · Esc to cancel</span>

View File

@@ -4,7 +4,7 @@ import type { ToastType } from "../hooks/useToast";
import { fetchModels, fetchSettings, fetchWorkflowSteps, refineText, getRefineErrorMessage, updateGlobalSettings, type RefinementType, type ModelInfo } from "../api";
import { applyPresetToSelection, getRecommendedPresetForSize } from "../utils/modelPresets";
import { CustomModelDropdown } from "./CustomModelDropdown";
import { Sparkles, Globe, ChevronUp, ChevronDown, X } from "lucide-react";
import { Sparkles, ChevronUp, ChevronDown, X } from "lucide-react";
const ALLOWED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
@@ -346,22 +346,11 @@ export function TaskForm({
onWorkflowStepsChange(selectedWorkflowSteps.filter((id) => id !== stepId));
}, [selectedWorkflowSteps, onWorkflowStepsChange]);
// Build a lookup for step names (includes both fetched steps and built-in browser-verification)
// Build a lookup for step names.
const workflowStepLookup = new Map<string, { name: string; description: string }>();
for (const step of workflowSteps) {
workflowStepLookup.set(step.id, { name: step.name, description: step.description });
}
workflowStepLookup.set("browser-verification", {
name: "Browser Verification",
description: "Verify web application functionality using browser automation (agent-browser)",
});
const browserVerificationResolvedIds = workflowSteps
.filter((step) => step.templateId === "browser-verification")
.map((step) => step.id);
const isBrowserVerificationSelected =
selectedWorkflowSteps.includes("browser-verification") ||
browserVerificationResolvedIds.some((id) => selectedWorkflowSteps.includes(id));
const availableDeps = tasks
.filter((t) => !dependencies.includes(t.id))
@@ -752,39 +741,6 @@ export function TaskForm({
</div>
</label>
))}
<label
key="browser-verification"
className="checkbox-label"
style={{ display: "flex", alignItems: "flex-start", gap: "8px" }}
data-testid="browser-verification-checkbox"
>
<input
type="checkbox"
checked={isBrowserVerificationSelected}
onChange={(e) => {
const filteredSteps = selectedWorkflowSteps.filter(
(id) => id !== "browser-verification" && !browserVerificationResolvedIds.includes(id),
);
onWorkflowStepsChange(
e.target.checked
? [...filteredSteps, "browser-verification"]
: filteredSteps,
);
}}
disabled={disabled}
style={{ marginTop: "2px" }}
/>
<div>
<span style={{ fontWeight: 500, fontSize: "13px" }}>
<Globe size={14} style={{ verticalAlign: "middle", marginRight: "4px" }} />
Browser Verification
</span>
<div style={{ fontSize: "12px", color: "var(--text-secondary)", marginTop: "2px" }}>
Verify web application functionality using browser automation (agent-browser)
</div>
</div>
</label>
</div>
{/* Selected steps — execution order with reorder controls */}

View File

@@ -19,7 +19,6 @@ vi.mock("lucide-react", () => ({
Zap: () => null,
ChevronDown: () => null,
ChevronUp: () => null,
Globe: () => null,
}));
// Mock ModelSelectionModal (renders via portal, so mock for testability)
@@ -811,88 +810,21 @@ describe("InlineCreateCard button visibility when collapsed", () => {
expect(document.getElementById("inline-create-controls")).toBeTruthy();
});
describe("browser verification", () => {
it("shows browser verification button when expanded", () => {
const { props } = renderCard();
// Button should not be visible when collapsed
expect(screen.queryByTestId("browser-verification-toggle")).toBeNull();
// Expand the card
expandCard();
// Button should now be visible
expect(screen.getByTestId("browser-verification-toggle")).toBeTruthy();
});
it("submits with enabledWorkflowSteps undefined", async () => {
const mockOnSubmit = vi.fn().mockResolvedValue(createMockTask());
renderCard([], { onSubmit: mockOnSubmit });
expandCard();
it("toggles browser verification when button is clicked", () => {
const { props } = renderCard();
expandCard();
const button = screen.getByTestId("browser-verification-toggle");
// Initially not active
expect(button).not.toHaveClass("btn-active");
// Click to enable
fireEvent.click(button);
expect(button).toHaveClass("btn-active");
// Click to disable
fireEvent.click(button);
expect(button).not.toHaveClass("btn-active");
});
const textarea = screen.getByPlaceholderText("What needs to be done?");
fireEvent.change(textarea, { target: { value: "Test task" } });
fireEvent.click(screen.getByTestId("save-button"));
it("includes browser-verification in enabledWorkflowSteps when submitting with browser verification enabled", async () => {
const mockOnSubmit = vi.fn().mockResolvedValue(createMockTask());
const { props } = renderCard([], { onSubmit: mockOnSubmit });
expandCard();
// Enable browser verification
fireEvent.click(screen.getByTestId("browser-verification-toggle"));
// Fill in description
const textarea = screen.getByPlaceholderText("What needs to be done?");
fireEvent.change(textarea, { target: { value: "Test task with browser verification" } });
// Submit
fireEvent.click(screen.getByTestId("save-button"));
await waitFor(() => {
expect(mockOnSubmit).toHaveBeenCalledWith(
expect.objectContaining({
enabledWorkflowSteps: ["browser-verification"],
}),
);
});
});
it("resets browser verification state after successful submission", async () => {
const mockOnSubmit = vi.fn().mockResolvedValue(createMockTask({ id: "FN-042" }));
const { props } = renderCard([], { onSubmit: mockOnSubmit });
expandCard();
// Enable browser verification
fireEvent.click(screen.getByTestId("browser-verification-toggle"));
// Verify button is active
expect(screen.getByTestId("browser-verification-toggle")).toHaveClass("btn-active");
// Fill in description and submit
const textarea = screen.getByPlaceholderText("What needs to be done?");
fireEvent.change(textarea, { target: { value: "Task to complete" } });
fireEvent.click(screen.getByTestId("save-button"));
// Wait for submission to complete
await waitFor(() => {
expect(mockOnSubmit).toHaveBeenCalled();
});
// Collapse and expand to reset
expandCard();
// Browser verification should be reset (button not active)
expect(screen.getByTestId("browser-verification-toggle")).not.toHaveClass("btn-active");
await waitFor(() => {
expect(mockOnSubmit).toHaveBeenCalledWith(
expect.objectContaining({
enabledWorkflowSteps: undefined,
}),
);
});
});

View File

@@ -246,77 +246,6 @@ describe("NewTaskModal", () => {
expect(createButton).not.toBeDisabled();
});
// Browser verification tests
describe("browser verification", () => {
it("shows browser verification checkbox", () => {
renderNewTaskModal();
expect(screen.getByTestId("browser-verification-checkbox")).toBeTruthy();
expect(screen.getByText("Browser Verification")).toBeTruthy();
});
it("adds browser-verification to selected workflow steps when checkbox is checked", async () => {
const { props } = renderNewTaskModal();
const checkbox = screen.getByTestId("browser-verification-checkbox").querySelector('input[type="checkbox"]') as HTMLInputElement;
fireEvent.click(checkbox);
await waitFor(() => {
expect(checkbox.checked).toBe(true);
});
const descTextarea = screen.getByLabelText(/Description/i);
fireEvent.change(descTextarea, { target: { value: "Test task" } });
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
await waitFor(() => {
expect(props.onCreateTask).toHaveBeenCalledWith(
expect.objectContaining({
enabledWorkflowSteps: expect.arrayContaining(["browser-verification"]),
}),
);
});
});
it("removes browser-verification from selected workflow steps when checkbox is unchecked", async () => {
renderNewTaskModal();
const checkbox = screen.getByTestId("browser-verification-checkbox").querySelector('input[type="checkbox"]') as HTMLInputElement;
// Check then uncheck
fireEvent.click(checkbox);
await waitFor(() => {
expect(checkbox.checked).toBe(true);
});
fireEvent.click(checkbox);
await waitFor(() => {
expect(checkbox.checked).toBe(false);
});
});
it("includes browser-verification in enabledWorkflowSteps when submitting with checkbox checked", async () => {
const { props } = renderNewTaskModal();
const checkbox = screen.getByTestId("browser-verification-checkbox").querySelector('input[type="checkbox"]') as HTMLInputElement;
fireEvent.click(checkbox);
const descTextarea = screen.getByLabelText(/Description/i);
fireEvent.change(descTextarea, { target: { value: "Browser test task" } });
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
await waitFor(() => {
expect(props.onCreateTask).toHaveBeenCalledWith(
expect.objectContaining({
enabledWorkflowSteps: ["browser-verification"],
}),
);
});
});
});
// Preset selection tests (FN-819)
describe("model preset selection payload", () => {
it("omits modelPresetId from payload when in default mode", async () => {
@@ -493,24 +422,6 @@ describe("NewTaskModal", () => {
});
});
it("sends browser-verification and custom steps in selected order", async () => {
const { props } = renderNewTaskModal();
// Select browser-verification first, then nothing else — order is just one
const bvCheckbox = screen.getByTestId("browser-verification-checkbox").querySelector('input[type="checkbox"]') as HTMLInputElement;
fireEvent.click(bvCheckbox);
fireEvent.change(screen.getByLabelText(/Description/i), { target: { value: "BV task" } });
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
await waitFor(() => {
expect(props.onCreateTask).toHaveBeenCalledWith(
expect.objectContaining({
enabledWorkflowSteps: ["browser-verification"],
}),
);
});
});
});
// DefaultOn workflow step handling (FN-883)

View File

@@ -3651,7 +3651,6 @@ describe("TaskDetailModal", () => {
// Model configuration and workflow steps should be present via TaskForm
expect(screen.getByText(/Model Configuration/i)).toBeTruthy();
expect(screen.getByText(/Workflow Steps/i)).toBeTruthy();
expect(screen.getByTestId("browser-verification-checkbox")).toBeTruthy();
});
it("save sends all changed fields via updateTask", async () => {

View File

@@ -6,7 +6,6 @@ import type { Task, Column } from "@fusion/core";
// Mock lucide-react
vi.mock("lucide-react", () => ({
Sparkles: () => null,
Globe: () => null,
ChevronUp: () => null,
ChevronDown: () => null,
X: () => null,
@@ -134,11 +133,11 @@ describe("TaskForm", () => {
});
});
it("renders workflow step checkboxes with browser verification", () => {
it("does not render a hardcoded browser verification checkbox", () => {
renderTaskForm();
expect(screen.getByTestId("browser-verification-checkbox")).toBeTruthy();
expect(screen.getByText("Browser Verification")).toBeTruthy();
expect(screen.queryByTestId("browser-verification-checkbox")).toBeNull();
expect(screen.queryByText("Browser Verification")).toBeNull();
});
it("in create mode: shows Plan and Subtask buttons", () => {
@@ -206,17 +205,7 @@ describe("TaskForm", () => {
expect(container.querySelector(".inline-create-previews")).toBeTruthy();
});
it("calls onWorkflowStepsChange when browser verification is toggled", () => {
const onWorkflowStepsChange = vi.fn();
renderTaskForm({ onWorkflowStepsChange });
const checkbox = screen.getByTestId("browser-verification-checkbox").querySelector('input[type="checkbox"]') as HTMLInputElement;
fireEvent.click(checkbox);
expect(onWorkflowStepsChange).toHaveBeenCalledWith(["browser-verification"]);
});
it("shows browser verification checkbox as checked when selectedWorkflowSteps has resolved WS step ID", async () => {
it("calls onWorkflowStepsChange when a fetched workflow step is toggled", async () => {
const { fetchWorkflowSteps } = await import("../../api");
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([
{
@@ -231,95 +220,17 @@ describe("TaskForm", () => {
},
]);
renderTaskForm({ selectedWorkflowSteps: ["WS-005"] });
await waitFor(() => {
const checkbox = screen.getByTestId("browser-verification-checkbox").querySelector('input[type="checkbox"]') as HTMLInputElement;
expect(checkbox.checked).toBe(true);
});
});
it("removes resolved WS step IDs when browser verification is unchecked", async () => {
const { fetchWorkflowSteps } = await import("../../api");
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([
{
id: "WS-005",
name: "Browser Verification",
description: "Verify in browser",
prompt: "Run browser verification",
templateId: "browser-verification",
enabled: true,
createdAt: "",
updatedAt: "",
},
{
id: "WS-001",
name: "QA Check",
description: "Run tests",
prompt: "Run tests",
enabled: true,
createdAt: "",
updatedAt: "",
},
]);
const onWorkflowStepsChange = vi.fn();
renderTaskForm({
selectedWorkflowSteps: ["WS-001", "WS-005"],
onWorkflowStepsChange,
});
const checkbox = screen.getByTestId("browser-verification-checkbox").querySelector('input[type="checkbox"]') as HTMLInputElement;
renderTaskForm({ onWorkflowStepsChange });
await waitFor(() => {
expect(checkbox.checked).toBe(true);
expect(screen.getByTestId("workflow-step-checkbox-WS-005")).toBeTruthy();
});
const checkbox = screen.getByTestId("workflow-step-checkbox-WS-005").querySelector('input[type="checkbox"]') as HTMLInputElement;
fireEvent.click(checkbox);
expect(onWorkflowStepsChange).toHaveBeenCalledWith(["WS-001"]);
});
it("normalizes resolved WS step IDs to browser-verification when checkbox is checked", async () => {
const { fetchWorkflowSteps } = await import("../../api");
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([
{
id: "WS-005",
name: "Browser Verification",
description: "Verify in browser",
prompt: "Run browser verification",
templateId: "browser-verification",
enabled: true,
createdAt: "",
updatedAt: "",
},
{
id: "WS-001",
name: "QA Check",
description: "Run tests",
prompt: "Run tests",
enabled: true,
createdAt: "",
updatedAt: "",
},
]);
const onWorkflowStepsChange = vi.fn();
renderTaskForm({
selectedWorkflowSteps: ["WS-001", "WS-005"],
onWorkflowStepsChange,
});
const checkbox = screen.getByTestId("browser-verification-checkbox").querySelector('input[type="checkbox"]') as HTMLInputElement;
await waitFor(() => {
expect(checkbox.checked).toBe(true);
});
checkbox.checked = false;
fireEvent.click(checkbox);
expect(onWorkflowStepsChange).toHaveBeenCalledWith(["WS-001", "browser-verification"]);
expect(onWorkflowStepsChange).toHaveBeenCalledWith(["WS-005"]);
});
it("disables all inputs when disabled prop is true", () => {
@@ -654,7 +565,7 @@ describe("TaskForm workflow step reordering (FN-836)", () => {
});
it("does not show reorder controls when only one step is selected", () => {
renderTaskForm({ selectedWorkflowSteps: ["browser-verification"] });
renderTaskForm({ selectedWorkflowSteps: ["WS-001"] });
expect(screen.queryByTestId("workflow-step-order")).toBeNull();
});
@@ -781,23 +692,22 @@ describe("TaskForm workflow step reordering (FN-836)", () => {
expect(onWorkflowStepsChange).toHaveBeenCalledWith(["WS-002"]);
});
it("shows browser-verification step name in reorder list", async () => {
it("falls back to raw step ID in reorder list when metadata is missing", async () => {
const { fetchWorkflowSteps } = await import("../../api");
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([
{ id: "WS-001", name: "QA Check", description: "Run tests", prompt: "Check tests", enabled: true, createdAt: "", updatedAt: "" },
]);
renderTaskForm({ selectedWorkflowSteps: ["WS-001", "browser-verification"] });
renderTaskForm({ selectedWorkflowSteps: ["WS-001", "WS-999"] });
await waitFor(() => {
expect(screen.getByTestId("workflow-step-order")).toBeTruthy();
});
// browser-verification should show its friendly name in the reorder list
const orderItem1 = screen.getByTestId("workflow-step-order-item-WS-001");
const orderItem2 = screen.getByTestId("workflow-step-order-item-browser-verification");
const orderItem2 = screen.getByTestId("workflow-step-order-item-WS-999");
expect(orderItem1.textContent).toContain("QA Check");
expect(orderItem2.textContent).toContain("Browser Verification");
expect(orderItem2.textContent).toContain("WS-999");
});
it("preserves order when adding a new step via checkbox after reorder", async () => {