From c9aa3b465c54ccbec9247409c115903158feebf2 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Thu, 16 Jul 2026 18:30:14 -0700 Subject: [PATCH] FN-8166: fix mobile task-detail right padding Remove the residual mobile Feed right inset so task details use balanced body padding. - Let the task-detail body provide the mobile right inset. - Retain first-row clearance for overlay controls. - Cover mobile Feed padding and attached tab behavior with regression tests. - Add a patch changeset for the dashboard fix. Files changed: .../fn-8166-task-detail-mobile-right-padding.md | 7 +++ .../dashboard/app/components/TaskDetailModal.css | 8 +-- .../TaskDetailModal.attachments-and-tabs.test.tsx | 2 +- ...etailModal.responsive-and-dependencies.test.tsx | 63 +++++++++++++++++++++- 4 files changed, 74 insertions(+), 6 deletions(-) Fusion-Task-Id: FN-8166 Fusion-Task-Lineage: ae58ae8b-4659-429f-8939-1d7e1a82d34c Co-authored-by: Fusion (runfusion.ai) --- ...n-8166-task-detail-mobile-right-padding.md | 7 +++ .../app/components/TaskDetailModal.css | 8 +-- ...kDetailModal.attachments-and-tabs.test.tsx | 2 +- ...Modal.responsive-and-dependencies.test.tsx | 63 ++++++++++++++++++- 4 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 .changeset/fn-8166-task-detail-mobile-right-padding.md diff --git a/.changeset/fn-8166-task-detail-mobile-right-padding.md b/.changeset/fn-8166-task-detail-mobile-right-padding.md new file mode 100644 index 0000000000..c2c3ab7b32 --- /dev/null +++ b/.changeset/fn-8166-task-detail-mobile-right-padding.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Fix lopsided right padding in the task detail view on mobile. +category: fix +dev: Sets the mobile `.detail-activity` right inset to `0` while retaining first-row overlay-toggle clearance. diff --git a/packages/dashboard/app/components/TaskDetailModal.css b/packages/dashboard/app/components/TaskDetailModal.css index abe5833b19..5545a1b6b6 100644 --- a/packages/dashboard/app/components/TaskDetailModal.css +++ b/packages/dashboard/app/components/TaskDetailModal.css @@ -3329,12 +3329,12 @@ Live and Feed Activity expansion overlays the content instead of reserving a too /* FNXC:TaskDetailActivity 2026-07-16-00:00: - Mobile Feed uses a normal `var(--space-md)` right inset so the full scrolling list - uses available width. Reserve the overlay-toggle clearance only on top-of-panel - first-row elements, keeping the first visible row uncovered without a deep gutter. + FN-8166 removes the residual mobile Feed container right inset so `.detail-body` + provides equal left and right insets. Reserve overlay-toggle clearance only on + top-of-panel first-row elements, keeping the first visible row uncovered. */ .detail-activity { - padding-inline-end: var(--space-md); + padding-inline-end: 0; } .detail-activity:not(.detail-activity--interventions) > h4, diff --git a/packages/dashboard/app/components/__tests__/TaskDetailModal.attachments-and-tabs.test.tsx b/packages/dashboard/app/components/__tests__/TaskDetailModal.attachments-and-tabs.test.tsx index 1ad0a98c9d..0e53f2dcb5 100644 --- a/packages/dashboard/app/components/__tests__/TaskDetailModal.attachments-and-tabs.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskDetailModal.attachments-and-tabs.test.tsx @@ -933,7 +933,7 @@ describe("TaskDetailModal", () => { expect(activityOverlayRule).toContain("top: var(--space-md)"); expect(activityOverlayRule).toContain("right: var(--space-md)"); expect(activityOverlayRule).toContain("z-index: 3"); - expect(mobileCss).toContain(" .detail-activity {\n padding-inline-end: var(--space-md);\n }"); + expect(mobileCss).toContain(" .detail-activity {\n padding-inline-end: 0;\n }"); expect(mobileCss).toContain(" .activity-expand-toggle--overlay {\n top: var(--space-sm);\n right: var(--space-sm);\n }"); expect(expandedTitleRule).not.toContain("display: none"); expect(expandedMetaRule).toContain("display: none"); diff --git a/packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx b/packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx index b965b6afb4..12a502450a 100644 --- a/packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx @@ -70,6 +70,17 @@ function getCssAtRuleBlockContaining(css: string, atRule: string, selector: stri throw new Error(`Missing ${atRule} block containing ${selector}`); } +function getCssAtRuleBlocks(css: string, atRule: string): string[] { + const blocks: string[] = []; + let startAt = 0; + while (css.indexOf(atRule, startAt) >= 0) { + const { block, endIndex } = getCssAtRuleBlock(css, atRule, startAt); + blocks.push(block); + startAt = endIndex; + } + return blocks; +} + function getExactCssRuleBlock(css: string, selector: string): string { const escapedSelector = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const ruleMatch = neutralizeCssCommentBraces(css).match(new RegExp(`(?:^|[}\\n])\\s*${escapedSelector}\\s*\\{([^}]*)\\}`)); @@ -666,9 +677,59 @@ describe("TaskDetailModal", () => { expect(overlayBlock).toContain("position: absolute;"); expect(overlayBlock).toContain("top: var(--space-md);"); expect(overlayBlock).toContain("right: var(--space-md);"); - expect(mobileBlock).toContain(" .detail-activity {\n padding-inline-end: var(--space-md);\n }"); + expect(mobileBlock).toContain(" .detail-activity {\n padding-inline-end: 0;\n }"); expect(mobileOverlayBlock).toContain("top: var(--space-sm);"); expect(mobileOverlayBlock).toContain("right: var(--space-sm);"); + + /* + FNXC:TaskDetailActivity 2026-07-16-00:00: + FN-8166 requires Feed to inherit the symmetric mobile `.detail-body` inset, + while only its first visible row reserves space for the opaque overlay toggle. + This contract also prevents non-Feed task-detail surfaces from adding a + container-level right-only inset or mobile horizontal overflow. + */ + const mobileDetailBodyBlock = getExactCssRuleBlock(mobileBlock, ".detail-body"); + const mobileInterventionsBlock = getExactCssRuleBlock(mobileBlock, ".detail-activity--interventions"); + const mobilePrBlock = getExactCssRuleBlock( + getCssAtRuleBlockContainingExactRule(css, "@media (max-width: 768px)", ".detail-pr-tab"), + ".detail-pr-tab", + ); + const embeddedBodyBlock = getExactCssRuleBlock( + css, + ".task-detail-content--embedded .modal-header,\n.task-detail-content--embedded .detail-body,\n.task-detail-content--embedded .detail-tabs,\n.task-detail-content--embedded .modal-actions", + ); + const allMobileCss = getCssAtRuleBlocks(css, "@media (max-width: 768px)").join("\n"); + + expect(mobileDetailBodyBlock).toContain("padding: calc(var(--space-md) + var(--space-xs) / 2);"); + expect(mobileDetailBodyBlock).toContain("overflow-x: hidden;"); + expect(mobileInterventionsBlock).toContain("padding-inline-end: 0;"); + expect(mobileBlock).toContain(".detail-activity:not(.detail-activity--interventions) > h4,"); + expect(mobileBlock).toContain(".detail-activity:not(.detail-activity--interventions) > .detail-log-loading,"); + expect(mobileBlock).toContain(".detail-activity:not(.detail-activity--interventions) > .detail-log-empty,"); + expect(mobileBlock).toContain(".detail-activity:not(.detail-activity--interventions) > .detail-activity-list > .detail-log-entry:first-child"); + expect(mobileBlock).toContain("padding-inline-end: calc(var(--space-2xl) + var(--space-sm));"); + expect(mobilePrBlock.trim()).toBe("gap: var(--space-md);"); + expect(embeddedBodyBlock).toContain("width: 100%;"); + expect(embeddedBodyBlock).toContain("min-width: 0;"); + expect(embeddedBodyBlock).toContain("max-width: 100%;"); + expect(allMobileCss).not.toMatch(/\.task-changes-tab\s*\{[^}]*\bpadding(?:-[\w-]+)?\s*:/); + + for (const selector of [ + ".detail-section", + ".detail-section--plan-prompt", + ".detail-section--original-prompt", + ".detail-body--chat", + ".detail-section--chat", + ".detail-body--agent-log", + ]) { + const escapedSelector = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const exactSelector = `${escapedSelector}(?![-\\w])`; + const declarations = [...neutralizeCssCommentBraces(allMobileCss).matchAll(new RegExp(`${exactSelector}[^{}]*\\{([^{}]*)\\}`, "g"))] + .map((match) => match[1]) + .join("\n"); + expect(declarations, `${selector} mobile right inset`).not.toMatch(/\bpadding-(?:inline-end|right)\s*:/); + expect(declarations, `${selector} mobile asymmetric padding`).not.toMatch(/\bpadding\s*:/); + } }); it("renders responsive structural classes (modal-lg, overlay, spacer, tabs, detail-body)", () => {