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 () => {