diff --git a/.changeset/fn-7408-task-detail-tab-padding.md b/.changeset/fn-7408-task-detail-tab-padding.md new file mode 100644 index 0000000000..15083e9c69 --- /dev/null +++ b/.changeset/fn-7408-task-detail-tab-padding.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Normalize task-detail tab padding across Activity, Chat, and Plan views. +category: fix +dev: Keeps chat-like task-detail tabs on canonical body padding while preserving internal scroll behavior. diff --git a/packages/dashboard/app/components/TaskDetailModal.css b/packages/dashboard/app/components/TaskDetailModal.css index 51ef5acebc..46a9cff1a6 100644 --- a/packages/dashboard/app/components/TaskDetailModal.css +++ b/packages/dashboard/app/components/TaskDetailModal.css @@ -128,24 +128,16 @@ Task detail workflow badges share the board badge's slight token-based icon-to-l overflow-y: hidden; } -/* Activity → Live and expanded Activity mirror the Raw Logs fill-height layout: the modal body does not scroll; - each segment owns internal scrolling while composers and controls stay visible. */ +/* +FNXC:TaskDetailPadding 2026-07-01-12:00: +Task-detail tabs share the `.detail-body` outer content inset so switching Activity, planner Chat, Plan, or standard sections does not shift the shell padding. Chat-like tabs may only change flex and internal-scroll ownership here; transcript/composer spacing stays inside their own surfaces. +*/ .detail-body--chat, .detail-body--planner-chat { display: flex; flex-direction: column; min-height: 0; overflow-y: hidden; - /* FNXC:TaskDetailChat 2026-06-13-19:55: Chat box should sit in the chat view without excess vertical spacing around the composer (FN-6418), so only the chat body tightens vertical padding while shared detail tab padding remains unchanged. */ - padding-block: var(--space-md); -} - -/* -FNXC:TaskDetailPlannerChat 2026-06-30-23:58: -Planner Chat expand/collapse may reallocate height or hide mobile chrome, but it must not change the chat surface spacing. Keep body padding on the collapsed base selector and let expanded selectors own only flex/height behavior. -*/ -.detail-body--planner-chat { - padding: var(--space-md); } .detail-title { @@ -853,7 +845,6 @@ The same expanded-mode chrome collapse now applies to all Activity segments: Liv .task-detail-content--chat-expanded .detail-body--chat { flex: 1; min-height: 0; - padding: var(--space-md); } .task-detail-content--chat-expanded .detail-section--chat { @@ -899,7 +890,6 @@ Planner Chat should not open expanded by default. When the user expands it, the .task-detail-content--planner-chat-expanded .detail-body--planner-chat { flex: 1; min-height: 0; - padding: var(--space-md); } .task-detail-content--planner-chat-expanded .detail-section--planner-chat { @@ -1191,7 +1181,6 @@ FN-6500 fixes a tablet regression from FN-5599: the task-detail overlay offset a flex-direction: column; min-height: 0; overflow-y: hidden; - padding-block: var(--space-sm); } .detail-section--chat { @@ -1200,10 +1189,6 @@ FN-6500 fixes a tablet regression from FN-5599: the task-detail overlay offset a margin-top: 0; } - .task-detail-content--chat-expanded .detail-body--chat { - padding: var(--space-sm); - } - .task-detail-content--chat-expanded .detail-tabs { display: flex; } @@ -1212,9 +1197,6 @@ FN-6500 fixes a tablet regression from FN-5599: the task-detail overlay offset a display: none; } - .detail-body--planner-chat { - padding: var(--space-sm); - } } .detail-actions-menu-item-danger { 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 764281426a..d61fdde6b1 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 @@ -870,12 +870,19 @@ describe("TaskDetailModal", () => { describe("Chat full-height layout", () => { it("FN-6347 defines chat modal-body and section fill-height CSS for desktop and mobile", () => { const css = readDashboardStylesSource(); - const bodyRule = getCssRuleBlock(css, ".detail-body--chat"); + const bodyRuleStart = css.indexOf("Task-detail tabs share the `.detail-body` outer content inset"); + const bodyRuleCss = css.slice(bodyRuleStart, css.indexOf(".detail-title", bodyRuleStart)); + const bodyRule = bodyRuleCss; const sectionRule = getCssRuleBlock(css, ".detail-section--chat"); const mobileCss = css.slice(css.indexOf("@media (max-width: 768px)")); const mobileBodyRule = getCssRuleBlock(mobileCss, ".detail-body--chat"); const mobileSectionRule = getCssRuleBlock(mobileCss, ".detail-section--chat"); + expect(bodyRule).toContain("display: flex"); + expect(bodyRule).toContain("flex-direction: column"); + expect(bodyRule).toContain("min-height: 0"); + expect(bodyRule).toContain("overflow-y: hidden"); + expect(bodyRule).not.toMatch(/\bpadding(?:-[\w-]+)?:/); expectBaseRule(css, ".detail-body--planner-chat", "display: flex"); expectBaseRule(css, ".detail-body--planner-chat", "flex-direction: column"); expectBaseRule(css, ".detail-body--planner-chat", "min-height: 0"); @@ -886,6 +893,7 @@ describe("TaskDetailModal", () => { expect(sectionRule).toContain("min-height: 0"); expectBaseRule(mobileCss, ".detail-body--chat", "overflow-y: hidden"); expectBaseRule(mobileCss, ".detail-body--chat", "min-height: 0"); + expect(mobileBodyRule).not.toMatch(/\bpadding(?:-[\w-]+)?:/); expect(mobileSectionRule).toContain("flex: 1"); expect(mobileSectionRule).toContain("min-height: 0"); }); diff --git a/packages/dashboard/app/components/__tests__/TaskDetailModal.css.test.ts b/packages/dashboard/app/components/__tests__/TaskDetailModal.css.test.ts index 20cecba18b..427ac46c4d 100644 --- a/packages/dashboard/app/components/__tests__/TaskDetailModal.css.test.ts +++ b/packages/dashboard/app/components/__tests__/TaskDetailModal.css.test.ts @@ -1,6 +1,18 @@ import { describe, expect, it } from "vitest"; import { loadAllAppCssBaseOnly } from "../../test/cssFixture"; +function getCssRuleBlock(css: string, selector: string): string { + const escapedSelector = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const ruleMatch = css.match(new RegExp(`(?:^|[}\n])\\s*(?:[^{}]*,\\s*)?${escapedSelector}(?:\\s*,[^{}]*)?\\s*\\{([^}]*)\\}`)); + return ruleMatch?.[1] ?? ""; +} + +function expectNoOuterPaddingOverride(css: string, selector: string): void { + const ruleBlock = getCssRuleBlock(css, selector); + expect(ruleBlock, `${selector} rule`).not.toBe(""); + expect(ruleBlock, `${selector} padding`).not.toMatch(/\bpadding(?:-[\w-]+)?\s*:/); +} + describe("TaskDetailModal CSS contract", () => { it("FN-4183 keeps detail source headers top-aligned so the disclosure toggle stays on the first row", async () => { const css = await loadAllAppCssBaseOnly(); @@ -16,6 +28,22 @@ describe("TaskDetailModal CSS contract", () => { expect(css).toMatch(/\.detail-tab\s*\{[^}]*flex-shrink\s*:\s*0\s*;/); }); + it("FN-7408 keeps task-detail tab body padding canonical across Activity, planner Chat, and Plan surfaces", async () => { + const css = await loadAllAppCssBaseOnly(); + const detailBodyBlock = getCssRuleBlock(css, ".detail-body"); + const rawBodyBlock = getCssRuleBlock(css, ".detail-body--agent-log"); + const planBlock = getCssRuleBlock(css, ".detail-section--plan-prompt"); + + expect(detailBodyBlock).toContain("padding: calc(var(--space-lg) + var(--space-xs));"); + expectNoOuterPaddingOverride(css, ".detail-body--chat"); + expectNoOuterPaddingOverride(css, ".detail-body--planner-chat"); + expectNoOuterPaddingOverride(css, ".task-detail-content--chat-expanded .detail-body--chat"); + expectNoOuterPaddingOverride(css, ".task-detail-content--planner-chat-expanded .detail-body--planner-chat"); + expect(rawBodyBlock).not.toMatch(/\bpadding(?:-[\w-]+)?\s*:/); + expect(planBlock).toContain("width: 100%;"); + expect(planBlock).toContain("max-width: 100%;"); + }); + it("FN-7351/FN-7375 keeps the Activity tab dropdown portal-safe on narrow task-detail surfaces", async () => { const css = await loadAllAppCssBaseOnly(); 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 06adc9b11b..5bf7f21caa 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 @@ -170,36 +170,42 @@ describe("TaskDetailModal", () => { expect(plannerExpandedMetaBlock).toContain("flex: 0 0 auto;"); }); - it("keeps Planner Chat expand spacing identical across desktop and mobile", () => { + it("keeps task-detail outer padding canonical while Planner Chat owns only internal spacing", () => { const css = readDashboardStylesSource(); - const plannerBodySpacingStart = css.indexOf("Planner Chat expand/collapse may reallocate height"); - expect(plannerBodySpacingStart).toBeGreaterThanOrEqual(0); - const plannerBodySpacingCss = css.slice(plannerBodySpacingStart, css.indexOf(".detail-title", plannerBodySpacingStart)); - const plannerBodyBlock = getExactCssRuleBlock(plannerBodySpacingCss, ".detail-body--planner-chat"); + const paddingContractStart = css.indexOf("Task-detail tabs share the `.detail-body` outer content inset"); + expect(paddingContractStart).toBeGreaterThanOrEqual(0); + const detailBodyBlock = getExactCssRuleBlock(css, ".detail-body"); + const activityBodyBlock = getCssRuleBlock(css, ".detail-body--chat"); + const plannerBodyBlock = getCssRuleBlock(css, ".detail-body--planner-chat"); const plannerPanelBlock = getExactCssRuleBlock(css, ".task-planner-chat"); const plannerTranscriptBlock = getExactCssRuleBlock(css, ".task-planner-chat-transcript"); const plannerComposerBlock = getExactCssRuleBlock(css, ".task-planner-chat-composer"); const expandedPlannerBodyBlock = getExactCssRuleBlock(css, ".task-detail-content--planner-chat-expanded .detail-body--planner-chat"); const expandedPlannerSectionBlock = getExactCssRuleBlock(css, ".task-detail-content--planner-chat-expanded .detail-section--planner-chat"); - const mobilePlannerBlock = getCssAtRuleBlockContaining(css, "@media (max-width: 768px)", ".detail-body--planner-chat"); + const mobileBodyBlock = getCssAtRuleBlockContainingExactRule(css, "@media (max-width: 768px)", ".detail-body"); + const mobileDetailBodyBlock = getExactCssRuleBlock(mobileBodyBlock, ".detail-body"); + const mobilePlannerBlock = getCssAtRuleBlockContaining(css, "@media (max-width: 768px)", ".detail-body--chat"); const mobilePlannerBodyBlock = getStandaloneCssRuleBlock(mobilePlannerBlock, ".detail-body--planner-chat"); const mobileExpandedPlannerBodyBlock = getExactCssRuleBlock(mobilePlannerBlock, ".task-detail-content--planner-chat-expanded .detail-body--planner-chat"); - expect(plannerBodyBlock).toContain("padding: var(--space-md);"); + expect(detailBodyBlock).toContain("padding: calc(var(--space-lg) + var(--space-xs));"); + expect(mobileDetailBodyBlock).toContain("padding: calc(var(--space-md) + var(--space-xs) / 2);"); + expectNoSpacingOverrides(activityBodyBlock, "desktop Activity body modifier"); + expectNoSpacingOverrides(plannerBodyBlock, "desktop planner body modifier"); expect(expandedPlannerBodyBlock).toContain("flex: 1;"); expect(expandedPlannerBodyBlock).toContain("min-height: 0;"); - expect(expandedPlannerBodyBlock).toContain("padding: var(--space-md);"); + expectNoSpacingOverrides(expandedPlannerBodyBlock, "desktop expanded planner body"); expect(expandedPlannerSectionBlock).toContain("flex: 1;"); expect(expandedPlannerSectionBlock).toContain("min-height: 0;"); expectNoSpacingOverrides(expandedPlannerSectionBlock, "desktop expanded planner section"); - expect(mobilePlannerBodyBlock).toContain("padding: var(--space-sm);"); + expect(mobilePlannerBodyBlock).toBe(""); expect(mobileExpandedPlannerBodyBlock).toBe(""); expect(plannerPanelBlock).toContain("gap: var(--space-md);"); expect(plannerTranscriptBlock).toContain("padding: var(--space-md);"); expect(plannerTranscriptBlock).toContain("gap: var(--space-md);"); expect(plannerComposerBlock).toContain("gap: var(--space-sm);"); expect(css).not.toMatch(/task-detail-content--planner-chat-expanded[^{]+\.(?:task-planner-chat|task-planner-chat-transcript|task-planner-chat-composer)\s*\{[^}]*(?:padding|margin|gap)\s*:/); - expect(css).not.toMatch(/task-detail-content--planner-chat-expanded[^{]+\.detail-body--planner-chat\s*\{[^}]*(?:margin|gap)\s*:/); + expect(css).not.toMatch(/task-detail-content--planner-chat-expanded[^{]+\.detail-body--planner-chat\s*\{[^}]*(?:padding|margin|gap)\s*:/); }); it("keeps detail metadata as a single wrapping flex row without mobile column fallbacks", () => {