fix(dashboard): stop mobile task-detail panel shifting left

The full-screen mobile task-detail sheet hides all resize handles, so
FN-8015's `margin-inline-end: var(--space-lg)` gutter on the shared
`.floating-window__body` (added to keep the scrollbar clear of desktop
resize hot zones) only added dead space on the right and shifted the
entire panel left. Zero it for `.floating-window--task-detail` inside
the mobile breakpoint so `.detail-body`'s own padding defines both
insets equally; desktop resize-handle clearance is untouched.

Refined the FN-8015 invariant test to enforce the desktop hot-zone gutter
media-aware (strips @media blocks) and added a regression guard for the
mobile zeroing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-07-17 08:35:44 -07:00
parent f27965d711
commit c6adac6e7c
3 changed files with 76 additions and 2 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Fix the mobile task detail panel being shifted left with a dead gutter on the right.
category: fix
dev: The full-screen mobile task-detail sheet hides all resize handles, so FN-8015's `margin-inline-end: var(--space-lg)` scrollbar/resize-hot-zone gutter on the shared `.floating-window__body` only added dead space on the right. Zeroed it for `.floating-window--task-detail` inside the mobile breakpoint; desktop resize-handle clearance is untouched.

View File

@@ -192,6 +192,21 @@ On mobile/narrow app viewports, opening Quick Chat should present the full Chat
display: none;
}
/*
FNXC:MobileTaskPopups 2026-07-17-08:20:
FN-8015 reserves `margin-inline-end: var(--space-lg)` on the shared
`.floating-window__body` so a hosted scrollbar clears the desktop
east/north-east/south-east resize hot zones. The mobile task-detail sheet is
full-screen and hides every resize handle (above), so that gutter protects
nothing and instead shifts the whole panel left — leaving an uneven right
inset on every row. Zero it here so `.detail-body`'s own symmetric padding
defines both insets equally. Desktop resize-handle clearance is untouched
(this rule is mobile-only).
*/
.floating-window--task-detail .floating-window__body {
margin-inline-end: 0;
}
.floating-window--task-detail .task-detail-content--embedded,
.floating-window--task-detail .task-detail-content--embedded > .modal-header {
border-radius: 0;

View File

@@ -38,6 +38,38 @@ function cssRulesForClass(css: string, className: string): string[] {
return [...css.matchAll(new RegExp(`\\.${escaped}[^{}]*\\{[^}]*\\}`, "g"))].map((match) => match[0]);
}
/*
FNXC:FloatingWindow 2026-07-17-08:20:
The FN-8015 desktop resize-hot-zone invariant only governs desktop widths. The
mobile full-screen sheet variants hide every resize handle, so removing the
inherited body gutter there is legitimate (and required — see the mobile
task-detail left-shift fix). Strip `@media` blocks with balanced-brace matching
before scanning so the desktop invariant ignores mobile-only overrides.
*/
function stripAtMediaBlocks(css: string): string {
let out = "";
let i = 0;
while (i < css.length) {
const at = css.indexOf("@media", i);
if (at === -1) {
out += css.slice(i);
break;
}
out += css.slice(i, at);
const open = css.indexOf("{", at);
if (open === -1) break;
let depth = 1;
let j = open + 1;
while (j < css.length && depth > 0) {
if (css[j] === "{") depth++;
else if (css[j] === "}") depth--;
j++;
}
i = j;
}
return out;
}
function setSheetViewport(isSheetWidth: boolean): void {
vi.stubGlobal("matchMedia", vi.fn((query: string) => ({
matches: query === "(max-width: 768px)" ? isSheetWidth : query === "(max-height: 480px)",
@@ -107,7 +139,10 @@ describe("FloatingWindow", () => {
expect(cssRuleFor(floatingWindowCss, ".floating-window__resize-handle--ne")).toContain("right: 0;");
expect(cssRuleFor(floatingWindowCss, ".floating-window__resize-handle--se")).toContain("right: 0;");
// No shared caller may move a right handle back into the reserved scrollbar gutter.
// 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.
const desktopAppCss = stripAtMediaBlocks(allAppCss);
for (const callerClass of [
"floating-window--task-detail",
"floating-window--automation",
@@ -117,7 +152,7 @@ describe("FloatingWindow", () => {
"floating-window--workflow-editor",
"artifacts-gallery-window",
]) {
const rules = cssRulesForClass(allAppCss, callerClass);
const rules = cssRulesForClass(desktopAppCss, callerClass);
const rightHandleRules = rules.filter((rule) => /floating-window__resize-handle(?:--(?:e|ne|se))?/.test(rule));
const bodyRules = rules.filter((rule) => rule.includes("floating-window__body"));
@@ -125,6 +160,23 @@ describe("FloatingWindow", () => {
expect(bodyRules.some((rule) => /margin-inline-end\s*:/.test(rule)), callerClass).toBe(false);
}
/*
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.
*/
const mobileTaskDetailBody = cssRuleContaining(
allAppCss,
".floating-window--task-detail .floating-window__body",
"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);
// Headerless and chat variants replace only body overflow; the inherited gutter remains intact for their inner scrollers.
expect(cssRuleFor(floatingWindowCss, ".floating-window--headerless .floating-window__body")).toContain("overflow: hidden;");
expect(cssRuleFor(floatingWindowCss, ".floating-window--chat.floating-window--headerless .floating-window__body")).toContain("overflow: hidden;");