FN-8766: align task detail popup inset

Align the desktop Task Detail popup header and content edges without losing resize access.

- Remove the Task Detail desktop body gutter while preserving shared scrollbar clearance.
- Move Task Detail east resize targets outside the painted shell and retain mobile clipping.
- Add geometry regression coverage and a patch changeset.

Files changed:
 .changeset/fn-8766-task-detail-popup-spacing.md    |  7 +++
 .../dashboard/app/components/FloatingWindow.css    | 30 +++++++++++
 .../components/__tests__/FloatingWindow.test.tsx   | 32 +++++++-----
 ...etailModal.responsive-and-dependencies.test.tsx | 58 ++++++++++++++++++++++
 4 files changed, 115 insertions(+), 12 deletions(-)

Fusion-Task-Id: FN-8766

Fusion-Task-Lineage: b4d23a5e-51d0-45cb-8cbb-4fefb81fa09f

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-08-03 12:49:23 -07:00
parent c8ff721362
commit fef9692cba
4 changed files with 115 additions and 12 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Align the floating Task Detail popup close control with its header edge.
category: fix
dev: Moves only Task Detail desktop resize targets outboard while retaining shared scrollbar clearance.

View File

@@ -261,6 +261,35 @@ Phone sheets are strictly below 768px. At exactly 768px this query must not matc
FNXC:ChatModal 2026-06-22-14:49:
On mobile/narrow app viewports, opening Quick Chat should present the full Chat modal as a full-screen sheet instead of a small draggable desktop window. Scope this to the chat FloatingWindow and override the inline desktop geometry only at the mobile breakpoint; desktop pop-out behavior remains movable/resizable.
*/
/*
FNXC:TaskDetailLayout 2026-08-03-19:36:
FN-8766 keeps the desktop Task Detail popup's visible header/content edge symmetric while
preserving the shared body's scrollbar clearance for every other FloatingWindow consumer. This
headerless host owns its close button, so remove only its visible body gutter and move its desktop
east resize hit areas outside the painted shell; the scrollbar remains fully usable and the close
control does not sit behind a resize target. Tablet retains its existing touch geometry and phones
remain full-screen sheets below.
*/
.floating-window--task-detail:not(.floating-window--tablet-viewport) {
overflow: visible;
}
.floating-window--task-detail:not(.floating-window--tablet-viewport) .floating-window__body {
margin-inline-end: 0;
}
.floating-window--task-detail:not(.floating-window--tablet-viewport) .floating-window__resize-handle--e {
right: calc(var(--space-sm) * -1);
}
.floating-window--task-detail:not(.floating-window--tablet-viewport) .floating-window__resize-handle--ne {
right: calc(var(--space-lg) * -1);
}
.floating-window--task-detail:not(.floating-window--tablet-viewport) .floating-window__resize-handle--se {
right: calc(var(--space-lg) * -1);
}
/*
FNXC:ModalTouchGeometry 2026-07-26-13:25:
FN-8606 migrated core and workflow dialogs share FloatingWindow's pointer contract. Their
@@ -452,6 +481,7 @@ all floating affordances so persisted desktop geometry cannot imply a draggable
*/
.floating-window--task-detail {
inset: 0 !important;
overflow: hidden !important;
width: 100vw !important;
height: 100dvh !important;
min-width: 0 !important;

View File

@@ -161,12 +161,14 @@ describe("FloatingWindow", () => {
expect(cssRuleContaining(floatingWindowCss, ".floating-window__resize-handle--ne", "right: 0;")).toContain("right: 0;");
expect(cssRuleContaining(floatingWindowCss, ".floating-window__resize-handle--se", "right: 0;")).toContain("right: 0;");
// No shared caller may move a right handle back into the reserved scrollbar
// gutter, nor override the body gutter, AT DESKTOP WIDTHS. Mobile full-screen
// sheet overrides (inside @media) are legitimate and excluded from this scan.
/*
FNXC:TaskDetailLayout 2026-08-03-19:36:
FN-8766 is the sole desktop exception: its header-owned close control requires a symmetric
painted edge, so its gutter is removed only while its east resize targets move outboard.
Every unrelated consumer keeps the shared scrollbar-clearance geometry.
*/
const desktopAppCss = stripAtMediaBlocks(allAppCss);
for (const callerClass of [
"floating-window--task-detail",
"floating-window--automation",
"floating-window--mission-interview",
"floating-window--pr-create",
@@ -182,14 +184,20 @@ describe("FloatingWindow", () => {
expect(bodyRules.some((rule) => /margin-inline-end\s*:/.test(rule)), callerClass).toBe(false);
}
const desktopTaskBody = cssRuleContaining(
desktopAppCss,
".floating-window--task-detail:not(.floating-window--tablet-viewport) .floating-window__body",
"margin-inline-end",
);
expect(desktopTaskBody).toContain("margin-inline-end: 0;");
expect(cssRuleContaining(desktopAppCss, ".floating-window--task-detail:not(.floating-window--tablet-viewport) .floating-window__resize-handle--e", "right")).toContain("right: calc(var(--space-sm) * -1);");
expect(cssRuleContaining(desktopAppCss, ".floating-window--task-detail:not(.floating-window--tablet-viewport) .floating-window__resize-handle--ne", "right")).toContain("right: calc(var(--space-lg) * -1);");
expect(cssRuleContaining(desktopAppCss, ".floating-window--task-detail:not(.floating-window--tablet-viewport) .floating-window__resize-handle--se", "right")).toContain("right: calc(var(--space-lg) * -1);");
/*
FNXC:MobileTaskPopups 2026-07-17-08:20:
Regression guard for the mobile task-detail left-shift fix: the full-screen
task-detail sheet hides all resize handles, so FN-8015's inherited
`margin-inline-end: var(--space-lg)` body gutter only added dead space on the
right and shifted the whole panel left. The mobile breakpoint must zero it so
`.detail-body`'s own padding defines both insets equally. This is the sole
legitimate body-gutter override and lives only inside the mobile @media block.
FNXC:MobileTaskPopups 2026-08-03-19:36:
The full-screen task-detail sheet hides every resize handle, so the inherited gutter must
remain zero and the desktop outboard-target host must resume clipping for sheet geometry.
*/
const mobileTaskDetailBody = cssRuleContaining(
allAppCss,
@@ -197,7 +205,7 @@ describe("FloatingWindow", () => {
"margin-inline-end",
);
expect(mobileTaskDetailBody).toContain("margin-inline-end: 0;");
expect(cssRulesForClass(desktopAppCss, "floating-window--task-detail").some((rule) => rule.includes("floating-window__body"))).toBe(false);
expect(cssRuleContaining(allAppCss, ".floating-window--task-detail", "overflow: hidden !important;")).toContain("overflow: hidden !important;");
/*
FNXC:ModalTouchGeometry 2026-08-01-03:48:

View File

@@ -24,6 +24,7 @@ import {
setupTaskDetailModalHooks,
} from "./TaskDetailModal.test-helpers";
import { TaskDetailModal, TaskDetailContent } from "../TaskDetailModal";
import { FloatingWindow } from "../FloatingWindow";
import {
assertModalGeometryRecoveryAndSheetContracts,
assertRenderedModalTouchGeometry,
@@ -856,6 +857,63 @@ describe("TaskDetailModal", () => {
}
});
it("keeps the floating task header symmetric without sacrificing its resize targets", () => {
const floatingCss = readFileSync(resolve(__dirname, "../FloatingWindow.css"), "utf8");
const desktopTaskSelector = ".floating-window--task-detail:not(.floating-window--tablet-viewport)";
const taskPopupBody = getExactCssRuleBlock(floatingCss, `${desktopTaskSelector} .floating-window__body`);
const sharedBody = getExactCssRuleBlock(floatingCss, ".floating-window__body");
const header = getExactCssRuleBlock(readDashboardStylesSource(), ".modal-header");
const eastResize = getExactCssRuleBlock(floatingCss, `${desktopTaskSelector} .floating-window__resize-handle--e`);
const northEastResize = getExactCssRuleBlock(floatingCss, `${desktopTaskSelector} .floating-window__resize-handle--ne`);
const southEastResize = getExactCssRuleBlock(floatingCss, `${desktopTaskSelector} .floating-window__resize-handle--se`);
const onRequestClose = vi.fn();
/*
FNXC:TaskDetailLayout 2026-08-03-19:36:
The shared body reserves desktop scrollbar clearance. Task Detail moves only its resize
hit areas outboard, so the embedded header retains matching tokenized edges.
*/
expect(sharedBody).toContain("margin-inline-end: var(--space-lg);");
expect(taskPopupBody).toContain("margin-inline-end: 0;");
expect(header).toContain("padding: var(--modal-padding);");
expect(eastResize).toContain("right: calc(var(--space-sm) * -1);");
expect(northEastResize).toContain("right: calc(var(--space-lg) * -1);");
expect(southEastResize).toContain("right: calc(var(--space-lg) * -1);");
const { baseElement, unmount } = render(
<FloatingWindow
windowKey="task-detail-inset"
title="FN-8766"
onClose={onRequestClose}
hideHeader
dragHandleSelector=".task-detail-content--embedded > .modal-header"
className="floating-window--task-detail"
layer="task-detail"
>
<TaskDetailContent
task={makeTask({ id: "FN-8766", title: "A task title long enough to exercise header alignment" })}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
embedded
onRequestClose={onRequestClose}
/>
</FloatingWindow>,
);
const popup = baseElement.querySelector("[data-testid='floating-window-task-detail-inset']");
const close = screen.getByRole("button", { name: "Close" });
expect(popup).toHaveClass("floating-window--task-detail");
expect(popup?.querySelector(".task-detail-content--embedded > .modal-header")).toContainElement(close);
expect(close).toHaveClass("task-detail-floating-close");
expect(popup?.querySelectorAll(".floating-window__resize-handle")).toHaveLength(8);
fireEvent.click(close);
expect(onRequestClose).toHaveBeenCalledTimes(1);
unmount();
});
it("uses production FloatingWindow geometry for touch drag, eight-direction resize, recovery, and sheets", () => {
const mount = () => render(
<TaskDetailModal