diff --git a/.changeset/task-card-fast-mode-badge-wrap.md b/.changeset/task-card-fast-mode-badge-wrap.md
new file mode 100644
index 0000000000..f0aee7cf60
--- /dev/null
+++ b/.changeset/task-card-fast-mode-badge-wrap.md
@@ -0,0 +1,7 @@
+---
+"@runfusion/fusion": patch
+---
+
+summary: Keep fast-mode task-card badges aligned with status badges when space allows.
+category: fix
+dev: The task-card meta badge wrapper is layout-transparent so nested chips share the header wrap context.
diff --git a/packages/dashboard/app/components/TaskCard.css b/packages/dashboard/app/components/TaskCard.css
index dad9b8b8a6..ae56f40173 100644
--- a/packages/dashboard/app/components/TaskCard.css
+++ b/packages/dashboard/app/components/TaskCard.css
@@ -125,13 +125,12 @@ The size chip is a direct header child immediately after the task id, before thi
min-height: var(--card-chip-height);
}
+/*
+FNXC:TaskCardLayout 2026-08-01-05:38:
+The fast-mode badge must share the header line with status, priority, and oversight badges whenever space remains. Keep this nested semantic wrapper layout-transparent so its meta badges join `.card-header-badges` directly instead of wrapping as one atomic group.
+*/
.card-meta-badges {
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- gap: var(--space-xs);
- row-gap: var(--space-xs);
- min-width: 0;
+ display: contents;
}
/*
@@ -577,6 +576,7 @@ The size chip must derive its box from the shared header-chip padding, line-heig
.card-priority-badge {
/* FNXC:PriorityIconOnlyBadge 2026-07-12-00:00: FN-7867 removes visible priority text from board cards, so these shared spacing declarations no longer add card text width; keep them to preserve the Task Detail priority chip/select geometry that reuses the same badge class. */
+ flex-shrink: 0;
gap: var(--space-xs);
letter-spacing: 0.4px;
}
@@ -604,6 +604,7 @@ Zap at the badge text size so it remains centered without making the header row
.card-execution-mode-badge {
display: inline-flex;
align-items: center;
+ flex-shrink: 0;
justify-content: center;
box-sizing: border-box;
font-size: 0.625rem;
@@ -2026,8 +2027,7 @@ The three-dot menu is the sole card move/action entry point. Keep this shared bu
}
- .card-header-badges,
- .card-meta-badges {
+ .card-header-badges {
gap: calc(var(--space-xs) / 2);
row-gap: var(--space-xs);
}
diff --git a/packages/dashboard/app/components/__tests__/TaskCard.badge-wrap.test.tsx b/packages/dashboard/app/components/__tests__/TaskCard.badge-wrap.test.tsx
index 694fecda3c..85ab865f63 100644
--- a/packages/dashboard/app/components/__tests__/TaskCard.badge-wrap.test.tsx
+++ b/packages/dashboard/app/components/__tests__/TaskCard.badge-wrap.test.tsx
@@ -282,6 +282,52 @@ describe("TaskCard badge wrapping (FN-5162)", () => {
expect(container.querySelector(".card-header-actions")).toBeNull();
});
+ it.each([
+ ["fast alone", { priority: "normal", plannerOversightLevel: "off" }],
+ ["fast with priority", { priority: "urgent", plannerOversightLevel: "off" }],
+ ["fast with oversight", { priority: "normal", plannerOversightLevel: "steer" }],
+ ["fast with priority and oversight", { priority: "urgent", plannerOversightLevel: "steer" }],
+ ] as const)("keeps %s beside a queued-to-plan status chip in the shared wrap context", (_name, meta) => {
+ const { container: queuedContainer } = render(
+ ,
+ );
+
+ const headerBadges = queuedContainer.querySelector(".card-header-badges") as HTMLElement;
+ const statusBadge = queuedContainer.querySelector(".card-status-badge") as HTMLElement;
+ const metaBadges = queuedContainer.querySelector('[data-testid="card-meta-badges"]') as HTMLElement;
+ const fastBadge = queuedContainer.querySelector(".card-execution-mode-badge") as HTMLElement;
+
+ expect(headerBadges).toBeTruthy();
+ expect(statusBadge).toHaveTextContent("Queued to plan");
+ expect(metaBadges).toBeTruthy();
+ expect(fastBadge).toBeTruthy();
+ expect(metaBadges.parentElement).toBe(headerBadges);
+ expect(headerBadges.contains(statusBadge)).toBe(true);
+ expect(headerBadges.contains(fastBadge)).toBe(true);
+ expect(getComputedStyle(metaBadges).display).toBe("contents");
+ expect(getComputedStyle(fastBadge).flexShrink).toBe("0");
+ const priorityBadge = queuedContainer.querySelector(".card-priority-badge") as HTMLElement | null;
+ if (priorityBadge) expect(getComputedStyle(priorityBadge).flexShrink).toBe("0");
+ });
+
+ it("keeps the layout-transparent meta wrapper contract in the mobile badge context", () => {
+ const mobileSection = getCssBlocks(loadedCss, "max-width: 768px").join("\n");
+
+ expectCssRuleToContain(loadedCss, ".card-meta-badges", "display: contents;");
+ expectCssRuleToContain(mobileSection, ".card-header-badges", "gap: calc(var(--space-xs) / 2);");
+ expect(mobileSection).not.toMatch(/\.card-meta-badges\s*\{/);
+ });
+
it("places a fast-mode size badge after the task id before wrapping header badges", () => {
const { container: sizedContainer } = render(
{
expect(container.querySelector(".card-footer-row-right")).toBeNull();
});
- it("defines responsive flex-wrap styling for grouped card meta badges", () => {
+ it("keeps grouped card meta badges layout-transparent in the shared header wrap context", () => {
const fullCss = loadAllAppCss();
- expect(fullCss).toMatch(/\.card-meta-badges\s*\{[^}]*display:\s*flex;[^}]*flex-wrap:\s*wrap;[^}]*gap:\s*var\(--space-xs\);[^}]*\}/);
- expect(fullCss).toMatch(/@media[^{]*\(max-width:\s*768px\)[^{]*\{[\s\S]*?\.card-meta-badges\s*\{[^}]*gap:\s*calc\(var\(--space-xs\) \/ 2\);[^}]*\}/);
+ expect(fullCss).toMatch(/\.card-meta-badges\s*\{[^}]*display:\s*contents;[^}]*\}/);
+ expect(fullCss).toMatch(/@media[^{]*\(max-width:\s*768px\)[^{]*\{[\s\S]*?\.card-header-badges\s*\{[^}]*gap:\s*calc\(var\(--space-xs\) \/ 2\);[^}]*\}/);
});
describe("retry button on failed tasks", () => {