From f5a776d92361053c243389a1bd0855814a34bcbb Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 31 Jul 2026 22:51:07 -0700 Subject: [PATCH] FN-8667: align fast-mode badges with task status Keep fast-mode metadata badges in the task-card header's shared wrapping context. - Make the meta badge wrapper layout-transparent so fast, priority, and oversight badges wrap independently with status. - Prevent fast and priority chips from shrinking and update desktop and mobile layout coverage. - Add a patch changeset for aligned task-card badges. Files changed: .changeset/task-card-fast-mode-badge-wrap.md | 7 ++++ packages/dashboard/app/components/TaskCard.css | 16 ++++---- .../__tests__/TaskCard.badge-wrap.test.tsx | 46 ++++++++++++++++++++++ .../app/components/__tests__/TaskCard.test.tsx | 6 +-- 4 files changed, 64 insertions(+), 11 deletions(-) Fusion-Task-Id: FN-8667 Fusion-Task-Lineage: 34bdb410-8d8a-4b1f-8bc4-105ae324874e Co-authored-by: Fusion (runfusion.ai) --- .changeset/task-card-fast-mode-badge-wrap.md | 7 +++ .../dashboard/app/components/TaskCard.css | 16 +++---- .../__tests__/TaskCard.badge-wrap.test.tsx | 46 +++++++++++++++++++ .../components/__tests__/TaskCard.test.tsx | 6 +-- 4 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 .changeset/task-card-fast-mode-badge-wrap.md 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", () => {