fix(FN-7344): compact planner and activity chat inputs

This commit is contained in:
gsxdsm
2026-07-01 00:07:42 -07:00
parent 7c53c97137
commit 35d37d603d
8 changed files with 51 additions and 96 deletions

View File

@@ -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.

View File

@@ -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 {

View File

@@ -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}

View File

@@ -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 {

View File

@@ -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>

View File

@@ -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");

View File

@@ -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;");

View File

@@ -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" }));