diff --git a/.changeset/fn-7328-planner-chat-compact.md b/.changeset/fn-7328-planner-chat-compact.md new file mode 100644 index 0000000000..7c91fcb12d --- /dev/null +++ b/.changeset/fn-7328-planner-chat-compact.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Keep Planner Chat compact while preserving expanded task controls. +category: fix +dev: Aligns Planner Chat composer height with Activity chat and keeps Priority, Execution Mode, and tabs visible in expanded mobile task details. diff --git a/packages/dashboard/app/components/TaskDetailModal.css b/packages/dashboard/app/components/TaskDetailModal.css index 326f7211a3..b1dd0a90d1 100644 --- a/packages/dashboard/app/components/TaskDetailModal.css +++ b/packages/dashboard/app/components/TaskDetailModal.css @@ -868,7 +868,10 @@ The same expanded-mode chrome collapse now applies to all Activity segments: Liv /* FNXC:TaskDetailPlannerChat 2026-06-30-23:58: -Planner Chat defaults to a focused state, but operator steering says large screens should keep the full task-detail context visible. Desktop therefore uses the same internal-scroll chat body without hiding rows; narrow/mobile layouts collapse nonessential title/meta/tabs/actions so the composer remains reachable while header identity and close/back controls stay available. +Planner Chat defaults to a focused state, but operator steering says large screens should keep the full task-detail context visible. Desktop therefore uses the same internal-scroll chat body without hiding rows; narrow/mobile layouts collapse nonessential title/provenance/actions so the composer remains reachable while header identity and close/back controls stay available. + +FNXC:TaskDetailPlannerChat 2026-06-30-20:06: +Expanded Planner Chat must still expose task Priority, Execution Mode, and the tab bar on mobile. Hide only lower-priority metadata in the metadata row so inline handlers and tab navigation remain reachable without overlapping the transcript/composer. */ .detail-section--planner-chat { display: flex; @@ -1168,12 +1171,12 @@ FN-6500 fixes a tablet regression from FN-5599: the task-detail overlay offset a .task-detail-content--planner-chat-expanded .detail-heading-row, .task-detail-content--planner-chat-expanded .detail-description-toggle, - .task-detail-content--planner-chat-expanded .detail-meta, .task-detail-content--planner-chat-expanded .detail-near-duplicate-banner, - .task-detail-content--planner-chat-expanded .detail-tabs, .task-detail-content--planner-chat-expanded .branch-group-card, .task-detail-content--planner-chat-expanded .workspace-worktrees-summary, - .task-detail-content--planner-chat-expanded .modal-actions { + .task-detail-content--planner-chat-expanded .modal-actions, + .task-detail-content--planner-chat-expanded .detail-provenance, + .task-detail-content--planner-chat-expanded .detail-timestamps { display: none; } @@ -1183,6 +1186,16 @@ FN-6500 fixes a tablet regression from FN-5599: the task-detail overlay offset a padding-block: var(--space-sm); } + .task-detail-content--planner-chat-expanded .detail-meta { + flex: 0 0 auto; + margin-bottom: var(--space-sm); + } + + .task-detail-content--planner-chat-expanded .detail-tabs { + flex: 0 0 auto; + margin-bottom: var(--space-sm); + } + .task-detail-content--planner-chat-expanded .detail-body--planner-chat { padding: var(--space-sm); } diff --git a/packages/dashboard/app/components/TaskPlannerChatTab.css b/packages/dashboard/app/components/TaskPlannerChatTab.css index f82b60af97..0cd5ad0e26 100644 --- a/packages/dashboard/app/components/TaskPlannerChatTab.css +++ b/packages/dashboard/app/components/TaskPlannerChatTab.css @@ -13,12 +13,17 @@ gap: var(--space-md); } +.task-planner-chat-header > :first-child { + min-width: 0; +} + .task-planner-chat-header-actions { display: flex; align-items: center; justify-content: flex-end; gap: var(--space-sm); - flex-wrap: wrap; + flex: 0 1 auto; + min-width: 0; } .task-planner-chat-expand-toggle { @@ -39,7 +44,12 @@ } .task-planner-chat-model { - flex: 0 0 auto; + flex: 1 1 auto; + min-width: 0; + max-inline-size: min(100%, calc(var(--space-2xl) * 8)); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; border: var(--btn-border-width) solid var(--border); border-radius: var(--radius-pill); padding: var(--space-xs) var(--space-sm); @@ -203,10 +213,13 @@ /* FNXC:TaskDetailPlannerChat 2026-06-30-00:00: -The Planner Chat composer should behave like a compact input/button row on desktop: match the textarea's default height to the Send button and let flex wrapping, not a forced column, decide when the controls no longer fit inline. +The Planner Chat composer should behave like a compact input/button row on desktop: match the textarea's default height to the Activity chat input and let flex wrapping, not a forced column, decide when the controls no longer fit inline. FNXC:TaskDetailPlannerChat 2026-06-30-23:58: Keep the composer outside the transcript scroller and make the panel fill the modal body. Loading, errors, starters, history, and streaming content scroll inside `.task-planner-chat-transcript`, while the input row remains pinned and reachable in normal and mobile-expanded detail layouts. + +FNXC:TaskDetailPlannerChat 2026-06-30-20:06: +Planner Chat must keep visual height parity with Activity chat: desktop textareas use the Activity compact min-height token, and the stacked mobile composer switches to the Activity mobile touch-height token without hardcoded dimensions. */ .task-planner-chat-composer { display: flex; @@ -220,14 +233,16 @@ Keep the composer outside the transcript scroller and make the panel fill the mo box-sizing: border-box; flex: 1 1 calc(var(--space-2xl) * 8); min-width: min(100%, calc(var(--space-2xl) * 8)); - height: calc(var(--space-lg) + (var(--space-sm) * 2) + (var(--btn-border-width) * 2)); - min-height: calc(var(--space-lg) + (var(--space-sm) * 2) + (var(--btn-border-width) * 2)); + height: calc(var(--space-2xl) + var(--space-sm)); + min-height: calc(var(--space-2xl) + var(--space-sm)); + max-height: var(--task-chat-composer-max-height, 40vh); padding: var(--btn-padding); resize: vertical; } .task-planner-chat-send { flex: 0 0 auto; + min-block-size: calc(var(--space-2xl) + var(--space-sm)); } @media (max-width: 768px) { @@ -235,20 +250,33 @@ Keep the composer outside the transcript scroller and make the panel fill the mo min-height: 0; } - .task-planner-chat-header, + .task-planner-chat-header { + align-items: flex-start; + gap: var(--space-sm); + } + + .task-planner-chat-header-actions { + flex: 0 1 auto; + justify-content: flex-end; + } + + .task-planner-chat-model { + max-inline-size: calc(var(--space-2xl) * 5); + } + + .task-planner-chat-expand-toggle { + min-inline-size: calc(var(--space-2xl) + var(--space-sm)); + min-block-size: calc(var(--space-2xl) + var(--space-sm)); + } + .task-planner-chat-composer { flex-direction: column; align-items: stretch; } - .task-planner-chat-header-actions { - justify-content: space-between; - } - - .task-planner-chat-expand-toggle { - align-self: flex-end; - min-inline-size: calc(var(--space-2xl) + var(--space-sm)); - min-block-size: calc(var(--space-2xl) + var(--space-sm)); + .task-planner-chat-input { + height: calc(var(--space-2xl) + var(--space-lg)); + min-height: calc(var(--space-2xl) + var(--space-lg)); } .task-planner-chat-starters { @@ -258,6 +286,7 @@ Keep the composer outside the transcript scroller and make the panel fill the mo .task-planner-chat-send { justify-content: center; width: 100%; + min-block-size: calc(var(--space-2xl) + var(--space-lg)); } .task-planner-chat-message { diff --git a/packages/dashboard/app/components/TaskPlannerChatTab.tsx b/packages/dashboard/app/components/TaskPlannerChatTab.tsx index d137a9e630..c1099f35d9 100644 --- a/packages/dashboard/app/components/TaskPlannerChatTab.tsx +++ b/packages/dashboard/app/components/TaskPlannerChatTab.tsx @@ -495,6 +495,11 @@ export function TaskPlannerChatTab({ task, projectId, active, expanded = false,

{t("taskDetail.plannerChat.description", "Ask planning questions about this task's current status, recent activity, blockers, next steps, or definition.")}

+ {isUsableModel(planningModel) && ( + + {planningModel.provider}/{planningModel.modelId} + + )} {onExpandedChange && ( )} - {isUsableModel(planningModel) && ( - - {planningModel.provider}/{planningModel.modelId} - - )}
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 3514054d85..f2aaa8459c 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 @@ -92,28 +92,39 @@ describe("TaskDetailModal", () => { describe("mobile responsive structure", () => { it("keeps planner chat composer usable on narrow task-detail layouts", () => { const css = readDashboardStylesSource(); + const headerBlock = getExactCssRuleBlock(css, ".task-planner-chat-header"); + const headerActionsBlock = getExactCssRuleBlock(css, ".task-planner-chat-header-actions"); + const modelBlock = getExactCssRuleBlock(css, ".task-planner-chat-model"); const composerBlock = getExactCssRuleBlock(css, ".task-planner-chat-composer"); const inputBlock = getExactCssRuleBlock(css, ".task-planner-chat-input"); const mobileBlock = getCssAtRuleBlockContaining(css, "@media (max-width: 768px)", ".task-planner-chat-composer"); const mobileComposerBlock = getCssRuleBlock(mobileBlock, ".task-planner-chat-composer"); + const mobileInputBlock = getCssRuleBlock(mobileBlock, ".task-planner-chat-input"); const mobileSendBlock = getCssRuleBlock(mobileBlock, ".task-planner-chat-send"); expectBaseRule(css, ".task-planner-chat", "display: flex;"); expectBaseRule(css, ".task-planner-chat", "min-height: 0;"); expectBaseRule(css, ".task-planner-chat-transcript", "overflow: auto;"); expectBaseRule(css, ".task-planner-chat-transcript", "min-height: 0;"); + expect(headerBlock).toContain("justify-content: space-between;"); + expect(headerActionsBlock).toContain("justify-content: flex-end;"); + expect(headerActionsBlock).not.toContain("flex-wrap: wrap;"); + expect(modelBlock).toContain("text-overflow: ellipsis;"); expect(composerBlock).toContain("display: flex;"); expect(composerBlock).toContain("flex-wrap: wrap;"); expect(composerBlock).toContain("align-items: stretch;"); expect(composerBlock).toContain("flex: 0 0 auto;"); expect(composerBlock).not.toContain("flex-direction: column;"); - expect(inputBlock).toContain("height: calc(var(--space-lg) + (var(--space-sm) * 2) + (var(--btn-border-width) * 2));"); - expect(inputBlock).toContain("min-height: calc(var(--space-lg) + (var(--space-sm) * 2) + (var(--btn-border-width) * 2));"); + expect(inputBlock).toContain("height: calc(var(--space-2xl) + var(--space-sm));"); + expect(inputBlock).toContain("min-height: calc(var(--space-2xl) + var(--space-sm));"); expect(inputBlock).not.toContain("min-height: 5rem;"); expect(mobileComposerBlock).toContain("flex-direction: column;"); expect(mobileComposerBlock).toContain("align-items: stretch;"); + expect(mobileInputBlock).toContain("height: calc(var(--space-2xl) + var(--space-lg));"); + expect(mobileInputBlock).toContain("min-height: calc(var(--space-2xl) + var(--space-lg));"); expect(mobileSendBlock).toContain("justify-content: center;"); expect(mobileSendBlock).toContain("width: 100%;"); + expect(mobileSendBlock).toContain("min-block-size: calc(var(--space-2xl) + var(--space-lg));"); expectBaseRule(css, ".task-planner-chat-starters", "grid-template-columns: repeat(2, minmax(0, 1fr));"); expectBaseRule(css, ".task-planner-chat-message .chat-question-response", "overflow-wrap: anywhere;"); expect(mobileBlock).toContain(".task-planner-chat-starters"); @@ -122,13 +133,20 @@ describe("TaskDetailModal", () => { expect(mobileBlock).toContain("margin-inline: 0;"); const detailCss = readDashboardStylesSource(); - const plannerMobileBlock = getCssAtRuleBlockContaining(detailCss, "@media (max-width: 768px)", ".task-detail-content--planner-chat-expanded .detail-tabs"); + const plannerMobileBlock = getCssAtRuleBlockContaining(detailCss, "@media (max-width: 768px)", ".task-detail-content--planner-chat-expanded .detail-meta"); + const plannerExpandedTabsBlock = getCssRuleBlock(plannerMobileBlock, ".task-detail-content--planner-chat-expanded .detail-tabs"); + const plannerExpandedMetaBlock = getCssRuleBlock(plannerMobileBlock, ".task-detail-content--planner-chat-expanded .detail-meta"); expectBaseRule(detailCss, ".detail-body--planner-chat", "overflow-y: hidden;"); expectBaseRule(detailCss, ".detail-section--planner-chat", "min-height: 0;"); expect(plannerMobileBlock).toContain(".task-detail-content--planner-chat-expanded .detail-heading-row"); - expect(plannerMobileBlock).toContain(".task-detail-content--planner-chat-expanded .detail-tabs"); + expect(plannerMobileBlock).toContain(".task-detail-content--planner-chat-expanded .detail-provenance"); + expect(plannerMobileBlock).toContain(".task-detail-content--planner-chat-expanded .detail-timestamps"); expect(plannerMobileBlock).toContain(".task-detail-content--planner-chat-expanded .modal-actions"); expect(plannerMobileBlock).toContain("display: none;"); + expect(plannerMobileBlock).not.toMatch(/\.task-detail-content--planner-chat-expanded \.detail-meta,\s*\.task-detail-content--planner-chat-expanded \.detail-near-duplicate-banner/); + expect(plannerMobileBlock).not.toMatch(/\.task-detail-content--planner-chat-expanded \.detail-tabs,\s*\.task-detail-content--planner-chat-expanded \.branch-group-card/); + expect(plannerExpandedMetaBlock).toContain("flex: 0 0 auto;"); + expect(plannerExpandedTabsBlock).toContain("flex: 0 0 auto;"); }); it("keeps detail metadata as a single wrapping flex row without mobile column fallbacks", () => { diff --git a/packages/dashboard/app/components/__tests__/TaskPlannerChatTab.test.tsx b/packages/dashboard/app/components/__tests__/TaskPlannerChatTab.test.tsx index 5a03e30888..605b05f285 100644 --- a/packages/dashboard/app/components/__tests__/TaskPlannerChatTab.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskPlannerChatTab.test.tsx @@ -129,8 +129,10 @@ describe("TaskPlannerChatTab", () => { expect(await screen.findByTestId("task-planner-chat-empty")).toBeInTheDocument(); const toggle = screen.getByTestId("task-planner-chat-expand-toggle"); + const modelBadge = screen.getByTestId("task-planner-chat-model"); expect(toggle).toHaveAccessibleName("Collapse planner chat"); expect(toggle).toHaveAttribute("aria-expanded", "true"); + expect(modelBadge.compareDocumentPosition(toggle) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); expect(screen.getByTestId("task-planner-chat-panel")).toContainElement(screen.getByLabelText("Message planner chat")); expect(screen.getByTestId("task-planner-chat-panel")).toContainElement(screen.getByRole("button", { name: "Send" }));