FN-6910: hide subtask quick-add behind experiment
Hide the AI subtask-breakdown quick-add action unless the experimental flag is explicitly enabled. - Gate Subtask Breakdown callbacks from App on the default-off subtaskBreakdown experiment. - Remove Subtask quick-add buttons from inline create and quick entry surfaces when no callback is wired. - Add settings, quick-add, documentation, and changeset coverage for the new experiment toggle. Files changed: .changeset/fn-6910-subtask-breakdown-flag.md | 5 ++++ docs/dashboard-guide.md | 2 +- packages/dashboard/app/App.tsx | 8 ++++--- .../dashboard/app/components/InlineCreateCard.tsx | 27 ++++++++++++---------- .../dashboard/app/components/QuickEntryBox.tsx | 27 ++++++++++++---------- .../dashboard/app/components/SettingsModal.tsx | 2 ++ .../components/__tests__/InlineCreateCard.test.tsx | 13 +++++++++++ .../components/__tests__/QuickEntryBox.test.tsx | 12 ++++++++++ .../components/__tests__/SettingsModal.test.tsx | 13 +++++++++++ 9 files changed, 81 insertions(+), 28 deletions(-) Fusion-Task-Id: FN-6910 Fusion-Task-Lineage: a7738b1f-7b41-438d-bd94-79ef56928262
This commit is contained in:
5
.changeset/fn-6910-subtask-breakdown-flag.md
Normal file
5
.changeset/fn-6910-subtask-breakdown-flag.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
Hide the dashboard AI subtask-breakdown quick-add button behind the default-off `subtaskBreakdown` experimental feature flag.
|
||||
@@ -259,7 +259,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. Both buttons remain disabled until the description has content, matching the quick-add row behavior.
|
||||
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.
|
||||
|
||||
## Chat View
|
||||
|
||||
|
||||
@@ -1029,6 +1029,8 @@ function AppInner() {
|
||||
const nodesEnabled = experimentalFeatures.nodesView === true;
|
||||
const researchEnabled = experimentalFeatures.researchView === true;
|
||||
const evalsEnabled = experimentalFeatures.evalsView === true;
|
||||
/* FNXC:QuickAddSubtaskFlag 2026-06-21-00:00: Missing or false `subtaskBreakdown` settings must hide the AI Subtask quick-add handoff across List, Board, and New Task Modal surfaces; only an explicit true wires the callback. */
|
||||
const subtaskBreakdownEnabled = experimentalFeatures.subtaskBreakdown === true;
|
||||
/*
|
||||
FNXC:Navigation 2026-06-19-00:00:
|
||||
Experimental left sidebar navigation replaces the Header view shortcuts with a persistent sidebar on non-mobile project screens, while mobile continues to use the bottom navigation bar as the only primary navigation surface.
|
||||
@@ -1861,7 +1863,7 @@ function AppInner() {
|
||||
onQuickCreate={handleBoardQuickCreate}
|
||||
onNewTask={openNewTaskWithNav}
|
||||
onPlanningMode={openPlanningWithInitialPlanWithNav}
|
||||
onSubtaskBreakdown={openSubtaskBreakdownWithNav}
|
||||
onSubtaskBreakdown={subtaskBreakdownEnabled ? openSubtaskBreakdownWithNav : undefined}
|
||||
autoMerge={autoMerge}
|
||||
onToggleAutoMerge={toggleAutoMerge}
|
||||
globalPaused={globalPaused}
|
||||
@@ -1915,7 +1917,7 @@ function AppInner() {
|
||||
onNewTask={openNewTaskWithNav}
|
||||
onQuickCreate={handleBoardQuickCreate}
|
||||
onPlanningMode={openPlanningWithInitialPlanWithNav}
|
||||
onSubtaskBreakdown={openSubtaskBreakdownWithNav}
|
||||
onSubtaskBreakdown={subtaskBreakdownEnabled ? openSubtaskBreakdownWithNav : undefined}
|
||||
availableModels={availableModels}
|
||||
favoriteProviders={favoriteProviders}
|
||||
favoriteModels={favoriteModels}
|
||||
@@ -2273,7 +2275,7 @@ function AppInner() {
|
||||
handleGitHubImport,
|
||||
}}
|
||||
onPlanningMode={openPlanningWithInitialPlanWithNav}
|
||||
onSubtaskBreakdown={openSubtaskBreakdownWithNav}
|
||||
onSubtaskBreakdown={subtaskBreakdownEnabled ? openSubtaskBreakdownWithNav : undefined}
|
||||
taskOperations={{ moveTask, deleteTask, mergeTask, archiveTask, retryTask, resetTask, duplicateTask }}
|
||||
deepLink={{ handleDetailClose }}
|
||||
settings={{ prAuthAvailable, autoMerge, themeMode, colorTheme, dashboardFontScalePct, shadcnCustomColors, resolvedThemeMode, setThemeMode, setColorTheme, setDashboardFontScalePct, setShadcnCustomColors }}
|
||||
|
||||
@@ -904,18 +904,21 @@ export function InlineCreateCard({
|
||||
<Lightbulb size={12} style={{ verticalAlign: "middle", marginRight: 4 }} />
|
||||
{t("inline.plan", "Plan")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm"
|
||||
onClick={handleSubtaskClick}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
disabled={!description.trim()}
|
||||
data-testid="subtask-button"
|
||||
title={t("inline.breakDownSubtasks", "Break down into AI-generated subtasks")}
|
||||
>
|
||||
<ListTree size={12} style={{ verticalAlign: "middle", marginRight: 4 }} />
|
||||
{t("inline.subtask", "Subtask")}
|
||||
</button>
|
||||
{/* FNXC:QuickAddSubtaskFlag 2026-06-21-00:00: Render no Subtask button or orphaned inline-create click target unless the default-off `subtaskBreakdown` experiment wires this callback. */}
|
||||
{onSubtaskBreakdown && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm"
|
||||
onClick={handleSubtaskClick}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
disabled={!description.trim()}
|
||||
data-testid="subtask-button"
|
||||
title={t("inline.breakDownSubtasks", "Break down into AI-generated subtasks")}
|
||||
>
|
||||
<ListTree size={12} style={{ verticalAlign: "middle", marginRight: 4 }} />
|
||||
{t("inline.subtask", "Subtask")}
|
||||
</button>
|
||||
)}
|
||||
<div className="dep-trigger-wrap">
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -1656,18 +1656,21 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
||||
<Lightbulb size={12} style={{ verticalAlign: "middle", marginRight: 4 }} />
|
||||
{t("tasks.plan", "Plan")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm"
|
||||
onClick={handleSubtaskClick}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
disabled={!description.trim()}
|
||||
data-testid="subtask-button"
|
||||
title={t("tasks.subtaskButtonTitle", "Break down into AI-generated subtasks")}
|
||||
>
|
||||
<ListTree size={12} style={{ verticalAlign: "middle", marginRight: 4 }} />
|
||||
{t("tasks.subtask", "Subtask")}
|
||||
</button>
|
||||
{/* FNXC:QuickAddSubtaskFlag 2026-06-21-00:00: Render no Subtask button or click target unless App wires the default-off `subtaskBreakdown` experiment callback. */}
|
||||
{onSubtaskBreakdown && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm"
|
||||
onClick={handleSubtaskClick}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
disabled={!description.trim()}
|
||||
data-testid="subtask-button"
|
||||
title={t("tasks.subtaskButtonTitle", "Break down into AI-generated subtasks")}
|
||||
>
|
||||
<ListTree size={12} style={{ verticalAlign: "middle", marginRight: 4 }} />
|
||||
{t("tasks.subtask", "Subtask")}
|
||||
</button>
|
||||
)}
|
||||
<div className="refine-trigger-wrap" ref={refineMenuRef}>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -276,6 +276,8 @@ const KNOWN_EXPERIMENTAL_FEATURES: Record<string, string> = {
|
||||
researchView: "Research View",
|
||||
evalsView: "Evals View",
|
||||
goalsView: "Goals View",
|
||||
/* FNXC:QuickAddSubtaskFlag 2026-06-21-00:00: The AI subtask-breakdown quick-add affordance is exposed only through this default-off experimental flag so missing settings keep every quick-add Subtask button hidden. */
|
||||
subtaskBreakdown: "Subtask Breakdown",
|
||||
leftSidebarNav: "Left Sidebar Navigation",
|
||||
rightDock: "Right Dock Panel",
|
||||
sandbox: "Sandbox (command isolation)",
|
||||
|
||||
@@ -197,6 +197,7 @@ function renderCard(
|
||||
addToast: vi.fn(),
|
||||
availableModels: MOCK_MODELS,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
onSubtaskBreakdown: vi.fn(),
|
||||
...overrides,
|
||||
};
|
||||
const result = render(<InlineCreateCard {...props} />);
|
||||
@@ -859,6 +860,18 @@ describe("InlineCreateCard Plan and Subtask buttons", () => {
|
||||
expect((textarea as HTMLTextAreaElement).value).toBe("");
|
||||
});
|
||||
|
||||
it("hides the Subtask quick-add action without leaving an inline controls shell when the callback is omitted", () => {
|
||||
renderCard([], { onSubtaskBreakdown: undefined });
|
||||
expandCard();
|
||||
|
||||
const controlsRow = document.querySelector(".inline-create-controls") as HTMLElement;
|
||||
expect(controlsRow).toBeTruthy();
|
||||
expect(screen.queryByTestId("subtask-button")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTitle("Break down into AI-generated subtasks")).not.toBeInTheDocument();
|
||||
expect(controlsRow.contains(screen.getByTestId("plan-button"))).toBe(true);
|
||||
expect(controlsRow.querySelector(".dep-trigger")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("shows toast when Plan clicked with empty description (via direct handler call)", () => {
|
||||
const addToast = vi.fn();
|
||||
const onPlanningMode = vi.fn();
|
||||
|
||||
@@ -242,6 +242,7 @@ function renderQuickEntryBox(props = {}, { startExpanded = false } = {}) {
|
||||
tasks: mockTasks,
|
||||
availableModels: MOCK_MODELS,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
onSubtaskBreakdown: vi.fn(),
|
||||
};
|
||||
const result = render(<QuickEntryBox {...defaultProps} {...props} />);
|
||||
return { ...result, props: { ...defaultProps, ...props } };
|
||||
@@ -3349,6 +3350,17 @@ describe("QuickEntryBox", () => {
|
||||
expect(actionsContainer.contains(screen.getByTestId("refine-button"))).toBe(true);
|
||||
});
|
||||
|
||||
it("hides the Subtask quick-add action without leaving an action-row shell when the callback is omitted", () => {
|
||||
renderQuickEntryBox({ onSubtaskBreakdown: undefined });
|
||||
expandQuickEntry();
|
||||
|
||||
const actionsContainer = screen.getByTestId("quick-entry-actions");
|
||||
expect(screen.queryByTestId("subtask-button")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTitle("Break down into AI-generated subtasks")).not.toBeInTheDocument();
|
||||
expect(actionsContainer.contains(screen.getByTestId("plan-button"))).toBe(true);
|
||||
expect(actionsContainer.contains(screen.getByTestId("refine-button"))).toBe(true);
|
||||
});
|
||||
|
||||
it("does not render actions when not expanded", () => {
|
||||
renderQuickEntryBox({});
|
||||
toggleQuickEntry();
|
||||
|
||||
@@ -3777,6 +3777,7 @@ describe("SettingsModal", () => {
|
||||
for (const featureLabel of [
|
||||
"Research View",
|
||||
"Evals View",
|
||||
"Subtask Breakdown",
|
||||
"Chat Rooms",
|
||||
"Sandbox (command isolation)",
|
||||
"Planning-style Agent Onboarding",
|
||||
@@ -3788,6 +3789,18 @@ describe("SettingsModal", () => {
|
||||
expect(screen.getAllByLabelText("Dev Server")).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("shows the Subtask Breakdown toggle as off when the setting is missing", async () => {
|
||||
mockFetchSettings.mockResolvedValue({
|
||||
...defaultSettings,
|
||||
experimentalFeatures: {},
|
||||
});
|
||||
|
||||
renderModal();
|
||||
await openExperimentalFeaturesSection();
|
||||
|
||||
expect(screen.getByLabelText("Subtask Breakdown")).not.toBeChecked();
|
||||
});
|
||||
|
||||
it("does not render duplicate Dev Server rows when legacy and canonical keys are both present", async () => {
|
||||
mockFetchSettings.mockResolvedValue({
|
||||
...defaultSettings,
|
||||
|
||||
Reference in New Issue
Block a user