FN-7655: make planner chat stop button icon-only
Removes the visible "Stop generation" text label from the planner chat's stop button while keeping it accessible via aria-label.
- Add showStopText prop to StandardChatActionButton (defaults to showSendText) to independently control Send vs Stop visible text
- Set showStopText={false} in TaskPlannerChatTab so the streaming stop button renders icon-only
- Update TaskPlannerChatTab test to assert no visible text span on the stop button while aria-label is retained
- Add changeset for @runfusion/fusion (patch)
Files changed:
.changeset/fn-7655-planner-stop-icon-only.md | 7 +++++++
.../dashboard/app/components/StandardChatSurface.tsx | 16 ++++++++++++++--
packages/dashboard/app/components/TaskPlannerChatTab.tsx | 3 +++
.../app/components/__tests__/TaskPlannerChatTab.test.tsx | 4 +++-
4 files changed, 27 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-7655
Fusion-Task-Lineage: f68a8bfa-30ba-439e-97d0-28654a614c54
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-7655-planner-stop-icon-only.md
Normal file
7
.changeset/fn-7655-planner-stop-icon-only.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Planner chat stop button now shows just the stop icon, not a text label.
|
||||||
|
category: fix
|
||||||
|
dev: StandardChatActionButton gains showStopText (defaults to showSendText); TaskPlannerChatTab sets showStopText={false}. aria-label "Stop generation" retained.
|
||||||
@@ -75,6 +75,15 @@ export interface StandardChatActionButtonProps {
|
|||||||
classNameSend?: string;
|
classNameSend?: string;
|
||||||
classNameStop?: string;
|
classNameStop?: string;
|
||||||
showSendText?: boolean;
|
showSendText?: boolean;
|
||||||
|
/**
|
||||||
|
* FNXC:StandardChatSurface 2026-07-07-00:00:
|
||||||
|
* Send and Stop visible-text are independently controllable so callers like
|
||||||
|
* the planner (FN-7655) can render the Stop button icon-only while keeping
|
||||||
|
* the Send button's text label. Defaults to `showSendText` when unset so
|
||||||
|
* existing callers keep prior combined behavior (accessible name via
|
||||||
|
* aria-label is always preserved regardless of this flag).
|
||||||
|
*/
|
||||||
|
showStopText?: boolean;
|
||||||
sendTestId?: string;
|
sendTestId?: string;
|
||||||
stopTestId?: string;
|
stopTestId?: string;
|
||||||
}
|
}
|
||||||
@@ -515,11 +524,14 @@ export function useStandardChatActionGesture() {
|
|||||||
return { beginTouchActionGesture, markHandledSendTouch, consumeHandledSendTouch };
|
return { beginTouchActionGesture, markHandledSendTouch, consumeHandledSendTouch };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function StandardChatActionButton({ isStreaming, canSend, onSend, onStop, sendLabel, stopLabel, classNameSend = "chat-input-send", classNameStop = "chat-input-stop", showSendText = false, sendTestId = "chat-send-btn", stopTestId = "chat-stop-btn" }: StandardChatActionButtonProps) {
|
export function StandardChatActionButton({ isStreaming, canSend, onSend, onStop, sendLabel, stopLabel, classNameSend = "chat-input-send", classNameStop = "chat-input-stop", showSendText = false, showStopText, sendTestId = "chat-send-btn", stopTestId = "chat-stop-btn" }: StandardChatActionButtonProps) {
|
||||||
const { t } = useTranslation("app");
|
const { t } = useTranslation("app");
|
||||||
const { beginTouchActionGesture, markHandledSendTouch, consumeHandledSendTouch } = useStandardChatActionGesture();
|
const { beginTouchActionGesture, markHandledSendTouch, consumeHandledSendTouch } = useStandardChatActionGesture();
|
||||||
|
// FNXC:StandardChatSurface 2026-07-07-00:00: resolve the Stop button's visible-text flag
|
||||||
|
// independently of Send's, defaulting to showSendText when the caller doesn't opt in (FN-7655).
|
||||||
|
const showStop = showStopText ?? showSendText;
|
||||||
if (isStreaming) {
|
if (isStreaming) {
|
||||||
return <button type="button" className={classNameStop} onPointerDown={(event) => { if (event.pointerType && event.pointerType !== "mouse") { event.preventDefault(); if (!beginTouchActionGesture()) return; markHandledSendTouch(); onStop?.(); } }} onTouchStart={(event) => { event.preventDefault(); if (!beginTouchActionGesture()) return; markHandledSendTouch(); onStop?.(); }} onMouseDown={(event) => event.preventDefault()} onClick={() => { if (consumeHandledSendTouch()) return; onStop?.(); }} aria-label={stopLabel ?? t("chat.stopGeneration", "Stop generation")} data-testid={stopTestId} style={{ touchAction: "manipulation" }}><span className="chat-input-stop-icon" aria-hidden="true" />{showSendText && <span>{stopLabel ?? t("chat.stopGeneration", "Stop generation")}</span>}</button>;
|
return <button type="button" className={classNameStop} onPointerDown={(event) => { if (event.pointerType && event.pointerType !== "mouse") { event.preventDefault(); if (!beginTouchActionGesture()) return; markHandledSendTouch(); onStop?.(); } }} onTouchStart={(event) => { event.preventDefault(); if (!beginTouchActionGesture()) return; markHandledSendTouch(); onStop?.(); }} onMouseDown={(event) => event.preventDefault()} onClick={() => { if (consumeHandledSendTouch()) return; onStop?.(); }} aria-label={stopLabel ?? t("chat.stopGeneration", "Stop generation")} data-testid={stopTestId} style={{ touchAction: "manipulation" }}><span className="chat-input-stop-icon" aria-hidden="true" />{showStop && <span>{stopLabel ?? t("chat.stopGeneration", "Stop generation")}</span>}</button>;
|
||||||
}
|
}
|
||||||
return <button type="button" className={classNameSend} onPointerDown={(event) => { if (event.pointerType && event.pointerType !== "mouse") { event.preventDefault(); if (!beginTouchActionGesture()) return; markHandledSendTouch(); void onSend(); } }} onTouchStart={(event) => { event.preventDefault(); if (!beginTouchActionGesture()) return; markHandledSendTouch(); void onSend(); }} onMouseDown={(event) => event.preventDefault()} onClick={() => { if (consumeHandledSendTouch()) return; void onSend(); }} disabled={!canSend} data-testid={sendTestId} aria-label={sendLabel ?? t("chat.send", "Send")} style={{ touchAction: "manipulation" }}><Send size={16} />{showSendText && <span>{sendLabel ?? t("chat.send", "Send")}</span>}</button>;
|
return <button type="button" className={classNameSend} onPointerDown={(event) => { if (event.pointerType && event.pointerType !== "mouse") { event.preventDefault(); if (!beginTouchActionGesture()) return; markHandledSendTouch(); void onSend(); } }} onTouchStart={(event) => { event.preventDefault(); if (!beginTouchActionGesture()) return; markHandledSendTouch(); void onSend(); }} onMouseDown={(event) => event.preventDefault()} onClick={() => { if (consumeHandledSendTouch()) return; void onSend(); }} disabled={!canSend} data-testid={sendTestId} aria-label={sendLabel ?? t("chat.send", "Send")} style={{ touchAction: "manipulation" }}><Send size={16} />{showSendText && <span>{sendLabel ?? t("chat.send", "Send")}</span>}</button>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -964,6 +964,9 @@ export function TaskPlannerChatTab({ task, projectId, active, expanded = false,
|
|||||||
sendLabel={t("taskDetail.plannerChat.send", "Send")}
|
sendLabel={t("taskDetail.plannerChat.send", "Send")}
|
||||||
stopLabel={t("chat.stopGeneration", "Stop generation")}
|
stopLabel={t("chat.stopGeneration", "Stop generation")}
|
||||||
showSendText
|
showSendText
|
||||||
|
// FNXC:TaskPlannerChat 2026-07-07-00:00: planner stop button is icon-only
|
||||||
|
// per FN-7655 — aria-label above keeps the accessible name "Stop generation".
|
||||||
|
showStopText={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -710,7 +710,9 @@ describe("TaskPlannerChatTab", () => {
|
|||||||
const stopIcon = stopButton.querySelector(".chat-input-stop-icon");
|
const stopIcon = stopButton.querySelector(".chat-input-stop-icon");
|
||||||
expect(stopIcon).toBeTruthy();
|
expect(stopIcon).toBeTruthy();
|
||||||
expect(stopIcon).toHaveAttribute("aria-hidden", "true");
|
expect(stopIcon).toHaveAttribute("aria-hidden", "true");
|
||||||
expect(stopButton).toHaveTextContent("Stop generation");
|
// FN-7655: the streaming stop button is icon-only — no visible "Stop generation" text span,
|
||||||
|
// even though the accessible name (aria-label) above still resolves to "Stop generation".
|
||||||
|
expect(stopButton.querySelector("span:not(.chat-input-stop-icon)")).toBeNull();
|
||||||
expect(screen.getByText("Thinking…")).toBeInTheDocument();
|
expect(screen.getByText("Thinking…")).toBeInTheDocument();
|
||||||
expect(screen.getByText("checking the plan")).toBeInTheDocument();
|
expect(screen.getByText("checking the plan")).toBeInTheDocument();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user