FN-8624: fix task detail activity right padding

Keep Task Detail activity insets symmetric while reserving overlay clearance only for first-row content.

- Move Live/Feed overlay clearance from the activity container to first-row elements.
- Preserve responsive overlay spacing and the full-width Interventions timeline.
- Cover modal, pop-out, embedded, and responsive activity layouts with updated tests.
- Add a patch changeset for the Task Detail padding fix.

Files changed:
 .changeset/fn-8624-task-detail-inset.md            |  7 ++++
 .../dashboard/app/components/TaskDetailModal.css   | 41 ++++++++++------------
 .../TaskDetailModal.attachments-and-tabs.test.tsx  |  6 ++--
 .../__tests__/TaskDetailModal.css.test.ts          | 21 ++++++-----
 ...etailModal.responsive-and-dependencies.test.tsx | 25 +++++++------
 5 files changed, 54 insertions(+), 46 deletions(-)

Fusion-Task-Id: FN-8624

Fusion-Task-Lineage: 578cfe86-3021-421d-841b-2670efc10a5d

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-26 19:45:47 -07:00
parent d3c1955866
commit 7715a83e6f
5 changed files with 54 additions and 46 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Fix asymmetric right padding in Task Detail activity content.
category: fix
dev: Overlay clearance now applies only to Activity first rows at every breakpoint.

View File

