diff --git a/.changeset/fn-7382-triage-auto-approve-toggle.md b/.changeset/fn-7382-triage-auto-approve-toggle.md
new file mode 100644
index 0000000000..98f46dd860
--- /dev/null
+++ b/.changeset/fn-7382-triage-auto-approve-toggle.md
@@ -0,0 +1,7 @@
+---
+"@runfusion/fusion": patch
+---
+
+summary: Add a Triage column shortcut for plan auto-approval.
+category: feature
+dev: Adds a Board column switch that mirrors project planApprovalMode=auto-approve-all.
diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md
index e07cf5cfa1..cc91490a95 100644
--- a/docs/dashboard-guide.md
+++ b/docs/dashboard-guide.md
@@ -149,6 +149,8 @@ FNXC:TaskCardMobileSelection 2026-07-01-00:00: Mobile Board long-press is a task
+
+- The Triage column actions dropdown includes **Auto-approve plan**. Turning it on sets the project plan approval mode to auto-approve all planned tasks; turning it off returns to the workflow/default plan approval behavior. In workflow-mode Boards, the same switch appears on intake/hold planning columns and on the equivalent **All workflows** aggregate intake column. Use Settings → Merge for the full three-state project control, including **Require approval for all tasks**.
- Column ordering semantics: `todo` mirrors scheduler pickup order (priority descending, then oldest `createdAt`, then task ID); `triage`, `in-progress`, `in-review`, and `archived` remain priority-first with task-ID tie-breaks; `done` defaults to most recent completion first (`columnMovedAt`, then `updatedAt`, then `createdAt` fallback) and can be switched from the Done/complete column actions dropdown to descending task ID. In workflow mode, non-archived columns marked with the `complete` flag use the same Done menu items even when their column ID or label is customized.
- Done-column sorting has two descending modes: **Completion date (newest first)** keeps the default completion-time order, while **Task ID (newest first)** places the highest numeric task IDs first. The sort actions are only shown in Done/complete column action menus, including custom workflow completion lanes; Archive All Done lives in the same menu when available.
- On mobile, both default and workflow-mode boards fill the project viewport while the column strip remains the internal horizontal scroller with contained edge overscroll.
diff --git a/docs/settings-reference.md b/docs/settings-reference.md
index 621d04967b..f0e2df603c 100644
--- a/docs/settings-reference.md
+++ b/docs/settings-reference.md
@@ -397,7 +397,7 @@ Security-sensitive file-browser escape hatches are project-only. `allowAbsoluteF
| `overlapIgnorePaths` | `string[]` | `[]` | Optional project-relative file or directory paths to exclude from overlap blocking (for example `docs` or `generated/openapi.json`). Entries are trimmed, deduplicated, and must not be absolute or contain `..` traversal. |
| `allowAbsoluteFileBrowserPaths` | `boolean` | `false` | Project-scoped Settings → General toggle for the workspace file browser. When enabled, slash-prefixed paths such as `/tmp` can be listed/read/written/downloaded through workspace file-browser routes while keeping existing file-size, binary, type, null-byte, traversal, and permission checks. Windows drive-letter paths remain blocked, and task-local file routes, memory APIs, worktree-copy validation, plugin bundle paths, and other validators are unchanged. |
| `autoMerge` | `boolean` | `true` | Auto-finalize tasks from `in-review`. Tasks can override this per-task (including at create time in New Task modal via **Auto-merge** = Default/Enabled/Disabled); explicit overrides are tagged with `autoMergeProvenance: "user"`, while tasks left at **Default** keep following the live global setting and do not snapshot it when entering review. Legacy pre-FN-6245 in-review rows that were stamped `autoMerge: true` are marked `autoMergeProvenance: "legacy-stamp"` on startup and can be inspected/cleared with Settings → Merge → **Legacy auto-merge stamp cleanup**, `fn pr automerge-cleanup [--apply] [--json]`, or `reconcileLegacyAutoMergeStamps({ apply: true })` after operator review. For grouped branch flows, per-task `autoMerge` governs member→group-integration landing while group `autoMerge` governs group→default-branch promotion eligibility. |
-| `planApprovalMode` | `"workflow" \| "auto-approve-all" \| "require-all"` | `"workflow"` | Project-scoped override for the manual planning approval gate. `"workflow"` preserves the workflow-resolved `requirePlanApproval`; `"auto-approve-all"` moves every successfully specified task to `todo` without manual plan approval even when the selected workflow or stored workflow setting has `requirePlanApproval: true`; `"require-all"` parks every specified task at `status: "awaiting-approval"` regardless of workflow settings. This does not disable Workflow Plan Review, release authorization, or other non-plan safety gates. |
+| `planApprovalMode` | `"workflow" \| "auto-approve-all" \| "require-all"` | `"workflow"` | Project-scoped override for the manual planning approval gate. `"workflow"` preserves the workflow-resolved `requirePlanApproval`; `"auto-approve-all"` moves every successfully specified task to `todo` without manual plan approval even when the selected workflow or stored workflow setting has `requirePlanApproval: true`; `"require-all"` parks every specified task at `status: "awaiting-approval"` regardless of workflow settings. Settings → Merge remains the full three-state editor; the Board Triage/intake **Auto-approve plan** switch is a binary shortcut for `"auto-approve-all"` vs `"workflow"`. This does not disable Workflow Plan Review, release authorization, or other non-plan safety gates. |
| `maxAutoMergeRetries` | `number` | `3` | Project-scoped positive-integer cap for auto-merge conflict-resolution retries before Fusion parks or bounces a task for human/recovery handling. Unset, non-finite, zero, or negative values fall back to `3` to preserve historical behavior. |
| `mergeRequestContractShadowEnabled` | `boolean` | `false` | Phase-1 FN-5741 write-only shadow flag (project/global setting). When enabled, executor/self-healing/merger persist merge-request records and `completion_handoff_accepted` markers for observation only; legacy mergeQueue + lifecycle remains authoritative. |
| `mergeStrategy` | `"direct" \| "pull-request"` | `"direct"` | Completion mode (local direct merge vs PR-first). |
diff --git a/packages/dashboard/app/App.tsx b/packages/dashboard/app/App.tsx
index aebef2c01e..eff31fa2fc 100644
--- a/packages/dashboard/app/App.tsx
+++ b/packages/dashboard/app/App.tsx
@@ -551,6 +551,7 @@ function AppInner() {
maxConcurrent,
autoMerge,
mergeStrategy,
+ planAutoApproveEnabled,
showWorktreeGrouping,
globalPaused,
isTestMode,
@@ -575,6 +576,7 @@ function AppInner() {
goalsEnabled,
setQuickChatButtonModeImmediate,
toggleAutoMerge,
+ togglePlanAutoApprove,
refresh: refreshAppSettings,
} = useAppSettings(currentProject?.id);
@@ -1219,6 +1221,7 @@ function AppInner() {
prAuthAvailable,
autoMerge,
mergeStrategy,
+ planAutoApproveEnabled,
settingsLoaded,
taskDetailChatFirst,
skillsEnabled,
@@ -1270,6 +1273,7 @@ function AppInner() {
subtaskBreakdownEnabled,
openSubtaskBreakdownWithNav,
toggleAutoMerge,
+ togglePlanAutoApprove,
globalPaused,
updateTask,
retryTask,
diff --git a/packages/dashboard/app/components/Board.tsx b/packages/dashboard/app/components/Board.tsx
index b23fe1bd2b..9c5dcdaba6 100644
--- a/packages/dashboard/app/components/Board.tsx
+++ b/packages/dashboard/app/components/Board.tsx
@@ -45,6 +45,8 @@ interface BoardProps {
/** Project merge strategy passed to Board-owned card context menus. */
mergeStrategy?: string;
onToggleAutoMerge: () => void;
+ planAutoApproveEnabled: boolean;
+ onTogglePlanAutoApprove: () => void;
globalPaused?: boolean;
onUpdateTask?: (
id: string,
@@ -156,7 +158,7 @@ function BoardWorkflowSkeleton({ empty = false }: { empty?: boolean }) {
);
}
-export function Board({ tasks, projectId, maxConcurrent, showWorktreeGrouping, onMoveTask, onPauseTask, onUnpauseTask, onResetTask, onDuplicateTask, onMergeTask, onOpenDetail, onOpenRefine, onOpenGroupModal, addToast, onQuickCreate, onNewTask, autoMerge, mergeStrategy = "direct", onToggleAutoMerge, globalPaused, onUpdateTask, onRetryTask, onArchiveTask, onUnarchiveTask, onDeleteTask, onArchiveAllDone, onLoadArchivedTasks, searchQuery = "", availableModels, onPlanningMode, onSubtaskBreakdown, onOpenDetailWithTab, favoriteProviders, favoriteModels, onToggleFavorite, onToggleModelFavorite, taskStuckTimeoutMs, onOpenMission, staleHighFanoutBlockerAgeThresholdMs, lastFetchTimeMs, prAuthAvailable, onOpenWorkflowEditor, onCreateWorkflow, workflowColumnsEnabled, settingsLoaded, workflowControlsInHeader = false }: BoardProps) {
+export function Board({ tasks, projectId, maxConcurrent, showWorktreeGrouping, onMoveTask, onPauseTask, onUnpauseTask, onResetTask, onDuplicateTask, onMergeTask, onOpenDetail, onOpenRefine, onOpenGroupModal, addToast, onQuickCreate, onNewTask, autoMerge, mergeStrategy = "direct", onToggleAutoMerge, planAutoApproveEnabled, onTogglePlanAutoApprove, globalPaused, onUpdateTask, onRetryTask, onArchiveTask, onUnarchiveTask, onDeleteTask, onArchiveAllDone, onLoadArchivedTasks, searchQuery = "", availableModels, onPlanningMode, onSubtaskBreakdown, onOpenDetailWithTab, favoriteProviders, favoriteModels, onToggleFavorite, onToggleModelFavorite, taskStuckTimeoutMs, onOpenMission, staleHighFanoutBlockerAgeThresholdMs, lastFetchTimeMs, prAuthAvailable, onOpenWorkflowEditor, onCreateWorkflow, workflowColumnsEnabled, settingsLoaded, workflowControlsInHeader = false }: BoardProps) {
const [archivedCollapsed, setArchivedCollapsed] = useState(true);
/*
FNXC:DoneColumnSorting 2026-06-29-16:57:
@@ -859,6 +861,7 @@ export function Board({ tasks, projectId, maxConcurrent, showWorktreeGrouping, o
prAuthAvailable={prAuthAvailable}
autoMerge={autoMerge}
mergeStrategy={mergeStrategy}
+ {...(((columnDef.flags.intake || columnDef.flags.hold) && !columnDef.flags.archived && !columnDef.flags.complete && !columnDef.flags.countsTowardWip && !columnDef.flags.mergeBlocker && !columnDef.flags.humanReview) ? { planAutoApproveEnabled, onTogglePlanAutoApprove } : {})}
{...(isCreateColumn && aggregateQuickCreateTarget ? { workflowId: aggregateQuickCreateTarget.workflowId, workflowOptions, defaultWorkflowId: boardWorkflows?.defaultWorkflowId ?? null, onQuickCreate: handleAggregateWorkflowQuickCreate, onNewTask, onPlanningMode, onSubtaskBreakdown } : {})}
{...(columnDef.flags.mergeBlocker || columnDef.flags.humanReview ? { onToggleAutoMerge: handleToggleAutoMerge } : {})}
{...(columnDef.id === "done" ? { onArchiveAllDone } : {})}
@@ -938,6 +941,7 @@ export function Board({ tasks, projectId, maxConcurrent, showWorktreeGrouping, o
prAuthAvailable={prAuthAvailable}
autoMerge={autoMerge}
mergeStrategy={mergeStrategy}
+ {...(((columnDef.flags.intake || columnDef.flags.hold) && !columnDef.flags.archived && !columnDef.flags.complete && !columnDef.flags.countsTowardWip && !columnDef.flags.mergeBlocker && !columnDef.flags.humanReview) ? { planAutoApproveEnabled, onTogglePlanAutoApprove } : {})}
{...(isCreateColumn ? { workflowOptions, defaultWorkflowId: selectedWorkflow.id, onQuickCreate: handleWorkflowQuickCreate, onNewTask, onPlanningMode, onSubtaskBreakdown } : {})}
{...(columnDef.flags.mergeBlocker || columnDef.flags.humanReview ? { onToggleAutoMerge: handleToggleAutoMerge } : {})}
{...(columnDef.id === "done" ? { onArchiveAllDone } : {})}
@@ -1045,6 +1049,7 @@ export function Board({ tasks, projectId, maxConcurrent, showWorktreeGrouping, o
prAuthAvailable={prAuthAvailable}
autoMerge={autoMerge}
mergeStrategy={mergeStrategy}
+ {...(col === "triage" ? { planAutoApproveEnabled, onTogglePlanAutoApprove } : {})}
{...(col === "triage" ? { onQuickCreate, onNewTask, onPlanningMode, onSubtaskBreakdown } : {})}
{...(col === "in-review" ? { onToggleAutoMerge: handleToggleAutoMerge } : {})}
{...(col === "done" ? { onArchiveAllDone, doneSortMode, onDoneSortModeChange: setDoneSortMode } : {})}
diff --git a/packages/dashboard/app/components/Column.tsx b/packages/dashboard/app/components/Column.tsx
index 23a4463163..9973a13de8 100644
--- a/packages/dashboard/app/components/Column.tsx
+++ b/packages/dashboard/app/components/Column.tsx
@@ -110,6 +110,8 @@ interface ColumnProps {
/** Project merge strategy for Task Detail-equivalent card context actions. */
mergeStrategy?: string;
onToggleAutoMerge?: () => void;
+ planAutoApproveEnabled?: boolean;
+ onTogglePlanAutoApprove?: () => void;
globalPaused?: boolean;
onUpdateTask?: (
id: string,
@@ -195,7 +197,7 @@ interface ColumnProps {
getDraggingTaskId?: () => string | null;
}
-function ColumnComponent({ column, tasks, projectId, maxConcurrent, showWorktreeGrouping, onMoveTask, onPauseTask, onUnpauseTask, onResetTask, onDuplicateTask, onMergeTask, onOpenDetail, onOpenRefine, onOpenGroupModal, addToast, onQuickCreate, onNewTask, autoMerge, mergeStrategy = "direct", onToggleAutoMerge, globalPaused, onUpdateTask, onRetryTask, onArchiveTask, onUnarchiveTask, onDeleteTask, onArchiveAllDone, doneSortMode, onDoneSortModeChange, collapsed, onToggleCollapse, allTasks, availableModels, onPlanningMode, onSubtaskBreakdown, onOpenDetailWithTab, favoriteProviders, favoriteModels, onToggleFavorite, onToggleModelFavorite, isSearchActive, taskStuckTimeoutMs, onOpenMission, lastFetchTimeMs, taskCardFieldDefs, taskWorkflowBadges, blockerFanoutMap, prAuthAvailable, workflowMode, workflowId, workflowOptions, defaultWorkflowId, columnDisplayName, columnFlags, workflowContextMenuColumns, taskContextMenuColumnsByTaskId, onPromote, canDropTask, getDraggingTaskId }: ColumnProps) {
+function ColumnComponent({ column, tasks, projectId, maxConcurrent, showWorktreeGrouping, onMoveTask, onPauseTask, onUnpauseTask, onResetTask, onDuplicateTask, onMergeTask, onOpenDetail, onOpenRefine, onOpenGroupModal, addToast, onQuickCreate, onNewTask, autoMerge, mergeStrategy = "direct", onToggleAutoMerge, planAutoApproveEnabled, onTogglePlanAutoApprove, globalPaused, onUpdateTask, onRetryTask, onArchiveTask, onUnarchiveTask, onDeleteTask, onArchiveAllDone, doneSortMode, onDoneSortModeChange, collapsed, onToggleCollapse, allTasks, availableModels, onPlanningMode, onSubtaskBreakdown, onOpenDetailWithTab, favoriteProviders, favoriteModels, onToggleFavorite, onToggleModelFavorite, isSearchActive, taskStuckTimeoutMs, onOpenMission, lastFetchTimeMs, taskCardFieldDefs, taskWorkflowBadges, blockerFanoutMap, prAuthAvailable, workflowMode, workflowId, workflowOptions, defaultWorkflowId, columnDisplayName, columnFlags, workflowContextMenuColumns, taskContextMenuColumnsByTaskId, onPromote, canDropTask, getDraggingTaskId }: ColumnProps) {
const { t } = useTranslation("app");
// Anchor the board.rejection.* catalog keys for the i18next extractor (it
// scopes `t` to the useTranslation binding, so the shared translateRejection
@@ -571,8 +573,13 @@ function ColumnComponent({ column, tasks, projectId, maxConcurrent, showWorktree
const isDoneSortColumn = workflowMode ? columnFlags?.complete === true && columnFlags?.archived !== true : column === "done";
const showDoneSortControl = isDoneSortColumn && doneSortMode !== undefined && !!onDoneSortModeChange;
const showDoneArchiveAction = isDoneSortColumn && !!onArchiveAllDone;
+ /*
+ FNXC:PlanApproval 2026-07-01-08:44:
+ Triage and workflow intake/hold column actions need a Board shortcut that mirrors the project auto-approve plan override without replacing Settings modal's full workflow/auto-approve/require-all editor.
+ */
+ const hasPlanAutoApproveAction = !!onTogglePlanAutoApprove;
const hasDoneMenuActions = showDoneSortControl || showDoneArchiveAction;
- const hasColumnMenu = hasColumnBulkActions || hasDoneMenuActions;
+ const hasColumnMenu = hasColumnBulkActions || hasDoneMenuActions || hasPlanAutoApproveAction;
const doneSortControlLabel = t("column.doneSortControlLabel", "Sort Done tasks");
const doneSortOptions: Array<{ mode: DoneColumnSortMode; label: string }> = [
{ mode: "completion-date-desc", label: t("column.doneSortCompletionDateDesc", "Completion date (newest first)") },
@@ -584,6 +591,11 @@ function ColumnComponent({ column, tasks, projectId, maxConcurrent, showWorktree
setIsMenuOpen(false);
}, [onDoneSortModeChange]);
+ const handlePlanAutoApproveToggle = useCallback(() => {
+ onTogglePlanAutoApprove?.();
+ setIsMenuOpen(false);
+ }, [onTogglePlanAutoApprove]);
+
const handleArchiveAll = useCallback(async () => {
setIsMenuOpen(false);
if (!onArchiveAllDone) return;
@@ -664,6 +676,25 @@ function ColumnComponent({ column, tasks, projectId, maxConcurrent, showWorktree
{isMenuOpen && (
+ {hasPlanAutoApproveAction && (
+
+ )}
{showDoneSortControl && (
{doneSortOptions.map((option) => (
diff --git a/packages/dashboard/app/components/__tests__/Board.test.tsx b/packages/dashboard/app/components/__tests__/Board.test.tsx
index 75ee4bafd4..1cee9c8ec8 100644
--- a/packages/dashboard/app/components/__tests__/Board.test.tsx
+++ b/packages/dashboard/app/components/__tests__/Board.test.tsx
@@ -59,6 +59,8 @@ vi.mock("../Column", () => ({
onQuickCreate,
onNewTask,
onToggleAutoMerge,
+ planAutoApproveEnabled,
+ onTogglePlanAutoApprove,
onArchiveAllDone,
favoriteProviders,
favoriteModels,
@@ -83,6 +85,8 @@ vi.mock("../Column", () => ({
onQuickCreate?: unknown;
onNewTask?: unknown;
onToggleAutoMerge?: () => void;
+ planAutoApproveEnabled?: boolean;
+ onTogglePlanAutoApprove?: () => void;
onArchiveAllDone?: unknown;
favoriteProviders?: string[];
favoriteModels?: string[];
@@ -101,7 +105,7 @@ vi.mock("../Column", () => ({
}) => {
columnRenderCounts[column] = (columnRenderCounts[column] ?? 0) + 1;
return (
-
workflow.id))} data-default-workflow-id={defaultWorkflowId ?? ""} data-column-display-name={columnDisplayName ?? ""} data-has-can-drop={canDropTask ? "yes" : "no"} data-has-planning={onPlanningMode ? "yes" : "no"} data-has-subtask={onSubtaskBreakdown ? "yes" : "no"}>
+
workflow.id))} data-default-workflow-id={defaultWorkflowId ?? ""} data-column-display-name={columnDisplayName ?? ""} data-has-can-drop={canDropTask ? "yes" : "no"} data-has-planning={onPlanningMode ? "yes" : "no"} data-has-subtask={onSubtaskBreakdown ? "yes" : "no"}>
{onQuickCreate ? (