FN-7591: make dashboard create surfaces resolve intake column from workflow instead of hard-coding triage

Fixes dashboard task creation so new cards land in the selected/default workflow's intake column instead of always forcing legacy triage, letting workflows like Coding (Ideas) park new cards in 'ideas' until an operator promotes them.

- InlineCreateCard, QuickEntryBox, and NewTaskModal no longer hard-code column:"triage"; InlineCreateCard now forwards workflowId at create time instead of applying it post-create.
- Fixed a glue-layer regression in useTaskHandlers.ts (handleBoardQuickCreate/handleModalCreate) that re-forced column:"triage" even after UI surfaces stopped sending it.
- Added/updated tests covering the store's intake-column resolution and the dashboard create surfaces/hooks.
- Documented the new manual-intake-column parking behavior in dashboard-guide.md and workflow-steps.md.
- Added a patch changeset for @runfusion/fusion.

Files changed:
 .changeset/fn-7591-coding-ideas-intake.md          |  7 +++
 docs/dashboard-guide.md                            |  4 ++
 docs/workflow-steps.md                             |  1 +
 packages/core/src/__tests__/store-create-intake-column.test.ts   | 20 ++++++++
 packages/dashboard/app/App.tsx                     |  5 +-
 packages/dashboard/app/components/InlineCreateCard.tsx  | 28 ++++------
 packages/dashboard/app/components/NewTaskModal.tsx |  5 +-
 packages/dashboard/app/components/QuickEntryBox.tsx     |  5 +-
 packages/dashboard/app/components/TodoView.tsx     |  7 ++-
 packages/dashboard/app/components/__tests__/InlineCreateCard.test.tsx | 59 +++++++++++++++++++++-
 packages/dashboard/app/components/__tests__/QuickEntryBox.test.tsx    | 14 +++--
 packages/dashboard/app/components/__tests__/TodoView.test.tsx | 10 ++--
 packages/dashboard/app/components/__tests__/board-quickcreate-workflow-lane-visibility.test.tsx | 45 ++++++++++++++++-
 packages/dashboard/app/hooks/__tests__/useTaskHandlers.test.ts    | 27 ++++++++--
 packages/dashboard/app/hooks/useTaskHandlers.ts    |  8 ++-
 15 files changed, 207 insertions(+), 38 deletions(-)

Fusion-Task-Id: FN-7591

Fusion-Task-Lineage: 510f0e6a-89e7-468f-a6df-ad6aebd5c33a

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-05 13:15:56 -07:00
parent 09a10bc5e7
commit cf3fe8b485
15 changed files with 207 additions and 38 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: New tasks created under the Coding (Ideas) workflow now land in the Ideas column and wait for you to promote them.
category: fix
dev: Dashboard create surfaces (InlineCreateCard, QuickEntryBox, NewTaskModal, insight/todo → task) no longer hard-code column:"triage"; the store now resolves the selected/default workflow's intake column. InlineCreateCard forwards workflowId at create time instead of applying it post-create. Also fixed a glue-layer regression in `useTaskHandlers.ts` (`handleBoardQuickCreate`/`handleModalCreate`) that re-forced column:"triage" even after the UI surfaces stopped sending it.

View File

