From 541f1f653369e1e7d556c22c354c8eaeb9d5c165 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Thu, 25 Jun 2026 18:59:34 -0700 Subject: [PATCH] FN-7017: expose optional workflow steps in task creation Surface active workflow optional steps on task creation surfaces so users can opt into workflow-specific steps before creating tasks. - Add optional-step dropdown state and creation payload handling to quick entry. - Move New Task optional-step controls into the inline quick-button row. - Cover default-on, workflow selection, duplicate-confirmed create, empty-step rendering, and mobile touch-target behavior with tests. - Document create-time optional-step controls and add the published package changeset. Files changed: .../fn-7017-optional-steps-quick-dropdown.md | 7 + docs/dashboard-guide.md | 4 +- docs/workflow-steps.md | 2 + .../dashboard/app/components/QuickEntryBox.css | 6 +- .../dashboard/app/components/QuickEntryBox.tsx | 65 ++++++++- packages/dashboard/app/components/TaskForm.tsx | 34 ++--- .../app/components/__tests__/NewTaskModal.test.tsx | 21 ++- .../components/__tests__/QuickEntryBox.test.tsx | 145 ++++++++++++++++++++- .../app/components/__tests__/TaskForm.test.tsx | 1 + 9 files changed, 258 insertions(+), 27 deletions(-) Fusion-Task-Id: FN-7017 Fusion-Task-Lineage: 1b7d21e6-ff7f-4d27-b285-4fd865eafce6 --- .../fn-7017-optional-steps-quick-dropdown.md | 7 + docs/dashboard-guide.md | 4 +- docs/workflow-steps.md | 2 + .../app/components/QuickEntryBox.css | 6 +- .../app/components/QuickEntryBox.tsx | 65 +++++++- .../dashboard/app/components/TaskForm.tsx | 34 ++-- .../__tests__/NewTaskModal.test.tsx | 21 ++- .../__tests__/QuickEntryBox.test.tsx | 145 +++++++++++++++++- .../components/__tests__/TaskForm.test.tsx | 1 + 9 files changed, 258 insertions(+), 27 deletions(-) create mode 100644 .changeset/fn-7017-optional-steps-quick-dropdown.md diff --git a/.changeset/fn-7017-optional-steps-quick-dropdown.md b/.changeset/fn-7017-optional-steps-quick-dropdown.md new file mode 100644 index 0000000000..c8aa45d69f --- /dev/null +++ b/.changeset/fn-7017-optional-steps-quick-dropdown.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": minor +--- + +summary: Add optional workflow-step quick dropdowns to task creation surfaces. +category: feature +dev: Surfaces active workflow optional steps in QuickEntryBox and NewTaskModal create payloads. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index 2e4c156d68..1946c032c6 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -301,6 +301,8 @@ When inline quick-create, Planning Mode, or Subtask Breakdown is opened from a w The **New Task** dialog's workflow selector also defaults to the current or last selected Board/List workflow lane for the current project. If no valid lane has been selected, or the remembered lane was deleted, the selector falls back to the project default workflow and task creation omits an explicit `workflowId`. +Optional workflow steps declared by the active workflow are available from the quick-add action row and the **New Task** dialog's inline quick buttons. For example, the coding workflow's browser verification option appears as a quick drop-down when that workflow is active; each option is seeded from the workflow step's `defaultOn` setting and is sent with the task's `enabledWorkflowSteps` payload at creation time. + Quick entry, inline quick-create, and the full **New Task** dialog all check for similar active tasks before creating. When possible duplicates exist, the warning lists each match by task description (falling back to title, then “No description”) and lets you open an existing task, cancel, or create anyway with the duplicates acknowledged. Completed single-task planning sessions remain in the Planning Mode history after you create the task, and selecting one restores the completed summary instead of restarting the composer. History rows are deduplicated by session id even if the initial load and live session updates arrive out of order, and deleting a history entry now waits for the server delete to persist (failures keep the row visible and surface an error instead of silently disappearing until refresh). @@ -322,7 +324,7 @@ Rules: - `Merge target / base branch` stays optional for all modes and uses the same branch-dropdown + `Custom…` fallback behavior as Planning Mode. - In **More options → Model Configuration**, **Auto-merge** is a per-task override with three states: **Default** (follow project setting), **Enabled**, or **Disabled**. -The dialog also exposes the board quick-add AI handoffs: **Plan** opens Planning Mode with the current description, and **Subtask** opens Subtask Breakdown with the current description when **Settings → Experimental Features → Subtask Breakdown** is enabled. The Subtask handoff is hidden by default; visible handoff buttons remain disabled until the description has content, matching the quick-add row behavior. **Execution mode** is available in the New Task dialog as well as quick entry, so users can choose Fast or standard execution before creating a task from either surface. +The dialog also exposes the board quick-add AI handoffs: **Plan** opens Planning Mode with the current description, and **Subtask** opens Subtask Breakdown with the current description when **Settings → Experimental Features → Subtask Breakdown** is enabled. The Subtask handoff is hidden by default; visible handoff buttons remain disabled until the description has content, matching the quick-add row behavior. **Execution mode** and optional workflow-step selection are available in the New Task dialog as well as quick entry, so users can choose Fast or standard execution and opt into workflow-specific creation-time steps before creating a task from either surface. The full **New Task** dialog includes a compact **GitHub issue or PR** picker near the description. It detects GitHub remotes for the current project, auto-selects a single remote or `origin`, and asks you to choose a remote when multiple non-`origin` remotes are available. Selecting an issue replaces the description with a prompt that tells the executor to fetch/read the issue and includes `Source: `; selecting a pull request creates a PR-focused prompt with `PR: ` and explicit instructions to inspect the PR conversation, review comments, checks, and changed files, then resolve or address actionable review comments. If you already typed a description, Fusion asks before replacing it. This picker only seeds the prompt; it does not import, close, or comment on GitHub items. On mobile, the full-screen New Task sheet keeps the GitHub picker, dependency picker, agent picker, quick handoff buttons, and action row tappable and scrollable even when the keyboard reduces the visual viewport. diff --git a/docs/workflow-steps.md b/docs/workflow-steps.md index 530b612129..8d7d98b718 100644 --- a/docs/workflow-steps.md +++ b/docs/workflow-steps.md @@ -244,6 +244,8 @@ Workflows can advertise optional workflow-step templates with `optionalSteps: [{ `defaultOn` is optional and seeds the UI toggle when a task is created or edited before the task has made its own selection. Unknown or removed template ids are skipped during resolution so stale declarations do not render blank controls or break workflow loading. +Create-time optional-step controls appear in the quick-add action row and the **New Task** dialog inline quick buttons for the active workflow. Workflows with no optional steps render no trigger, and selected ids are submitted through `enabledWorkflowSteps` when the task is created. + The built-in coding workflow (`builtin:coding`) declares `browser-verification` as an optional step. It remains opt-in by default, so browser verification only runs for tasks whose `enabledWorkflowSteps` includes `browser-verification`. ## What They Are diff --git a/packages/dashboard/app/components/QuickEntryBox.css b/packages/dashboard/app/components/QuickEntryBox.css index 191c5f75c7..30e1bb38d6 100644 --- a/packages/dashboard/app/components/QuickEntryBox.css +++ b/packages/dashboard/app/components/QuickEntryBox.css @@ -457,8 +457,10 @@ The global `.description-with-refine textarea { padding-right: 70px }` (styles.c touch-action: manipulation; } - .quick-entry-actions .btn { - min-height: 36px; + .quick-entry-actions .btn, + .quick-entry-actions .wf-optional-steps-dropdown-trigger { + /* FNXC:WorkflowOptionalSteps 2026-06-25-00:00: The optional-steps trigger is not a `.btn`, so opt it into the same mobile quick-entry touch-target contract as the surrounding action buttons without adding a wrapper that could leave an empty shell for zero-step workflows. */ + min-height: calc(var(--space-2xl) + var(--space-xs)); } .quick-entry-box .dep-dropdown { diff --git a/packages/dashboard/app/components/QuickEntryBox.tsx b/packages/dashboard/app/components/QuickEntryBox.tsx index 319af1bb59..4d53ae05c8 100644 --- a/packages/dashboard/app/components/QuickEntryBox.tsx +++ b/packages/dashboard/app/components/QuickEntryBox.tsx @@ -4,9 +4,9 @@ import { useTranslation } from "react-i18next"; import { createPortal } from "react-dom"; import type { ToastType } from "../hooks/useToast"; import { DEFAULT_TASK_PRIORITY, TASK_PRIORITIES, getErrorMessage } from "@fusion/core"; -import type { Task, Settings, TaskPriority } from "@fusion/core"; +import type { Task, Settings, TaskPriority, ResolvedWorkflowOptionalStep } from "@fusion/core"; import type { ModelInfo, RefinementType, Agent, CreateTaskInput, DuplicateMatch } from "../api"; -import { checkDuplicateTasks, fetchModels, fetchSettings, refineText, getRefineErrorMessage, updateGlobalSettings, fetchAgents, uploadAttachment } from "../api"; +import { checkDuplicateTasks, fetchModels, fetchSettings, refineText, getRefineErrorMessage, updateGlobalSettings, fetchAgents, uploadAttachment, fetchWorkflowOptionalSteps } from "../api"; import { DuplicateWarningModal } from "./DuplicateWarningModal"; import { Link, Paperclip, Brain, Lightbulb, ListTree, Sparkles, Save, ChevronDown, ChevronUp, ChevronRight, Bot, Server, Flag } from "lucide-react"; import { CustomModelDropdown } from "./CustomModelDropdown"; @@ -15,6 +15,7 @@ import { getScopedItem, removeScopedItem, setScopedItem } from "../utils/project import { useNodes } from "../hooks/useNodes"; import { NodeHealthDot } from "./NodeHealthDot"; import { ProviderIcon } from "./ProviderIcon"; +import { WorkflowOptionalStepsDropdown } from "./WorkflowOptionalStepsDropdown"; const STORAGE_KEY = "kb-quick-entry-text"; const MOBILE_BREAKPOINT_PX = 768; @@ -172,6 +173,8 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels, const [favoriteModels, setFavoriteModels] = useState([]); const [settings, setSettings] = useState(null); const [selectedPresetId, setSelectedPresetId] = useState(undefined); + const [optionalSteps, setOptionalSteps] = useState([]); + const [enabledOptionalStepIds, setEnabledOptionalStepIds] = useState([]); const [isFastMode, setIsFastMode] = useState(false); const [githubTrackingOverride, setGithubTrackingOverride] = useState(null); const [priority, setPriority] = useState(DEFAULT_TASK_PRIORITY); @@ -253,6 +256,51 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels, }; }, [projectId]); + /* + FNXC:WorkflowOptionalSteps 2026-06-25-00:00: + Quick-add creation needs the active workflow's optional steps in the immediate action row, seeded from each step's `defaultOn`, and forwarded through `enabledWorkflowSteps`. `null` workflow is an explicit no-workflow opt-out, while `undefined` inherits the project default so Board and List quick-add match TaskForm's create-time resolution. + */ + const effectiveOptionalWorkflowId = + workflowId === null + ? null + : (workflowId ?? settings?.defaultWorkflowId ?? null); + + useEffect(() => { + let cancelled = false; + setOptionalSteps([]); + setEnabledOptionalStepIds([]); + + if (!effectiveOptionalWorkflowId) { + return () => { + cancelled = true; + }; + } + + fetchWorkflowOptionalSteps(effectiveOptionalWorkflowId, projectId) + .then((steps) => { + if (cancelled) return; + setOptionalSteps(steps); + setEnabledOptionalStepIds(steps.filter((step) => step.defaultOn).map((step) => step.templateId)); + }) + .catch(() => { + if (cancelled) return; + setOptionalSteps([]); + setEnabledOptionalStepIds([]); + }); + + return () => { + cancelled = true; + }; + }, [effectiveOptionalWorkflowId, projectId]); + + const toggleOptionalStep = useCallback((templateId: string) => { + setEnabledOptionalStepIds((prev) => ( + prev.includes(templateId) + ? prev.filter((id) => id !== templateId) + : [...prev, templateId] + )); + }, []); + const executorSelectionValue = getModelSelectionValue(executorProvider, executorModelId); const validatorSelectionValue = getModelSelectionValue(validatorProvider, validatorModelId); const planningSelectionValue = getModelSelectionValue(planningProvider, planningModelId); @@ -492,6 +540,7 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels, setPlanningProvider(undefined); setPlanningModelId(undefined); setSelectedPresetId(undefined); + setEnabledOptionalStepIds(optionalSteps.filter((step) => step.defaultOn).map((step) => step.templateId)); setIsFastMode(false); setGithubTrackingOverride(null); setPriority(DEFAULT_TASK_PRIORITY); @@ -510,7 +559,7 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels, if (typeof window !== "undefined") { removeScopedItem(STORAGE_KEY, projectId); } - }, [pendingImages, projectId]); + }, [pendingImages, projectId, optionalSteps]); const handleImageFiles = useCallback((files: FileList | null | undefined) => { if (!files || files.length === 0) return; @@ -563,6 +612,7 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels, validatorModelId: hasValidatorOverride ? validatorModelId : undefined, planningModelProvider: hasPlanningOverride ? planningProvider : undefined, planningModelId: hasPlanningOverride ? planningModelId : undefined, + enabledWorkflowSteps: enabledOptionalStepIds.length ? enabledOptionalStepIds : undefined, ...(isFastMode ? { executionMode: "fast" } : {}), githubTracking: githubTrackingOverride !== null ? { enabled: githubTrackingOverride } : undefined, priority, @@ -607,6 +657,7 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels, hasPlanningOverride, planningProvider, planningModelId, + enabledOptionalStepIds, isFastMode, settings, githubTrackingOverride, @@ -1663,6 +1714,14 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels, portalRoot, )} + + )} + {optionalStepsLoading ? ( + + {t("taskForm.optionalStepsLoading", "Loading optional steps…")} + + ) : ( + + )} +