FN-7867: make task-card priority badges icon-only to stop meta-badge wrapping

Board TaskCard priority badges no longer render visible priority text, so the .card-meta-badges row cannot wrap onto a new line when a priority label widens it.

- TaskCard.tsx: drop the visible priority-label <span>, add title/aria-label with the full priority label, and keep the label reachable via a visually-hidden span for assistive tech
- TaskCard.css: update the FNXC comment on .card-priority-badge to reflect the icon-only rationale (spacing/geometry rules unchanged, still shared with the Task Detail chip/select)
- TaskCard.test.tsx: assert icon-only rendering (no visible text node/span), title/aria-label correctness, visually-hidden label content, and that the badge is absent for tasks without a priority; loosen the mocked lucide icons to forward arbitrary props
- Add a patch changeset documenting the icon-only priority badge fix

Files changed:
 .changeset/icon-only-priority-badges.md            |  7 +++++
 packages/dashboard/app/components/TaskCard.css     |  2 +-
 packages/dashboard/app/components/TaskCard.tsx     | 12 ++++++---
 .../app/components/__tests__/TaskCard.test.tsx     | 30 +++++++++++++++++-----
 4 files changed, 39 insertions(+), 12 deletions(-)

Fusion-Task-Id: FN-7867

Fusion-Task-Lineage: 5c845c23-0a9f-47b4-9a75-6c410b507ef4

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-12 12:26:54 -07:00
parent 139ae7e4bc
commit b10f823672
4 changed files with 39 additions and 12 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Task card priority badges now show icon-only so they no longer wrap to a new line.
category: fix
dev: Updates the board TaskCard priority badge to keep labels in title, aria-label, and visually-hidden text.

View File

@@ -516,7 +516,7 @@ FN-6774 removes the saturated stuck-task edge stripe from board cards. Keep the
}
.card-priority-badge {
/* FNXC:PriorityColorCoding 2026-07-11-00:00: Priority badges add a glyph beside the label; keep inline-flex geometry and token spacing so badge height/wrap invariants do not change. */
/* 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. */
gap: var(--space-xs);
letter-spacing: 0.4px;
}

View File

