diff --git a/.changeset/fn-8612-tablet-modal-padding.md b/.changeset/fn-8612-tablet-modal-padding.md
new file mode 100644
index 0000000000..446d0f0989
--- /dev/null
+++ b/.changeset/fn-8612-tablet-modal-padding.md
@@ -0,0 +1,7 @@
+---
+"@runfusion/fusion": patch
+---
+
+summary: Remove excess tablet padding from task modals while preserving touch resize targets.
+category: fix
+dev: Task-detail drag targets stay out of flow; browser coverage protects generic FloatingWindow geometry.
diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md
index 423ba6ae49..2edb6aed48 100644
--- a/docs/dashboard-guide.md
+++ b/docs/dashboard-guide.md
@@ -86,15 +86,15 @@ Press `Escape` to close the current/topmost dashboard popup. Popped-out task win
Movable dashboard pop-outs remember their last desktop location and size, while centered resizable dialogs remember their size. When a pop-out becomes a full-screen sheet at mobile widths (or, for Artifact Gallery, its short-height sheet breakpoint), it leaves that desktop record untouched; reopening it on desktop restores the prior floating geometry.
-
+
### Task modal resizing on tablets
-Task Detail and New Task remain resizable on known touch tablets, including a 768px-wide tablet viewport. Task Detail exposes its accessible bottom-right resize grip; New Task keeps its draggable header and edge/corner resize controls. On that tablet-touch surface, the painted control remains compact but its explicit resize hit target is at least 44px, sits outside the panel content, and owns touch gestures with pointer capture. Their geometry stays within the viewport and is restored from browser storage on later tablet or desktop opens. True phones, narrow folded panes, and desktop coarse-pointer devices do not receive the enlarged target: phones remain full-screen sheets and desktop preserves cursor-sized resize chrome.
+Task Detail and New Task remain resizable on known touch tablets, including a 768px-wide tablet viewport. Task Detail exposes its accessible bottom-right resize grip; New Task keeps its draggable header and edge/corner resize controls. On that tablet-touch surface, the painted control remains compact but its explicit resize hit target is at least 44px, sits outside the panel content, and owns touch gestures with pointer capture. The touch target is hit-area-only: task-modal headers and bodies retain desktop density without a visible tablet padding band. Their geometry stays within the viewport and is restored from browser storage on later tablet or desktop opens. True phones, narrow folded panes, and desktop coarse-pointer devices do not receive the enlarged target: phones remain full-screen sheets and desktop preserves cursor-sized resize chrome.
-
+
### Shared floating-window touch contract
-Use `FloatingWindow` for a moveable and resizable dashboard surface rather than adding per-modal pointer code. On known tablet touch viewports it uses `isTabletTouchViewport`, applies `data-resize-hit-target="true"` to the drag handle and all eight edge/corner handles, and expands only their hit areas to the shared 44px target without thickening painted borders or covering content/footer controls. Never gate these controls on bare `(pointer: coarse)`: desktop hybrids keep desktop geometry. Phone full-screen sheets are strictly **below 768px** (`max-width: 767.98px`); a 768px viewport is tablet-class, so JS geometry and CSS must preserve active targets there.
+Use `FloatingWindow` for a moveable and resizable dashboard surface rather than adding per-modal pointer code. On known tablet touch viewports it uses `isTabletTouchViewport`, applies `data-resize-hit-target="true"` to the drag handle and all eight edge/corner handles, and expands only their hit areas to the shared 44px target without thickening painted borders or covering content/footer controls. Task-detail uses an out-of-flow drag target so its hit area does not add painted header padding; this density adjustment is scoped to task modals, while a classless browser-fixture control verifies that generic FloatingWindow geometry stays unchanged. Never gate these controls on bare `(pointer: coarse)`: desktop hybrids keep desktop geometry. Phone full-screen sheets are strictly **below 768px** (`max-width: 767.98px`); a 768px viewport is tablet-class, so JS geometry and CSS must preserve active targets there.
diff --git a/packages/dashboard/app/__tests__/task-detail-modal-tablet-width.test.ts b/packages/dashboard/app/__tests__/task-detail-modal-tablet-width.test.ts
index 2bd35f97a6..6ef412dc52 100644
--- a/packages/dashboard/app/__tests__/task-detail-modal-tablet-width.test.ts
+++ b/packages/dashboard/app/__tests__/task-detail-modal-tablet-width.test.ts
@@ -33,6 +33,14 @@ describe("task detail modal tablet width (FN-5599, FN-6500)", () => {
expect(modalRuleMatch![0]).toContain("max-width: 98vw;");
});
+ it("keeps the tablet touch resize grip out of task-detail layout padding", () => {
+ const touchSurfaceRule = detailModalCss.match(/\.modal\.task-detail-modal\.task-modal--touch-resize\s*\{[^}]*\}/s)?.[0] ?? "";
+
+ expect(touchSurfaceRule).toContain("overflow: visible;");
+ expect(touchSurfaceRule).not.toMatch(/\bpadding(?:-[\w-]+)?:/);
+ expect(touchSurfaceRule).not.toMatch(/\bmargin(?:-[\w-]+)?:/);
+ });
+
it("overrides phone-sheet geometry and restores the resize grip for a known 768px tablet", () => {
const mobileBlockMatch = detailModalCss.match(/@media\s*\(max-width:\s*768px\)\s*\{[\s\S]*?\.task-modal--tablet \.modal-resize-grip\s*\{[^}]*\}[\s\S]*?\n\}/);
expect(mobileBlockMatch).toBeTruthy();
diff --git a/packages/dashboard/app/components/FloatingWindow.css b/packages/dashboard/app/components/FloatingWindow.css
index 1751a118dc..5c37eebd4f 100644
--- a/packages/dashboard/app/components/FloatingWindow.css
+++ b/packages/dashboard/app/components/FloatingWindow.css
@@ -132,9 +132,8 @@ not cover close controls, hosted footer actions, or scrollable content.
/*
FNXC:ModalTouchGeometry 2026-07-26-12:34:
A headerless window delegates dragging to a caller-owned header, so that resolved element must
-receive the same tablet-only target floor as FloatingWindow's built-in header. `min-block-size`
-enlarges only the interactive header box when necessary; it does not thicken resize borders or
-extend an invisible target into hosted content, close controls, or footer actions.
+receive the same tablet-only target floor as FloatingWindow's built-in header. Generic windows
+retain that established 44px layout contract.
*/
.floating-window--touch-geometry .floating-window__delegated-drag-handle {
box-sizing: border-box;
@@ -144,6 +143,41 @@ extend an invisible target into hosted content, close controls, or footer action
touch-action: none;
}
+/*
+FNXC:ModalTouchGeometry 2026-07-26-15:30:
+Task-detail pop-outs are the only delegated-header surface with the reported tablet density
+regression. Their 44px drag contract is hit-area-only: an absolute pseudo-element extends the
+pointer target without making the painted task header taller. Keep the target below controls so a
+close or action button receives its tap. Do not widen this selector because FloatingWindow also
+hosts unrelated chat, import, workflow, and utility panels.
+*/
+.floating-window--task-detail.floating-window--touch-geometry .floating-window__delegated-drag-handle {
+ min-block-size: 0;
+ position: relative;
+ z-index: 0;
+}
+
+.floating-window--task-detail.floating-window--touch-geometry .floating-window__delegated-drag-handle::before {
+ content: "";
+ position: absolute;
+ z-index: 0;
+ inset-inline: 0;
+ block-size: var(--modal-resize-touch-target);
+ inset-block-start: 50%;
+ transform: translateY(-50%);
+}
+
+/*
+FNXC:ModalTouchGeometry 2026-07-26-16:00:
+The invisible tablet drag extension must stay beneath actionable task-header descendants.
+Explicit stacking preserves the 44px parent hit area while buttons and links retain their normal
+activation instead of beginning a window drag.
+*/
+.floating-window--task-detail.floating-window--touch-geometry .floating-window__delegated-drag-handle :is(button, a, input, select, textarea, [role="button"]) {
+ position: relative;
+ z-index: 1;
+}
+
.floating-window--touch-geometry .floating-window__delegated-drag-handle:active {
cursor: grabbing;
}
diff --git a/packages/dashboard/app/components/NewTaskModal.css b/packages/dashboard/app/components/NewTaskModal.css
index f46c593c52..0911cd53b6 100644
--- a/packages/dashboard/app/components/NewTaskModal.css
+++ b/packages/dashboard/app/components/NewTaskModal.css
@@ -794,6 +794,20 @@ The New Task create workflow selector is a real dropdown so workflow identity ic
padding: var(--space-md) var(--space-sm);
}
+ /*
+ FNXC:TaskModalResize 2026-07-26-15:30:
+ The physical-screen tablet path shares this CSS query with phone sheets. Restore the desktop
+ body density only when the runtime tablet class is present; phones keep their compact sheet
+ padding while the tablet resize surface gains no visible inset from its touch controls.
+ */
+ .new-task-modal.task-modal--tablet .modal-header {
+ padding: var(--modal-padding);
+ }
+
+ .new-task-modal.task-modal--tablet .modal-body {
+ padding: var(--space-xl);
+ }
+
.new-task-modal .form-group {
padding: 0 var(--space-md);
}
diff --git a/packages/dashboard/app/components/__tests__/FloatingWindow.touch-geometry.test.tsx b/packages/dashboard/app/components/__tests__/FloatingWindow.touch-geometry.test.tsx
index 3e33509cf5..dc907aaee6 100644
--- a/packages/dashboard/app/components/__tests__/FloatingWindow.touch-geometry.test.tsx
+++ b/packages/dashboard/app/components/__tests__/FloatingWindow.touch-geometry.test.tsx
@@ -1,4 +1,6 @@
import { fireEvent, render, screen } from "@testing-library/react";
+import { readFileSync } from "node:fs";
+import { resolve } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const viewport = vi.hoisted(() => ({ tabletTouch: false }));
@@ -15,6 +17,7 @@ vi.mock("../../hooks/useViewportMode", async () => {
import { FloatingWindow } from "../FloatingWindow";
const directions = ["n", "s", "e", "w", "ne", "nw", "se", "sw"] as const;
+const floatingWindowCss = readFileSync(resolve(__dirname, "../FloatingWindow.css"), "utf8");
function renderWindow(key = "touch-geometry") {
return render(
@@ -62,6 +65,17 @@ describe("FloatingWindow tablet touch geometry", () => {
expect(screen.getByTestId("floating-window-drag-handle-touch-geometry")).toHaveAttribute("data-resize-hit-target", "true");
});
+ it("keeps generic delegated headers at the shared 44px layout target while task detail uses an out-of-flow target", () => {
+ const genericRule = floatingWindowCss.match(/\.floating-window--touch-geometry \.floating-window__delegated-drag-handle\s*\{[^}]*\}/s)?.[0] ?? "";
+ const taskRule = floatingWindowCss.match(/\.floating-window--task-detail\.floating-window--touch-geometry \.floating-window__delegated-drag-handle\s*\{[^}]*\}/s)?.[0] ?? "";
+ const taskTargetRule = floatingWindowCss.match(/\.floating-window--task-detail\.floating-window--touch-geometry \.floating-window__delegated-drag-handle::before\s*\{[^}]*\}/s)?.[0] ?? "";
+
+ expect(genericRule).toContain("min-block-size: var(--modal-resize-touch-target);");
+ expect(taskRule).toContain("min-block-size: 0;");
+ expect(taskTargetRule).toContain("block-size: var(--modal-resize-touch-target);");
+ expect(taskTargetRule).toContain("position: absolute;");
+ });
+
it("applies the touch contract and drag gesture to a headerless delegated handle", () => {
viewport.tabletTouch = true;
render(
diff --git a/packages/dashboard/app/components/__tests__/NewTaskModal.test.tsx b/packages/dashboard/app/components/__tests__/NewTaskModal.test.tsx
index 87e0c1812a..808d40999d 100644
--- a/packages/dashboard/app/components/__tests__/NewTaskModal.test.tsx
+++ b/packages/dashboard/app/components/__tests__/NewTaskModal.test.tsx
@@ -155,6 +155,14 @@ describe("NewTaskModal", () => {
});
});
+ it("restores desktop body density only for the 768px tablet resize class", () => {
+ const tabletRule = newTaskModalCss.match(/\.new-task-modal\.task-modal--tablet \.modal-body\s*\{[^}]*\}/s)?.[0] ?? "";
+ const phoneRule = newTaskModalCss.match(/\.new-task-modal \.modal-body\s*\{[^}]*padding:\s*var\(--space-md\) var\(--space-sm\);[^}]*\}/s)?.[0] ?? "";
+
+ expect(tabletRule).toContain("padding: var(--space-xl);");
+ expect(phoneRule).toContain("padding: var(--space-md) var(--space-sm);");
+ });
+
it("applies keyboard CSS variables when mobile keyboard is open", () => {
mockUseMobileKeyboard.mockReturnValue({
keyboardOpen: true,
diff --git a/packages/dashboard/app/task-modal-touch-resize-e2e-fixture.tsx b/packages/dashboard/app/task-modal-touch-resize-e2e-fixture.tsx
index eb390d10a3..2d3e8d09e1 100644
--- a/packages/dashboard/app/task-modal-touch-resize-e2e-fixture.tsx
+++ b/packages/dashboard/app/task-modal-touch-resize-e2e-fixture.tsx
@@ -1,4 +1,4 @@
-import React, { useRef } from "react";
+import React, { useRef, useState } from "react";
import { createRoot } from "react-dom/client";
import i18n from "i18next";
import { I18nextProvider, initReactI18next } from "react-i18next";
@@ -15,6 +15,20 @@ const params = new URLSearchParams(window.location.search);
const surface = params.get("surface") ?? "new-task";
if (params.has("reset")) localStorage.clear();
+/*
+FNXC:TaskModalResize 2026-07-26-16:00:
+The browser fixture seeds the production persistence path only for resize gestures that need
+headroom. It never supplies inline panel geometry, so density assertions continue to exercise
+TaskDetailModal.css's real tablet overlay and width rules.
+*/
+const detailSize = params.get("detailSize");
+if (detailSize) {
+ const [width, height] = detailSize.split("x").map(Number);
+ if (Number.isFinite(width) && Number.isFinite(height)) {
+ localStorage.setItem("task-detail-modal-size", JSON.stringify({ width, height }));
+ }
+}
+
void i18n.use(initReactI18next).init({
lng: "en",
fallbackLng: "en",
@@ -38,9 +52,9 @@ function TaskDetailResizeHarness() {
const viewportMode = useViewportMode();
const touchTargets = isTabletTouchViewport(viewportMode);
useModalResizePersist(ref, true, "task-detail-modal-size", { touchTargets });
- return