@@ -3258,20 +3258,27 @@ The body-portaled menu must paint above the owning task-detail modal/popup while
font-weight: 600;
}
/*
FNXC:TaskDetailActivity 2026-07-27-02:15:
FN-8624 requires Task Detail content insets to stay symmetric at every breakpoint,
including modal, pop-out, and embedded surfaces. The fixed Live/Feed overlay can cover
only the first row, so its tokenized clearance belongs there rather than on this container;
this generalizes FN-8166's mobile-only correction to desktop and tablet.
*/
.detail-activity {
position: relative;
padding-inline-end: 0;
}
.detail-activity:not(.detail-activity--interventions) > h4,
.detail-activity:not(.detail-activity--interventions) > .detail-log-truncated,
.detail-activity:not(.detail-activity--interventions) > .detail-log-loading,
.detail-activity:not(.detail-activity--interventions) > .detail-log-empty,
.detail-activity:not(.detail-activity--interventions) > .detail-activity-list > .detail-log-entry:first-child {
padding-inline-end: calc(var(--space-2xl) + var(--space-md));
}
/*
FNXC:PlannerOversight 2026-07-05-00:00:
The Interventions Activity segment (FN-7571) renders no `.activity-expand-toggle--overlay`
button, so it must not reserve the shared `.detail-activity` padding-inline-end that exists
solely to keep that overlay toggle from covering Live/Feed content. Without this override the
FN-7519 Intervention Timeline is inset from the right edge (worse on mobile, where the base
reserved padding grows). FN-7581 zeroes the reserved padding for this segment only; Live/Feed/
Raw keep the base rule unchanged.
*/
/* FNXC:PlannerOversight 2026-07-27-02:15: Retain the explicit zero inset for the overlay-free Interventions segment; it documents the FN-7581 full-width timeline contract even though the shared container is now symmetric. */
.detail-activity--interventions {
padding-inline-end: 0;
}
@@ -3315,15 +3322,10 @@ Live and Feed Activity expansion overlays the content instead of reserving a too
}
/*
FNXC:TaskDetailActivity 2026-07-16-00:00:
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.
FNXC:TaskDetailActivity 2026-07-27-02:15:
FN-8624 keeps the base container symmetric; at mobile sizes the smaller fixed
overlay needs correspondingly smaller first-row clearance without covering log text.
*/
.detail-activity {
padding-inline-end: 0;
}
.detail-activity:not(.detail-activity--interventions) > h4,
.detail-activity:not(.detail-activity--interventions) > .detail-log-truncated,
.detail-activity:not(.detail-activity--interventions) > .detail-log-loading,
@@ -3332,11 +3334,6 @@ Live and Feed Activity expansion overlays the content instead of reserving a too
padding-inline-end: calc(var(--space-2xl) + var(--space-sm));
}
/* FNXC:PlannerOversight 2026-07-05-00:00: mobile reserves more padding for the overlay toggle; neutralize it for Interventions too so FN-7519's timeline stays full width on mobile per FN-7581. */
.detail-activity--interventions {
padding-inline-end: 0;
}
.activity-expand-toggle {
min-inline-size: calc(var(--space-2xl) + var(--space-sm));
min-block-size: calc(var(--space-2xl) + var(--space-sm));

View File

@@ -928,12 +928,14 @@ describe("TaskDetailModal", () => {
expect(css).not.toContain(".activity-toolbar");
expect(css).not.toContain("activity-toolbar--expand-only");
expect(css).toContain(".detail-activity {\n position: relative;\n padding-inline-end: calc(var(--space-2xl) + var(--space-md));\n}");
expect(css).toContain(".detail-activity {\n position: relative;\n padding-inline-end: 0;\n}");
expect(css).toContain(".detail-activity:not(.detail-activity--interventions) > h4,");
expect(css).toContain("padding-inline-end: calc(var(--space-2xl) + var(--space-md));");
expect(activityOverlayRule).toContain("position: absolute");
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: 0;\n }");
expect(mobileCss).not.toContain(" .detail-activity {\n padding-inline-end:");
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");

View File

@@ -45,13 +45,12 @@ describe("TaskDetailModal CSS contract", () => {
});
/*
FNXC:TaskDetailActivity 2026-07-18-07:25:
FN-8166 zeroed mobile `.detail-activity` padding-inline-end (equal insets from
`.detail-body`); overlay clearance lives only on first-row selectors. Keep the
contract aligned with TaskDetailModal.css so full-suite does not assert the
pre-8166 `var(--space-md)` residual inset.
FNXC:TaskDetailActivity 2026-07-27-02:15:
FN-8624 extends FN-8166's symmetric Activity container inset from mobile to
desktop and tablet. Only possible overlay-covered first rows may reserve tokenized
clearance, so modal, pop-out, and embedded task-detail renders share this CSS contract.
*/
it("FN-8154 keeps the mobile Feed inset narrow while clearing its overlay toggle from first rows", async () => {
it("FN-8624 keeps Activity container insets symmetric while clearing the overlay from first rows", async () => {
const css = await loadAllAppCss();
const baseCss = await loadAllAppCssBaseOnly();
const mobileCss = css.slice(css.indexOf("@media (max-width: 768px)"));
@@ -63,13 +62,13 @@ describe("TaskDetailModal CSS contract", () => {
".detail-activity:not(.detail-activity--interventions) > .detail-activity-list > .detail-log-entry:first-child",
];
expect(baseCss).toContain(".detail-activity {\n position: relative;\n padding-inline-end: calc(var(--space-2xl) + var(--space-md));\n}");
expect(mobileCss).toContain(" .detail-activity {\n padding-inline-end: 0;\n }");
expect(mobileCss).not.toContain(" .detail-activity {\n padding-inline-end: calc(var(--space-2xl) + var(--space-lg));\n }");
expect(mobileCss).not.toContain(" .detail-activity {\n padding-inline-end: var(--space-md);\n }");
expect(mobileCss).toContain(" .detail-activity--interventions {\n padding-inline-end: 0;\n }");
expect(baseCss).toContain(".detail-activity {\n position: relative;\n padding-inline-end: 0;\n}");
expect(baseCss).toContain(".detail-activity--interventions {\n padding-inline-end: 0;\n}");
expect(baseCss).not.toContain(".detail-activity {\n position: relative;\n padding-inline-end: calc(var(--space-2xl) + var(--space-md));\n}");
expect(mobileCss).not.toContain(" .detail-activity {\n padding-inline-end:");
for (const selector of firstRowSelectors) {
const escapedSelector = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
expect(baseCss, selector).toMatch(new RegExp(`${escapedSelector}(?:\\s*,\\s*[^{}]+)*\\s*\\{[^}]*padding-inline-end: calc\\(var\\(--space-2xl\\) \\+ var\\(--space-md\\)\\);`));
expect(mobileCss, selector).toMatch(new RegExp(`${escapedSelector}(?:\\s*,\\s*[^{}]+)*\\s*\\{[^}]*padding-inline-end: calc\\(var\\(--space-2xl\\) \\+ var\\(--space-sm\\)\\);`));
}
});

View File

@@ -729,23 +729,24 @@ describe("TaskDetailModal", () => {
expect(css).not.toContain(".activity-toolbar");
expect(css).not.toContain("activity-toolbar--expand-only");
expect(css).toContain(".detail-activity {\n position: relative;\n padding-inline-end: calc(var(--space-2xl) + var(--space-md));\n}");
expect(css).toContain(".detail-activity {\n position: relative;\n padding-inline-end: 0;\n}");
expect(css).toContain(".detail-activity:not(.detail-activity--interventions) > h4,");
expect(css).toContain("padding-inline-end: calc(var(--space-2xl) + var(--space-md));");
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: 0;\n }");
expect(mobileBlock).not.toContain(" .detail-activity {\n padding-inline-end:");
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.
FNXC:TaskDetailActivity 2026-07-27-02:15:
FN-8624 requires Feed to inherit the symmetric `.detail-body` inset at every
breakpoint, while only its first visible row reserves space for the opaque overlay.
This contract covers modal, pop-out, embedded, and mobile task-detail surfaces.
*/
const mobileDetailBodyBlock = getExactCssRuleBlock(mobileBlock, ".detail-body");
const mobileInterventionsBlock = getExactCssRuleBlock(mobileBlock, ".detail-activity--interventions");
const baseInterventionsBlock = getExactCssRuleBlock(css, ".detail-activity--interventions");
const mobilePrBlock = getExactCssRuleBlock(
getCssAtRuleBlockContainingExactRule(css, "@media (max-width: 768px)", ".detail-pr-tab"),
".detail-pr-tab",
@@ -758,7 +759,7 @@ describe("TaskDetailModal", () => {
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(baseInterventionsBlock).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,");
@@ -797,6 +798,7 @@ describe("TaskDetailModal", () => {
const mobileScrollbarBlock = getExactCssRuleBlock(mobileBlock, ".detail-body::-webkit-scrollbar");
const mobileActivityBlock = getExactCssRuleBlock(mobileBlock, ".detail-activity");
const mobileInterventionsBlock = getExactCssRuleBlock(mobileBlock, ".detail-activity--interventions");
const baseInterventionsBlock = getExactCssRuleBlock(css, ".detail-activity--interventions");
const mobilePrBlock = getExactCssRuleBlock(
getCssAtRuleBlockContainingExactRule(css, "@media (max-width: 768px)", ".detail-pr-tab"),
".detail-pr-tab",
@@ -815,9 +817,10 @@ describe("TaskDetailModal", () => {
expect(mobileDetailBodyBlock).toContain("scrollbar-width: none;");
expect(mobileScrollbarBlock).toContain("display: none;");
expect(mobileActivityBlock).toContain("padding-inline-end: 0;");
expect(mobileActivityBlock).toBe("");
expect(mobileBlock).toContain("padding-inline-end: calc(var(--space-2xl) + var(--space-sm));");
expect(mobileInterventionsBlock).toContain("padding-inline-end: 0;");
expect(mobileInterventionsBlock).toBe("");
expect(baseInterventionsBlock).toContain("padding-inline-end: 0;");
expect(mobilePrBlock.trim()).toBe("gap: var(--space-md);");
expect(allMobileCss).not.toMatch(/\.task-changes-tab\s*\{[^}]*\bpadding(?:-[\w-]+)?\s*:/);
expect(embeddedBodyBlock).toContain("width: 100%;");