fix(FN-7344): compact planner chat on mobile
This commit is contained in:
7
.changeset/mobile-planner-chat-inline-send.md
Normal file
7
.changeset/mobile-planner-chat-inline-send.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Keep Planner Chat compact on mobile with an inline composer and provider-icon model badge.
|
||||
category: fix
|
||||
dev: Adjusts TaskPlannerChatTab mobile CSS and replaces the text model badge with a ProviderIcon tooltip.
|
||||
@@ -44,18 +44,23 @@
|
||||
}
|
||||
|
||||
.task-planner-chat-model {
|
||||
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;
|
||||
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));
|
||||
border: var(--btn-border-width) solid var(--border);
|
||||
border-radius: var(--radius-pill);
|
||||
padding: var(--space-xs) var(--space-sm);
|
||||
border-radius: var(--radius);
|
||||
padding: 0;
|
||||
color: var(--text-muted);
|
||||
background: var(--surface-subtle);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
.task-planner-chat-model .provider-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-planner-chat-error {
|
||||
@@ -220,6 +225,9 @@ Keep the composer outside the transcript scroller and make the panel fill the mo
|
||||
|
||||
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.
|
||||
|
||||
FNXC:TaskDetailPlannerChat 2026-07-01-23:54:
|
||||
Mobile Planner Chat should match regular task chat: keep the composer as a single input row with a one-line textarea and a square send affordance on the right instead of stacking the labeled button below the input.
|
||||
*/
|
||||
.task-planner-chat-composer {
|
||||
display: flex;
|
||||
@@ -261,7 +269,8 @@ Planner Chat must keep visual height parity with Activity chat: desktop textarea
|
||||
}
|
||||
|
||||
.task-planner-chat-model {
|
||||
max-inline-size: calc(var(--space-2xl) * 5);
|
||||
inline-size: calc(var(--space-2xl) + var(--space-sm));
|
||||
block-size: calc(var(--space-2xl) + var(--space-sm));
|
||||
}
|
||||
|
||||
.task-planner-chat-expand-toggle {
|
||||
@@ -270,13 +279,19 @@ Planner Chat must keep visual height parity with Activity chat: desktop textarea
|
||||
}
|
||||
|
||||
.task-planner-chat-composer {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
align-items: flex-end;
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
|
||||
.task-planner-chat-input {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
height: calc(var(--space-2xl) + var(--space-lg));
|
||||
min-height: calc(var(--space-2xl) + var(--space-lg));
|
||||
max-height: calc(var(--space-2xl) + var(--space-lg));
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.task-planner-chat-starters {
|
||||
@@ -284,9 +299,26 @@ Planner Chat must keep visual height parity with Activity chat: desktop textarea
|
||||
}
|
||||
|
||||
.task-planner-chat-send {
|
||||
--btn-icon-size: calc(var(--space-2xl) + var(--space-sm));
|
||||
flex: 0 0 auto;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
inline-size: calc(var(--space-2xl) + var(--space-lg));
|
||||
min-inline-size: calc(var(--space-2xl) + var(--space-lg));
|
||||
block-size: calc(var(--space-2xl) + var(--space-lg));
|
||||
min-block-size: calc(var(--space-2xl) + var(--space-lg));
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.task-planner-chat-send span {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0 0 0 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.task-planner-chat-message {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { ensureTaskPlannerChatSession, fetchChatMessages, fetchTaskDetail, fetch
|
||||
import { parseQuestionToolCall, type ParsedQuestionToolCall } from "../utils/parseQuestionToolCall";
|
||||
import { markdownComponents } from "./AgentLogViewer";
|
||||
import { ChatQuestionResponse } from "./ChatQuestionResponse";
|
||||
import { ProviderIcon } from "./ProviderIcon";
|
||||
import "./TaskPlannerChatTab.css";
|
||||
|
||||
interface TaskPlannerChatTabProps {
|
||||
@@ -246,6 +247,7 @@ export function TaskPlannerChatTab({ task, projectId, active, expanded = false,
|
||||
|
||||
const planningModelProvider = isUsableModel(planningModel) ? planningModel.provider : undefined;
|
||||
const planningModelId = isUsableModel(planningModel) ? planningModel.modelId : undefined;
|
||||
const planningModelLabel = planningModelProvider && planningModelId ? `${planningModelProvider}/${planningModelId}` : "";
|
||||
const modelPayload = useMemo(() => {
|
||||
return planningModelProvider && planningModelId
|
||||
? { modelProvider: planningModelProvider, modelId: planningModelId }
|
||||
@@ -505,8 +507,13 @@ export function TaskPlannerChatTab({ task, projectId, active, expanded = false,
|
||||
</div>
|
||||
<div className="task-planner-chat-header-actions">
|
||||
{isUsableModel(planningModel) && (
|
||||
<span className="task-planner-chat-model" data-testid="task-planner-chat-model">
|
||||
{planningModel.provider}/{planningModel.modelId}
|
||||
<span
|
||||
className="task-planner-chat-model"
|
||||
data-testid="task-planner-chat-model"
|
||||
title={planningModelLabel}
|
||||
aria-label={planningModelLabel}
|
||||
>
|
||||
<ProviderIcon provider={planningModel.provider} size="md" />
|
||||
</span>
|
||||
)}
|
||||
{onExpandedChange && (
|
||||
@@ -623,6 +630,7 @@ export function TaskPlannerChatTab({ task, projectId, active, expanded = false,
|
||||
onChange={(event) => setDraft(event.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
disabled={composerState === "sending"}
|
||||
rows={1}
|
||||
/>
|
||||
<button type="button" className="btn btn-primary task-planner-chat-send" onClick={() => void sendMessage()} disabled={!canSend}>
|
||||
{composerState === "sending" ? <Loader2 className="animate-spin" aria-hidden="true" /> : <Send aria-hidden="true" />}
|
||||
|
||||
@@ -118,7 +118,11 @@ describe("TaskPlannerChatTab", () => {
|
||||
);
|
||||
expect(mockEnsureTaskPlannerChatSession).not.toHaveBeenCalled();
|
||||
expect(mockFetchChatMessages).toHaveBeenCalledWith("chat-planner", { order: "asc" }, undefined);
|
||||
expect(screen.getByTestId("task-planner-chat-model")).toHaveTextContent("anthropic/claude-plan");
|
||||
const modelBadge = screen.getByTestId("task-planner-chat-model");
|
||||
expect(modelBadge).toHaveAccessibleName("anthropic/claude-plan");
|
||||
expect(modelBadge).toHaveAttribute("title", "anthropic/claude-plan");
|
||||
expect(modelBadge).toHaveTextContent("");
|
||||
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();
|
||||
expect(screen.getByRole("button", { name: /Identify the next best action/ })).toBeInTheDocument();
|
||||
|
||||
Reference in New Issue
Block a user