FN-7271: move mobile workflow badge into timestamp row
Move the mobile task-detail workflow badge next to the updated timestamp while preserving desktop placement. - Add separate desktop and mobile workflow badge render targets in the task detail modal. - Hide the header badge on mobile and reveal the timestamp-row badge with scoped responsive styles. - Cover the mobile badge placement and CSS guardrails in task detail tests. - Add a patch changeset for the published Fusion package. Files changed: .changeset/fn-7271-mobile-workflow-badge.md | 7 +++++++ .../dashboard/app/components/TaskDetailModal.css | 18 ++++++++++++++++++ .../dashboard/app/components/TaskDetailModal.tsx | 7 ++++++- .../__tests__/TaskDetailModal.rendering.test.tsx | 10 ++++++++-- ...DetailModal.responsive-and-dependencies.test.tsx | 21 +++++++++++++++++---- 5 files changed, 56 insertions(+), 7 deletions(-) Fusion-Task-Id: FN-7271 Fusion-Task-Lineage: def0b5c5-b881-45f8-aa7c-80243d8de8ec Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-7271-mobile-workflow-badge.md
Normal file
7
.changeset/fn-7271-mobile-workflow-badge.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Move the mobile task-detail workflow badge beside the updated timestamp.
|
||||
category: fix
|
||||
dev: Keeps the desktop task-detail header badge while showing a mobile-only timestamp-group badge.
|
||||
@@ -57,6 +57,10 @@ The gray top header band (task id + column badge) was over-padded. Trim its vert
|
||||
background: var(--surface-raised);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.detail-workflow-badge--mobile {
|
||||
display: none;
|
||||
}
|
||||
.badge-triage {
|
||||
background: var(--status-triage-bg);
|
||||
color: var(--triage);
|
||||
@@ -308,12 +312,26 @@ The task-detail modal metadata must keep priority, execution mode, provenance, P
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
.detail-workflow-badge--desktop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.detail-timestamps {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.detail-timestamps .detail-workflow-badge--mobile {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.detail-provenance-context {
|
||||
max-width: 20ch;
|
||||
}
|
||||
|
||||
@@ -2717,7 +2717,7 @@ export function TaskDetailContent({
|
||||
{columnLabel(task.column)}
|
||||
</span>
|
||||
{taskWorkflowName && (
|
||||
<span className="detail-workflow-badge" data-testid="task-detail-workflow-badge">
|
||||
<span className="detail-workflow-badge detail-workflow-badge--desktop" data-testid="task-detail-workflow-badge">
|
||||
{taskWorkflowName}
|
||||
</span>
|
||||
)}
|
||||
@@ -3093,6 +3093,11 @@ export function TaskDetailContent({
|
||||
{formatTimestamp(task.updatedAt)}
|
||||
</time>
|
||||
</span>
|
||||
{taskWorkflowName && (
|
||||
<span className="detail-workflow-badge detail-workflow-badge--mobile" data-testid="task-detail-workflow-badge-mobile">
|
||||
{taskWorkflowName}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{task.branchContext?.groupId && (
|
||||
|
||||
@@ -170,10 +170,10 @@ describe("TaskDetailModal", () => {
|
||||
expect(container.querySelector(".detail-workflow-badge")).toBeNull();
|
||||
});
|
||||
|
||||
it("renders in the mobile back-header variant", async () => {
|
||||
it("renders beside the Updated timestamp in the mobile back-header variant", async () => {
|
||||
vi.mocked(dashboardApi.fetchBoardWorkflows).mockResolvedValueOnce(workflowPayload);
|
||||
|
||||
render(
|
||||
const { container } = render(
|
||||
<TaskDetailModal
|
||||
initialTab="definition"
|
||||
mobileHeaderMode="back"
|
||||
@@ -188,6 +188,12 @@ describe("TaskDetailModal", () => {
|
||||
);
|
||||
|
||||
expect(await screen.findByTestId("task-detail-workflow-badge")).toHaveTextContent("Docs");
|
||||
const mobileBadge = screen.getByTestId("task-detail-workflow-badge-mobile");
|
||||
const timestamps = container.querySelector(".detail-timestamps");
|
||||
const updatedLabel = screen.getByText("Updated").closest(".detail-timestamp-item");
|
||||
expect(mobileBadge).toHaveTextContent("Docs");
|
||||
expect(mobileBadge.parentElement).toBe(timestamps);
|
||||
expect(updatedLabel?.nextElementSibling).toBe(mobileBadge);
|
||||
expect(screen.getByRole("button", { name: "Back to task list" })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -120,6 +120,20 @@ describe("TaskDetailModal", () => {
|
||||
expect(css).not.toMatch(/@media[^{]*\(max-width: 768px\)[^{]*\{[\s\S]*?\.detail-timestamps\s*\{[^}]*flex-direction:\s*column;/);
|
||||
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", () => {
|
||||
const css = readDashboardStylesSource();
|
||||
const mobileBlock = getCssAtRuleBlockContaining(css, "@media (max-width: 768px)", ".detail-timestamps .detail-workflow-badge--mobile");
|
||||
const mobileTimestampBadgeBlock = getCssRuleBlock(mobileBlock, ".detail-timestamps .detail-workflow-badge--mobile");
|
||||
const mobileDesktopBadgeBlock = getCssRuleBlock(mobileBlock, ".detail-workflow-badge--desktop");
|
||||
|
||||
expectBaseRule(css, ".detail-workflow-badge--mobile", "display: none;");
|
||||
expect(mobileDesktopBadgeBlock).toContain("display: none;");
|
||||
expect(mobileTimestampBadgeBlock).toContain("display: inline-flex;");
|
||||
expect(mobileTimestampBadgeBlock).toContain("align-items: center;");
|
||||
expect(mobileTimestampBadgeBlock).toContain("flex: 0 0 auto;");
|
||||
expect(css).not.toMatch(/@media[^{]*\(max-width: 768px\)[^{]*\{[\s\S]*?\.detail-title-row\s+\.detail-workflow-badge--mobile\s*\{/);
|
||||
});
|
||||
it("keeps desktop and mobile modal sizing guards unchanged", () => {
|
||||
const css = readDashboardStylesSource();
|
||||
const mobileBlock = getCssAtRuleBlockContaining(css, "@media (max-width: 768px)", ".modal-overlay:has(.task-detail-modal)");
|
||||
@@ -737,7 +751,7 @@ describe("TaskDetailModal", () => {
|
||||
expect(screen.queryByRole("button", { name: "Finish & Close" })).toBeNull();
|
||||
});
|
||||
|
||||
it("shows Start PR Review and calls onMergeTask for pull-request strategy when autoMerge is off and no PR exists", async () => {
|
||||
it("shows Start PR Review and opens PR creation for pull-request strategy when autoMerge is off and no PR exists", async () => {
|
||||
const { fetchSettings } = await import("../../api");
|
||||
const onMergeTask = vi.fn(async () => ({ merged: false } as MergeResult));
|
||||
vi.mocked(fetchSettings).mockResolvedValueOnce({
|
||||
@@ -764,9 +778,8 @@ describe("TaskDetailModal", () => {
|
||||
const button = await screen.findByRole("button", { name: "Start PR Review" });
|
||||
fireEvent.click(button);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onMergeTask).toHaveBeenCalledWith("FN-099");
|
||||
});
|
||||
expect(await screen.findByRole("heading", { name: "Create Pull Request" })).toBeInTheDocument();
|
||||
expect(onMergeTask).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("refreshes PR status for Check PR Status without merge prompt", async () => {
|
||||
|
||||
Reference in New Issue
Block a user