fix(FN-7344): make planner chat expansion intentional
This commit is contained in:
7
.changeset/planner-chat-expanded-takeover.md
Normal file
7
.changeset/planner-chat-expanded-takeover.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Make Planner Chat collapsed by default with a floating expand control that takes over the detail view.
|
||||||
|
category: fix
|
||||||
|
dev: Planner Chat expansion now mirrors Activity's overlay control and hides most task-detail chrome while preserving title/workflow context.
|
||||||
@@ -883,6 +883,9 @@ Expanded Planner Chat must still expose task Priority, Execution Mode, and the t
|
|||||||
|
|
||||||
FNXC:TaskDetailPlannerChat 2026-07-02-00:04:
|
FNXC:TaskDetailPlannerChat 2026-07-02-00:04:
|
||||||
Expanded Planner Chat must keep the task title and workflow badge visible on mobile. The title preserves task identity, and the workflow badge is owned by the timestamps row, so this mode hides lower-priority metadata but not `.detail-heading-row` or `.detail-timestamps`; timestamp labels inside that row can hide so the workflow badge remains compact.
|
Expanded Planner Chat must keep the task title and workflow badge visible on mobile. The title preserves task identity, and the workflow badge is owned by the timestamps row, so this mode hides lower-priority metadata but not `.detail-heading-row` or `.detail-timestamps`; timestamp labels inside that row can hide so the workflow badge remains compact.
|
||||||
|
|
||||||
|
FNXC:TaskDetailPlannerChat 2026-07-02-00:17:
|
||||||
|
Planner Chat should not open expanded by default. When the user expands it, the view should behave like Activity expansion: the chat panel takes nearly the whole modal, with only task title and workflow identity left above it for context.
|
||||||
*/
|
*/
|
||||||
.detail-section--planner-chat {
|
.detail-section--planner-chat {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -895,6 +898,7 @@ Expanded Planner Chat must keep the task title and workflow badge visible on mob
|
|||||||
.task-detail-content--planner-chat-expanded .detail-body--planner-chat {
|
.task-detail-content--planner-chat-expanded .detail-body--planner-chat {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
padding: var(--space-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-detail-content--planner-chat-expanded .detail-section--planner-chat {
|
.task-detail-content--planner-chat-expanded .detail-section--planner-chat {
|
||||||
@@ -902,6 +906,34 @@ Expanded Planner Chat must keep the task title and workflow badge visible on mob
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.task-detail-content--planner-chat-expanded .detail-description-toggle,
|
||||||
|
.task-detail-content--planner-chat-expanded .detail-near-duplicate-banner,
|
||||||
|
.task-detail-content--planner-chat-expanded .task-fields-section,
|
||||||
|
.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 .detail-tabs,
|
||||||
|
.task-detail-content--planner-chat-expanded .detail-meta-inline-controls,
|
||||||
|
.task-detail-content--planner-chat-expanded .detail-provenance {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-detail-content--planner-chat-expanded .modal-header {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
justify-content: space-between;
|
||||||
|
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-timestamps .detail-timestamp-item,
|
||||||
|
.task-detail-content--planner-chat-expanded .detail-timestamps .detail-timestamp-separator {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.detail-spec-edit-trigger {
|
.detail-spec-edit-trigger {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1179,36 +1211,6 @@ FN-6500 fixes a tablet regression from FN-5599: the task-detail overlay offset a
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-detail-content--planner-chat-expanded .detail-description-toggle,
|
|
||||||
.task-detail-content--planner-chat-expanded .detail-near-duplicate-banner,
|
|
||||||
.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 .detail-provenance {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-detail-content--planner-chat-expanded .modal-header {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
justify-content: space-between;
|
|
||||||
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-timestamps .detail-timestamp-item,
|
|
||||||
.task-detail-content--planner-chat-expanded .detail-timestamps .detail-timestamp-separator {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-detail-content--planner-chat-expanded .detail-tabs {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
margin-bottom: var(--space-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-body--planner-chat {
|
.detail-body--planner-chat {
|
||||||
padding: var(--space-sm);
|
padding: var(--space-sm);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -557,7 +557,7 @@ export function TaskDetailContent({
|
|||||||
const [activeTab, setActiveTab] = useState<TabId>(() => resolveDefaultTab(initialTab, task.column, taskDetailChatFirst));
|
const [activeTab, setActiveTab] = useState<TabId>(() => resolveDefaultTab(initialTab, task.column, taskDetailChatFirst));
|
||||||
const [activitySegment, setActivitySegment] = useState<ActivitySegment>(() => resolveDefaultActivitySegment(initialTab));
|
const [activitySegment, setActivitySegment] = useState<ActivitySegment>(() => resolveDefaultActivitySegment(initialTab));
|
||||||
const [activityExpanded, setActivityExpanded] = useState(false);
|
const [activityExpanded, setActivityExpanded] = useState(false);
|
||||||
const [plannerChatExpanded, setPlannerChatExpanded] = useState(true);
|
const [plannerChatExpanded, setPlannerChatExpanded] = useState(false);
|
||||||
|
|
||||||
// ── CLI agent session (U11) ────────────────────────────────────────────────
|
// ── CLI agent session (U11) ────────────────────────────────────────────────
|
||||||
const [cliSession, setCliSession] = useState<CliSessionSummaryRecord | null>(null);
|
const [cliSession, setCliSession] = useState<CliSessionSummaryRecord | null>(null);
|
||||||
@@ -708,7 +708,7 @@ export function TaskDetailContent({
|
|||||||
// Reset description and planner-chat focus state when task changes
|
// Reset description and planner-chat focus state when task changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDescriptionExpanded(false);
|
setDescriptionExpanded(false);
|
||||||
setPlannerChatExpanded(true);
|
setPlannerChatExpanded(false);
|
||||||
}, [task.column, task.id]);
|
}, [task.column, task.id]);
|
||||||
|
|
||||||
const [highlightStallCode, setHighlightStallCode] = useState<string | null>(null);
|
const [highlightStallCode, setHighlightStallCode] = useState<string | null>(null);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
.task-planner-chat {
|
.task-planner-chat {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -33,6 +34,20 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
FNXC:TaskDetailPlannerChat 2026-07-02-00:16:
|
||||||
|
Planner Chat expansion should use the same in-view floating affordance as Activity Live/Feed, not a header action that shifts layout. Anchor it to the chat panel so it remains visible while the transcript scrolls.
|
||||||
|
*/
|
||||||
|
.task-planner-chat-expand-toggle--overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: var(--space-md);
|
||||||
|
right: var(--space-md);
|
||||||
|
z-index: 3;
|
||||||
|
background: var(--surface);
|
||||||
|
border-color: var(--border);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
.task-planner-chat-header h4 {
|
.task-planner-chat-header h4 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
@@ -261,6 +276,7 @@ Mobile Planner Chat should match regular task chat: keep the composer as a singl
|
|||||||
.task-planner-chat-header {
|
.task-planner-chat-header {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: var(--space-sm);
|
gap: var(--space-sm);
|
||||||
|
padding-right: calc(var(--space-2xl) + var(--space-lg));
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-planner-chat-header-actions {
|
.task-planner-chat-header-actions {
|
||||||
@@ -278,6 +294,11 @@ Mobile Planner Chat should match regular task chat: keep the composer as a singl
|
|||||||
min-block-size: calc(var(--space-2xl) + var(--space-sm));
|
min-block-size: calc(var(--space-2xl) + var(--space-sm));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.task-planner-chat-expand-toggle--overlay {
|
||||||
|
top: var(--space-sm);
|
||||||
|
right: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
.task-planner-chat-composer {
|
.task-planner-chat-composer {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
|
|||||||
@@ -500,6 +500,19 @@ export function TaskPlannerChatTab({ task, projectId, active, expanded = false,
|
|||||||
*/
|
*/
|
||||||
return (
|
return (
|
||||||
<section className="task-planner-chat" aria-label={t("taskDetail.plannerChat.label", "Planner chat")} data-testid="task-planner-chat-panel">
|
<section className="task-planner-chat" aria-label={t("taskDetail.plannerChat.label", "Planner chat")} data-testid="task-planner-chat-panel">
|
||||||
|
{onExpandedChange && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-icon btn-sm task-planner-chat-expand-toggle task-planner-chat-expand-toggle--overlay"
|
||||||
|
onClick={() => onExpandedChange(!expanded)}
|
||||||
|
aria-label={expanded ? t("taskDetail.plannerChat.collapse", "Collapse planner chat") : t("taskDetail.plannerChat.expand", "Expand planner chat")}
|
||||||
|
aria-pressed={expanded}
|
||||||
|
aria-expanded={expanded}
|
||||||
|
data-testid="task-planner-chat-expand-toggle"
|
||||||
|
>
|
||||||
|
{expanded ? <Minimize2 aria-hidden="true" /> : <Maximize2 aria-hidden="true" />}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
<div className="task-planner-chat-header">
|
<div className="task-planner-chat-header">
|
||||||
<div>
|
<div>
|
||||||
<h4>{t("taskDetail.plannerChat.heading", "Planner Chat")}</h4>
|
<h4>{t("taskDetail.plannerChat.heading", "Planner Chat")}</h4>
|
||||||
@@ -515,19 +528,6 @@ export function TaskPlannerChatTab({ task, projectId, active, expanded = false,
|
|||||||
<ProviderIcon provider={planningModel.provider} size="md" />
|
<ProviderIcon provider={planningModel.provider} size="md" />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{onExpandedChange && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-icon btn-sm task-planner-chat-expand-toggle"
|
|
||||||
onClick={() => onExpandedChange(!expanded)}
|
|
||||||
aria-label={expanded ? t("taskDetail.plannerChat.collapse", "Collapse planner chat") : t("taskDetail.plannerChat.expand", "Expand planner chat")}
|
|
||||||
aria-pressed={expanded}
|
|
||||||
aria-expanded={expanded}
|
|
||||||
data-testid="task-planner-chat-expand-toggle"
|
|
||||||
>
|
|
||||||
{expanded ? <Minimize2 aria-hidden="true" /> : <Maximize2 aria-hidden="true" />}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -477,8 +477,8 @@ describe("TaskDetailModal", () => {
|
|||||||
expect(screen.queryByRole("button", { name: "Logs" })).toBeNull();
|
expect(screen.queryByRole("button", { name: "Logs" })).toBeNull();
|
||||||
expect(screen.getByRole("button", { name: "Chat" })).toHaveClass("detail-tab-active");
|
expect(screen.getByRole("button", { name: "Chat" })).toHaveClass("detail-tab-active");
|
||||||
expect(screen.getByTestId("task-planner-chat-panel")).toBeTruthy();
|
expect(screen.getByTestId("task-planner-chat-panel")).toBeTruthy();
|
||||||
expect(screen.getByTestId("task-planner-chat-expand-toggle")).toHaveAttribute("aria-label", "Collapse planner chat");
|
expect(screen.getByTestId("task-planner-chat-expand-toggle")).toHaveAttribute("aria-label", "Expand planner chat");
|
||||||
expect(container.querySelector(".task-detail-content")).toHaveClass("task-detail-content--planner-chat-expanded");
|
expect(container.querySelector(".task-detail-content")).not.toHaveClass("task-detail-content--planner-chat-expanded");
|
||||||
expect(container.querySelector(".activity-segmented-control")).toBeNull();
|
expect(container.querySelector(".activity-segmented-control")).toBeNull();
|
||||||
expect(container.querySelector(".activity-segment")).toBeNull();
|
expect(container.querySelector(".activity-segment")).toBeNull();
|
||||||
expect(screen.queryByTestId("task-chat-expand-toggle")).toBeNull();
|
expect(screen.queryByTestId("task-chat-expand-toggle")).toBeNull();
|
||||||
@@ -947,7 +947,7 @@ describe("TaskDetailModal", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const content = container.querySelector(".task-detail-content");
|
const content = container.querySelector(".task-detail-content");
|
||||||
expect(screen.getByTestId("task-planner-chat-expand-toggle")).toHaveAttribute("aria-label", "Collapse planner chat");
|
expect(screen.getByTestId("task-planner-chat-expand-toggle")).toHaveAttribute("aria-label", "Expand planner chat");
|
||||||
expect(screen.queryByTestId("task-chat-expand-toggle")).toBeNull();
|
expect(screen.queryByTestId("task-chat-expand-toggle")).toBeNull();
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole("button", { name: "Activity" }));
|
fireEvent.click(screen.getByRole("button", { name: "Activity" }));
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ describe("TaskDetailModal", () => {
|
|||||||
const headerBlock = getExactCssRuleBlock(css, ".task-planner-chat-header");
|
const headerBlock = getExactCssRuleBlock(css, ".task-planner-chat-header");
|
||||||
const headerActionsBlock = getExactCssRuleBlock(css, ".task-planner-chat-header-actions");
|
const headerActionsBlock = getExactCssRuleBlock(css, ".task-planner-chat-header-actions");
|
||||||
const modelBlock = getExactCssRuleBlock(css, ".task-planner-chat-model");
|
const modelBlock = getExactCssRuleBlock(css, ".task-planner-chat-model");
|
||||||
|
const expandOverlayBlock = getExactCssRuleBlock(css, ".task-planner-chat-expand-toggle--overlay");
|
||||||
const composerBlock = getExactCssRuleBlock(css, ".task-planner-chat-composer");
|
const composerBlock = getExactCssRuleBlock(css, ".task-planner-chat-composer");
|
||||||
const inputBlock = getExactCssRuleBlock(css, ".task-planner-chat-input");
|
const inputBlock = getExactCssRuleBlock(css, ".task-planner-chat-input");
|
||||||
const mobileBlock = getCssAtRuleBlockContaining(css, "@media (max-width: 768px)", ".task-planner-chat-composer");
|
const mobileBlock = getCssAtRuleBlockContaining(css, "@media (max-width: 768px)", ".task-planner-chat-composer");
|
||||||
@@ -115,9 +116,13 @@ describe("TaskDetailModal", () => {
|
|||||||
const mobileSendBlock = getCssRuleBlock(mobileBlock, ".task-planner-chat-send");
|
const mobileSendBlock = getCssRuleBlock(mobileBlock, ".task-planner-chat-send");
|
||||||
|
|
||||||
expectBaseRule(css, ".task-planner-chat", "display: flex;");
|
expectBaseRule(css, ".task-planner-chat", "display: flex;");
|
||||||
|
expectBaseRule(css, ".task-planner-chat", "position: relative;");
|
||||||
expectBaseRule(css, ".task-planner-chat", "min-height: 0;");
|
expectBaseRule(css, ".task-planner-chat", "min-height: 0;");
|
||||||
expectBaseRule(css, ".task-planner-chat-transcript", "overflow: auto;");
|
expectBaseRule(css, ".task-planner-chat-transcript", "overflow: auto;");
|
||||||
expectBaseRule(css, ".task-planner-chat-transcript", "min-height: 0;");
|
expectBaseRule(css, ".task-planner-chat-transcript", "min-height: 0;");
|
||||||
|
expect(expandOverlayBlock).toContain("position: absolute;");
|
||||||
|
expect(expandOverlayBlock).toContain("top: var(--space-md);");
|
||||||
|
expect(expandOverlayBlock).toContain("right: var(--space-md);");
|
||||||
expect(headerBlock).toContain("justify-content: space-between;");
|
expect(headerBlock).toContain("justify-content: space-between;");
|
||||||
expect(headerActionsBlock).toContain("justify-content: flex-end;");
|
expect(headerActionsBlock).toContain("justify-content: flex-end;");
|
||||||
expect(headerActionsBlock).not.toContain("flex-wrap: wrap;");
|
expect(headerActionsBlock).not.toContain("flex-wrap: wrap;");
|
||||||
@@ -150,21 +155,19 @@ describe("TaskDetailModal", () => {
|
|||||||
expect(mobileBlock).toContain("margin-inline: 0;");
|
expect(mobileBlock).toContain("margin-inline: 0;");
|
||||||
|
|
||||||
const detailCss = readDashboardStylesSource();
|
const detailCss = readDashboardStylesSource();
|
||||||
const plannerMobileBlock = getCssAtRuleBlockContaining(detailCss, "@media (max-width: 768px)", ".task-detail-content--planner-chat-expanded .detail-meta");
|
const plannerExpandedMetaBlock = getExactCssRuleBlock(detailCss, ".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-body--planner-chat", "overflow-y: hidden;");
|
||||||
expectBaseRule(detailCss, ".detail-section--planner-chat", "min-height: 0;");
|
expectBaseRule(detailCss, ".detail-section--planner-chat", "min-height: 0;");
|
||||||
expect(plannerMobileBlock).toContain(".task-detail-content--planner-chat-expanded .detail-provenance");
|
expect(detailCss).toContain(".task-detail-content--planner-chat-expanded .detail-provenance");
|
||||||
expect(plannerMobileBlock).toContain(".task-detail-content--planner-chat-expanded .modal-actions");
|
expect(detailCss).toContain(".task-detail-content--planner-chat-expanded .modal-actions");
|
||||||
expect(plannerMobileBlock).toContain("display: none;");
|
expect(detailCss).toContain(".task-detail-content--planner-chat-expanded .detail-tabs");
|
||||||
expect(plannerMobileBlock).not.toContain(".task-detail-content--planner-chat-expanded .detail-heading-row");
|
expect(detailCss).toContain(".task-detail-content--planner-chat-expanded .detail-meta-inline-controls");
|
||||||
expect(plannerMobileBlock).not.toMatch(/\.task-detail-content--planner-chat-expanded \.detail-timestamps\s*\{/);
|
expect(detailCss).not.toContain(".task-detail-content--planner-chat-expanded .detail-heading-row");
|
||||||
expect(plannerMobileBlock).toContain(".task-detail-content--planner-chat-expanded .detail-timestamps .detail-timestamp-item");
|
expect(detailCss).not.toMatch(/\.task-detail-content--planner-chat-expanded \.detail-timestamps\s*\{/);
|
||||||
expect(plannerMobileBlock).not.toMatch(/\.task-detail-content--planner-chat-expanded \.detail-meta,\s*\.task-detail-content--planner-chat-expanded \.detail-near-duplicate-banner/);
|
expect(detailCss).toContain(".task-detail-content--planner-chat-expanded .detail-timestamps .detail-timestamp-item");
|
||||||
expect(plannerMobileBlock).not.toMatch(/\.task-detail-content--planner-chat-expanded \.detail-tabs,\s*\.task-detail-content--planner-chat-expanded \.branch-group-card/);
|
expect(detailCss).not.toMatch(/\.task-detail-content--planner-chat-expanded \.detail-meta,\s*\.task-detail-content--planner-chat-expanded \.detail-near-duplicate-banner/);
|
||||||
|
expect(detailCss).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(plannerExpandedMetaBlock).toContain("flex: 0 0 auto;");
|
||||||
expect(plannerExpandedTabsBlock).toContain("flex: 0 0 auto;");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps Planner Chat expand spacing identical across desktop and mobile", () => {
|
it("keeps Planner Chat expand spacing identical across desktop and mobile", () => {
|
||||||
@@ -178,14 +181,14 @@ describe("TaskDetailModal", () => {
|
|||||||
const plannerComposerBlock = getExactCssRuleBlock(css, ".task-planner-chat-composer");
|
const plannerComposerBlock = getExactCssRuleBlock(css, ".task-planner-chat-composer");
|
||||||
const expandedPlannerBodyBlock = getExactCssRuleBlock(css, ".task-detail-content--planner-chat-expanded .detail-body--planner-chat");
|
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 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 mobilePlannerBlock = getCssAtRuleBlockContaining(css, "@media (max-width: 768px)", ".detail-body--planner-chat");
|
||||||
const mobilePlannerBodyBlock = getStandaloneCssRuleBlock(mobilePlannerBlock, ".detail-body--planner-chat");
|
const mobilePlannerBodyBlock = getStandaloneCssRuleBlock(mobilePlannerBlock, ".detail-body--planner-chat");
|
||||||
const mobileExpandedPlannerBodyBlock = getExactCssRuleBlock(mobilePlannerBlock, ".task-detail-content--planner-chat-expanded .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(plannerBodyBlock).toContain("padding: var(--space-md);");
|
||||||
expect(expandedPlannerBodyBlock).toContain("flex: 1;");
|
expect(expandedPlannerBodyBlock).toContain("flex: 1;");
|
||||||
expect(expandedPlannerBodyBlock).toContain("min-height: 0;");
|
expect(expandedPlannerBodyBlock).toContain("min-height: 0;");
|
||||||
expectNoSpacingOverrides(expandedPlannerBodyBlock, "desktop expanded planner body");
|
expect(expandedPlannerBodyBlock).toContain("padding: var(--space-md);");
|
||||||
expect(expandedPlannerSectionBlock).toContain("flex: 1;");
|
expect(expandedPlannerSectionBlock).toContain("flex: 1;");
|
||||||
expect(expandedPlannerSectionBlock).toContain("min-height: 0;");
|
expect(expandedPlannerSectionBlock).toContain("min-height: 0;");
|
||||||
expectNoSpacingOverrides(expandedPlannerSectionBlock, "desktop expanded planner section");
|
expectNoSpacingOverrides(expandedPlannerSectionBlock, "desktop expanded planner section");
|
||||||
@@ -196,7 +199,7 @@ describe("TaskDetailModal", () => {
|
|||||||
expect(plannerTranscriptBlock).toContain("gap: var(--space-md);");
|
expect(plannerTranscriptBlock).toContain("gap: var(--space-md);");
|
||||||
expect(plannerComposerBlock).toContain("gap: var(--space-sm);");
|
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[^{]+\.(?: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*:/);
|
expect(css).not.toMatch(/task-detail-content--planner-chat-expanded[^{]+\.detail-body--planner-chat\s*\{[^}]*(?:margin|gap)\s*:/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps detail metadata as a single wrapping flex row without mobile column fallbacks", () => {
|
it("keeps detail metadata as a single wrapping flex row without mobile column fallbacks", () => {
|
||||||
|
|||||||
@@ -137,25 +137,25 @@ describe("TaskDetailModal planner Chat tab", () => {
|
|||||||
expect(screen.getByTestId("task-planner-chat-panel")).toBeInTheDocument();
|
expect(screen.getByTestId("task-planner-chat-panel")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("defaults planner Chat to focused mode and lets the in-view control collapse it", async () => {
|
it("defaults planner Chat to collapsed mode and lets the in-view control expand it", async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const { container } = renderTask("todo");
|
const { container } = renderTask("todo");
|
||||||
const detail = container.querySelector(".task-detail-content");
|
const detail = container.querySelector(".task-detail-content");
|
||||||
|
|
||||||
expect(detail).toHaveClass("task-detail-content--planner-chat-expanded");
|
|
||||||
const toggle = screen.getByTestId("task-planner-chat-expand-toggle");
|
const toggle = screen.getByTestId("task-planner-chat-expand-toggle");
|
||||||
expect(toggle).toHaveAccessibleName("Collapse planner chat");
|
expect(detail).not.toHaveClass("task-detail-content--planner-chat-expanded");
|
||||||
expect(toggle).toHaveAttribute("aria-expanded", "true");
|
expect(toggle).toHaveAccessibleName("Expand planner chat");
|
||||||
|
expect(toggle).toHaveAttribute("aria-expanded", "false");
|
||||||
|
expect(screen.getByRole("button", { name: "Chat" })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole("button", { name: "Activity" })).toBeInTheDocument();
|
||||||
|
|
||||||
await user.click(toggle);
|
await user.click(toggle);
|
||||||
|
|
||||||
expect(detail).not.toHaveClass("task-detail-content--planner-chat-expanded");
|
expect(detail).toHaveClass("task-detail-content--planner-chat-expanded");
|
||||||
expect(screen.getByRole("button", { name: "Chat" })).toBeInTheDocument();
|
expect(screen.getByTestId("task-planner-chat-expand-toggle")).toHaveAccessibleName("Collapse planner chat");
|
||||||
expect(screen.getByRole("button", { name: "Activity" })).toBeInTheDocument();
|
|
||||||
expect(screen.getByTestId("task-planner-chat-expand-toggle")).toHaveAccessibleName("Expand planner chat");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("resets planner Chat focused mode when switching tasks", async () => {
|
it("resets planner Chat expanded mode when switching tasks", async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const { container, rerender } = render(
|
const { container, rerender } = render(
|
||||||
<TaskDetailModal
|
<TaskDetailModal
|
||||||
@@ -172,7 +172,7 @@ describe("TaskDetailModal planner Chat tab", () => {
|
|||||||
const detail = container.querySelector(".task-detail-content");
|
const detail = container.querySelector(".task-detail-content");
|
||||||
|
|
||||||
await user.click(screen.getByTestId("task-planner-chat-expand-toggle"));
|
await user.click(screen.getByTestId("task-planner-chat-expand-toggle"));
|
||||||
expect(detail).not.toHaveClass("task-detail-content--planner-chat-expanded");
|
expect(detail).toHaveClass("task-detail-content--planner-chat-expanded");
|
||||||
|
|
||||||
rerender(
|
rerender(
|
||||||
<TaskDetailModal
|
<TaskDetailModal
|
||||||
@@ -187,7 +187,7 @@ describe("TaskDetailModal planner Chat tab", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(detail).toHaveClass("task-detail-content--planner-chat-expanded");
|
expect(detail).not.toHaveClass("task-detail-content--planner-chat-expanded");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps Activity expansion independent from planner Chat expansion", async () => {
|
it("keeps Activity expansion independent from planner Chat expansion", async () => {
|
||||||
@@ -201,11 +201,11 @@ describe("TaskDetailModal planner Chat tab", () => {
|
|||||||
const chatTab = container.querySelectorAll<HTMLButtonElement>(".detail-tabs .detail-tab")[0];
|
const chatTab = container.querySelectorAll<HTMLButtonElement>(".detail-tabs .detail-tab")[0];
|
||||||
expect(chatTab?.textContent?.trim()).toBe("Chat");
|
expect(chatTab?.textContent?.trim()).toBe("Chat");
|
||||||
fireEvent.click(chatTab!);
|
fireEvent.click(chatTab!);
|
||||||
expect(detail).toHaveClass("task-detail-content--planner-chat-expanded");
|
expect(detail).not.toHaveClass("task-detail-content--planner-chat-expanded");
|
||||||
expect(detail).not.toHaveClass("task-detail-content--chat-expanded");
|
expect(detail).not.toHaveClass("task-detail-content--chat-expanded");
|
||||||
|
|
||||||
await user.click(screen.getByTestId("task-planner-chat-expand-toggle"));
|
await user.click(screen.getByTestId("task-planner-chat-expand-toggle"));
|
||||||
expect(detail).not.toHaveClass("task-detail-content--planner-chat-expanded");
|
expect(detail).toHaveClass("task-detail-content--planner-chat-expanded");
|
||||||
expect(detail).not.toHaveClass("task-detail-content--chat-expanded");
|
expect(detail).not.toHaveClass("task-detail-content--chat-expanded");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -182,7 +182,9 @@ describe("TaskPlannerChatTab", () => {
|
|||||||
const modelBadge = screen.getByTestId("task-planner-chat-model");
|
const modelBadge = screen.getByTestId("task-planner-chat-model");
|
||||||
expect(toggle).toHaveAccessibleName("Collapse planner chat");
|
expect(toggle).toHaveAccessibleName("Collapse planner chat");
|
||||||
expect(toggle).toHaveAttribute("aria-expanded", "true");
|
expect(toggle).toHaveAttribute("aria-expanded", "true");
|
||||||
expect(modelBadge.compareDocumentPosition(toggle) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
|
expect(toggle).toHaveClass("task-planner-chat-expand-toggle--overlay");
|
||||||
|
expect(screen.getByTestId("task-planner-chat-panel")).toContainElement(toggle);
|
||||||
|
expect(modelBadge).toBeInTheDocument();
|
||||||
expect(screen.getByTestId("task-planner-chat-panel")).toContainElement(screen.getByLabelText("Message planner chat"));
|
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" }));
|
expect(screen.getByTestId("task-planner-chat-panel")).toContainElement(screen.getByRole("button", { name: "Send" }));
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user