FN-7333: stabilize Planner Chat expanded spacing

Stabilize Planner Chat task detail spacing across expanded and mobile layouts.

- Move Planner Chat body padding to the base selector so expand/collapse only changes height and flex behavior.
- Apply the mobile Planner Chat padding override to the same base body selector.
- Add CSS regression coverage and a patch changeset for the published CLI bundle.

Files changed:
 .changeset/planner-chat-stable-spacing.md          |  7 ++++
 .../dashboard/app/components/TaskDetailModal.css   | 11 +++++-
 ...etailModal.responsive-and-dependencies.test.tsx | 44 ++++++++++++++++++++++
 3 files changed, 60 insertions(+), 2 deletions(-)

Fusion-Task-Id: FN-7333

Fusion-Task-Lineage: 84130908-fabb-433f-a48d-5d9c6649c234

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-06-30 21:23:14 -07:00
parent 1c1206a1ca
commit 65822a02b4
3 changed files with 60 additions and 2 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Keep Planner Chat spacing stable when expanding task details.
category: fix
dev: Stabilizes task-detail Planner Chat CSS so expanded mode changes height allocation without padding jumps.

View File

@@ -140,6 +140,14 @@ Task detail workflow badges share the board badge's slight token-based icon-to-l
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 {
font-size: 18px;
font-weight: 600;
@@ -884,7 +892,6 @@ Expanded Planner Chat must still expose task Priority, Execution Mode, and the t
.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 {
@@ -1196,7 +1203,7 @@ FN-6500 fixes a tablet regression from FN-5599: the task-detail overlay offset a
margin-bottom: var(--space-sm);
}
.task-detail-content--planner-chat-expanded .detail-body--planner-chat {
.detail-body--planner-chat {
padding: var(--space-sm);
}
}

View File

@@ -63,6 +63,12 @@ function getExactCssRuleBlock(css: string, selector: string): string {
return ruleMatch?.[1] ?? "";
}
function getStandaloneCssRuleBlock(css: string, selector: string): string {
const escapedSelector = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const ruleMatch = css.match(new RegExp(`(?:^|})\\s*${escapedSelector}\\s*\\{([^}]*)\\}`));
return ruleMatch?.[1] ?? "";
}
function getCssAtRuleBlockContainingExactRule(css: string, atRule: string, selector: string): string {
let startAt = 0;
while (startAt < css.length) {
@@ -88,6 +94,12 @@ function expectTabTouchAction(ruleBlock: string, surface: string): void {
expect(ruleBlock, `${surface} touch-action`).toContain("touch-action: pan-x pan-y;");
}
function expectNoSpacingOverrides(ruleBlock: string, surface: string): void {
expect(ruleBlock, `${surface} padding`).not.toMatch(/\bpadding(?:-[\w-]+)?:/);
expect(ruleBlock, `${surface} margin`).not.toMatch(/\bmargin(?:-[\w-]+)?:/);
expect(ruleBlock, `${surface} gap`).not.toMatch(/(?:^|\s)gap:/);
}
describe("TaskDetailModal", () => {
describe("mobile responsive structure", () => {
it("keeps planner chat composer usable on narrow task-detail layouts", () => {
@@ -149,6 +161,38 @@ describe("TaskDetailModal", () => {
expect(plannerExpandedTabsBlock).toContain("flex: 0 0 auto;");
});
it("keeps Planner Chat expand spacing identical across desktop and mobile", () => {
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 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)", ".task-detail-content--planner-chat-expanded .detail-meta");
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(expandedPlannerBodyBlock).toContain("flex: 1;");
expect(expandedPlannerBodyBlock).toContain("min-height: 0;");
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(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*\{[^}]*(?:padding|margin|gap)\s*:/);
});
it("keeps detail metadata as a single wrapping flex row without mobile column fallbacks", () => {
const css = readDashboardStylesSource();