diff --git a/.changeset/fn-7279-task-detail-workflow-timestamps.md b/.changeset/fn-7279-task-detail-workflow-timestamps.md
new file mode 100644
index 0000000000..1f83426412
--- /dev/null
+++ b/.changeset/fn-7279-task-detail-workflow-timestamps.md
@@ -0,0 +1,7 @@
+---
+"@runfusion/fusion": patch
+---
+
+summary: Move task-detail workflow badges into the Updated timestamp metadata row.
+category: fix
+dev: Uses one canonical task-detail workflow badge across desktop and mobile detail surfaces.
diff --git a/packages/dashboard/app/components/TaskDetailModal.css b/packages/dashboard/app/components/TaskDetailModal.css
index 1edb2a85dd..632960c338 100644
--- a/packages/dashboard/app/components/TaskDetailModal.css
+++ b/packages/dashboard/app/components/TaskDetailModal.css
@@ -29,7 +29,7 @@ The gray top header band (task id + column badge) was over-padded. Trim its vert
.detail-title-row {
display: flex;
align-items: center;
- gap: 10px;
+ gap: var(--space-sm);
}
.detail-id {
@@ -39,28 +39,33 @@ The gray top header band (task id + column badge) was over-padded. Trim its vert
color: var(--text-muted);
}
-.detail-column-badge,
-.detail-workflow-badge {
- font-size: 11px;
- padding: 2px 8px;
+.detail-column-badge {
+ font-size: 0.6875rem;
+ padding: calc(var(--space-xs) / 2) var(--space-sm);
border-radius: var(--radius-pill);
font-weight: 600;
- letter-spacing: 0.5px;
-}
-
-.detail-column-badge {
+ letter-spacing: 0.03125rem;
text-transform: uppercase;
}
.detail-workflow-badge {
+ font-size: 0.6875rem;
+ padding: calc(var(--space-xs) / 2) var(--space-sm);
+ border-radius: var(--radius-pill);
+ font-weight: 600;
+ letter-spacing: 0.03125rem;
+ display: inline-flex;
+ align-items: center;
+ flex: 0 1 auto;
+ min-width: 0;
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
color: var(--text-muted);
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);
@@ -314,24 +319,14 @@ The task-detail modal metadata must keep priority, execution mode, provenance, P
/*
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.
+ Task details show workflow identity in the Updated timestamp section across desktop and mobile. Keep the canonical badge inline with Created/Updated metadata so header rows, embedded detail hosts, and mobile back-header details do not render duplicate workflow badges or empty mobile-only shells.
*/
- .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 e9a2239388..ff29c7b72d 100644
--- a/packages/dashboard/app/components/TaskDetailModal.tsx
+++ b/packages/dashboard/app/components/TaskDetailModal.tsx
@@ -2716,11 +2716,6 @@ export function TaskDetailContent({
{columnLabel(task.column)}
- {taskWorkflowName && (
-
- {taskWorkflowName}
-
- )}
{!isEditing && canEdit && (
@@ -3094,7 +3089,7 @@ export function TaskDetailContent({
{taskWorkflowName && (
-
+
{taskWorkflowName}
)}
diff --git a/packages/dashboard/app/components/__tests__/TaskDetailModal.rendering.test.tsx b/packages/dashboard/app/components/__tests__/TaskDetailModal.rendering.test.tsx
index ad5815aa5b..ea5ceaf599 100644
--- a/packages/dashboard/app/components/__tests__/TaskDetailModal.rendering.test.tsx
+++ b/packages/dashboard/app/components/__tests__/TaskDetailModal.rendering.test.tsx
@@ -35,7 +35,7 @@ import { FileBrowserProvider } from "../../context/FileBrowserContext";
setupTaskDetailModalHooks();
describe("TaskDetailModal", () => {
- describe("workflow header badge", () => {
+ describe("workflow timestamp badge", () => {
const workflowPayload = {
flagEnabled: true,
defaultWorkflowId: "builtin:coding",
@@ -71,14 +71,17 @@ describe("TaskDetailModal", () => {
);
}
- it("renders the resolved workflow name beside the task id and column badge", async () => {
+ it("renders the resolved workflow name in the timestamp section instead of the title row", async () => {
vi.mocked(dashboardApi.fetchBoardWorkflows).mockResolvedValueOnce(workflowPayload);
- renderDetail();
+ const { container } = renderDetail();
const badge = await screen.findByTestId("task-detail-workflow-badge");
expect(badge).toHaveTextContent("Docs");
- expect(badge.parentElement).toHaveClass("detail-title-row");
+ expect(badge.closest(".detail-timestamps")).toBeTruthy();
+ expect(badge.closest(".detail-title-row")).toBeNull();
+ expect(container.querySelector(".detail-title-row .detail-workflow-badge")).toBeNull();
+ expect(screen.getAllByTestId("task-detail-workflow-badge")).toHaveLength(1);
expect(screen.getByText("FN-101")).toBeInTheDocument();
expect(screen.getByText("Todo")).toBeInTheDocument();
expect(dashboardApi.fetchBoardWorkflows).toHaveBeenCalledTimes(1);
@@ -170,7 +173,7 @@ describe("TaskDetailModal", () => {
expect(container.querySelector(".detail-workflow-badge")).toBeNull();
});
- it("renders beside the Updated timestamp in the mobile back-header variant", async () => {
+ it("renders the canonical badge beside the Updated timestamp in the mobile back-header variant", async () => {
vi.mocked(dashboardApi.fetchBoardWorkflows).mockResolvedValueOnce(workflowPayload);
const { container } = render(
@@ -187,13 +190,15 @@ describe("TaskDetailModal", () => {
/>,
);
- expect(await screen.findByTestId("task-detail-workflow-badge")).toHaveTextContent("Docs");
- const mobileBadge = screen.getByTestId("task-detail-workflow-badge-mobile");
+ const badge = await screen.findByTestId("task-detail-workflow-badge");
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(badge).toHaveTextContent("Docs");
+ expect(badge.parentElement).toBe(timestamps);
+ expect(updatedLabel?.nextElementSibling).toBe(badge);
+ expect(screen.getAllByTestId("task-detail-workflow-badge")).toHaveLength(1);
+ expect(screen.queryByTestId("task-detail-workflow-badge-mobile")).toBeNull();
+ expect(container.querySelector(".detail-title-row .detail-workflow-badge")).toBeNull();
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 74f89b6c0d..ee61c3ba1e 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
@@ -121,18 +121,23 @@ describe("TaskDetailModal", () => {
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", () => {
+ it("keeps the canonical workflow badge owned by the timestamp group across breakpoints", () => {
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");
+ const workflowBadgeBlock = css.match(/^\.detail-workflow-badge\s*\{([^}]*)\}/m)?.[1] ?? "";
+ const mobileBlock = getCssAtRuleBlockContaining(css, "@media (max-width: 768px)", ".detail-timestamps");
+ const mobileTimestampsBlock = getCssRuleBlock(mobileBlock, ".detail-timestamps");
- 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*\{/);
+ expect(workflowBadgeBlock).toContain("display: inline-flex;");
+ expect(workflowBadgeBlock).toContain("align-items: center;");
+ expect(workflowBadgeBlock).toContain("flex: 0 1 auto;");
+ expect(workflowBadgeBlock).toContain("text-overflow: ellipsis;");
+ expect(mobileTimestampsBlock).toContain("display: flex;");
+ expect(mobileTimestampsBlock).toContain("align-items: center;");
+ expect(mobileTimestampsBlock).toContain("flex-wrap: nowrap;");
+ expect(css).not.toMatch(/detail-workflow-badge--desktop/);
+ expect(css).not.toMatch(/detail-workflow-badge--mobile/);
+ expect(css).not.toMatch(/task-detail-workflow-badge-mobile/);
+ expect(css).not.toMatch(/\.detail-title-row\s+\.detail-workflow-badge\s*\{/);
});
it("keeps desktop and mobile modal sizing guards unchanged", () => {
const css = readDashboardStylesSource();