diff --git a/.changeset/tidy-plan-prompt-editor.md b/.changeset/tidy-plan-prompt-editor.md new file mode 100644 index 0000000000..fde61eb263 --- /dev/null +++ b/.changeset/tidy-plan-prompt-editor.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": minor +--- + +summary: Rename the task Definition tab to Plan and add a PROMPT.md editor action. +category: feature +dev: Dashboard task details now open the current task's PROMPT.md via FileBrowserProvider. diff --git a/packages/dashboard/app/components/TaskDetailModal.css b/packages/dashboard/app/components/TaskDetailModal.css index 4f0b9aa716..e2b050abe5 100644 --- a/packages/dashboard/app/components/TaskDetailModal.css +++ b/packages/dashboard/app/components/TaskDetailModal.css @@ -851,9 +851,23 @@ Expanded chat must also cover the task metadata row — priority, execution mode .detail-spec-edit-trigger { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--space-sm); margin-bottom: var(--space-md); } +@media (max-width: 768px) { + .detail-spec-edit-trigger { + align-items: stretch; + } + + .detail-spec-edit-trigger .btn { + flex: 1 1 auto; + } +} + .detail-attachments-grid { display: flex; flex-wrap: wrap; diff --git a/packages/dashboard/app/components/TaskDetailModal.tsx b/packages/dashboard/app/components/TaskDetailModal.tsx index f2baf47624..64aa27a64c 100644 --- a/packages/dashboard/app/components/TaskDetailModal.tsx +++ b/packages/dashboard/app/components/TaskDetailModal.tsx @@ -66,6 +66,7 @@ import { getTaskLogEntryAction, getTaskLogEntryOutcome } from "../utils/taskLogE import { getRelativeTimeBucket } from "../utils/relativeTimeAgo"; import { ACTIVE_STATUSES, resolveEffectiveExecutor, resolveEffectivePlanning, resolveEffectiveValidator, type ModelSelection } from "./effective-model-resolution"; import { TaskContextMenu, buildTaskActionMenuModel, getTaskPrAutomationLabel } from "./TaskContextMenu"; +import { useFileBrowser } from "../context/FileBrowserContext"; const STALE_PAUSED_REVIEW_LOG_REGEX = /^Stale paused review surfaced \[([^\]]+)\]/; const EMPTY_MARKDOWN_CHILD_SEPARATOR = ""; @@ -513,6 +514,7 @@ export function TaskDetailContent({ }: TaskDetailContentProps) { const { t } = useTranslation("app"); const columnLabel = useColumnLabel(); + const fileBrowser = useFileBrowser(); const [activeTab, setActiveTab] = useState(() => resolveDefaultTab(initialTab, task.column)); const [chatExpanded, setChatExpanded] = useState(false); @@ -607,6 +609,9 @@ export function TaskDetailContent({ () => getUnifiedTaskProgress(workingTask), [workingTask.steps, workingTask.enabledWorkflowSteps, workingTask.workflowStepResults], ); + const openPromptFile = useCallback(() => { + fileBrowser?.openFile(`.fusion/tasks/${workingTask.id}/PROMPT.md`, { workspace: "project" }); + }, [fileBrowser, workingTask.id]); const canRetryTask = task.status === "failed" || task.status === "stuck-killed" || @@ -3155,7 +3160,7 @@ export function TaskDetailContent({ className={`detail-tab${activeTab === "definition" ? " detail-tab-active" : ""}`} onClick={() => setActiveTab("definition")} > - {t("taskDetail.tabs.definition", "Definition")} + {t("taskDetail.tabs.definition", "Plan")} + )} diff --git a/packages/dashboard/app/components/__tests__/TaskDetailModal.definition-actions.test.tsx b/packages/dashboard/app/components/__tests__/TaskDetailModal.definition-actions.test.tsx index 69c917df21..72451e72f3 100644 --- a/packages/dashboard/app/components/__tests__/TaskDetailModal.definition-actions.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskDetailModal.definition-actions.test.tsx @@ -15,13 +15,14 @@ import { setupTaskDetailModalHooks, } from "./TaskDetailModal.test-helpers"; import { TaskDetailModal, TaskDetailContent } from "../TaskDetailModal"; +import { FileBrowserProvider } from "../../context/FileBrowserContext"; import { readBoardWorkflowSelection, removeBoardWorkflowSelection, writeBoardWorkflowSelection } from "../../utils/boardWorkflowSelection"; setupTaskDetailModalHooks(); describe("TaskDetailModal", () => { - describe("Definition tab edit mode", () => { - it("shows Edit button in Definition tab", () => { + describe("Plan tab edit mode", () => { + it("shows Edit button in Plan tab", () => { render( { />, ); + expect(screen.getByRole("button", { name: "Plan" })).toBeTruthy(); + expect(screen.queryByRole("button", { name: "Definition" })).toBeNull(); expect(screen.getByText("Edit")).toBeTruthy(); }); + it("opens the task PROMPT.md file from the near-top Plan action", async () => { + const user = userEvent.setup(); + const openFile = vi.fn(); + const { container } = render( + + + , + ); + + const actionRow = container.querySelector(".detail-spec-edit-trigger"); + expect(actionRow).toBeTruthy(); + const promptButton = screen.getByRole("button", { name: "Open PROMPT.md" }); + expect(actionRow?.contains(promptButton)).toBe(true); + + await user.click(promptButton); + + expect(openFile).toHaveBeenCalledWith(".fusion/tasks/FN-099/PROMPT.md", { workspace: "project" }); + }); + it("clicking Edit shows textarea with current prompt content", () => { const { container } = render( { ); // In-progress tasks show exactly 11 tabs: - // Chat, Definition, Logs, Changes, Review, Comments, Artifacts, Model, Workflow, Stats, Routing + // Chat, Plan, Logs, Changes, Review, Comments, Artifacts, Model, Workflow, Stats, Routing const tabs = container.querySelectorAll(".detail-tab"); expect(tabs.length).toBe(11); expect(tabs[0].textContent).toBe("Chat"); - expect(tabs[1].textContent).toBe("Definition"); + expect(tabs[1].textContent).toBe("Plan"); expect(tabs[2].textContent).toBe("Logs"); expect(tabs[3].textContent).toBe("Changes"); expect(tabs[4].textContent).toBe("Review"); @@ -227,7 +258,7 @@ describe("TaskDetailModal", () => { const tabs = container.querySelectorAll(".detail-tab"); expect(tabs.length).toBe(11); expect(tabs[0].textContent).toBe("Chat"); - expect(tabs[1].textContent).toBe("Definition"); + expect(tabs[1].textContent).toBe("Plan"); expect(tabs[2].textContent).toBe("Logs"); expect(tabs[3].textContent).toBe("Changes"); expect(tabs[4].textContent).toBe("Review"); @@ -256,12 +287,12 @@ describe("TaskDetailModal", () => { />, ); - // Done task with commit SHA: Summary, Chat, Definition, Logs, Changes, Review, Comments, Artifacts, Model, Workflow, Stats, Routing (12 tabs, no Commits) + // Done task with commit SHA: Summary, Chat, Plan, Logs, Changes, Review, Comments, Artifacts, Model, Workflow, Stats, Routing (12 tabs, no Commits) const tabs = container.querySelectorAll(".detail-tab"); expect(tabs.length).toBe(12); expect(tabs[0].textContent).toBe("Summary"); expect(tabs[1].textContent).toBe("Chat"); - expect(tabs[2].textContent).toBe("Definition"); + expect(tabs[2].textContent).toBe("Plan"); expect(tabs[3].textContent).toBe("Logs"); expect(tabs[4].textContent).toBe("Changes"); expect(tabs[5].textContent).toBe("Review"); @@ -298,7 +329,7 @@ describe("TaskDetailModal", () => { expect(tabs.length).toBe(12); expect(tabs[0].textContent).toBe("Summary"); expect(tabs[1].textContent).toBe("Chat"); - expect(tabs[2].textContent).toBe("Definition"); + expect(tabs[2].textContent).toBe("Plan"); expect(tabs[3].textContent).toBe("Logs"); expect(tabs[4].textContent).toBe("Changes"); expect(tabs[5].textContent).toBe("Review"); @@ -327,9 +358,9 @@ describe("TaskDetailModal", () => { ); const triageTabs = triageContainer.querySelectorAll(".detail-tab"); - expect(triageTabs.length).toBe(10); // Chat, Definition, Logs, Review, Comments, Artifacts, Model, Workflow, Stats, Routing + expect(triageTabs.length).toBe(10); // Chat, Plan, Logs, Review, Comments, Artifacts, Model, Workflow, Stats, Routing expect(Array.from(triageTabs).map(t => t.textContent)).toEqual([ - "Chat", "Definition", "Logs", "Review", "Comments", "Artifacts", "Model", "Workflow", "Stats", "Routing", + "Chat", "Plan", "Logs", "Review", "Comments", "Artifacts", "Model", "Workflow", "Stats", "Routing", ]); const { container: todoContainer } = render( @@ -346,9 +377,9 @@ describe("TaskDetailModal", () => { ); const todoTabs = todoContainer.querySelectorAll(".detail-tab"); - expect(todoTabs.length).toBe(10); // Chat, Definition, Logs, Review, Comments, Artifacts, Model, Workflow, Stats, Routing + expect(todoTabs.length).toBe(10); // Chat, Plan, Logs, Review, Comments, Artifacts, Model, Workflow, Stats, Routing expect(Array.from(todoTabs).map(t => t.textContent)).toEqual([ - "Chat", "Definition", "Logs", "Review", "Comments", "Artifacts", "Model", "Workflow", "Stats", "Routing", + "Chat", "Plan", "Logs", "Review", "Comments", "Artifacts", "Model", "Workflow", "Stats", "Routing", ]); });