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) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/task-card-fast-mode-badge-wrap.md
Normal file
7
.changeset/task-card-fast-mode-badge-wrap.md
Normal file
@@ -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.
|
||||||
@@ -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);
|
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 {
|
.card-meta-badges {
|
||||||
display: flex;
|
display: contents;
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: var(--space-xs);
|
|
||||||
row-gap: var(--space-xs);
|
|
||||||
min-width: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -577,6 +576,7 @@ The size chip must derive its box from the shared header-chip padding, line-heig
|
|||||||
|
|
||||||
.card-priority-badge {
|
.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. */
|
/* 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);
|
gap: var(--space-xs);
|
||||||
letter-spacing: 0.4px;
|
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 {
|
.card-execution-mode-badge {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-size: 0.625rem;
|
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-header-badges {
|
||||||
.card-meta-badges {
|
|
||||||
gap: calc(var(--space-xs) / 2);
|
gap: calc(var(--space-xs) / 2);
|
||||||
row-gap: var(--space-xs);
|
row-gap: var(--space-xs);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -282,6 +282,52 @@ describe("TaskCard badge wrapping (FN-5162)", () => {
|
|||||||
expect(container.querySelector(".card-header-actions")).toBeNull();
|
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(
|
||||||
|
<TaskCard
|
||||||
|
task={makeTask({
|
||||||
|
column: "todo",
|
||||||
|
status: undefined,
|
||||||
|
steps: [],
|
||||||
|
executionMode: "fast",
|
||||||
|
...meta,
|
||||||
|
})}
|
||||||
|
onOpenDetail={noop}
|
||||||
|
addToast={noop}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
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", () => {
|
it("places a fast-mode size badge after the task id before wrapping header badges", () => {
|
||||||
const { container: sizedContainer } = render(
|
const { container: sizedContainer } = render(
|
||||||
<TaskCard
|
<TaskCard
|
||||||
|
|||||||
@@ -3873,11 +3873,11 @@ describe("TaskCard", () => {
|
|||||||
expect(container.querySelector(".card-footer-row-right")).toBeNull();
|
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();
|
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(/\.card-meta-badges\s*\{[^}]*display:\s*contents;[^}]*\}/);
|
||||||
expect(fullCss).toMatch(/@media[^{]*\(max-width:\s*768px\)[^{]*\{[\s\S]*?\.card-meta-badges\s*\{[^}]*gap:\s*calc\(var\(--space-xs\) \/ 2\);[^}]*\}/);
|
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", () => {
|
describe("retry button on failed tasks", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user