@@ -54,7 +54,7 @@ import { WorkspaceWorktreesSummary, isWorkspaceTask } from "./WorkspaceWorktrees
import { WorkflowIcon } from "./WorkflowIcon";
import { TaskContextMenu, buildTaskActionMenuModel, getTaskPrAutomationLabel, type TaskContextMenuColumnFlags, type TaskContextMenuColumnMetadata, type TaskMenuActionDescriptor } from "./TaskContextMenu";
import { formatCost, hasTaskCost, taskTotalCost } from "../utils/taskTokenCost";
import { getPriorityColorVar, getPriorityIcon } from "../utils/priorityIndicator";
import { getPriorityColorVar, getPriorityIcon, getPriorityLabel } from "../utils/priorityIndicator";
/** Per-branch progress snapshot (U13). Surfaced as an optional additive field
* on the task payload for the parallel-window badge (U9). */
@@ -3086,10 +3086,14 @@ function TaskCardComponent({
{hasCardMetaBadges && (
<div className="card-meta-badges" data-testid="card-meta-badges">
{showPriorityBadge && (
<span className={`card-priority-badge card-priority-badge--${normalizedPriority}`}>
{/* FNXC:PriorityColorCoding 2026-07-11-00:00: Cards render the shared priority glyph with the priorityIndicator urgency color while preserving the existing badge text and non-normal visibility gate. */}
<span
className={`card-priority-badge card-priority-badge--${normalizedPriority}`}
title={getPriorityLabel(normalizedPriority)}
aria-label={getPriorityLabel(normalizedPriority)}
>
{/* FNXC:PriorityIconOnlyBadge 2026-07-12-00:00: FN-7867 makes task-card priority badges icon-only so priority text cannot widen .card-meta-badges and force wrapping; preserve the label through title, aria-label, and visually-hidden text while keeping the shared urgency color. */}
<PriorityBadgeIcon size={10} aria-hidden="true" style={{ color: getPriorityColorVar(normalizedPriority) }} />
<span>{normalizedPriority}</span>
<span className="visually-hidden">{getPriorityLabel(normalizedPriority)}</span>
</span>
)}
{task.executionMode === "fast" && (

View File

@@ -21,7 +21,7 @@ import { NavigationHistoryProvider, useNavigationHistory } from "../../hooks/use
import { useOverlayDismiss } from "../../hooks/useOverlayDismiss";
import type { ConfirmOptions } from "../../hooks/useConfirm";
import { TASK_PRIORITIES, type Task, type TaskPriority } from "@fusion/core";
import { getPriorityColorVar } from "../../utils/priorityIndicator";
import { getPriorityColorVar, getPriorityLabel } from "../../utils/priorityIndicator";
// Mock lucide-react to avoid SVG rendering issues in test env
vi.mock("lucide-react", () => ({
@@ -42,10 +42,10 @@ vi.mock("lucide-react", () => ({
RotateCw: () => null,
Zap: () => <svg data-testid="icon-zap" />,
AlertTriangle: () => null,
ArrowDown: ({ style }: { style?: React.CSSProperties }) => <svg data-testid="priority-icon-low" className="lucide-arrow-down" style={style} />,
Flag: ({ style }: { style?: React.CSSProperties }) => <svg data-testid="priority-icon-normal" className="lucide-flag" style={style} />,
ArrowUp: ({ style }: { style?: React.CSSProperties }) => <svg data-testid="priority-icon-high" className="lucide-arrow-up" style={style} />,
TriangleAlert: ({ style }: { style?: React.CSSProperties }) => <svg data-testid="priority-icon-urgent" className="lucide-triangle-alert" style={style} />,
ArrowDown: ({ style, ...props }: React.SVGProps<SVGSVGElement>) => <svg data-testid="priority-icon-low" className="lucide-arrow-down" style={style} {...props} />,
Flag: ({ style, ...props }: React.SVGProps<SVGSVGElement>) => <svg data-testid="priority-icon-normal" className="lucide-flag" style={style} {...props} />,
ArrowUp: ({ style, ...props }: React.SVGProps<SVGSVGElement>) => <svg data-testid="priority-icon-high" className="lucide-arrow-up" style={style} {...props} />,
TriangleAlert: ({ style, ...props }: React.SVGProps<SVGSVGElement>) => <svg data-testid="priority-icon-urgent" className="lucide-triangle-alert" style={style} {...props} />,
ArrowUpRight: () => null,
// FN-7592: the overseer badge now renders an icon child instead of a text label,
// so tests must see a real SVG (like Zap) rather than a no-op render.
@@ -2790,7 +2790,7 @@ describe("TaskCard", () => {
]);
});
it("renders shared urgency-colored glyphs for visible priority badges while normal stays hidden", () => {
it("renders icon-only urgency-colored priority badges with accessible labels while normal stays hidden", () => {
for (const priority of TASK_PRIORITIES) {
const { container, unmount } = render(
<TaskCard
@@ -2807,13 +2807,29 @@ describe("TaskCard", () => {
continue;
}
const label = getPriorityLabel(priority);
expect(badge).not.toBeNull();
expect(badge).toHaveTextContent(priority);
expect(badge).toHaveAttribute("aria-label", label);
expect(badge).toHaveAttribute("title", label);
expect(Array.from(badge?.childNodes ?? []).filter((node) => node.nodeType === Node.TEXT_NODE && node.textContent?.trim())).toEqual([]);
const visibleLabelSpans = Array.from(badge?.querySelectorAll("span") ?? []).filter((span) => !span.classList.contains("visually-hidden"));
expect(visibleLabelSpans).toEqual([]);
expect(badge?.querySelector(".visually-hidden")).toHaveTextContent(label);
const icon = badge?.querySelector("svg");
expect(icon).not.toBeNull();
expect(icon).toHaveAttribute("aria-hidden", "true");
expect(icon?.getAttribute("style")).toContain(`color: ${getPriorityColorVar(priority)}`);
unmount();
}
const { container } = render(
<TaskCard
task={makeTask({ priority: undefined })}
onOpenDetail={noop}
addToast={noop}
/>,
);
expect(container.querySelector(".card-priority-badge")).toBeNull();
});
it("renders partial card meta groups without empty wrappers when time is absent", () => {