diff --git a/.changeset/fn-7271-mobile-workflow-badge.md b/.changeset/fn-7271-mobile-workflow-badge.md new file mode 100644 index 0000000000..608f87552b --- /dev/null +++ b/.changeset/fn-7271-mobile-workflow-badge.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Move the mobile task-detail workflow badge beside the updated timestamp. +category: fix +dev: Keeps the desktop task-detail header badge while showing a mobile-only timestamp-group badge. diff --git a/packages/dashboard/app/components/TaskDetailModal.css b/packages/dashboard/app/components/TaskDetailModal.css index 8621ac01ae..1edb2a85dd 100644 --- a/packages/dashboard/app/components/TaskDetailModal.css +++ b/packages/dashboard/app/components/TaskDetailModal.css @@ -57,6 +57,10 @@ The gray top header band (task id + column badge) was over-padded. Trim its vert background: var(--surface-raised); border: 1px solid var(--border); } + +.detail-workflow-badge--mobile { + display: none; +} .badge-triage { background: var(--status-triage-bg); color: var(--triage); @@ -308,12 +312,26 @@ The task-detail modal metadata must keep priority, execution mode, provenance, P align-items: center; } + /* + FNXC:TaskDetailWorkflow 2026-06-30-07:37: + Mobile task details need the workflow-name badge to read with the Updated timestamp instead of the compact header band. Keep desktop's header badge placement while showing exactly one mobile badge inside the timestamp group. + */ + .detail-workflow-badge--desktop { + display: none; + } + .detail-timestamps { display: flex; align-items: center; flex-wrap: nowrap; } + .detail-timestamps .detail-workflow-badge--mobile { + display: inline-flex; + align-items: center; + flex: 0 0 auto; + } + .detail-provenance-context { max-width: 20ch; } diff --git a/packages/dashboard/app/components/TaskDetailModal.tsx b/packages/dashboard/app/components/TaskDetailModal.tsx index 6a6a2895e7..e9a2239388 100644 --- a/packages/dashboard/app/components/TaskDetailModal.tsx +++ b/packages/dashboard/app/components/TaskDetailModal.tsx @@ -2717,7 +2717,7 @@ export function TaskDetailContent({ {columnLabel(task.column)} {taskWorkflowName && ( - + {taskWorkflowName} )} @@ -3093,6 +3093,11 @@ export function TaskDetailContent({ {formatTimestamp(task.updatedAt)} + {taskWorkflowName && ( + + {taskWorkflowName} + + )} {task.branchContext?.groupId && ( diff --git a/packages/dashboard/app/components/__tests__/TaskDetailModal.rendering.test.tsx b/packages/dashboard/app/components/__tests__/TaskDetailModal.rendering.test.tsx index 87c50c0741..ad5815aa5b 100644 --- a/packages/dashboard/app/components/__tests__/TaskDetailModal.rendering.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskDetailModal.rendering.test.tsx @@ -170,10 +170,10 @@ describe("TaskDetailModal", () => { expect(container.querySelector(".detail-workflow-badge")).toBeNull(); }); - it("renders in the mobile back-header variant", async () => { + it("renders beside the Updated timestamp in the mobile back-header variant", async () => { vi.mocked(dashboardApi.fetchBoardWorkflows).mockResolvedValueOnce(workflowPayload); - render( + const { container } = render( { ); expect(await screen.findByTestId("task-detail-workflow-badge")).toHaveTextContent("Docs"); + const mobileBadge = screen.getByTestId("task-detail-workflow-badge-mobile"); + const timestamps = container.querySelector(".detail-timestamps"); + const updatedLabel = screen.getByText("Updated").closest(".detail-timestamp-item"); + expect(mobileBadge).toHaveTextContent("Docs"); + expect(mobileBadge.parentElement).toBe(timestamps); + expect(updatedLabel?.nextElementSibling).toBe(mobileBadge); expect(screen.getByRole("button", { name: "Back to task list" })).toBeInTheDocument(); }); }); diff --git a/packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx b/packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx index c678eee0fd..74f89b6c0d 100644 --- a/packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx @@ -120,6 +120,20 @@ describe("TaskDetailModal", () => { expect(css).not.toMatch(/@media[^{]*\(max-width: 768px\)[^{]*\{[\s\S]*?\.detail-timestamps\s*\{[^}]*flex-direction:\s*column;/); expect(css).not.toMatch(/@media[^{]*\(max-width: 768px\)[^{]*\{[\s\S]*?\.detail-timestamp-separator\s*\{[^}]*display:\s*none;/); }); + + it("places only the mobile workflow badge inside the timestamp group at the mobile breakpoint", () => { + const css = readDashboardStylesSource(); + const mobileBlock = getCssAtRuleBlockContaining(css, "@media (max-width: 768px)", ".detail-timestamps .detail-workflow-badge--mobile"); + const mobileTimestampBadgeBlock = getCssRuleBlock(mobileBlock, ".detail-timestamps .detail-workflow-badge--mobile"); + const mobileDesktopBadgeBlock = getCssRuleBlock(mobileBlock, ".detail-workflow-badge--desktop"); + + expectBaseRule(css, ".detail-workflow-badge--mobile", "display: none;"); + expect(mobileDesktopBadgeBlock).toContain("display: none;"); + expect(mobileTimestampBadgeBlock).toContain("display: inline-flex;"); + expect(mobileTimestampBadgeBlock).toContain("align-items: center;"); + expect(mobileTimestampBadgeBlock).toContain("flex: 0 0 auto;"); + expect(css).not.toMatch(/@media[^{]*\(max-width: 768px\)[^{]*\{[\s\S]*?\.detail-title-row\s+\.detail-workflow-badge--mobile\s*\{/); + }); it("keeps desktop and mobile modal sizing guards unchanged", () => { const css = readDashboardStylesSource(); const mobileBlock = getCssAtRuleBlockContaining(css, "@media (max-width: 768px)", ".modal-overlay:has(.task-detail-modal)"); @@ -737,7 +751,7 @@ describe("TaskDetailModal", () => { expect(screen.queryByRole("button", { name: "Finish & Close" })).toBeNull(); }); - it("shows Start PR Review and calls onMergeTask for pull-request strategy when autoMerge is off and no PR exists", async () => { + it("shows Start PR Review and opens PR creation for pull-request strategy when autoMerge is off and no PR exists", async () => { const { fetchSettings } = await import("../../api"); const onMergeTask = vi.fn(async () => ({ merged: false } as MergeResult)); vi.mocked(fetchSettings).mockResolvedValueOnce({ @@ -764,9 +778,8 @@ describe("TaskDetailModal", () => { const button = await screen.findByRole("button", { name: "Start PR Review" }); fireEvent.click(button); - await waitFor(() => { - expect(onMergeTask).toHaveBeenCalledWith("FN-099"); - }); + expect(await screen.findByRole("heading", { name: "Create Pull Request" })).toBeInTheDocument(); + expect(onMergeTask).not.toHaveBeenCalled(); }); it("refreshes PR status for Check PR Status without merge prompt", async () => {