FN-8637: restrict Quick Add Start to manual-intake workflows
Restrict Quick Add Start eligibility to verified manual intake lanes. - Require the server-derived manualIntake flag instead of hold alone. - Preserve Coding Ideas routing while hiding Start for Coding's merged planning lane. - Cover desktop and mobile eligibility behavior and document the updated rule. - Add a patch changeset for the corrected workflow gating. Files changed: .changeset/fn-8637-quick-add-start-manual-intake.md | 7 ++++ docs/dashboard-guide.md | 2 +- packages/dashboard/app/components/QuickEntryBox.tsx | 21 ++++++------ packages/dashboard/app/components/__tests__/Column.test.tsx | 20 ++++++++--- packages/dashboard/app/components/__tests__/ListView.test.tsx | 40 ++++++++++++++++++---- packages/dashboard/app/components/__tests__/QuickEntryBox.test.tsx | 21 +++++++++--- packages/dashboard/app/utils/__tests__/quickAddStart.test.ts | 38 ++++++++++++++++++-- packages/dashboard/app/utils/quickAddStart.ts | 9 ++++- 8 files changed, 128 insertions(+), 30 deletions(-) Fusion-Task-Id: FN-8637 Fusion-Task-Lineage: 9652d7d8-f954-49e8-9a76-a2421654baae Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-8637-quick-add-start-manual-intake.md
Normal file
7
.changeset/fn-8637-quick-add-start-manual-intake.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Show Quick Add Start only for workflows with a manual waiting intake lane.
|
||||
category: fix
|
||||
dev: Uses the server-derived manualIntake workflow-column flag instead of hold alone.
|
||||
@@ -634,7 +634,7 @@ Quick Add and Inline Create model selection include Plan, Executor, Reviewer, an
|
||||
<!-- FNXC:QuickAddStart 2026-07-22-17:45: Coding (Ideas) Start is an atomic Todo create, while ordinary Save and Enter remain create-only in Ideas.
|
||||
FNXC:QuickAddStart 2026-07-24-11:20: Start is now a visible action-row button for eligible workflows instead of a hidden long-press/right-click menu on Save; eligibility, snapshotting, and fail-closed routing are unchanged. -->
|
||||
|
||||
Quick Add shows a visible **Start** button in its action row — next to Models/Agent, beside the right-aligned Save — only when the exact selected workflow has complete runtime metadata: a real non-sentinel id, nonempty ordered columns with unique nonblank ids, and an object `flags` value on every column. It is eligible only for validated `builtin:coding-ideas` or a validated workflow whose first visible column is a hold. Start snapshots that exact definition and id before duplicate confirmation and submits it unchanged; later selection or metadata refreshes cannot alter routing. For Coding (Ideas), Start proves that visible ordered metadata places a non-intake, non-complete **Todo** after **Ideas**, then includes Todo in the original Board/List create request—there is no follow-up move. Missing, hidden, malformed, reordered, or ambiguous metadata renders no Start button at all, so Save stays the only create affordance there. With an empty description Start is visible but disabled. Ordinary Save and Enter still create in Ideas. Other eligible hold workflows retain their matching-returned-task promotion through the host Board/List move path only to the first later visible working column, skipping intake, hold, and complete columns; no forward target also remains create-only.
|
||||
Quick Add shows a visible **Start** button in its action row — next to Models/Agent, beside the right-aligned Save — only when the exact selected workflow has complete runtime metadata: a real non-sentinel id, nonempty ordered columns with unique nonblank ids, and an object `flags` value on every column. It is eligible only for validated `builtin:coding-ideas` or a validated workflow whose first visible column is a manual/waiting intake (`manualIntake`); a hold alone is not enough because auto-triaging Planning lanes can also hold cards. Start snapshots that exact definition and id before duplicate confirmation and submits it unchanged; later selection or metadata refreshes cannot alter routing. For Coding (Ideas), Start proves that visible ordered metadata places a non-intake, non-complete **Todo** after **Ideas**, then includes Todo in the original Board/List create request—there is no follow-up move. Missing, hidden, malformed, reordered, or ambiguous metadata renders no Start button at all, so Save stays the only create affordance there. With an empty description Start is visible but disabled. Ordinary Save and Enter still create in Ideas. Other eligible manual-intake workflows retain their matching-returned-task promotion through the host Board/List move path only to the first later visible working column, skipping intake, hold, and complete columns; no forward target also remains create-only.
|
||||
|
||||
Quick Add's paperclip accepts supported photos and files: PNG, JPEG, GIF, WebP, MP4, WebM, QuickTime video, plain text, Markdown, JSON, YAML, TOML, CSV, and XML. Select files, paste them into the Quick Add input, or drag them onto the box; pending attachments upload to the newly created task sequentially. Image attachments show compact previews that open in a movable, resizable window (a full-screen sheet on mobile); file attachments show an accessible filename and remove action without an image-open control. Unsupported selections are ignored, and if one upload fails after task creation, the task remains created while Quick Add reports the filenames that need retrying. The same bottom action row places the GitHub tracking override beside the paperclip; Priority is an icon-only control whose glyph changes by selected level (down arrow for low, flag for normal, up arrow for high, alert for urgent) and is color-coded by urgency (low blue/info, normal muted, high amber/warning, urgent red/error), and Fast is an icon-only lightning control. These icon-only controls keep accessible labels and the same create-payload behavior as the previous text chips.
|
||||
|
||||
|
||||
@@ -372,13 +372,13 @@ export function QuickEntryBox({ onCreate, onMoveTask, addToast, tasks = [], avai
|
||||
const validatedStartWorkflow = useMemo(() => validateQuickAddStartWorkflow(selectedQuickEntryWorkflow), [selectedQuickEntryWorkflow]);
|
||||
const startInitialColumn = validatedStartWorkflow ? resolveQuickAddStartInitialColumn(validatedStartWorkflow) : null;
|
||||
/*
|
||||
FNXC:QuickAddStart 2026-07-24-11:20:
|
||||
FNXC:QuickAddStart 2026-07-31-23:51:
|
||||
Start is a VISIBLE button in the quick-add action row for eligible workflows only, replacing the hidden
|
||||
long-press/right-click Save menu that operators could not discover. Eligibility is unchanged
|
||||
(`workflowSupportsQuickAddStart`: Coding (Ideas), or any workflow whose first visible lane is a hold/"waiting"
|
||||
column) and a provable target is still required (`startInitialColumn` for the create-time column override, or
|
||||
`onMoveTask` for the follow-up move). Workflows without a waiting lane render no Start button at all — Save
|
||||
stays the single create affordance there.
|
||||
long-press/right-click Save menu that operators could not discover. Eligibility is `workflowSupportsQuickAddStart`:
|
||||
Coding (Ideas), or any workflow whose first visible lane is a server-derived manual-intake/"waiting" column.
|
||||
A provable target is still required (`startInitialColumn` for the create-time column override, or `onMoveTask`
|
||||
for the follow-up move). Workflows without a waiting lane render no Start button at all — Save stays the single
|
||||
create affordance there.
|
||||
*/
|
||||
const canQuickAddStart = Boolean(validatedStartWorkflow && workflowSupportsQuickAddStart(validatedStartWorkflow) && (startInitialColumn || onMoveTask));
|
||||
const canQuickAddStartNow = canQuickAddStart && Boolean(description.trim()) && !isSubmitting;
|
||||
@@ -2154,12 +2154,13 @@ export function QuickEntryBox({ onCreate, onMoveTask, addToast, tasks = [], avai
|
||||
)}
|
||||
|
||||
{/*
|
||||
FNXC:QuickAddStart 2026-07-24-11:20:
|
||||
FNXC:QuickAddStart 2026-07-31-23:51:
|
||||
Start renders as the last chip in the options group so it wraps onto the same line as Models/Agent and
|
||||
reads as an alternate create action beside Save (which stays right-aligned in the primary group). It is
|
||||
present ONLY for hold-first/"waiting"-column workflows — most workflows show no Start chip. With an
|
||||
empty description it stays visible but DISABLED (matching Save) so the affordance does not appear and
|
||||
vanish as the operator types; the whole action row still unmounts while a create is in flight.
|
||||
present ONLY for manual-intake/"waiting"-first workflows — `hold` alone is not eligibility because the
|
||||
merged auto-triaging Planning lane also holds cards. With an empty description it stays visible but
|
||||
DISABLED (matching Save) so the affordance does not appear and vanish as the operator types; the whole
|
||||
action row still unmounts while a create is in flight.
|
||||
*/}
|
||||
{canQuickAddStart && (
|
||||
<button
|
||||
|
||||
@@ -34,7 +34,10 @@ vi.mock("../WorktreeGroup", () => ({
|
||||
),
|
||||
}));
|
||||
vi.mock("../QuickEntryBox", () => ({
|
||||
QuickEntryBox: ({ favoriteProviders, favoriteModels, onToggleFavorite, onToggleModelFavorite, autoExpand, onCreate, onMoveTask }: { favoriteProviders?: string[]; favoriteModels?: string[]; onToggleFavorite?: (provider: string) => void; onToggleModelFavorite?: (modelId: string) => void; autoExpand?: boolean; onCreate?: (input: { description: string; workflowId?: string; column?: string }) => void; onMoveTask?: (id: string, column: string) => Promise<unknown> }) => (
|
||||
QuickEntryBox: ({ favoriteProviders, favoriteModels, onToggleFavorite, onToggleModelFavorite, autoExpand, onCreate, onMoveTask, workflowId, workflowOptions }: { favoriteProviders?: string[]; favoriteModels?: string[]; onToggleFavorite?: (provider: string) => void; onToggleModelFavorite?: (modelId: string) => void; autoExpand?: boolean; onCreate?: (input: { description: string; workflowId?: string; column?: string }) => void; onMoveTask?: (id: string, column: string) => Promise<unknown>; workflowId?: string; workflowOptions?: { id: string; columns?: { flags?: { manualIntake?: boolean } }[] }[] }) => {
|
||||
const selectedWorkflow = workflowOptions?.find((option) => option.id === workflowId);
|
||||
const showStart = workflowId === "builtin:coding-ideas" || selectedWorkflow?.columns?.[0]?.flags?.manualIntake === true;
|
||||
return (
|
||||
<div
|
||||
data-testid="quick-entry-box"
|
||||
data-favorite-providers={JSON.stringify(favoriteProviders ?? [])}
|
||||
@@ -44,10 +47,11 @@ vi.mock("../QuickEntryBox", () => ({
|
||||
data-auto-expand={autoExpand === false ? "false" : "true"}
|
||||
>
|
||||
<button type="button" onClick={() => onCreate?.({ description: "Quick task" })}>create</button>
|
||||
<button type="button" onClick={() => onCreate?.({ description: "Started task", workflowId: "builtin:coding-ideas", column: "todo" })}>start</button>
|
||||
{showStart && <button type="button" data-testid="quick-entry-start" onClick={() => onCreate?.({ description: "Started task", workflowId: "builtin:coding-ideas", column: "todo" })}>start</button>}
|
||||
<button type="button" data-testid="quick-entry-move" onClick={() => void onMoveTask?.("FN-created", "todo")}>move</button>
|
||||
</div>
|
||||
),
|
||||
);
|
||||
},
|
||||
}));
|
||||
vi.mock("lucide-react", () => ({
|
||||
Link: () => null,
|
||||
@@ -842,9 +846,9 @@ describe("Column QuickEntryBox", () => {
|
||||
|
||||
it("preserves the explicit Coding Ideas Start column in workflow mode", async () => {
|
||||
const onQuickCreate = vi.fn().mockResolvedValue({});
|
||||
render(<Column {...defaultProps} column="ideas" workflowMode workflowId="builtin:coding-ideas" tasks={[]} onQuickCreate={onQuickCreate} />);
|
||||
render(<Column {...defaultProps} column="ideas" workflowMode workflowId="builtin:coding-ideas" workflowOptions={[{ id: "builtin:coding-ideas", name: "Coding (Ideas)", columns: [{ id: "ideas", name: "Ideas", flags: { intake: true, hold: true, manualIntake: true } }] }]} tasks={[]} onQuickCreate={onQuickCreate} />);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "start" }));
|
||||
fireEvent.click(screen.getByTestId("quick-entry-start"));
|
||||
|
||||
await waitFor(() => expect(onQuickCreate).toHaveBeenCalledWith({
|
||||
description: "Started task",
|
||||
@@ -853,6 +857,12 @@ describe("Column QuickEntryBox", () => {
|
||||
}));
|
||||
});
|
||||
|
||||
it("does not expose a Quick Add Start control for Coding's merged intake/hold lane", () => {
|
||||
render(<Column {...defaultProps} workflowMode workflowId="builtin:coding" workflowOptions={[{ id: "builtin:coding", name: "Coding", columns: [{ id: "planning", name: "Planning", flags: { intake: true, hold: true } }] }]} tasks={[]} onQuickCreate={vi.fn()} />);
|
||||
|
||||
expect(screen.queryByTestId("quick-entry-start")).toBeNull();
|
||||
});
|
||||
|
||||
it("preserves selected built-in workflow id when quick-creating in workflow mode", async () => {
|
||||
const onQuickCreate = vi.fn().mockResolvedValue({});
|
||||
render(
|
||||
|
||||
@@ -63,7 +63,7 @@ vi.mock("../QuickEntryBox", () => ({
|
||||
onPlanningMode?: (initialPlan: string, workflowId?: string | null) => void;
|
||||
onSubtaskBreakdown?: (description: string, workflowId?: string | null) => void;
|
||||
workflowId?: string | null;
|
||||
workflowOptions?: { id: string; name: string }[];
|
||||
workflowOptions?: { id: string; name: string; columns?: Array<{ flags?: { manualIntake?: boolean } }> }[];
|
||||
defaultWorkflowId?: string | null;
|
||||
onMoveTask?: (id: string, column: string) => Promise<unknown>;
|
||||
}) => {
|
||||
@@ -92,6 +92,11 @@ vi.mock("../QuickEntryBox", () => ({
|
||||
}
|
||||
};
|
||||
|
||||
const selectedWorkflow = workflowOptions?.find((option) => option.id === selectedWorkflowId);
|
||||
const showStart = selectedWorkflowId === "builtin:coding-ideas"
|
||||
|| defaultWorkflowId === "builtin:coding-ideas"
|
||||
|| selectedWorkflow?.columns?.[0]?.flags?.manualIntake === true;
|
||||
|
||||
const handoff = (callback?: (description: string, workflowId?: string | null) => void) => {
|
||||
const description = value.trim();
|
||||
if (!description || !callback) return;
|
||||
@@ -148,9 +153,11 @@ vi.mock("../QuickEntryBox", () => ({
|
||||
<button type="button" data-testid="quick-entry-save" onClick={() => void submit()}>
|
||||
Save
|
||||
</button>
|
||||
<button type="button" data-testid="quick-entry-start" onClick={() => void onCreate?.({ description: "Started task", workflowId: "builtin:coding-ideas", column: "todo" })}>
|
||||
Start
|
||||
</button>
|
||||
{showStart && (
|
||||
<button type="button" data-testid="quick-entry-start" onClick={() => void onCreate?.({ description: "Started task", workflowId: "builtin:coding-ideas", column: "todo" })}>
|
||||
Start
|
||||
</button>
|
||||
)}
|
||||
<button type="button" data-testid="quick-entry-move" onClick={() => void onMoveTask?.("FN-created", "todo")}>
|
||||
Move
|
||||
</button>
|
||||
@@ -4081,14 +4088,14 @@ describe("ListView Quick Entry", () => {
|
||||
id: "builtin:coding-ideas",
|
||||
name: "Coding (Ideas)",
|
||||
columns: [
|
||||
{ id: "ideas", name: "Ideas", flags: { intake: true } },
|
||||
{ id: "ideas", name: "Ideas", flags: { intake: true, hold: true, manualIntake: true } },
|
||||
{ id: "todo", name: "Todo", flags: { hold: true } },
|
||||
],
|
||||
}],
|
||||
taskWorkflowIds: {},
|
||||
});
|
||||
renderListView({ onQuickCreate });
|
||||
await screen.findByTestId("quick-entry-box");
|
||||
await waitFor(() => expect(screen.getByTestId("quick-entry-workflow-props")).toHaveAttribute("data-default-workflow-id", "builtin:coding-ideas"));
|
||||
fireEvent.click(screen.getByTestId("quick-entry-start"));
|
||||
|
||||
await waitFor(() => expect(onQuickCreate).toHaveBeenCalledWith(expect.objectContaining({
|
||||
@@ -4098,6 +4105,27 @@ describe("ListView Quick Entry", () => {
|
||||
})));
|
||||
});
|
||||
|
||||
it.each([
|
||||
["desktop", mockDesktopViewport],
|
||||
["mobile", mockMobileViewport],
|
||||
])("does not expose Quick Add Start for Coding's merged intake/hold lane on %s", async (_label, mockViewport) => {
|
||||
mockViewport();
|
||||
vi.mocked(fetchBoardWorkflows).mockResolvedValue({
|
||||
flagEnabled: true,
|
||||
defaultWorkflowId: "builtin:coding",
|
||||
workflows: [{
|
||||
id: "builtin:coding",
|
||||
name: "Coding",
|
||||
columns: [{ id: "planning", name: "Planning", flags: { intake: true, hold: true } }],
|
||||
}],
|
||||
taskWorkflowIds: {},
|
||||
});
|
||||
renderListView({ onQuickCreate: vi.fn() });
|
||||
await waitFor(() => expect(screen.getByTestId("quick-entry-workflow-props")).toHaveAttribute("data-default-workflow-id", "builtin:coding"));
|
||||
|
||||
expect(screen.queryByTestId("quick-entry-start")).toBeNull();
|
||||
});
|
||||
|
||||
it("wires QuickEntry Start moves through the list host callback", async () => {
|
||||
const onMoveTask = vi.fn().mockResolvedValue(createMockTask({ id: "FN-created", column: "todo" }));
|
||||
renderListView({ onQuickCreate: vi.fn(), onMoveTask });
|
||||
|
||||
@@ -5562,7 +5562,7 @@ describe("QuickEntryBox", () => {
|
||||
id: "custom:waiting",
|
||||
name: "Waiting first",
|
||||
columns: [
|
||||
{ id: "waiting", name: "Waiting", flags: { hold: true } },
|
||||
{ id: "waiting", name: "Waiting", flags: { intake: true, hold: true, manualIntake: true } },
|
||||
{ id: "working", name: "Working", flags: {} },
|
||||
{ id: "done", name: "Done", flags: { complete: true } },
|
||||
],
|
||||
@@ -5645,13 +5645,26 @@ describe("QuickEntryBox", () => {
|
||||
expect(onMoveTask).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("renders no Start button or shell for unrelated or malformed workflow metadata and keeps Save create-only", async () => {
|
||||
const onCreate = vi.fn().mockResolvedValue({ ...CREATED_TASK, id: "FN-plain", column: "ideas", workflowId: "custom" });
|
||||
it.each([
|
||||
["desktop", mockDesktopViewport],
|
||||
["mobile", mockMobileViewport],
|
||||
])("renders no Start button or shell for Coding and malformed workflow metadata on %s", async (_label, mockViewport) => {
|
||||
mockViewport();
|
||||
const onCreate = vi.fn().mockResolvedValue({ ...CREATED_TASK, id: "FN-plain", column: "planning", workflowId: "builtin:coding" });
|
||||
const onMoveTask = vi.fn();
|
||||
const { rerender } = renderQuickEntryBox({ onCreate, onMoveTask, workflowId: "custom", workflowOptions: [{ ...ideasWorkflow, id: "custom", columns: [{ id: "ideas", name: "Ideas", flags: { intake: true } }, ...ideasWorkflow.columns.slice(1)] }] });
|
||||
const codingWorkflow = {
|
||||
id: "builtin:coding",
|
||||
name: "Coding",
|
||||
columns: [
|
||||
{ id: "planning", name: "Planning", flags: { intake: true, hold: true } },
|
||||
{ id: "todo", name: "Todo", flags: {} },
|
||||
],
|
||||
};
|
||||
const { rerender } = renderQuickEntryBox({ onCreate, onMoveTask, workflowId: codingWorkflow.id, workflowOptions: [codingWorkflow] });
|
||||
enterDescription();
|
||||
expect(screen.queryByTestId("quick-entry-save-start")).toBeNull();
|
||||
expect(screen.queryByRole("button", { name: "Start" })).toBeNull();
|
||||
expect(screen.queryByTitle("Create and start the task")).toBeNull();
|
||||
clickSave();
|
||||
await waitFor(() => expect(onCreate).toHaveBeenCalled());
|
||||
expect(onMoveTask).not.toHaveBeenCalled();
|
||||
|
||||
@@ -13,9 +13,41 @@ const workflow = (overrides: Record<string, unknown> = {}) => ({
|
||||
});
|
||||
|
||||
describe("quick add Start workflow guards", () => {
|
||||
it("requires complete runtime metadata before builtin or hold eligibility", () => {
|
||||
expect(workflowSupportsQuickAddStart(validateQuickAddStartWorkflow(workflow({ id: "builtin:coding-ideas" })))).toBe(true);
|
||||
expect(workflowSupportsQuickAddStart(validateQuickAddStartWorkflow(workflow()))).toBe(true);
|
||||
it("limits resolved built-ins to Coding Ideas", () => {
|
||||
const resolvedBuiltinFirstColumns = [
|
||||
["builtin:coding-ideas", { intake: true, hold: true, manualIntake: true }, true],
|
||||
["builtin:coding", { intake: true, hold: true }, false],
|
||||
["builtin:quick-fix", { intake: true, hold: true }, false],
|
||||
["builtin:stepwise-coding", { intake: true, hold: true }, false],
|
||||
["builtin:review-heavy", { intake: true, hold: true }, false],
|
||||
["builtin:design", { intake: true, hold: true }, false],
|
||||
["builtin:compound-engineering", { intake: true, hold: true }, false],
|
||||
["builtin:marketing", { intake: true, hold: true }, false],
|
||||
["builtin:pr-workflow", { intake: true, hold: true }, false],
|
||||
] as const;
|
||||
|
||||
for (const [id, flags, expected] of resolvedBuiltinFirstColumns) {
|
||||
expect(workflowSupportsQuickAddStart(validateQuickAddStartWorkflow(workflow({
|
||||
id,
|
||||
columns: [{ id: "planning", flags }, { id: "done", flags: { complete: true } }],
|
||||
})))).toBe(expected);
|
||||
}
|
||||
});
|
||||
|
||||
it("requires a first visible manual intake and complete runtime metadata", () => {
|
||||
expect(workflowSupportsQuickAddStart(validateQuickAddStartWorkflow(workflow()))).toBe(false);
|
||||
expect(workflowSupportsQuickAddStart(validateQuickAddStartWorkflow(workflow({ columns: [
|
||||
{ id: "waiting", flags: { intake: true, hold: true, manualIntake: true } },
|
||||
{ id: "todo", flags: {} },
|
||||
] })))).toBe(true);
|
||||
expect(workflowSupportsQuickAddStart(validateQuickAddStartWorkflow(workflow({ columns: [
|
||||
{ id: "archived", flags: { archived: true, manualIntake: true } },
|
||||
{ id: "waiting", flags: { manualIntake: true } },
|
||||
] })))).toBe(true);
|
||||
expect(workflowSupportsQuickAddStart(validateQuickAddStartWorkflow(workflow({ columns: [
|
||||
{ id: "hidden", flags: { hiddenFromBoard: true, manualIntake: true } },
|
||||
{ id: "planning", flags: { intake: true, hold: true } },
|
||||
] })))).toBe(false);
|
||||
expect(validateQuickAddStartWorkflow(workflow({ id: "__all_workflows__" }))).toBeNull();
|
||||
expect(validateQuickAddStartWorkflow(workflow({ columns: [] }))).toBeNull();
|
||||
expect(validateQuickAddStartWorkflow(workflow({ columns: [{ id: "", flags: {} }] }))).toBeNull();
|
||||
|
||||
@@ -27,10 +27,17 @@ function visibleColumns(workflow: ValidatedQuickAddWorkflow) {
|
||||
return workflow.columns.filter((column) => !column.flags.archived && !column.flags.hiddenFromBoard);
|
||||
}
|
||||
|
||||
/*
|
||||
* FNXC:QuickAddStart 2026-07-31-23:51:
|
||||
* Start is reserved for a workflow's first visible manual/waiting intake lane. `hold` alone is
|
||||
* insufficient because the canonical merged Planning column carries both `intake` and `hold` while
|
||||
* auto-triaging. Mirror TaskCard.showStartAction's server-derived `manualIntake` fact so absent
|
||||
* older payload metadata fails closed without exposing an unusable Quick Add action.
|
||||
*/
|
||||
export function workflowSupportsQuickAddStart(workflow: ValidatedQuickAddWorkflow | null): boolean {
|
||||
if (!workflow) return false;
|
||||
if (workflow.id === "builtin:coding-ideas") return true;
|
||||
return visibleColumns(workflow)[0]?.flags.hold === true;
|
||||
return visibleColumns(workflow)[0]?.flags.manualIntake === true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user