FN-7279: move workflow badges to task timestamps
Move the task-detail workflow badge into the Updated timestamp metadata row. - Render one canonical workflow badge in the timestamp group for desktop and mobile task details. - Remove desktop/mobile workflow badge variants and keep timestamp layout from wrapping on mobile. - Update rendering and responsive CSS tests for the new badge ownership. - Add a patch changeset for the published Fusion package. Files changed: .../fn-7279-task-detail-workflow-timestamps.md | 7 ++++ .../dashboard/app/components/TaskDetailModal.css | 43 ++++++++++------------ .../dashboard/app/components/TaskDetailModal.tsx | 7 +--- .../__tests__/TaskDetailModal.rendering.test.tsx | 25 ++++++++----- ...etailModal.responsive-and-dependencies.test.tsx | 27 ++++++++------ 5 files changed, 58 insertions(+), 51 deletions(-) Fusion-Task-Id: FN-7279 Fusion-Task-Lineage: a1f11f51-99c9-42c7-bbb6-bb58384594a2 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-7279-task-detail-workflow-timestamps.md
Normal file
7
.changeset/fn-7279-task-detail-workflow-timestamps.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Move task-detail workflow badges into the Updated timestamp metadata row.
|
||||||
|
category: fix
|
||||||
|
dev: Uses one canonical task-detail workflow badge across desktop and mobile detail surfaces.
|
||||||
@@ -29,7 +29,7 @@ The gray top header band (task id + column badge) was over-padded. Trim its vert
|
|||||||
.detail-title-row {
|
.detail-title-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: var(--space-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-id {
|
.detail-id {
|
||||||
@@ -39,28 +39,33 @@ The gray top header band (task id + column badge) was over-padded. Trim its vert
|
|||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-column-badge,
|
.detail-column-badge {
|
||||||
.detail-workflow-badge {
|
font-size: 0.6875rem;
|
||||||
font-size: 11px;
|
padding: calc(var(--space-xs) / 2) var(--space-sm);
|
||||||
padding: 2px 8px;
|
|
||||||
border-radius: var(--radius-pill);
|
border-radius: var(--radius-pill);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.03125rem;
|
||||||
}
|
|
||||||
|
|
||||||
.detail-column-badge {
|
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-workflow-badge {
|
.detail-workflow-badge {
|
||||||
|
font-size: 0.6875rem;
|
||||||
|
padding: calc(var(--space-xs) / 2) var(--space-sm);
|
||||||
|
border-radius: var(--radius-pill);
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.03125rem;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
background: var(--surface-raised);
|
background: var(--surface-raised);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-workflow-badge--mobile {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.badge-triage {
|
.badge-triage {
|
||||||
background: var(--status-triage-bg);
|
background: var(--status-triage-bg);
|
||||||
color: var(--triage);
|
color: var(--triage);
|
||||||
@@ -314,24 +319,14 @@ The task-detail modal metadata must keep priority, execution mode, provenance, P
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
FNXC:TaskDetailWorkflow 2026-06-30-07:37:
|
FNXC:TaskDetailWorkflow 2026-06-30-07:37:
|
||||||
Mobile task details need the workflow-name badge to read with the Updated timestamp instead of the compact header band. Keep desktop's header badge placement while showing exactly one mobile badge inside the timestamp group.
|
Task details show workflow identity in the Updated timestamp section across desktop and mobile. Keep the canonical badge inline with Created/Updated metadata so header rows, embedded detail hosts, and mobile back-header details do not render duplicate workflow badges or empty mobile-only shells.
|
||||||
*/
|
*/
|
||||||
.detail-workflow-badge--desktop {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-timestamps {
|
.detail-timestamps {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-timestamps .detail-workflow-badge--mobile {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-provenance-context {
|
.detail-provenance-context {
|
||||||
max-width: 20ch;
|
max-width: 20ch;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2716,11 +2716,6 @@ export function TaskDetailContent({
|
|||||||
<span className={`detail-column-badge badge-${task.column}`}>
|
<span className={`detail-column-badge badge-${task.column}`}>
|
||||||
{columnLabel(task.column)}
|
{columnLabel(task.column)}
|
||||||
</span>
|
</span>
|
||||||
{taskWorkflowName && (
|
|
||||||
<span className="detail-workflow-badge detail-workflow-badge--desktop" data-testid="task-detail-workflow-badge">
|
|
||||||
{taskWorkflowName}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="modal-header-actions">
|
<div className="modal-header-actions">
|
||||||
{!isEditing && canEdit && (
|
{!isEditing && canEdit && (
|
||||||
@@ -3094,7 +3089,7 @@ export function TaskDetailContent({
|
|||||||
</time>
|
</time>
|
||||||
</span>
|
</span>
|
||||||
{taskWorkflowName && (
|
{taskWorkflowName && (
|
||||||
<span className="detail-workflow-badge detail-workflow-badge--mobile" data-testid="task-detail-workflow-badge-mobile">
|
<span className="detail-workflow-badge" data-testid="task-detail-workflow-badge">
|
||||||
{taskWorkflowName}
|
{taskWorkflowName}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import { FileBrowserProvider } from "../../context/FileBrowserContext";
|
|||||||
setupTaskDetailModalHooks();
|
setupTaskDetailModalHooks();
|
||||||
|
|
||||||
describe("TaskDetailModal", () => {
|
describe("TaskDetailModal", () => {
|
||||||
describe("workflow header badge", () => {
|
describe("workflow timestamp badge", () => {
|
||||||
const workflowPayload = {
|
const workflowPayload = {
|
||||||
flagEnabled: true,
|
flagEnabled: true,
|
||||||
defaultWorkflowId: "builtin:coding",
|
defaultWorkflowId: "builtin:coding",
|
||||||
@@ -71,14 +71,17 @@ describe("TaskDetailModal", () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
it("renders the resolved workflow name beside the task id and column badge", async () => {
|
it("renders the resolved workflow name in the timestamp section instead of the title row", async () => {
|
||||||
vi.mocked(dashboardApi.fetchBoardWorkflows).mockResolvedValueOnce(workflowPayload);
|
vi.mocked(dashboardApi.fetchBoardWorkflows).mockResolvedValueOnce(workflowPayload);
|
||||||
|
|
||||||
renderDetail();
|
const { container } = renderDetail();
|
||||||
|
|
||||||
const badge = await screen.findByTestId("task-detail-workflow-badge");
|
const badge = await screen.findByTestId("task-detail-workflow-badge");
|
||||||
expect(badge).toHaveTextContent("Docs");
|
expect(badge).toHaveTextContent("Docs");
|
||||||
expect(badge.parentElement).toHaveClass("detail-title-row");
|
expect(badge.closest(".detail-timestamps")).toBeTruthy();
|
||||||
|
expect(badge.closest(".detail-title-row")).toBeNull();
|
||||||
|
expect(container.querySelector(".detail-title-row .detail-workflow-badge")).toBeNull();
|
||||||
|
expect(screen.getAllByTestId("task-detail-workflow-badge")).toHaveLength(1);
|
||||||
expect(screen.getByText("FN-101")).toBeInTheDocument();
|
expect(screen.getByText("FN-101")).toBeInTheDocument();
|
||||||
expect(screen.getByText("Todo")).toBeInTheDocument();
|
expect(screen.getByText("Todo")).toBeInTheDocument();
|
||||||
expect(dashboardApi.fetchBoardWorkflows).toHaveBeenCalledTimes(1);
|
expect(dashboardApi.fetchBoardWorkflows).toHaveBeenCalledTimes(1);
|
||||||
@@ -170,7 +173,7 @@ describe("TaskDetailModal", () => {
|
|||||||
expect(container.querySelector(".detail-workflow-badge")).toBeNull();
|
expect(container.querySelector(".detail-workflow-badge")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders beside the Updated timestamp in the mobile back-header variant", async () => {
|
it("renders the canonical badge beside the Updated timestamp in the mobile back-header variant", async () => {
|
||||||
vi.mocked(dashboardApi.fetchBoardWorkflows).mockResolvedValueOnce(workflowPayload);
|
vi.mocked(dashboardApi.fetchBoardWorkflows).mockResolvedValueOnce(workflowPayload);
|
||||||
|
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
@@ -187,13 +190,15 @@ describe("TaskDetailModal", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(await screen.findByTestId("task-detail-workflow-badge")).toHaveTextContent("Docs");
|
const badge = await screen.findByTestId("task-detail-workflow-badge");
|
||||||
const mobileBadge = screen.getByTestId("task-detail-workflow-badge-mobile");
|
|
||||||
const timestamps = container.querySelector(".detail-timestamps");
|
const timestamps = container.querySelector(".detail-timestamps");
|
||||||
const updatedLabel = screen.getByText("Updated").closest(".detail-timestamp-item");
|
const updatedLabel = screen.getByText("Updated").closest(".detail-timestamp-item");
|
||||||
expect(mobileBadge).toHaveTextContent("Docs");
|
expect(badge).toHaveTextContent("Docs");
|
||||||
expect(mobileBadge.parentElement).toBe(timestamps);
|
expect(badge.parentElement).toBe(timestamps);
|
||||||
expect(updatedLabel?.nextElementSibling).toBe(mobileBadge);
|
expect(updatedLabel?.nextElementSibling).toBe(badge);
|
||||||
|
expect(screen.getAllByTestId("task-detail-workflow-badge")).toHaveLength(1);
|
||||||
|
expect(screen.queryByTestId("task-detail-workflow-badge-mobile")).toBeNull();
|
||||||
|
expect(container.querySelector(".detail-title-row .detail-workflow-badge")).toBeNull();
|
||||||
expect(screen.getByRole("button", { name: "Back to task list" })).toBeInTheDocument();
|
expect(screen.getByRole("button", { name: "Back to task list" })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -121,18 +121,23 @@ describe("TaskDetailModal", () => {
|
|||||||
expect(css).not.toMatch(/@media[^{]*\(max-width: 768px\)[^{]*\{[\s\S]*?\.detail-timestamp-separator\s*\{[^}]*display:\s*none;/);
|
expect(css).not.toMatch(/@media[^{]*\(max-width: 768px\)[^{]*\{[\s\S]*?\.detail-timestamp-separator\s*\{[^}]*display:\s*none;/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("places only the mobile workflow badge inside the timestamp group at the mobile breakpoint", () => {
|
it("keeps the canonical workflow badge owned by the timestamp group across breakpoints", () => {
|
||||||
const css = readDashboardStylesSource();
|
const css = readDashboardStylesSource();
|
||||||
const mobileBlock = getCssAtRuleBlockContaining(css, "@media (max-width: 768px)", ".detail-timestamps .detail-workflow-badge--mobile");
|
const workflowBadgeBlock = css.match(/^\.detail-workflow-badge\s*\{([^}]*)\}/m)?.[1] ?? "";
|
||||||
const mobileTimestampBadgeBlock = getCssRuleBlock(mobileBlock, ".detail-timestamps .detail-workflow-badge--mobile");
|
const mobileBlock = getCssAtRuleBlockContaining(css, "@media (max-width: 768px)", ".detail-timestamps");
|
||||||
const mobileDesktopBadgeBlock = getCssRuleBlock(mobileBlock, ".detail-workflow-badge--desktop");
|
const mobileTimestampsBlock = getCssRuleBlock(mobileBlock, ".detail-timestamps");
|
||||||
|
|
||||||
expectBaseRule(css, ".detail-workflow-badge--mobile", "display: none;");
|
expect(workflowBadgeBlock).toContain("display: inline-flex;");
|
||||||
expect(mobileDesktopBadgeBlock).toContain("display: none;");
|
expect(workflowBadgeBlock).toContain("align-items: center;");
|
||||||
expect(mobileTimestampBadgeBlock).toContain("display: inline-flex;");
|
expect(workflowBadgeBlock).toContain("flex: 0 1 auto;");
|
||||||
expect(mobileTimestampBadgeBlock).toContain("align-items: center;");
|
expect(workflowBadgeBlock).toContain("text-overflow: ellipsis;");
|
||||||
expect(mobileTimestampBadgeBlock).toContain("flex: 0 0 auto;");
|
expect(mobileTimestampsBlock).toContain("display: flex;");
|
||||||
expect(css).not.toMatch(/@media[^{]*\(max-width: 768px\)[^{]*\{[\s\S]*?\.detail-title-row\s+\.detail-workflow-badge--mobile\s*\{/);
|
expect(mobileTimestampsBlock).toContain("align-items: center;");
|
||||||
|
expect(mobileTimestampsBlock).toContain("flex-wrap: nowrap;");
|
||||||
|
expect(css).not.toMatch(/detail-workflow-badge--desktop/);
|
||||||
|
expect(css).not.toMatch(/detail-workflow-badge--mobile/);
|
||||||
|
expect(css).not.toMatch(/task-detail-workflow-badge-mobile/);
|
||||||
|
expect(css).not.toMatch(/\.detail-title-row\s+\.detail-workflow-badge\s*\{/);
|
||||||
});
|
});
|
||||||
it("keeps desktop and mobile modal sizing guards unchanged", () => {
|
it("keeps desktop and mobile modal sizing guards unchanged", () => {
|
||||||
const css = readDashboardStylesSource();
|
const css = readDashboardStylesSource();
|
||||||
|
|||||||
Reference in New Issue
Block a user