diff --git a/.changeset/fn-7277-workflow-badge-bottom-left.md b/.changeset/fn-7277-workflow-badge-bottom-left.md
new file mode 100644
index 0000000000..d709994919
--- /dev/null
+++ b/.changeset/fn-7277-workflow-badge-bottom-left.md
@@ -0,0 +1,7 @@
+---
+"@runfusion/fusion": patch
+---
+
+summary: Move All workflows Board task-card workflow badges to the bottom-left.
+category: fix
+dev: Updates TaskCard workflowBadge placement and dashboard layout coverage.
diff --git a/packages/dashboard/app/components/TaskCard.css b/packages/dashboard/app/components/TaskCard.css
index cc53e9f62f..d040c45fb7 100644
--- a/packages/dashboard/app/components/TaskCard.css
+++ b/packages/dashboard/app/components/TaskCard.css
@@ -215,14 +215,26 @@ The global mobile touch-action reset applies to descendants, and browsers inters
/*
FNXC:WorkflowBoard 2026-06-29-00:00:
Aggregate Board cards need a compact workflow-name badge so operators can identify each card's source workflow while shared columns combine multiple workflows. The badge is opt-in metadata from Board and uses neutral card tokens so non-board TaskCard callers do not render empty workflow shells.
+
+FNXC:WorkflowBoard 2026-06-30-00:00:
+All workflows cards need workflow identity at the bottom-left below every other card icon/action/meta row, so the wrapper is rendered after those rows and left-aligns the neutral badge without adding click targets.
*/
+.card-workflow-badge-row {
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ min-width: 0;
+ margin-top: var(--space-xs);
+}
+
.card-workflow-badge {
- max-width: 18ch;
+ max-width: min(100%, 18ch);
background: var(--surface-2);
border-color: var(--border);
color: var(--text-muted);
text-transform: none;
letter-spacing: 0;
+ flex: 0 1 auto;
}
.card-no-commits-expected-badge {
@@ -1583,6 +1595,14 @@ executing. These map 1:1 to the unified progress status so the dot color encodes
row-gap: var(--space-xs);
}
+ .card-workflow-badge-row {
+ margin-top: var(--space-xs);
+ }
+
+ .card-workflow-badge {
+ max-width: 100%;
+ }
+
.card-time-indicator,
.card-github-tracking-chip,
.card-retry-badge,
diff --git a/packages/dashboard/app/components/TaskCard.tsx b/packages/dashboard/app/components/TaskCard.tsx
index 1bcfa1b73c..f2e56dfd47 100644
--- a/packages/dashboard/app/components/TaskCard.tsx
+++ b/packages/dashboard/app/components/TaskCard.tsx
@@ -2285,8 +2285,7 @@ function TaskCardComponent({
&& workflowBadge.workflowName.trim().length > 0;
const hasCardMetaBadges = showPriorityBadge
|| task.executionMode === "fast"
- || isAgentCreated
- || hasWorkflowBadge;
+ || isAgentCreated;
if (isEditing) {
return (
@@ -2505,17 +2504,6 @@ function TaskCardComponent({
)}
{hasCardMetaBadges && (
- {hasWorkflowBadge && (
-
- {workflowBadge.workflowName}
-
- )}
{showPriorityBadge && (
{normalizedPriority}
@@ -3045,6 +3033,23 @@ function TaskCardComponent({
{showInReviewMoveControl && !metaRowVisible && renderInReviewMoveControl()}
)}
+ {hasWorkflowBadge && (
+
+ {/*
+ FNXC:WorkflowBoard 2026-06-30-00:00:
+ All workflows Board cards need workflow identity anchored at the card's bottom-left, below footer chips, dependency/meta rows, provider icons, and action controls, while per-workflow cards keep omitting this opt-in metadata.
+ */}
+
+ {workflowBadge.workflowName}
+
+
+ )}
{(showCreatePrQuickAction || isPrCreateOpen) && (
{
expect(styles.maxWidth).not.toBe("none");
expect(styles.whiteSpace).toBe("nowrap");
});
+
+ it("places the workflow badge in a left-aligned bottom row outside the header badge cluster", () => {
+ const workflowRow = container.querySelector(".card-workflow-badge-row") as HTMLElement;
+ const workflowBadge = container.querySelector(".card-workflow-badge") as HTMLElement;
+ const metaBadges = container.querySelector(".card-meta-badges") as HTMLElement;
+
+ expect(workflowRow).toBeTruthy();
+ expect(workflowBadge).toBeTruthy();
+ expect(workflowRow.contains(workflowBadge)).toBe(true);
+ expect(metaBadges.contains(workflowBadge)).toBe(false);
+
+ const styles = getComputedStyle(workflowRow);
+ expect(styles.display).toBe("flex");
+ expect(styles.justifyContent).toBe("flex-start");
+ expect(styles.minWidth).toBe("0px");
+ });
});
diff --git a/packages/dashboard/app/components/__tests__/TaskCard.footer-wrap.test.tsx b/packages/dashboard/app/components/__tests__/TaskCard.footer-wrap.test.tsx
index 62840c5456..2d778fcb8b 100644
--- a/packages/dashboard/app/components/__tests__/TaskCard.footer-wrap.test.tsx
+++ b/packages/dashboard/app/components/__tests__/TaskCard.footer-wrap.test.tsx
@@ -22,6 +22,7 @@ vi.mock("lucide-react", () => ({
RotateCw: () => ,
Zap: () => ,
AlertTriangle: () => ,
+ ArrowUpRight: () => ,
}));
vi.mock("../ProviderIcon", () => ({
@@ -152,4 +153,36 @@ describe("TaskCard footer wrapping (FN-5210)", () => {
expect(styles.whiteSpace).toBe("nowrap");
expect(styles.flexShrink).toBe("0");
});
+
+ it("places workflow badges after footer and action rows in DOM order", () => {
+ const { container } = render(
+ undefined)}
+ workflowBadge={{ workflowId: "wf-footer", workflowName: "Workflow with a very long display name for wrapping" }}
+ />,
+ );
+
+ const footerRow = container.querySelector(".card-footer-row") as HTMLElement;
+ const actionRow = container.querySelector(".card-action-row") as HTMLElement;
+ const workflowRow = container.querySelector(".card-workflow-badge-row") as HTMLElement;
+ const workflowBadge = container.querySelector(".card-workflow-badge") as HTMLElement;
+
+ expect(footerRow).toBeTruthy();
+ expect(actionRow).toBeTruthy();
+ expect(workflowRow).toBeTruthy();
+ expect(workflowRow.contains(workflowBadge)).toBe(true);
+ expect(footerRow.compareDocumentPosition(workflowRow) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
+ expect(actionRow.compareDocumentPosition(workflowRow) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
+
+ const rowStyles = getComputedStyle(workflowRow);
+ expect(rowStyles.justifyContent).toBe("flex-start");
+ const badgeStyles = getComputedStyle(workflowBadge);
+ expect(badgeStyles.whiteSpace).toBe("nowrap");
+ expect(badgeStyles.overflow).toBe("hidden");
+ expect(badgeStyles.textOverflow).toBe("ellipsis");
+ });
});
diff --git a/packages/dashboard/app/components/__tests__/TaskCard.test.tsx b/packages/dashboard/app/components/__tests__/TaskCard.test.tsx
index 01b0a351a8..0fd2cb264a 100644
--- a/packages/dashboard/app/components/__tests__/TaskCard.test.tsx
+++ b/packages/dashboard/app/components/__tests__/TaskCard.test.tsx
@@ -4993,7 +4993,7 @@ describe("TaskCard memo comparator provenance behavior", () => {
describe("TaskCard workflow badges", () => {
it("renders a compact accessible workflow badge only when metadata is present", () => {
- const { rerender } = render(
+ const { container, rerender } = render(
{
);
const badge = screen.getByTestId("card-workflow-badge");
+ const row = screen.getByTestId("card-workflow-badge-row");
expect(badge).toHaveTextContent("Custom Flow");
expect(badge).toHaveAttribute("data-workflow-id", "wf-custom");
expect(badge).toHaveAccessibleName("Workflow Custom Flow");
+ expect(row).toContainElement(badge);
+ expect(badge.closest(".card-meta-badges")).toBeNull();
+ expect(container.querySelector(".card-meta-badges")).toBeNull();
rerender(
{
/>,
);
expect(screen.queryByTestId("card-workflow-badge")).toBeNull();
+ expect(screen.queryByTestId("card-workflow-badge-row")).toBeNull();
+ });
+
+ it("keeps top badges in the meta cluster while rendering workflow identity below card rows", () => {
+ vi.useFakeTimers();
+ vi.setSystemTime(new Date("2026-06-30T12:10:00.000Z"));
+
+ const { container } = render(
+ ,
+ );
+
+ const metaBadges = screen.getByTestId("card-meta-badges");
+ const badge = screen.getByTestId("card-workflow-badge");
+ const workflowRow = screen.getByTestId("card-workflow-badge-row");
+ expect(metaBadges.querySelector(".card-priority-badge")).not.toBeNull();
+ expect(metaBadges.querySelector(".card-execution-mode-badge")).not.toBeNull();
+ expect(metaBadges.querySelector(".card-agent-created-badge")).not.toBeNull();
+ expect(metaBadges.querySelector(".card-workflow-badge")).toBeNull();
+ expect(workflowRow).toContainElement(badge);
+
+ [".card-footer-row", ".card-meta", ".card-agent-row"].forEach((selector) => {
+ const row = container.querySelector(selector);
+ expect(row, `${selector} should render for the placement fixture`).not.toBeNull();
+ expect(row!.compareDocumentPosition(workflowRow) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
+ });
+
+ expect(badge.closest(".card-action-row")).toBeNull();
+ vi.useRealTimers();
});
});