fix(FN-7344): compact planner and activity chat inputs
This commit is contained in:
@@ -2,6 +2,6 @@
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Make Planner Chat collapsed by default with a floating expand control that takes over the detail view.
|
||||
summary: Compact Planner Chat chrome and align Activity Live with the same plain composer row.
|
||||
category: fix
|
||||
dev: Planner Chat expansion now mirrors Activity's overlay control and hides most task-detail chrome while preserving title/workflow context.
|
||||
dev: Planner Chat removes its redundant header, moves the provider icon to the empty state, and Activity Live drops its card-wrapped composer shell.
|
||||
|
||||
@@ -477,8 +477,11 @@ FN-7241 adds timestamps inside individual task-detail transcript blocks. Keep bl
|
||||
flex: 0 0 auto;
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
/* FNXC:TaskDetailChat 2026-06-13-19:55: Chat box should sit in the chat view without excess vertical spacing around the composer (FN-6418), while preserving readable horizontal inset for the input row. */
|
||||
padding: var(--space-sm) var(--space-md);
|
||||
/*
|
||||
FNXC:TaskDetailChat 2026-07-02-00:32:
|
||||
Activity Live and Planner Chat composers should share the same plain input-row treatment. Do not wrap Activity steering in a card shell or add extra composer padding around the input; the surrounding chat surface already owns the spacing.
|
||||
*/
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.task-chat-composer-row {
|
||||
@@ -615,7 +618,7 @@ FN-6660 corrects the repeated mobile sizing misses from FN-6507, FN-6604, and FN
|
||||
}
|
||||
|
||||
.task-chat-composer {
|
||||
padding: var(--space-xs) var(--space-sm);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.task-chat-composer-row {
|
||||
|
||||
@@ -937,7 +937,7 @@ export function TaskChatTab({ task, projectId, active, addToast, onTaskUpdated,
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<form className="task-chat-composer card" onSubmit={handleSubmit} aria-label={composerFormLabel}>
|
||||
<form className="task-chat-composer" onSubmit={handleSubmit} aria-label={composerFormLabel}>
|
||||
<div className="task-chat-composer-row">
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
|
||||
@@ -7,30 +7,14 @@
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.task-planner-chat-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
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: 0 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:TaskDetailPlannerChat 2026-07-02-00:28:
|
||||
Planner Chat should not render a redundant in-panel title/header. The transcript starts at the top of the chat area, the expand button floats over the view, and the planning model/provider icon appears only as a small empty-state affordance.
|
||||
*/
|
||||
.task-planner-chat-expand-toggle {
|
||||
flex: 0 0 auto;
|
||||
min-inline-size: var(--space-2xl);
|
||||
min-block-size: var(--space-2xl);
|
||||
min-inline-size: calc(var(--space-2xl) - var(--space-xs));
|
||||
min-block-size: calc(var(--space-2xl) - var(--space-xs));
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@@ -40,31 +24,24 @@ Planner Chat expansion should use the same in-view floating affordance as Activi
|
||||
*/
|
||||
.task-planner-chat-expand-toggle--overlay {
|
||||
position: absolute;
|
||||
top: var(--space-md);
|
||||
right: var(--space-md);
|
||||
top: var(--space-sm);
|
||||
right: var(--space-sm);
|
||||
z-index: 3;
|
||||
background: var(--surface);
|
||||
border-color: var(--border);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.task-planner-chat-header h4 {
|
||||
margin: 0;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.task-planner-chat-header p {
|
||||
margin: var(--space-xs) 0 0;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.task-planner-chat-model {
|
||||
.task-planner-chat-empty-model {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
inline-size: calc(var(--space-2xl) + var(--space-sm));
|
||||
block-size: calc(var(--space-2xl) + var(--space-sm));
|
||||
inline-size: calc(var(--space-2xl) - var(--space-xs));
|
||||
block-size: calc(var(--space-2xl) - var(--space-xs));
|
||||
border: var(--btn-border-width) solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 0;
|
||||
@@ -72,12 +49,6 @@ Planner Chat expansion should use the same in-view floating affordance as Activi
|
||||
background: var(--surface-subtle);
|
||||
}
|
||||
|
||||
.task-planner-chat-model .provider-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-planner-chat-error {
|
||||
border: var(--btn-border-width) solid var(--color-error);
|
||||
border-radius: var(--radius-md);
|
||||
@@ -110,10 +81,12 @@ Planner Chat expansion should use the same in-view floating affordance as Activi
|
||||
}
|
||||
|
||||
.task-planner-chat-empty {
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
max-width: 42rem;
|
||||
gap: var(--space-lg);
|
||||
padding-top: var(--space-md);
|
||||
}
|
||||
|
||||
.task-planner-chat-empty-copy {
|
||||
@@ -273,25 +246,9 @@ Mobile Planner Chat should match regular task chat: keep the composer as a singl
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.task-planner-chat-header {
|
||||
align-items: flex-start;
|
||||
gap: var(--space-sm);
|
||||
padding-right: calc(var(--space-2xl) + var(--space-lg));
|
||||
}
|
||||
|
||||
.task-planner-chat-header-actions {
|
||||
flex: 0 1 auto;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.task-planner-chat-model {
|
||||
inline-size: calc(var(--space-2xl) + var(--space-sm));
|
||||
block-size: calc(var(--space-2xl) + var(--space-sm));
|
||||
}
|
||||
|
||||
.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));
|
||||
min-inline-size: var(--space-2xl);
|
||||
min-block-size: var(--space-2xl);
|
||||
}
|
||||
|
||||
.task-planner-chat-expand-toggle--overlay {
|
||||
|
||||
@@ -513,24 +513,6 @@ export function TaskPlannerChatTab({ task, projectId, active, expanded = false,
|
||||
{expanded ? <Minimize2 aria-hidden="true" /> : <Maximize2 aria-hidden="true" />}
|
||||
</button>
|
||||
)}
|
||||
<div className="task-planner-chat-header">
|
||||
<div>
|
||||
<h4>{t("taskDetail.plannerChat.heading", "Planner Chat")}</h4>
|
||||
</div>
|
||||
<div className="task-planner-chat-header-actions">
|
||||
{isUsableModel(planningModel) && (
|
||||
<span
|
||||
className="task-planner-chat-model"
|
||||
data-testid="task-planner-chat-model"
|
||||
title={planningModelLabel}
|
||||
aria-label={planningModelLabel}
|
||||
>
|
||||
<ProviderIcon provider={planningModel.provider} size="md" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="task-planner-chat-transcript" ref={transcriptRef} data-testid="task-planner-chat-transcript">
|
||||
{error && <div className="task-planner-chat-error" role="alert">{error}</div>}
|
||||
{loading ? (
|
||||
@@ -540,6 +522,16 @@ export function TaskPlannerChatTab({ task, projectId, active, expanded = false,
|
||||
</div>
|
||||
) : showEmptyState ? (
|
||||
<div className="task-planner-chat-empty" data-testid="task-planner-chat-empty">
|
||||
{isUsableModel(planningModel) && (
|
||||
<span
|
||||
className="task-planner-chat-empty-model"
|
||||
data-testid="task-planner-chat-model"
|
||||
title={planningModelLabel}
|
||||
aria-label={planningModelLabel}
|
||||
>
|
||||
<ProviderIcon provider={planningModel.provider} size="sm" />
|
||||
</span>
|
||||
)}
|
||||
<div className="task-planner-chat-empty-copy">
|
||||
<h5>{t("taskDetail.plannerChat.emptyTitle", "Start a task-aware chat")}</h5>
|
||||
<p>{t("taskDetail.plannerChat.emptyBody", "Ask planning questions about this task's current status, recent activity, blockers, next steps, or definition. Starter prompts send as normal chat messages.")}</p>
|
||||
|
||||
@@ -2471,6 +2471,7 @@ describe("TaskChatTab", () => {
|
||||
expect(transcriptRule).toContain("overflow-y: auto");
|
||||
expect(transcriptRule).not.toContain("max-height");
|
||||
expect(composerRule).toContain("flex: 0 0 auto");
|
||||
expect(composerRule).toContain("padding: 0");
|
||||
expect(mobileTranscriptRule).toContain("flex: 1 1 auto");
|
||||
expect(mobileTranscriptRule).toContain("min-height: 0");
|
||||
expect(mobileTranscriptRule).not.toContain("max-height");
|
||||
|
||||
@@ -104,9 +104,7 @@ 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 modelBlock = getExactCssRuleBlock(css, ".task-planner-chat-empty-model");
|
||||
const expandOverlayBlock = getExactCssRuleBlock(css, ".task-planner-chat-expand-toggle--overlay");
|
||||
const composerBlock = getExactCssRuleBlock(css, ".task-planner-chat-composer");
|
||||
const inputBlock = getExactCssRuleBlock(css, ".task-planner-chat-input");
|
||||
@@ -121,13 +119,14 @@ describe("TaskDetailModal", () => {
|
||||
expectBaseRule(css, ".task-planner-chat-transcript", "overflow: auto;");
|
||||
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(headerActionsBlock).toContain("justify-content: flex-end;");
|
||||
expect(headerActionsBlock).not.toContain("flex-wrap: wrap;");
|
||||
expect(expandOverlayBlock).toContain("top: var(--space-sm);");
|
||||
expect(expandOverlayBlock).toContain("right: var(--space-sm);");
|
||||
expect(css).not.toContain(".task-planner-chat-header");
|
||||
expect(modelBlock).toContain("display: inline-flex;");
|
||||
expect(modelBlock).toContain("inline-size: calc(var(--space-2xl) + var(--space-sm));");
|
||||
expect(modelBlock).toContain("position: absolute;");
|
||||
expect(modelBlock).toContain("top: 0;");
|
||||
expect(modelBlock).toContain("left: 0;");
|
||||
expect(modelBlock).toContain("inline-size: calc(var(--space-2xl) - var(--space-xs));");
|
||||
expect(modelBlock).not.toContain("text-overflow: ellipsis;");
|
||||
expect(composerBlock).toContain("display: flex;");
|
||||
expect(composerBlock).toContain("flex-wrap: wrap;");
|
||||
|
||||
@@ -110,7 +110,8 @@ describe("TaskPlannerChatTab", () => {
|
||||
|
||||
const emptyState = await screen.findByTestId("task-planner-chat-empty");
|
||||
expect(emptyState).toHaveTextContent("Start a task-aware chat");
|
||||
expect(document.querySelector(".task-planner-chat-header p")).toBeNull();
|
||||
expect(document.querySelector(".task-planner-chat-header")).toBeNull();
|
||||
expect(screen.queryByText("Planner Chat")).toBeNull();
|
||||
expect(emptyState).toHaveTextContent("Ask planning questions about this task's current status, recent activity, blockers, next steps, or definition.");
|
||||
expect(emptyState).toHaveTextContent("Starter prompts send as normal chat messages.");
|
||||
expect(mockFetchTaskPlannerChatSession).toHaveBeenCalledWith(
|
||||
@@ -124,6 +125,8 @@ describe("TaskPlannerChatTab", () => {
|
||||
expect(modelBadge).toHaveAccessibleName("anthropic/claude-plan");
|
||||
expect(modelBadge).toHaveAttribute("title", "anthropic/claude-plan");
|
||||
expect(modelBadge).toHaveTextContent("");
|
||||
expect(emptyState).toContainElement(modelBadge);
|
||||
expect(modelBadge).toHaveClass("task-planner-chat-empty-model");
|
||||
expect(modelBadge.querySelector(".provider-icon[data-provider='anthropic']")).toBeTruthy();
|
||||
expect(screen.getByRole("button", { name: /Summarize recent activity/ })).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: /Explain status and blockers/ })).toBeInTheDocument();
|
||||
@@ -184,7 +187,7 @@ describe("TaskPlannerChatTab", () => {
|
||||
expect(toggle).toHaveAttribute("aria-expanded", "true");
|
||||
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-empty")).toContainElement(modelBadge);
|
||||
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" }));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user