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:
7
.changeset/fn-mobile-task-detail-left-shift.md
Normal file
7
.changeset/fn-mobile-task-detail-left-shift.md
Normal 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.
|
||||
@@ -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;
|
||||
|
||||
@@ -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;");
|
||||
|
||||
Reference in New Issue
Block a user