@@ -432,6 +432,10 @@ When quick-create task creation, Planning Mode, or Subtask Breakdown runs from a
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`.
<!-- FNXC:CodingIdeasWorkflow 2026-07-05-00:00: Every dashboard create surface (inline quick-create, quick-add box, New Task dialog, insight → task, todo → task) must omit an explicit `column` from the create request so the store resolves the landing column from the (selected or project-default) workflow's intake column instead of always forcing legacy `triage`. -->
Create requests never send an explicit `column`. The task store resolves the landing column from the (selected or project-default) workflow's intake column, so most tasks still land in `triage` under the default Coding workflow, byte-identical to before. A workflow with a **manual intake column** — for example the built-in **Coding (Ideas)** workflow's `ideas` column (`autoTriage: false`) — parks new cards there instead: they wait for you to promote them into `todo` and are not auto-planned by the triage service until you do.
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.
<!-- FNXC:QuickAddAttachments 2026-06-30-00:00: Quick Add attachments use a compact icon-only paperclip while keeping the Attach action and pending image count in accessible labels. The same pending preview/upload path accepts image selection, paste, and direct drag/drop onto the Quick Add box. -->

View File

@@ -82,6 +82,7 @@ Use this inventory as the documentation map for current workflow behavior:
| Routing boundary | Agents may select/change a workflow only for explicit user requests or tasks they created; no-commit markers do not imply Quick fix or any other workflow. | This page, [Selecting workflows](#selecting-workflows); [Agents](./agents.md#interactive-cli-chat). |
| Dashboard board/list/graph selection | Board/List/Header/Graph share durable per-project workflow selection; stale saved ids fall back to a valid workflow. Board adds a dashboard-only **All workflows** aggregate and task workflow-name badges; Graph uses **All workflows** for the full active graph. | [Dashboard Guide → Board View](./dashboard-guide.md#board-view), [Graph View](./dashboard-guide.md#graph-view), and [Workflow Selection and Editor](./dashboard-guide.md#workflow-selection-and-editor). |
| Create/planning forwarding | Quick-create task creation, Planning Mode, Subtask Breakdown, and the New Task dialog forward the active real workflow id when creating tasks; **All workflows** quick-create chooses a real workflow intake/default column instead of saving a synthetic aggregate id. | [Dashboard Guide → Planning Mode](./dashboard-guide.md#planning-mode). |
| Manual-intake column parking | Dashboard create surfaces never send an explicit `column`; the store resolves the landing column from the (selected or project-default) workflow's intake column. A workflow whose intake column sets `autoTriage: false` (e.g. built-in Coding (Ideas)'s `ideas` column) parks new cards there instead of auto-planning them, until an operator promotes the card. | [Dashboard Guide → Create/Planning Forwarding](./dashboard-guide.md#planning-mode). |
### Skill-backed workflow steps

View File

@@ -36,6 +36,26 @@ describe("createTask intake-column wiring (Coding (Ideas))", () => {
expect(task.column).toBe("ideas");
});
it("lands a task explicitly selecting builtin:coding in triage even when the project default is coding-ideas", async () => {
const store = harness.store();
await store.setDefaultWorkflowId("builtin:coding-ideas");
const task = await store.createTask({
description: "explicit default coding workflow task",
workflowId: "builtin:coding",
});
expect(task.column).toBe("triage");
});
it("does not throw and falls back to triage when workflowId is explicitly null (\"No workflow\")", async () => {
const store = harness.store();
await store.setDefaultWorkflowId("builtin:coding-ideas");
const task = await store.createTask({
description: "explicit no-workflow task",
workflowId: null,
});
expect(task.column).toBe("triage");
});
it("writes a bootstrap PROMPT.md for an ideas-column task (unplanned)", async () => {
const store = harness.store();
const task: Task = await store.createTask({

View File

@@ -792,10 +792,13 @@ function AppInner() {
const handleInsightTaskCreate = useCallback(
async ({ insightId, title, description }: { insightId: string; title: string; description: string }) => {
/*
FNXC:CodingIdeasWorkflow 2026-07-05-00:00:
Do not hard-code `column: "triage"` — this surface has no workflow picker, so it inherits the project-default workflow, and the store resolves the landing column from that workflow's intake column (e.g. Coding (Ideas) → "ideas") instead of forcing triage.
*/
await createTask({
title,
description,
column: "triage",
source: {
sourceType: "dashboard_ui",
sourceMetadata: {

View File

@@ -6,7 +6,7 @@ import { Brain, Link, ListTree, Zap, ChevronDown, ChevronUp, Bot, Maximize2, Min
import { DEFAULT_TASK_PRIORITY, TASK_PRIORITIES, type Task, type TaskPriority, type Settings, type ResolvedWorkflowOptionalStep } from "@fusion/core";
import { getErrorMessage } from "@fusion/core";
import type { ToastType } from "../hooks/useToast";
import { checkDuplicateTasks, fetchModels, uploadAttachment, fetchSettings, updateGlobalSettings, fetchAgents, selectTaskWorkflow, fetchWorkflowOptionalSteps, DuplicateCandidatesError } from "../api";
import { checkDuplicateTasks, fetchModels, uploadAttachment, fetchSettings, updateGlobalSettings, fetchAgents, fetchWorkflowOptionalSteps, DuplicateCandidatesError } from "../api";
import type { CreateTaskInput, ModelInfo, Agent, NodeInfo, DuplicateMatch } from "../api";
import { useNodes } from "../hooks/useNodes";
import { ModelSelectionModal } from "./ModelSelectionModal";
@@ -394,24 +394,15 @@ export function InlineCreateCard({
});
}, []);
/*
FNXC:CodingIdeasWorkflow 2026-07-05-00:00:
submitTask no longer applies the selected workflow post-create via selectTaskWorkflow — handleSubmit now forwards workflowId inside the CreateTaskInput so the store materializes the workflow and resolves the intake column (e.g. Coding (Ideas) → "ideas") atomically at create time. A post-create selectTaskWorkflow call would race the store's intake-column resolution and re-introduce the auto-triage bug this task fixes.
*/
const submitTask = useCallback(async (input: CreateTaskInput) => {
setSubmitting(true);
try {
const task = await onSubmit(input);
// Apply custom workflow if selected (non-blocking — task already exists)
if (selectedWorkflowId) {
try {
await selectTaskWorkflow(task.id, selectedWorkflowId, projectId);
} catch (err) {
if (addToast) {
addToast(getErrorMessage(err) || "Failed to apply workflow", "error");
} else {
console.warn("Failed to apply workflow:", getErrorMessage(err));
}
}
}
// Upload pending images as attachments
if (pendingImages.length > 0) {
const failures: string[] = [];
@@ -478,15 +469,18 @@ export function InlineCreateCard({
onSubmit,
addToast,
projectId,
selectedWorkflowId,
]);
const handleSubmit = useCallback(async () => {
if (!description.trim() || submitting) return;
/*
FNXC:CodingIdeasWorkflow 2026-07-05-00:00:
Do not hard-code `column: "triage"` here — the store resolves the landing column from the forwarded (or project-default) workflow's intake column, e.g. Coding (Ideas) → "ideas". Forwarding `workflowId` at create time (instead of applying it post-create via selectTaskWorkflow) lets the store materialize the workflow and land the card in its resolved intake column atomically, so a manual-intake workflow parks the card for the operator instead of being auto-triaged.
*/
const input: CreateTaskInput = {
description: description.trim(),
column: "triage",
...(selectedWorkflowId ? { workflowId: selectedWorkflowId } : {}),
dependencies: dependencies.length ? dependencies : undefined,
...(selectedAgentId ? { assignedAgentId: selectedAgentId } : {}),
modelPresetId: selectedPresetId,
@@ -517,7 +511,7 @@ export function InlineCreateCard({
}
await submitTask(input);
}, [description, submitting, dependencies, selectedAgentId, selectedPresetId, hasExecutorOverride, executorProvider, executorModelId, hasValidatorOverride, validatorProvider, validatorModelId, hasPlanningOverride, planningProvider, planningModelId, optionalSteps.length, enabledOptionalStepIds, priority, effectiveNodeId, projectId, addToast, submitTask]);
}, [description, submitting, selectedWorkflowId, dependencies, selectedAgentId, selectedPresetId, hasExecutorOverride, executorProvider, executorModelId, hasValidatorOverride, validatorProvider, validatorModelId, hasPlanningOverride, planningProvider, planningModelId, optionalSteps.length, enabledOptionalStepIds, priority, effectiveNodeId, projectId, addToast, submitTask]);
const handleDuplicateProceed = useCallback(async () => {
const matches = duplicateMatches;

View File

@@ -791,10 +791,13 @@ export function NewTaskModal({ isOpen, onClose, projectId, tasks, onCreateTask,
const validatorSlashIdx = validatorModel.indexOf("/");
const planningSlashIdx = planningModel.indexOf("/");
/*
FNXC:CodingIdeasWorkflow 2026-07-05-00:00:
Do not hard-code `column: "triage"` — the store resolves the landing column from the (materialized) workflowId below, so a manual-intake workflow (e.g. Coding (Ideas) → "ideas") parks the card for the operator instead of being auto-triaged.
*/
const createInput: NewTaskCreateInput = {
title: undefined,
description: trimmedDesc,
column: "triage",
dependencies: dependencies.length ? dependencies : undefined,
// U6/R3: forward the workflow selection only when the user changed it.
// - undefined → omit (store inherits the project default, today's behavior)

View File

@@ -715,9 +715,12 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
const originalDescription = description;
setDescription("");
try {
/*
FNXC:CodingIdeasWorkflow 2026-07-05-00:00:
Do not hard-code `column: "triage"` — the store resolves the landing column from the forwarded (or project-default) workflow's intake column, so a manual-intake workflow (e.g. Coding (Ideas) → "ideas") parks the card for the operator instead of being auto-triaged.
*/
const createdTask = await onCreate({
description: trimmed,
column: "triage",
...(selectedWorkflowForCreate !== undefined ? { workflowId: selectedWorkflowForCreate } : {}),
dependencies: dependencies.length ? dependencies : undefined,
...(selectedAgentId ? { assignedAgentId: selectedAgentId } : {}),

View File

@@ -299,9 +299,12 @@ export function TodoView({
const handleCreateTaskFromItem = useCallback(async (item: TodoItem) => {
try {
/*
FNXC:CodingIdeasWorkflow 2026-07-05-00:00:
Do not hard-code `column: "triage"` — this surface has no workflow picker, so it inherits the project-default workflow, and the store resolves the landing column from that workflow's intake column (e.g. Coding (Ideas) → "ideas") instead of forcing triage.
*/
const input: TaskCreateInput = {
description: item.text,
column: "triage",
source: { sourceType: "dashboard_ui" },
};
const task: Task = await createTask(input, projectId);
@@ -314,9 +317,9 @@ export function TodoView({
const handleCreateTaskAndAssign = useCallback(async (item: TodoItem, agentId: string) => {
try {
// FNXC:CodingIdeasWorkflow 2026-07-05-00:00: same rationale as handleCreateTaskFromItem above — omit column so the project-default workflow's intake column resolves it.
const input: TaskCreateInput = {
description: item.text,
column: "triage",
assignedAgentId: agentId,
source: { sourceType: "dashboard_ui" },
};

View File

@@ -3,7 +3,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render, screen, fireEvent, waitFor, act } from "@testing-library/react";
import { InlineCreateCard } from "../InlineCreateCard";
import type { Task, Column } from "@fusion/core";
import { fetchModels, fetchSettings, fetchAgents, checkDuplicateTasks, fetchWorkflows, fetchWorkflowOptionalSteps } from "../../api";
import { fetchModels, fetchSettings, fetchAgents, checkDuplicateTasks, fetchWorkflows, fetchWorkflowOptionalSteps, selectTaskWorkflow } from "../../api";
import { useNodes } from "../../hooks/useNodes";
import type { ModelInfo } from "../../api";
import { scopedKey } from "../../utils/projectStorage";
@@ -1773,4 +1773,61 @@ describe("InlineCreateCard node override", () => {
});
});
/*
FN-7591: InlineCreateCard must forward a selected workflow inside the create-time CreateTaskInput
(so the store materializes it and resolves the workflow's intake column atomically) instead of hard-coding
column:"triage" and applying the workflow post-create via selectTaskWorkflow.
*/
describe("InlineCreateCard workflow selection at create time (FN-7591)", () => {
beforeEach(() => {
vi.mocked(fetchWorkflows).mockResolvedValue([
{ id: "wf-a", name: "Workflow A" },
{ id: "builtin:coding-ideas", name: "Coding (Ideas)" },
]);
});
it("submits workflowId in the create input and omits column:triage when a workflow is selected", async () => {
const { props } = renderCard();
expandCard();
fireEvent.change(screen.getByPlaceholderText("What needs to be done?"), { target: { value: "Idea for later" } });
const select = await screen.findByLabelText("Workflow") as HTMLSelectElement;
fireEvent.change(select, { target: { value: "builtin:coding-ideas" } });
fireEvent.click(screen.getByTestId("save-button"));
await waitFor(() => expect(props.onSubmit).toHaveBeenCalled());
const submitted = vi.mocked(props.onSubmit).mock.calls[0][0];
expect(submitted.workflowId).toBe("builtin:coding-ideas");
expect(submitted.column).toBeUndefined();
});
it("omits workflowId when no workflow is explicitly selected (inherits project default)", async () => {
const { props } = renderCard();
expandCard();
fireEvent.change(screen.getByPlaceholderText("What needs to be done?"), { target: { value: "Plain task" } });
fireEvent.click(screen.getByTestId("save-button"));
await waitFor(() => expect(props.onSubmit).toHaveBeenCalled());
const submitted = vi.mocked(props.onSubmit).mock.calls[0][0];
expect(submitted.workflowId).toBeUndefined();
expect(submitted.column).toBeUndefined();
});
it("does not call the redundant post-create selectTaskWorkflow for the create path", async () => {
const { props } = renderCard();
expandCard();
fireEvent.change(screen.getByPlaceholderText("What needs to be done?"), { target: { value: "Idea for later" } });
const select = await screen.findByLabelText("Workflow") as HTMLSelectElement;
fireEvent.change(select, { target: { value: "builtin:coding-ideas" } });
fireEvent.click(screen.getByTestId("save-button"));
await waitFor(() => expect(props.onSubmit).toHaveBeenCalled());
expect(selectTaskWorkflow).not.toHaveBeenCalled();
});
});
});

View File

@@ -1373,7 +1373,9 @@ describe("QuickEntryBox", () => {
});
});
it("creates task on Enter key with TaskCreateInput", async () => {
// FN-7591: QuickEntryBox must not force column:"triage" — the store resolves the landing column from
// the (selected or default) workflow's intake column, so a manual-intake workflow parks the card instead.
it("creates task on Enter key with TaskCreateInput and without forcing column:triage", async () => {
const { props } = renderQuickEntryBox({});
const textarea = screen.getByTestId("quick-entry-input");
@@ -1384,9 +1386,10 @@ describe("QuickEntryBox", () => {
expect(props.onCreate).toHaveBeenCalledWith(
expect.objectContaining({
description: "New task description",
column: "triage",
}),
);
const submitted = vi.mocked(props.onCreate).mock.calls[0][0];
expect(submitted.column).toBeUndefined();
});
});
@@ -1639,6 +1642,8 @@ describe("QuickEntryBox", () => {
fireEvent.change(screen.getByTestId("quick-entry-input"), { target: { value: "Create in selected workflow" } });
clickSave();
await waitFor(() => expect(onCreate).toHaveBeenCalledWith(expect.objectContaining({ workflowId: "wf-default" })));
// FN-7591: forwarding workflowId at create time must not carry a hard-coded column:"triage".
expect(vi.mocked(onCreate).mock.calls[0][0].column).toBeUndefined();
expect(screen.queryByTestId("plan-button")).not.toBeInTheDocument();
expect(onPlanningMode).not.toHaveBeenCalled();
@@ -3722,7 +3727,7 @@ describe("QuickEntryBox", () => {
expect(localStorage.getItem(QUICK_ENTRY_STORAGE_KEY)).toBeNull();
});
it("clicking save action creates the task", async () => {
it("clicking save action creates the task without forcing column:triage", async () => {
const { props } = renderQuickEntryBox({});
expandQuickEntry();
const textarea = screen.getByTestId("quick-entry-input");
@@ -3734,9 +3739,10 @@ describe("QuickEntryBox", () => {
expect(props.onCreate).toHaveBeenCalledWith(
expect.objectContaining({
description: "Task to save",
column: "triage",
}),
);
const submitted = vi.mocked(props.onCreate).mock.calls[0][0];
expect(submitted.column).toBeUndefined();
});
});

View File

@@ -446,7 +446,9 @@ describe("TodoView", () => {
expect(mockCreateTask).not.toHaveBeenCalled();
});
it("clicking Create Task button calls createTask with item text", async () => {
// FN-7591: TodoView create handlers must not force column:"triage" — the store resolves the landing
// column from the project-default workflow's intake column instead.
it("clicking Create Task button calls createTask with item text and without forcing column:triage", async () => {
const onTaskCreated = vi.fn();
mockCreateTask.mockResolvedValueOnce({ id: "FN-123" });
render(<TodoView addToast={addToast} projectId="project-1" onTaskCreated={onTaskCreated} />);
@@ -455,7 +457,7 @@ describe("TodoView", () => {
await waitFor(() => {
expect(mockCreateTask).toHaveBeenCalledWith(
{ description: "Buy groceries", column: "triage", source: { sourceType: "dashboard_ui" } },
{ description: "Buy groceries", source: { sourceType: "dashboard_ui" } },
"project-1",
);
});
@@ -474,7 +476,7 @@ describe("TodoView", () => {
expect(screen.getByText("Builder")).toBeInTheDocument();
});
it("selecting an agent creates task assigned to that agent", async () => {
it("selecting an agent creates task assigned to that agent without forcing column:triage", async () => {
const onTaskCreated = vi.fn();
mockCreateTask.mockResolvedValueOnce({ id: "FN-234" });
render(<TodoView addToast={addToast} projectId="project-1" onTaskCreated={onTaskCreated} />);
@@ -486,7 +488,7 @@ describe("TodoView", () => {
await waitFor(() => {
expect(mockCreateTask).toHaveBeenCalledWith(
{ description: "Buy groceries", column: "triage", assignedAgentId: "agent-1", source: { sourceType: "dashboard_ui" } },
{ description: "Buy groceries", assignedAgentId: "agent-1", source: { sourceType: "dashboard_ui" } },
"project-1",
);
});

View File

@@ -94,6 +94,22 @@ const CUSTOM_WORKFLOW = {
],
};
/*
FNXC:CodingIdeasWorkflow 2026-07-05-00:00:
A task created under the Coding (Ideas) workflow (manual "ideas" intake, autoTriage:false) must render in the board's
"ideas" lane, not "triage" — mirrors the real builtin:coding-ideas workflow's intake column id/flag shape.
*/
const CODING_IDEAS_WORKFLOW = {
id: "builtin:coding-ideas",
name: "Coding (Ideas)",
columns: [
{ id: "ideas", name: "Ideas", flags: { intake: true } },
{ id: "todo", name: "Todo", flags: { hold: true } },
{ id: "done", name: "Done", flags: { complete: true } },
{ id: "archived", name: "Archived", flags: { archived: true } },
],
};
function mkTask(overrides: Partial<Task> & { id: string }): Task {
return {
title: overrides.id,
@@ -113,7 +129,7 @@ function workflowPayload(taskWorkflowIds: Record<string, string>, flagEnabled =
return {
flagEnabled,
defaultWorkflowId: DEFAULT_WORKFLOW.id,
workflows: flagEnabled ? [DEFAULT_WORKFLOW, CUSTOM_WORKFLOW] : [],
workflows: flagEnabled ? [DEFAULT_WORKFLOW, CUSTOM_WORKFLOW, CODING_IDEAS_WORKFLOW] : [],
taskWorkflowIds,
};
}
@@ -269,6 +285,33 @@ describe("workflow lane quick-create visibility", () => {
expect(screen.getByText(title)).toBeTruthy();
});
it("Board renders a task created under the Coding (Ideas) workflow in the ideas lane", async () => {
const refetch = deferred<BoardWorkflowsPayload>();
fetchBoardWorkflowsMock
.mockResolvedValueOnce(workflowPayload({}))
.mockResolvedValueOnce(workflowPayload({}))
.mockReturnValueOnce(refetch.promise);
render(<BoardHarness />);
await screen.findByTestId("workflow-switcher");
selectWorkflow(CODING_IDEAS_WORKFLOW.id);
await act(async () => {
fireEvent.click(screen.getByTestId("quick-create-ideas"));
});
const ideasColumn = screen.getByTestId("column-ideas");
expect(within(ideasColumn).getByText("Created builtin:coding-ideas")).toBeTruthy();
expect(JSON.parse(ideasColumn.getAttribute("data-task-ids") ?? "[]")).toContain("FN-new");
await act(async () => {
refetch.resolve(workflowPayload({ "FN-new": CODING_IDEAS_WORKFLOW.id }));
await refetch.promise;
});
expect(within(screen.getByTestId("column-ideas")).getByText("Created builtin:coding-ideas")).toBeTruthy();
});
it("leaves the legacy flag-off Board quick-create path unchanged", async () => {
const inputs: TaskCreateInput[] = [];
fetchBoardWorkflowsMock.mockResolvedValue(workflowPayload({}, false));

View File

@@ -37,7 +37,10 @@ describe("useTaskHandlers", () => {
vi.clearAllMocks();
});
it("handleBoardQuickCreate calls createTask with triage column and returns task", async () => {
// FN-7591: handleBoardQuickCreate/handleModalCreate must NOT force column:"triage" — the store resolves the
// landing column from the (selected or default) workflow's intake column, so a manual-intake workflow
// (e.g. Coding (Ideas) → "ideas") parks the card instead of being auto-triaged.
it("handleBoardQuickCreate forwards createTask without forcing a column", async () => {
const options = createOptions();
const { result } = renderHook(() => useTaskHandlers(options));
const input: TaskCreateInput = { description: "Do work" };
@@ -47,11 +50,27 @@ describe("useTaskHandlers", () => {
created = await result.current.handleBoardQuickCreate(input);
});
expect(options.createTask).toHaveBeenCalledWith({ description: "Do work", column: "triage", source: { sourceType: "dashboard_ui" } });
expect(options.createTask).toHaveBeenCalledWith({ description: "Do work", source: { sourceType: "dashboard_ui" } });
expect(created).toEqual(CREATED_TASK);
});
it("handleModalCreate calls createTask with triage column and returns task", async () => {
it("handleBoardQuickCreate forwards an explicit workflowId without forcing a column", async () => {
const options = createOptions();
const { result } = renderHook(() => useTaskHandlers(options));
const input: TaskCreateInput = { description: "Do work", workflowId: "builtin:coding-ideas" };
await act(async () => {
await result.current.handleBoardQuickCreate(input);
});
expect(options.createTask).toHaveBeenCalledWith({
description: "Do work",
workflowId: "builtin:coding-ideas",
source: { sourceType: "dashboard_ui" },
});
});
it("handleModalCreate forwards createTask without forcing a column", async () => {
const options = createOptions();
const { result } = renderHook(() => useTaskHandlers(options));
@@ -60,7 +79,7 @@ describe("useTaskHandlers", () => {
created = await result.current.handleModalCreate({ description: "From modal" });
});
expect(options.createTask).toHaveBeenCalledWith({ description: "From modal", column: "triage", source: { sourceType: "dashboard_ui" } });
expect(options.createTask).toHaveBeenCalledWith({ description: "From modal", source: { sourceType: "dashboard_ui" } });
expect(created).toEqual(CREATED_TASK);
});

View File

@@ -32,16 +32,20 @@ export function useTaskHandlers(options: UseTaskHandlersOptions): UseTaskHandler
addToast,
} = options;
/*
FNXC:CodingIdeasWorkflow 2026-07-05-00:00:
These wrappers previously forced `column: "triage"` (handleBoardQuickCreate defaulted to it when the caller omitted column; handleModalCreate hard-coded it unconditionally), which overrode InlineCreateCard/NewTaskModal even after those callers stopped sending an explicit column. Both must now forward the caller's `column` untouched (usually omitted) so the store resolves the landing column from the (selected or default) workflow's intake column — e.g. Coding (Ideas) → "ideas" — instead of always forcing legacy triage.
*/
const handleBoardQuickCreate = useCallback(
async (input: TaskCreateInput): Promise<Task> => {
return createTask({ ...input, column: input.column ?? "triage", source: { sourceType: "dashboard_ui" } });
return createTask({ ...input, source: { sourceType: "dashboard_ui" } });
},
[createTask],
);
const handleModalCreate = useCallback(
async (input: TaskCreateInput): Promise<Task> => {
const task = await createTask({ ...input, column: "triage", source: { sourceType: "dashboard_ui" } });
const task = await createTask({ ...input, source: { sourceType: "dashboard_ui" } });
return task;
},
[createTask],