FN-8501: align mobile task-detail footer actions

Align the mobile task-detail Move action with the footer edge.

- Let the footer spacer absorb surplus mobile width instead of the Move dropdown.
- Cover canonical footer order and triage actions-absent behavior.
- Add a patch changeset for the mobile footer fix.

Files changed:
 .changeset/fn-8501-mobile-footer-alignment.md      |  7 ++++
 .../dashboard/app/components/TaskDetailModal.css   |  8 ++++-
 ...etailModal.responsive-and-dependencies.test.tsx | 40 ++++++++++++++++++++++
 3 files changed, 54 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-8501

Fusion-Task-Lineage: a87bb5af-d16e-4ca8-9111-3168c8440f14

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-22 17:56:27 -07:00
parent c61336d24c
commit 295226ea64
3 changed files with 54 additions and 1 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Align mobile task-detail Move actions with the footer edge.
category: fix
dev: The Task Detail spacer now owns mobile footer surplus width.

View File

@@ -3402,6 +3402,12 @@ Live and Feed Activity expansion overlays the content instead of reserving a too
for overflow, so scoped tokenized packing, flex shrink guards, and label
ellipsis let every text-bearing footer control yield while upward menus stay
unclipped.
FNXC:TaskDetailModalResponsive 2026-07-22-17:12:
FN-8501 requires the flexible spacer—not the Move dropdown wrapper—to absorb
available mobile row space. Keeping the wrapper content-sized puts the primary
transition at the trailing edge while its controls retain the FN-8492 shrink
and ellipsis safeguards for dense review variants.
*/
.task-detail-content .modal-actions {
flex-wrap: nowrap;
@@ -3422,7 +3428,7 @@ Live and Feed Activity expansion overlays the content instead of reserving a too
}
.task-detail-content .detail-move-dropdown {
flex: 1 1 auto;
flex: 0 1 auto;
}
.task-detail-content .detail-move-actions-in-review {

View File

@@ -901,6 +901,7 @@ describe("TaskDetailModal", () => {
mobileBlock,
".task-detail-content .detail-actions-dropdown,\n .task-detail-content .detail-move-dropdown",
);
const desktopMoveDropdownBlock = getExactCssRuleBlock(css, ".detail-move-dropdown");
const expandedChatBlock = getExactCssRuleBlock(css, ".task-detail-content--chat-expanded .modal-actions");
/*
@@ -917,6 +918,13 @@ describe("TaskDetailModal", () => {
expect(spacerBlock).toContain("min-width: 0;");
expect(dropdownBlock).toContain("min-width: 0;");
expect(dropdownBlock).toContain("flex-shrink: 1;");
// FNXC:TaskDetailModalResponsive 2026-07-22-17:12: The Actions + Move
// symptom regressed when Move grew into the spacer's middle region. This
// mobile-only contract leaves all surplus width to the spacer, making the
// move control trailing-aligned without changing desktop dropdown layout.
expect(mobileBlock).toMatch(/\.task-detail-content \.detail-move-dropdown\s*\{\s*flex:\s*0 1 auto;/);
expect(mobileBlock).not.toMatch(/\.task-detail-content \.detail-move-dropdown\s*\{\s*flex:\s*1 1 auto;/);
expect(desktopMoveDropdownBlock).not.toContain("flex:");
expect(inReviewBlock).toContain("display: flex;");
expect(inReviewBlock).toContain("flex-wrap: nowrap;");
expect(inReviewBlock).toContain("min-width: 0;");
@@ -974,6 +982,38 @@ describe("TaskDetailModal", () => {
expect(standardFooter).toBeTruthy();
expect(standardFooter?.contains(screen.getByRole("button", { name: "Actions" }))).toBe(true);
expect(standardFooter?.querySelector(".detail-move-btn")).toBeTruthy();
const footerChildren = Array.from(standardFooter?.children ?? []);
const actionsIndex = footerChildren.findIndex((child) => child.classList.contains("detail-actions-dropdown"));
const spacerIndex = footerChildren.findIndex((child) => child.classList.contains("modal-actions-spacer"));
const moveIndex = footerChildren.findIndex((child) => child.classList.contains("detail-move-dropdown"));
// The shared modal, embedded panel, dock, and mobile sheet all mount this
// canonical footer order: leading Actions, flexible spacer, trailing Move.
expect(actionsIndex).toBeGreaterThanOrEqual(0);
expect(spacerIndex).toBeGreaterThan(actionsIndex);
expect(moveIndex).toBeGreaterThan(spacerIndex);
});
it("keeps the triage footer usable when Actions is absent", () => {
const { container } = render(
<TaskDetailModal
initialTab="definition"
task={makeTask({ column: "triage" as Column })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
const footer = container.querySelector(".modal-actions");
expect(footer?.querySelector(".detail-actions-dropdown")).toBeNull();
expect(footer?.querySelector(".modal-actions-spacer")).toBeTruthy();
expect(footer?.querySelector(".detail-move-dropdown .detail-move-btn")).toBeTruthy();
expect(screen.getByRole("button", { name: "Delete task" })).toBeTruthy();
});
it("modal-actions contains Delete and Pause buttons for non-done tasks (via Actions dropdown)", () => {