FN-7376: preserve planner chat stop icon
Keep Planner Chat mobile action text hiding from clipping the streaming stop icon. - Narrow the mobile Planner Chat send-button label selector to leave the shared stop icon span visible. - Add regression coverage for send and stop icon visibility during planner thinking. - Add a patch changeset for the published Fusion package. Files changed: .changeset/fn-7376-planner-chat-stop-icon.md | 7 ++++ .../app/components/TaskPlannerChatTab.css | 6 +++- .../__tests__/TaskPlannerChatTab.test.tsx | 41 ++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) Fusion-Task-Id: FN-7376 Fusion-Task-Lineage: 45ef720e-5ae1-4890-a2ed-afc4f97a8bb0 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-7376-planner-chat-stop-icon.md
Normal file
7
.changeset/fn-7376-planner-chat-stop-icon.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Keep the Planner Chat stop-generation icon visible on mobile while thinking.
|
||||
category: fix
|
||||
dev: Narrows the mobile Planner Chat text-hiding selector so the shared chat stop icon span remains visible.
|
||||
@@ -263,7 +263,11 @@ Mobile Planner Chat should match regular task chat: keep the composer as a singl
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.task-planner-chat-send span {
|
||||
/*
|
||||
FNXC:TaskDetailPlannerChat 2026-07-01-08:09:
|
||||
Mobile Planner Chat hides compact action text labels, but the shared thinking/streaming stop affordance draws its icon with `.chat-input-stop-icon` on a span. Exclude that icon span so the stop button never becomes an empty-looking shell while preserving the first-tap send/stop button contract.
|
||||
*/
|
||||
.task-planner-chat-send span:not(.chat-input-stop-icon) {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import React from "react";
|
||||
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { TaskPlannerChatTab } from "../TaskPlannerChatTab";
|
||||
|
||||
const taskPlannerChatCss = readFileSync(resolve(__dirname, "../TaskPlannerChatTab.css"), "utf8");
|
||||
|
||||
const { mockEnsureTaskPlannerChatSession, mockFetchTaskPlannerChatSession, mockFetchChatSession, mockFetchChatMessages, mockFetchTaskDetail, mockStreamChatResponse, mockAttachChatStream, mockTranslations, mockT } = vi.hoisted(() => {
|
||||
const translations = new Map<string, string>();
|
||||
return {
|
||||
@@ -622,6 +626,43 @@ describe("TaskPlannerChatTab", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps send and thinking stop icons visible in planner chat", async () => {
|
||||
const user = userEvent.setup();
|
||||
let streamHandlers: any;
|
||||
mockStreamChatResponse.mockImplementation((_sessionId, _content, handlers) => {
|
||||
streamHandlers = handlers;
|
||||
return { close: vi.fn(), isConnected: () => true };
|
||||
});
|
||||
renderPlannerChat();
|
||||
await screen.findByTestId("task-planner-chat-empty");
|
||||
|
||||
await user.type(screen.getByLabelText("Message planner chat"), "Think with an icon");
|
||||
const sendButton = screen.getByTestId("chat-send-btn");
|
||||
expect(sendButton).toHaveAccessibleName("Send");
|
||||
expect(sendButton.querySelector("svg")).toBeTruthy();
|
||||
expect(sendButton.querySelector("span")).toHaveTextContent("Send");
|
||||
|
||||
fireEvent.pointerDown(sendButton, { pointerType: "touch" });
|
||||
act(() => {
|
||||
streamHandlers.onThinking?.("checking the plan");
|
||||
});
|
||||
|
||||
const stopButton = await screen.findByTestId("chat-stop-btn");
|
||||
expect(stopButton).toHaveAccessibleName("Stop generation");
|
||||
const stopIcon = stopButton.querySelector(".chat-input-stop-icon");
|
||||
expect(stopIcon).toBeTruthy();
|
||||
expect(stopIcon).toHaveAttribute("aria-hidden", "true");
|
||||
expect(stopButton).toHaveTextContent("Stop generation");
|
||||
expect(screen.getByText("Thinking…")).toBeInTheDocument();
|
||||
expect(screen.getByText("checking the plan")).toBeInTheDocument();
|
||||
|
||||
const mobileTextHideRule = taskPlannerChatCss.match(/@media \(max-width: 768px\)[\s\S]*?\.task-planner-chat-send[^{}]*\{[^}]*clip:[^}]*\}/)?.[0] ?? "";
|
||||
expect(mobileTextHideRule).toContain("clip:");
|
||||
expect(mobileTextHideRule).toMatch(/span:not\(\.chat-input-stop-icon\)/);
|
||||
expect(mobileTextHideRule).not.toMatch(/\.task-planner-chat-send\s+span\s*\{/);
|
||||
expect(stopIcon).toHaveClass("chat-input-stop-icon");
|
||||
});
|
||||
|
||||
it("renders live and stored thinking output through the standard chat surface", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockFetchChatMessages.mockResolvedValueOnce({
|
||||
|
||||
Reference in New Issue
Block a user