diff --git a/.changeset/fn-8481-ideas-github-tracking.md b/.changeset/fn-8481-ideas-github-tracking.md new file mode 100644 index 0000000000..11c795cc0f --- /dev/null +++ b/.changeset/fn-8481-ideas-github-tracking.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Let operators enable or disable GitHub tracking from Coding Ideas task details. +category: feature +dev: GitHub tracking eligibility now recognizes the Ideas intake column and `builtin:coding-ideas` workflow ID. diff --git a/packages/dashboard/app/components/TaskDetailModal.tsx b/packages/dashboard/app/components/TaskDetailModal.tsx index 5c489184e1..aa1dbcf176 100644 --- a/packages/dashboard/app/components/TaskDetailModal.tsx +++ b/packages/dashboard/app/components/TaskDetailModal.tsx @@ -650,7 +650,16 @@ function getProvenanceLabel(task: Task | TaskDetail, options: ProvenanceLabelOpt // #1403: widened to ColumnId so `.has(task.column)` accepts custom column ids // (non-members correctly resolve to false → not editable). const EDITABLE_COLUMNS: Set = new Set(["triage", "todo"]); -const GITHUB_TRACKING_EDITABLE_COLUMNS: Set = new Set(["triage", "todo", "in-progress", "in-review"]); +const GITHUB_TRACKING_EDITABLE_COLUMNS: Set = new Set(["triage", "todo", "in-progress", "in-review", "ideas"]); +const CODING_IDEAS_WORKFLOW_ID = "builtin:coding-ideas"; + +/* +FNXC:GitHubTracking 2026-07-22-00:46: +Ideas tasks must be able to opt into or out of GitHub tracking before planning, whether they remain in the Ideas intake column or have advanced in Coding (Ideas). Use the resolved workflow ID rather than its display name so localized names and arbitrary custom workflows cannot gain this editing capability. +*/ +function canTaskEditGithubTracking(column: ColumnId, workflowId: string | undefined): boolean { + return GITHUB_TRACKING_EDITABLE_COLUMNS.has(column) || workflowId === CODING_IDEAS_WORKFLOW_ID; +} export function TaskDetailContent({ task, @@ -1618,7 +1627,7 @@ export function TaskDetailContent({ // Check if task can be edited const canEdit = EDITABLE_COLUMNS.has(task.column) && !isSaving; - const canEditGithubTracking = GITHUB_TRACKING_EDITABLE_COLUMNS.has(task.column) && !isSaving; + const canEditGithubTracking = canTaskEditGithubTracking(task.column, taskWorkflowBadge?.id) && !isSaving; const githubTrackingEnabled = githubTrackingEnabledDraft ?? (workingTask.githubTracking?.enabled === true); const githubTrackedIssue = workingTask.githubTracking?.issue; const gitlabTrackedItem = workingTask.gitlabTracking?.item; diff --git a/packages/dashboard/app/components/__tests__/TaskDetailModal.inline-editing-and-integrations.test.tsx b/packages/dashboard/app/components/__tests__/TaskDetailModal.inline-editing-and-integrations.test.tsx index fbc209a9a8..3943f49d0e 100644 --- a/packages/dashboard/app/components/__tests__/TaskDetailModal.inline-editing-and-integrations.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskDetailModal.inline-editing-and-integrations.test.tsx @@ -3414,14 +3414,50 @@ describe("TaskDetailModal inline action row parity (FN-8194)", () => { expect(toggle).toHaveClass("btn-primary"); }); - it("hides the GitHub toggle for non-editable and GitLab-tracked tasks", () => { + it("enables GitHub tracking for Ideas intake tasks after workflow metadata fails", async () => { + const { updateTask, fetchBoardWorkflows } = await import("../../api"); + const mockUpdate = vi.mocked(updateTask); + vi.mocked(fetchBoardWorkflows).mockRejectedValueOnce(new Error("workflow metadata unavailable")); + mockUpdate.mockResolvedValueOnce(makeTask({ id: "FN-ideas", column: "ideas", githubTracking: { enabled: true } }) as Task); + + renderDetail(makeTask({ id: "FN-ideas", column: "ideas", githubTracking: { enabled: false } })); + + const toggle = screen.getByTestId("detail-inline-github-toggle"); + expect(toggle).toHaveAttribute("aria-pressed", "false"); + fireEvent.click(toggle); + + await waitFor(() => { + expect(mockUpdate).toHaveBeenCalledWith("FN-ideas", { githubTracking: { enabled: true } }, undefined); + }); + expect(toggle).toHaveAttribute("aria-pressed", "true"); + }); + + it("shows the GitHub toggle after Coding (Ideas) workflow metadata resolves", async () => { + const { fetchBoardWorkflows } = await import("../../api"); + vi.mocked(fetchBoardWorkflows).mockResolvedValueOnce({ + flagEnabled: true, + defaultWorkflowId: "builtin:coding", + workflows: [ + { id: "builtin:coding", name: "Coding", columns: [{ id: "done", name: "Done", flags: {} }] }, + { id: "builtin:coding-ideas", name: "Coding (Ideas)", columns: [{ id: "done", name: "Done", flags: {} }] }, + ], + taskWorkflowIds: { "FN-ideas-workflow": "builtin:coding-ideas" }, + }); + + renderDetail(makeTask({ id: "FN-ideas-workflow", column: "done", githubTracking: { enabled: false } })); + + expect(screen.queryByTestId("detail-inline-github-toggle")).not.toBeInTheDocument(); + expect(await screen.findByTestId("detail-inline-github-toggle")).toHaveAttribute("aria-pressed", "false"); + }); + + it("hides the GitHub toggle for unrelated and GitLab-tracked tasks", () => { const { unmount } = renderDetail(makeTask({ id: "FN-8194", column: "done", githubTracking: { enabled: true } })); expect(screen.queryByTestId("detail-inline-github-toggle")).not.toBeInTheDocument(); unmount(); renderDetail(makeTask({ id: "FN-8195", - column: "todo", + column: "ideas", gitlabTracking: { item: { kind: "project_issue",