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:
7
.changeset/fn-8766-task-detail-popup-spacing.md
Normal file
7
.changeset/fn-8766-task-detail-popup-spacing.md
Normal 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.
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user