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) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-8166-task-detail-mobile-right-padding.md
Normal file
7
.changeset/fn-8166-task-detail-mobile-right-padding.md
Normal file
@@ -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.
|
||||||
@@ -3329,12 +3329,12 @@ Live and Feed Activity expansion overlays the content instead of reserving a too
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
FNXC:TaskDetailActivity 2026-07-16-00:00:
|
FNXC:TaskDetailActivity 2026-07-16-00:00:
|
||||||
Mobile Feed uses a normal `var(--space-md)` right inset so the full scrolling list
|
FN-8166 removes the residual mobile Feed container right inset so `.detail-body`
|
||||||
uses available width. Reserve the overlay-toggle clearance only on top-of-panel
|
provides equal left and right insets. Reserve overlay-toggle clearance only on
|
||||||
first-row elements, keeping the first visible row uncovered without a deep gutter.
|
top-of-panel first-row elements, keeping the first visible row uncovered.
|
||||||
*/
|
*/
|
||||||
.detail-activity {
|
.detail-activity {
|
||||||
padding-inline-end: var(--space-md);
|
padding-inline-end: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-activity:not(.detail-activity--interventions) > h4,
|
.detail-activity:not(.detail-activity--interventions) > h4,
|
||||||
|
|||||||
@@ -933,7 +933,7 @@ describe("TaskDetailModal", () => {
|
|||||||
expect(activityOverlayRule).toContain("top: var(--space-md)");
|
expect(activityOverlayRule).toContain("top: var(--space-md)");
|
||||||
expect(activityOverlayRule).toContain("right: var(--space-md)");
|
expect(activityOverlayRule).toContain("right: var(--space-md)");
|
||||||
expect(activityOverlayRule).toContain("z-index: 3");
|
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(mobileCss).toContain(" .activity-expand-toggle--overlay {\n top: var(--space-sm);\n right: var(--space-sm);\n }");
|
||||||
expect(expandedTitleRule).not.toContain("display: none");
|
expect(expandedTitleRule).not.toContain("display: none");
|
||||||
expect(expandedMetaRule).toContain("display: none");
|
expect(expandedMetaRule).toContain("display: none");
|
||||||
|
|||||||
@@ -70,6 +70,17 @@ function getCssAtRuleBlockContaining(css: string, atRule: string, selector: stri
|
|||||||
throw new Error(`Missing ${atRule} block containing ${selector}`);
|
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 {
|
function getExactCssRuleBlock(css: string, selector: string): string {
|
||||||
const escapedSelector = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
const escapedSelector = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
const ruleMatch = neutralizeCssCommentBraces(css).match(new RegExp(`(?:^|[}\\n])\\s*${escapedSelector}\\s*\\{([^}]*)\\}`));
|
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("position: absolute;");
|
||||||
expect(overlayBlock).toContain("top: var(--space-md);");
|
expect(overlayBlock).toContain("top: var(--space-md);");
|
||||||
expect(overlayBlock).toContain("right: 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("top: var(--space-sm);");
|
||||||
expect(mobileOverlayBlock).toContain("right: 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)", () => {
|
it("renders responsive structural classes (modal-lg, overlay, spacer, tabs, detail-body)", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user