FN-7541: remove chat markdown/plain-text toggle eye icon
Remove the chat header markdown/plain-text render toggle so chat messages always render as Markdown. - Delete the chat-thread-header-render-toggle button (desktop + mobile) and its eye icon from ChatView.tsx - Remove the associated showAllAsPlain state and related CSS rules from ChatView.css - Drop the chat.showRenderedMarkdown/chat.showPlainText i18n keys from all locale files and resources.d.ts - Update ChatView core/core-interactions/mobile tests to match the removed toggle - Add a changeset documenting the breaking removal Files changed: .changeset/FN-7541-remove-chat-eye-icon.md | 7 +++ packages/dashboard/app/components/ChatView.css | 60 +--------------------- packages/dashboard/app/components/ChatView.tsx | 52 +++++-------------- .../__tests__/ChatView.core-interactions.test.tsx | 9 ++-- .../components/__tests__/ChatView.core.test.tsx | 35 ++++--------- .../components/__tests__/ChatView.mobile.test.tsx | 29 ++++------- packages/i18n/locales/en/app.json | 2 - packages/i18n/locales/es/app.json | 2 - packages/i18n/locales/fr/app.json | 2 - packages/i18n/locales/ko/app.json | 2 - packages/i18n/locales/zh-CN/app.json | 2 - packages/i18n/locales/zh-TW/app.json | 2 - packages/i18n/src/resources.d.ts | 2 - 13 files changed, 46 insertions(+), 160 deletions(-) Fusion-Task-Id: FN-7541 Fusion-Task-Lineage: 8eac79af-80b0-4ae0-ab86-c3599dd3612c Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/FN-7541-remove-chat-eye-icon.md
Normal file
7
.changeset/FN-7541-remove-chat-eye-icon.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Remove the eye icon markdown/plain toggle from chat; messages always render as Markdown.
|
||||||
|
category: breaking
|
||||||
|
dev: Removed ChatView `chat-thread-header-render-toggle` (desktop + mobile), `showAllAsPlain` state, and `chat.showRenderedMarkdown`/`chat.showPlainText` i18n keys (FN-7541).
|
||||||
@@ -940,61 +940,9 @@ In the narrow/mobile chat layout there is no room for an expand/maximize afforda
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
FNXC:ChatRenderMode 2026-07-02-00:00:
|
FNXC:ChatRenderToggle 2026-07-04-00:00:
|
||||||
Chat keeps one thread-wide Markdown/plain control: desktop/tablet anchors it in the thread identity header, while mobile direct-thread view floats the same button above the transcript/composer so it does not reintroduce a second header row or per-message toggles.
|
The thread-wide Markdown/plain render toggle (`.chat-thread-header-render-toggle*`, desktop + mobile floating variants) was removed per FN-7541. Chat always renders Markdown now; the button and its CSS shells no longer exist.
|
||||||
*/
|
*/
|
||||||
.chat-thread-header-render-toggle {
|
|
||||||
margin-left: auto;
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: calc(var(--space-md) * 3);
|
|
||||||
height: calc(var(--space-md) * 3);
|
|
||||||
min-width: calc(var(--space-md) * 3);
|
|
||||||
min-height: calc(var(--space-md) * 3);
|
|
||||||
padding: 0;
|
|
||||||
border: none;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
color: var(--text-muted);
|
|
||||||
background: transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
outline: none;
|
|
||||||
transition: background var(--transition-fast), color var(--transition-fast);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-thread-header-render-toggle:hover {
|
|
||||||
background: var(--surface-hover);
|
|
||||||
color: var(--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-thread-header-render-toggle:focus-visible {
|
|
||||||
outline: none;
|
|
||||||
box-shadow: var(--focus-ring-strong);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-thread-header-render-toggle--plain {
|
|
||||||
color: var(--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-thread-header-render-toggle--floating {
|
|
||||||
position: absolute;
|
|
||||||
right: var(--space-md);
|
|
||||||
bottom: calc((var(--space-lg) * 2.5) + (var(--space-md) * 2) + var(--space-sm));
|
|
||||||
z-index: 3;
|
|
||||||
width: calc(var(--space-lg) * 2.25);
|
|
||||||
height: calc(var(--space-lg) * 2.25);
|
|
||||||
min-width: calc(var(--space-lg) * 2.25);
|
|
||||||
min-height: calc(var(--space-lg) * 2.25);
|
|
||||||
border: var(--btn-border-width) solid var(--border);
|
|
||||||
border-radius: var(--radius-full);
|
|
||||||
background: var(--surface);
|
|
||||||
box-shadow: var(--shadow-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-thread-header-render-toggle--floating:hover {
|
|
||||||
background: var(--surface-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Messages */
|
/* Messages */
|
||||||
.chat-messages {
|
.chat-messages {
|
||||||
@@ -2238,10 +2186,6 @@ Queued-message banners stack above the composer input with a capped scroll area,
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-thread-header-render-toggle {
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-thread-header-context {
|
.chat-thread-header-context {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import {
|
|||||||
Pencil,
|
Pencil,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
Bot,
|
Bot,
|
||||||
Eye,
|
|
||||||
EyeOff,
|
|
||||||
Paperclip,
|
Paperclip,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
Copy,
|
Copy,
|
||||||
@@ -545,11 +543,9 @@ export function ChatView({ projectId, addToast, floating = false, compactLayout
|
|||||||
const [mentionPopupVisible, setMentionPopupVisible] = useState(false);
|
const [mentionPopupVisible, setMentionPopupVisible] = useState(false);
|
||||||
const [mentionHighlightIndex, setMentionHighlightIndex] = useState(0);
|
const [mentionHighlightIndex, setMentionHighlightIndex] = useState(0);
|
||||||
const [mentionStartPos, setMentionStartPos] = useState(-1);
|
const [mentionStartPos, setMentionStartPos] = useState(-1);
|
||||||
// Single thread-wide toggle: when true, all assistant content (including the
|
// FNXC:ChatRenderToggle 2026-07-04-00:00: The markdown/plain eye toggle
|
||||||
// streaming bubble) renders as plain text instead of Markdown. Replaces the
|
// (showAllAsPlain / toggleAllAsPlain) was removed per FN-7541. Chat always
|
||||||
// earlier per-message toggle so the chat header owns this control instead
|
// renders Markdown now; forcePlain is hardcoded to false everywhere below.
|
||||||
// of every reply having its own button.
|
|
||||||
const [showAllAsPlain, setShowAllAsPlain] = useState(false);
|
|
||||||
// Attachment state mirrors QuickEntryBox: pending files selected before send.
|
// Attachment state mirrors QuickEntryBox: pending files selected before send.
|
||||||
const [pendingAttachments, setPendingAttachments] = useState<PendingAttachment[]>([]);
|
const [pendingAttachments, setPendingAttachments] = useState<PendingAttachment[]>([]);
|
||||||
const [isDragOver, setIsDragOver] = useState(false);
|
const [isDragOver, setIsDragOver] = useState(false);
|
||||||
@@ -2179,18 +2175,13 @@ export function ChatView({ projectId, addToast, floating = false, compactLayout
|
|||||||
|
|
||||||
// In model-only chats (no real agent picked) the agent identity *is* the
|
// In model-only chats (no real agent picked) the agent identity *is* the
|
||||||
// model name, which is already in the thread header. Repeating it on every
|
// model name, which is already in the thread header. Repeating it on every
|
||||||
// assistant bubble is noise. Hide the per-message identity row entirely;
|
// assistant bubble is noise. Hide the per-message identity row entirely.
|
||||||
// the render-mode toggle still appears in a slim toolbar.
|
|
||||||
const hideAssistantIdentity = activeSession?.agentId === FN_AGENT_ID;
|
const hideAssistantIdentity = activeSession?.agentId === FN_AGENT_ID;
|
||||||
|
|
||||||
const getPendingPreview = (message: string) => message.length > 50
|
const getPendingPreview = (message: string) => message.length > 50
|
||||||
? `${message.slice(0, 50)}…`
|
? `${message.slice(0, 50)}…`
|
||||||
: message;
|
: message;
|
||||||
|
|
||||||
const toggleAllAsPlain = useCallback(() => {
|
|
||||||
setShowAllAsPlain((value) => !value);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!mobileSessionMenuOpen) {
|
if (!mobileSessionMenuOpen) {
|
||||||
return;
|
return;
|
||||||
@@ -2336,7 +2327,7 @@ export function ChatView({ projectId, addToast, floating = false, compactLayout
|
|||||||
<StandardChatMessageItem
|
<StandardChatMessageItem
|
||||||
key={message.id}
|
key={message.id}
|
||||||
message={message}
|
message={message}
|
||||||
forcePlain={showAllAsPlain}
|
forcePlain={false}
|
||||||
agentName={agentName}
|
agentName={agentName}
|
||||||
hideAssistantIdentity={hideAssistantIdentity}
|
hideAssistantIdentity={hideAssistantIdentity}
|
||||||
showAssistantModelTag={showAssistantModelTag}
|
showAssistantModelTag={showAssistantModelTag}
|
||||||
@@ -2356,7 +2347,7 @@ export function ChatView({ projectId, addToast, floating = false, compactLayout
|
|||||||
streamingText={streamingText}
|
streamingText={streamingText}
|
||||||
streamingThinking={streamingThinking}
|
streamingThinking={streamingThinking}
|
||||||
streamingToolCalls={streamingToolCalls}
|
streamingToolCalls={streamingToolCalls}
|
||||||
forcePlain={showAllAsPlain}
|
forcePlain={false}
|
||||||
agentName={agentName}
|
agentName={agentName}
|
||||||
hideAssistantIdentity={hideAssistantIdentity}
|
hideAssistantIdentity={hideAssistantIdentity}
|
||||||
showAssistantModelTag={showAssistantModelTag}
|
showAssistantModelTag={showAssistantModelTag}
|
||||||
@@ -2378,7 +2369,7 @@ export function ChatView({ projectId, addToast, floating = false, compactLayout
|
|||||||
<StandardChatMessageItem
|
<StandardChatMessageItem
|
||||||
key={message.id}
|
key={message.id}
|
||||||
message={message}
|
message={message}
|
||||||
forcePlain={showAllAsPlain}
|
forcePlain={false}
|
||||||
agentName={agentName}
|
agentName={agentName}
|
||||||
hideAssistantIdentity={hideAssistantIdentity}
|
hideAssistantIdentity={hideAssistantIdentity}
|
||||||
showAssistantModelTag={showAssistantModelTag}
|
showAssistantModelTag={showAssistantModelTag}
|
||||||
@@ -3234,7 +3225,7 @@ export function ChatView({ projectId, addToast, floating = false, compactLayout
|
|||||||
<StandardChatMessageItem
|
<StandardChatMessageItem
|
||||||
key={message.id}
|
key={message.id}
|
||||||
message={roomMessage}
|
message={roomMessage}
|
||||||
forcePlain={showAllAsPlain}
|
forcePlain={false}
|
||||||
agentName={senderName}
|
agentName={senderName}
|
||||||
hideAssistantIdentity={false}
|
hideAssistantIdentity={false}
|
||||||
showAssistantModelTag={false}
|
showAssistantModelTag={false}
|
||||||
@@ -3377,6 +3368,10 @@ export function ChatView({ projectId, addToast, floating = false, compactLayout
|
|||||||
) : (
|
) : (
|
||||||
<div ref={chatThreadRef} className="chat-thread">
|
<div ref={chatThreadRef} className="chat-thread">
|
||||||
{/* Header - desktop/tablet keeps the thread identity row; mobile direct-thread controls move into ViewHeader. */}
|
{/* Header - desktop/tablet keeps the thread identity row; mobile direct-thread controls move into ViewHeader. */}
|
||||||
|
{/* FNXC:ChatRenderToggle 2026-07-04-00:00: The markdown/plain eye toggle
|
||||||
|
button (desktop `.chat-thread-header-render-toggle` and the mobile
|
||||||
|
floating `--floating` variant) was removed per FN-7541. Chat now
|
||||||
|
always renders Markdown (forcePlain is hardcoded to false). */}
|
||||||
{!isChatMobile && (hasThreadInView || !isChatMobile) && (
|
{!isChatMobile && (hasThreadInView || !isChatMobile) && (
|
||||||
<div className="chat-thread-header">
|
<div className="chat-thread-header">
|
||||||
<div className="chat-thread-header-identity" data-testid="chat-thread-header-identity">
|
<div className="chat-thread-header-identity" data-testid="chat-thread-header-identity">
|
||||||
@@ -3394,32 +3389,9 @@ export function ChatView({ projectId, addToast, floating = false, compactLayout
|
|||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{hasThreadInView && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`chat-thread-header-render-toggle${showAllAsPlain ? " chat-thread-header-render-toggle--plain" : ""}`}
|
|
||||||
data-testid="chat-thread-render-toggle"
|
|
||||||
aria-label={showAllAsPlain ? t("chat.showRenderedMarkdown", "Show all messages as rendered Markdown") : t("chat.showPlainText", "Show all messages as plain text")}
|
|
||||||
onClick={toggleAllAsPlain}
|
|
||||||
>
|
|
||||||
{showAllAsPlain ? <EyeOff size={14} /> : <Eye size={14} />}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isChatMobile && hasThreadInView && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`chat-thread-header-render-toggle chat-thread-header-render-toggle--floating${showAllAsPlain ? " chat-thread-header-render-toggle--plain" : ""}`}
|
|
||||||
data-testid="chat-thread-render-toggle"
|
|
||||||
aria-label={showAllAsPlain ? t("chat.showRenderedMarkdown", "Show all messages as rendered Markdown") : t("chat.showPlainText", "Show all messages as plain text")}
|
|
||||||
onClick={toggleAllAsPlain}
|
|
||||||
>
|
|
||||||
{showAllAsPlain ? <EyeOff size={14} /> : <Eye size={14} />}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Messages + composer. CLI-backed chat sessions delegate this
|
{/* Messages + composer. CLI-backed chat sessions delegate this
|
||||||
region to <CliChatSurface> (transcript/raw-terminal toggle +
|
region to <CliChatSurface> (transcript/raw-terminal toggle +
|
||||||
queued composer); generic-tier adapters render terminal-only. */}
|
queued composer); generic-tier adapters render terminal-only. */}
|
||||||
|
|||||||
@@ -1223,7 +1223,10 @@ describe("ChatView core interactions", () => {
|
|||||||
expect(headerModelTag).toBeNull();
|
expect(headerModelTag).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps provider identity text grouped in header while render toggle stays on the same row", async () => {
|
// FNXC:ChatRenderToggle 2026-07-04-00:00: the render toggle previously
|
||||||
|
// asserted here was removed per FN-7541; keep the identity-grouping
|
||||||
|
// assertions and add a regression check that no toggle remains.
|
||||||
|
it("keeps provider identity text grouped in header with no render toggle present", async () => {
|
||||||
setupMockChat({
|
setupMockChat({
|
||||||
activeSession: {
|
activeSession: {
|
||||||
id: "session-001",
|
id: "session-001",
|
||||||
@@ -1243,7 +1246,6 @@ describe("ChatView core interactions", () => {
|
|||||||
|
|
||||||
const header = document.querySelector(".chat-thread-header") as HTMLElement | null;
|
const header = document.querySelector(".chat-thread-header") as HTMLElement | null;
|
||||||
const identity = screen.getByTestId("chat-thread-header-identity");
|
const identity = screen.getByTestId("chat-thread-header-identity");
|
||||||
const toggle = screen.getByTestId("chat-thread-render-toggle");
|
|
||||||
const providerIcon = identity.querySelector(".provider-icon");
|
const providerIcon = identity.querySelector(".provider-icon");
|
||||||
const modelTag = identity.querySelector(".chat-model-tag");
|
const modelTag = identity.querySelector(".chat-model-tag");
|
||||||
const newChatButton = screen.getByTestId("chat-new-btn");
|
const newChatButton = screen.getByTestId("chat-new-btn");
|
||||||
@@ -1254,8 +1256,7 @@ describe("ChatView core interactions", () => {
|
|||||||
expect(within(identity).getByText("Agent Chat")).toBeInTheDocument();
|
expect(within(identity).getByText("Agent Chat")).toBeInTheDocument();
|
||||||
expect(modelTag).toBeInTheDocument();
|
expect(modelTag).toBeInTheDocument();
|
||||||
expect(modelTag).toHaveTextContent("Claude Sonnet 4.5");
|
expect(modelTag).toHaveTextContent("Claude Sonnet 4.5");
|
||||||
expect(toggle).toBeInTheDocument();
|
expect(screen.queryByTestId("chat-thread-render-toggle")).not.toBeInTheDocument();
|
||||||
expect(header?.children[header.children.length - 1]).toBe(toggle);
|
|
||||||
expect(document.querySelectorAll(".chat-thread-header .chat-model-tag")).toHaveLength(1);
|
expect(document.querySelectorAll(".chat-thread-header .chat-model-tag")).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -501,7 +501,12 @@ describe("ChatView", () => {
|
|||||||
expect(screen.queryByTestId("chat-render-mode-plain")).not.toBeInTheDocument();
|
expect(screen.queryByTestId("chat-render-mode-plain")).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("thread-header toggle flips every assistant bubble between rendered Markdown and plain text", async () => {
|
// FNXC:ChatRenderToggle 2026-07-04-00:00: The thread-header markdown/plain
|
||||||
|
// toggle was removed per FN-7541. Chat now always renders Markdown
|
||||||
|
// (forcePlain={false} everywhere), so these regression tests assert the
|
||||||
|
// toggle is gone and Markdown rendering stays intact for both persisted
|
||||||
|
// and streaming assistant bubbles, on the desktop surface.
|
||||||
|
it("has no thread-header render toggle and still renders assistant Markdown", async () => {
|
||||||
setupMockChat({
|
setupMockChat({
|
||||||
activeSession: { id: "session-001", agentId: "agent-001", status: "active", title: "Test Chat", createdAt: "2026-04-08T00:00:00.000Z", updatedAt: "2026-04-08T00:00:00.000Z" },
|
activeSession: { id: "session-001", agentId: "agent-001", status: "active", title: "Test Chat", createdAt: "2026-04-08T00:00:00.000Z", updatedAt: "2026-04-08T00:00:00.000Z" },
|
||||||
messages: [
|
messages: [
|
||||||
@@ -514,27 +519,14 @@ describe("ChatView", () => {
|
|||||||
|
|
||||||
const firstBubble = screen.getByTestId("chat-message-msg-001");
|
const firstBubble = screen.getByTestId("chat-message-msg-001");
|
||||||
const secondBubble = screen.getByTestId("chat-message-msg-002");
|
const secondBubble = screen.getByTestId("chat-message-msg-002");
|
||||||
const headerToggle = screen.getByTestId("chat-thread-render-toggle");
|
|
||||||
|
|
||||||
// Per-message toggles were intentionally removed; only the single
|
expect(screen.queryByTestId("chat-thread-render-toggle")).not.toBeInTheDocument();
|
||||||
// thread-level toggle should exist.
|
|
||||||
expect(screen.queryAllByTestId("chat-message-render-toggle")).toHaveLength(0);
|
expect(screen.queryAllByTestId("chat-message-render-toggle")).toHaveLength(0);
|
||||||
expect(within(firstBubble).getByText("First", { selector: "strong" })).toBeInTheDocument();
|
expect(within(firstBubble).getByText("First", { selector: "strong" })).toBeInTheDocument();
|
||||||
expect(within(secondBubble).getByText("Second", { selector: "strong" })).toBeInTheDocument();
|
expect(within(secondBubble).getByText("Second", { selector: "strong" })).toBeInTheDocument();
|
||||||
|
|
||||||
await userEvent.click(headerToggle);
|
|
||||||
|
|
||||||
expect(within(firstBubble).getByText(/\*\*First\*\* item/)).toBeInTheDocument();
|
|
||||||
expect(within(firstBubble).queryByText("First", { selector: "strong" })).toBeNull();
|
|
||||||
expect(within(secondBubble).getByText(/\*\*Second\*\* item/)).toBeInTheDocument();
|
|
||||||
expect(within(secondBubble).queryByText("Second", { selector: "strong" })).toBeNull();
|
|
||||||
|
|
||||||
await userEvent.click(headerToggle);
|
|
||||||
expect(within(firstBubble).getByText("First", { selector: "strong" })).toBeInTheDocument();
|
|
||||||
expect(within(secondBubble).getByText("Second", { selector: "strong" })).toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("thread-header toggle also drives the streaming bubble", async () => {
|
it("has no thread-header render toggle and still renders the streaming bubble as Markdown", async () => {
|
||||||
setupMockChat({
|
setupMockChat({
|
||||||
activeSession: { id: "session-001", agentId: "agent-001", status: "active", title: "Test Chat", createdAt: "2026-04-08T00:00:00.000Z", updatedAt: "2026-04-08T00:00:00.000Z" },
|
activeSession: { id: "session-001", agentId: "agent-001", status: "active", title: "Test Chat", createdAt: "2026-04-08T00:00:00.000Z", updatedAt: "2026-04-08T00:00:00.000Z" },
|
||||||
messages: [{ id: "msg-001", sessionId: "session-001", role: "assistant", content: "**Persisted**", createdAt: "2026-04-08T00:00:00.000Z" }],
|
messages: [{ id: "msg-001", sessionId: "session-001", role: "assistant", content: "**Persisted**", createdAt: "2026-04-08T00:00:00.000Z" }],
|
||||||
@@ -546,17 +538,8 @@ describe("ChatView", () => {
|
|||||||
|
|
||||||
const persistedBubble = screen.getByTestId("chat-message-msg-001");
|
const persistedBubble = screen.getByTestId("chat-message-msg-001");
|
||||||
const streamingBubble = document.querySelector(".chat-message--streaming") as HTMLElement;
|
const streamingBubble = document.querySelector(".chat-message--streaming") as HTMLElement;
|
||||||
const headerToggle = screen.getByTestId("chat-thread-render-toggle");
|
|
||||||
|
|
||||||
expect(within(streamingBubble).getByText("Live", { selector: "strong" })).toBeInTheDocument();
|
expect(screen.queryByTestId("chat-thread-render-toggle")).not.toBeInTheDocument();
|
||||||
expect(within(persistedBubble).getByText("Persisted", { selector: "strong" })).toBeInTheDocument();
|
|
||||||
|
|
||||||
await userEvent.click(headerToggle);
|
|
||||||
|
|
||||||
expect(within(streamingBubble).getByText(/\*\*Live\*\* stream/)).toBeInTheDocument();
|
|
||||||
expect(within(persistedBubble).getByText(/\*\*Persisted\*\*/)).toBeInTheDocument();
|
|
||||||
|
|
||||||
await userEvent.click(headerToggle);
|
|
||||||
expect(within(streamingBubble).getByText("Live", { selector: "strong" })).toBeInTheDocument();
|
expect(within(streamingBubble).getByText("Live", { selector: "strong" })).toBeInTheDocument();
|
||||||
expect(within(persistedBubble).getByText("Persisted", { selector: "strong" })).toBeInTheDocument();
|
expect(within(persistedBubble).getByText("Persisted", { selector: "strong" })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -286,7 +286,6 @@ describe("ChatView mobile behavior", () => {
|
|||||||
const headerTitle = viewHeader.querySelector(".view-header__title") as HTMLElement;
|
const headerTitle = viewHeader.querySelector(".view-header__title") as HTMLElement;
|
||||||
const backButton = screen.getByTestId("chat-back-btn");
|
const backButton = screen.getByTestId("chat-back-btn");
|
||||||
const sessionTrigger = screen.getByTestId("chat-mobile-session-trigger");
|
const sessionTrigger = screen.getByTestId("chat-mobile-session-trigger");
|
||||||
const renderToggle = screen.getByTestId("chat-thread-render-toggle");
|
|
||||||
|
|
||||||
expect(viewHeader).toContainElement(backButton);
|
expect(viewHeader).toContainElement(backButton);
|
||||||
expect(viewHeader).toContainElement(sessionTrigger);
|
expect(viewHeader).toContainElement(sessionTrigger);
|
||||||
@@ -301,8 +300,9 @@ describe("ChatView mobile behavior", () => {
|
|||||||
expect(sessionTrigger).not.toHaveTextContent("MiniMax M3");
|
expect(sessionTrigger).not.toHaveTextContent("MiniMax M3");
|
||||||
expect(within(sessionTrigger).getByTestId("minimax-icon")).toBeInTheDocument();
|
expect(within(sessionTrigger).getByTestId("minimax-icon")).toBeInTheDocument();
|
||||||
expect(sessionTrigger.querySelector(".chat-model-tag")).not.toBeInTheDocument();
|
expect(sessionTrigger.querySelector(".chat-model-tag")).not.toBeInTheDocument();
|
||||||
expect(viewHeader).not.toContainElement(renderToggle);
|
// FNXC:ChatRenderToggle 2026-07-04-00:00: the floating markdown/plain
|
||||||
expect(renderToggle).toHaveClass("chat-thread-header-render-toggle--floating");
|
// toggle was removed per FN-7541; confirm no such control remains.
|
||||||
|
expect(screen.queryByTestId("chat-thread-render-toggle")).not.toBeInTheDocument();
|
||||||
expect(screen.getAllByTestId("chat-back-btn")).toHaveLength(1);
|
expect(screen.getAllByTestId("chat-back-btn")).toHaveLength(1);
|
||||||
expect(screen.getAllByTestId("chat-mobile-session-trigger")).toHaveLength(1);
|
expect(screen.getAllByTestId("chat-mobile-session-trigger")).toHaveLength(1);
|
||||||
expect(screen.getByRole("heading", { name: "Chat" })).toBeInTheDocument();
|
expect(screen.getByRole("heading", { name: "Chat" })).toBeInTheDocument();
|
||||||
@@ -343,7 +343,11 @@ describe("ChatView mobile behavior", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("mobile mode: floating render toggle flips persisted and streaming assistant output", async () => {
|
// FNXC:ChatRenderToggle 2026-07-04-00:00: the floating markdown/plain
|
||||||
|
// toggle was removed per FN-7541; mobile chat always renders Markdown
|
||||||
|
// now (forcePlain={false}), so this regression test asserts the toggle
|
||||||
|
// is absent and persisted + streaming bubbles both still render Markdown.
|
||||||
|
it("mobile mode: has no floating render toggle and still renders persisted and streaming assistant output as Markdown", async () => {
|
||||||
const restoreMatchMedia = mockMobileViewport();
|
const restoreMatchMedia = mockMobileViewport();
|
||||||
try {
|
try {
|
||||||
setupMockChat({
|
setupMockChat({
|
||||||
@@ -355,19 +359,13 @@ describe("ChatView mobile behavior", () => {
|
|||||||
|
|
||||||
await renderWithAct(<ChatView projectId="proj-123" addToast={vi.fn()} />);
|
await renderWithAct(<ChatView projectId="proj-123" addToast={vi.fn()} />);
|
||||||
|
|
||||||
const toggle = screen.getByTestId("chat-thread-render-toggle");
|
|
||||||
const persistedBubble = screen.getByTestId("chat-message-msg-001");
|
const persistedBubble = screen.getByTestId("chat-message-msg-001");
|
||||||
const streamingBubble = document.querySelector(".chat-message--streaming") as HTMLElement;
|
const streamingBubble = document.querySelector(".chat-message--streaming") as HTMLElement;
|
||||||
|
|
||||||
expect(toggle).toHaveClass("chat-thread-header-render-toggle--floating");
|
expect(screen.queryByTestId("chat-thread-render-toggle")).not.toBeInTheDocument();
|
||||||
expect(screen.queryAllByTestId("chat-message-render-toggle")).toHaveLength(0);
|
expect(screen.queryAllByTestId("chat-message-render-toggle")).toHaveLength(0);
|
||||||
expect(within(persistedBubble).getByText("Persisted", { selector: "strong" })).toBeInTheDocument();
|
expect(within(persistedBubble).getByText("Persisted", { selector: "strong" })).toBeInTheDocument();
|
||||||
expect(within(streamingBubble).getByText("Live", { selector: "strong" })).toBeInTheDocument();
|
expect(within(streamingBubble).getByText("Live", { selector: "strong" })).toBeInTheDocument();
|
||||||
|
|
||||||
await userEvent.click(toggle);
|
|
||||||
|
|
||||||
expect(within(persistedBubble).getByText(/\*\*Persisted\*\* reply/)).toBeInTheDocument();
|
|
||||||
expect(within(streamingBubble).getByText(/\*\*Live\*\* reply/)).toBeInTheDocument();
|
|
||||||
} finally {
|
} finally {
|
||||||
restoreMatchMedia.mockRestore();
|
restoreMatchMedia.mockRestore();
|
||||||
}
|
}
|
||||||
@@ -2212,7 +2210,6 @@ describe("ChatView mobile CSS contract", () => {
|
|||||||
const actionsRule = css.match(/\.chat-view--mobile-direct-thread\s*>\s*\.view-header\s+\.view-header__actions\s*\{([^}]*)\}/)?.[1] ?? "";
|
const actionsRule = css.match(/\.chat-view--mobile-direct-thread\s*>\s*\.view-header\s+\.view-header__actions\s*\{([^}]*)\}/)?.[1] ?? "";
|
||||||
const menuRule = css.match(/\.chat-view--mobile-direct-thread\s+\.chat-mobile-session-menu\s*\{([^}]*)\}/)?.[1] ?? "";
|
const menuRule = css.match(/\.chat-view--mobile-direct-thread\s+\.chat-mobile-session-menu\s*\{([^}]*)\}/)?.[1] ?? "";
|
||||||
const triggerRule = css.match(/\.chat-mobile-session-trigger\s*\{([^}]*)\}/)?.[1] ?? "";
|
const triggerRule = css.match(/\.chat-mobile-session-trigger\s*\{([^}]*)\}/)?.[1] ?? "";
|
||||||
const floatingToggleRule = css.match(/\.chat-thread-header-render-toggle--floating\s*\{([^}]*)\}/)?.[1] ?? "";
|
|
||||||
|
|
||||||
expect(headerRule).toContain("flex-wrap: nowrap");
|
expect(headerRule).toContain("flex-wrap: nowrap");
|
||||||
expect(directTitleRule).toContain("position: absolute");
|
expect(directTitleRule).toContain("position: absolute");
|
||||||
@@ -2227,12 +2224,8 @@ describe("ChatView mobile CSS contract", () => {
|
|||||||
expect(triggerRule).toContain("min-width: 0");
|
expect(triggerRule).toContain("min-width: 0");
|
||||||
expect(triggerRule).toContain("gap: var(--space-sm)");
|
expect(triggerRule).toContain("gap: var(--space-sm)");
|
||||||
expect(triggerRule).not.toMatch(/#[0-9a-fA-F]{3,8}|rgb\(/);
|
expect(triggerRule).not.toMatch(/#[0-9a-fA-F]{3,8}|rgb\(/);
|
||||||
expect(floatingToggleRule).toContain("position: absolute");
|
// FNXC:ChatRenderToggle 2026-07-04-00:00: the floating render-toggle
|
||||||
expect(floatingToggleRule).toContain("right: var(--space-md)");
|
// button and its CSS were removed per FN-7541; no rule to assert here.
|
||||||
expect(floatingToggleRule).toContain("bottom: calc((var(--space-lg) * 2.5) + (var(--space-md) * 2) + var(--space-sm))");
|
|
||||||
expect(floatingToggleRule).toContain("min-width: calc(var(--space-lg) * 2.25)");
|
|
||||||
expect(floatingToggleRule).toContain("min-height: calc(var(--space-lg) * 2.25)");
|
|
||||||
expect(floatingToggleRule).toContain("box-shadow: var(--shadow-md)");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("FN-4352: response copy action stays compact on mobile", async () => {
|
it("FN-4352: response copy action stays compact on mobile", async () => {
|
||||||
|
|||||||
@@ -1374,8 +1374,6 @@
|
|||||||
"sendMessageFailed": "Failed to send message",
|
"sendMessageFailed": "Failed to send message",
|
||||||
"sendRoomMessageFailed": "Failed to send room message",
|
"sendRoomMessageFailed": "Failed to send room message",
|
||||||
"sessionsGroupLabel": "Sessions",
|
"sessionsGroupLabel": "Sessions",
|
||||||
"showPlainText": "Show all messages as plain text",
|
|
||||||
"showRenderedMarkdown": "Show all messages as rendered Markdown",
|
|
||||||
"skillSuggestions": "Skill suggestions",
|
"skillSuggestions": "Skill suggestions",
|
||||||
"startNewChat": "Start a new chat",
|
"startNewChat": "Start a new chat",
|
||||||
"startNewConversation": "Start a new conversation",
|
"startNewConversation": "Start a new conversation",
|
||||||
|
|||||||
@@ -1362,8 +1362,6 @@
|
|||||||
"sendMessageFailed": "Error al enviar el mensaje",
|
"sendMessageFailed": "Error al enviar el mensaje",
|
||||||
"sendRoomMessageFailed": "Error al enviar el mensaje al canal",
|
"sendRoomMessageFailed": "Error al enviar el mensaje al canal",
|
||||||
"sessionsGroupLabel": "Sesiones",
|
"sessionsGroupLabel": "Sesiones",
|
||||||
"showPlainText": "Mostrar texto sin formato",
|
|
||||||
"showRenderedMarkdown": "Mostrar markdown renderizado",
|
|
||||||
"skillSuggestions": "Sugerencias de habilidades",
|
"skillSuggestions": "Sugerencias de habilidades",
|
||||||
"startNewChat": "Iniciar un nuevo chat",
|
"startNewChat": "Iniciar un nuevo chat",
|
||||||
"startNewConversation": "Iniciar una nueva conversación",
|
"startNewConversation": "Iniciar una nueva conversación",
|
||||||
|
|||||||
@@ -1362,8 +1362,6 @@
|
|||||||
"sendMessageFailed": "Échec de l'envoi du message",
|
"sendMessageFailed": "Échec de l'envoi du message",
|
||||||
"sendRoomMessageFailed": "Échec de l'envoi du message dans le canal",
|
"sendRoomMessageFailed": "Échec de l'envoi du message dans le canal",
|
||||||
"sessionsGroupLabel": "Sessions",
|
"sessionsGroupLabel": "Sessions",
|
||||||
"showPlainText": "Afficher le texte brut",
|
|
||||||
"showRenderedMarkdown": "Afficher le markdown rendu",
|
|
||||||
"skillSuggestions": "Suggestions de compétences",
|
"skillSuggestions": "Suggestions de compétences",
|
||||||
"startNewChat": "Démarrer une nouvelle conversation",
|
"startNewChat": "Démarrer une nouvelle conversation",
|
||||||
"startNewConversation": "Commencer une nouvelle conversation",
|
"startNewConversation": "Commencer une nouvelle conversation",
|
||||||
|
|||||||
@@ -1362,8 +1362,6 @@
|
|||||||
"sendMessageFailed": "메시지 전송 실패",
|
"sendMessageFailed": "메시지 전송 실패",
|
||||||
"sendRoomMessageFailed": "방 메시지 전송 실패",
|
"sendRoomMessageFailed": "방 메시지 전송 실패",
|
||||||
"sessionsGroupLabel": "세션",
|
"sessionsGroupLabel": "세션",
|
||||||
"showPlainText": "모든 메시지를 일반 텍스트로 표시",
|
|
||||||
"showRenderedMarkdown": "모든 메시지를 렌더링된 Markdown으로 표시",
|
|
||||||
"skillSuggestions": "스킬 제안",
|
"skillSuggestions": "스킬 제안",
|
||||||
"startNewChat": "새 채팅 시작",
|
"startNewChat": "새 채팅 시작",
|
||||||
"startNewConversation": "새 대화 시작",
|
"startNewConversation": "새 대화 시작",
|
||||||
|
|||||||
@@ -1362,8 +1362,6 @@
|
|||||||
"sendMessageFailed": "发送消息失败",
|
"sendMessageFailed": "发送消息失败",
|
||||||
"sendRoomMessageFailed": "发送频道消息失败",
|
"sendRoomMessageFailed": "发送频道消息失败",
|
||||||
"sessionsGroupLabel": "会话",
|
"sessionsGroupLabel": "会话",
|
||||||
"showPlainText": "显示纯文本",
|
|
||||||
"showRenderedMarkdown": "显示渲染后的 Markdown",
|
|
||||||
"skillSuggestions": "技能建议",
|
"skillSuggestions": "技能建议",
|
||||||
"startNewChat": "开始新聊天",
|
"startNewChat": "开始新聊天",
|
||||||
"startNewConversation": "开始新对话",
|
"startNewConversation": "开始新对话",
|
||||||
|
|||||||
@@ -1362,8 +1362,6 @@
|
|||||||
"sendMessageFailed": "傳送訊息失敗",
|
"sendMessageFailed": "傳送訊息失敗",
|
||||||
"sendRoomMessageFailed": "傳送頻道訊息失敗",
|
"sendRoomMessageFailed": "傳送頻道訊息失敗",
|
||||||
"sessionsGroupLabel": "工作階段",
|
"sessionsGroupLabel": "工作階段",
|
||||||
"showPlainText": "顯示純文字",
|
|
||||||
"showRenderedMarkdown": "顯示渲染後的 Markdown",
|
|
||||||
"skillSuggestions": "技能建議",
|
"skillSuggestions": "技能建議",
|
||||||
"startNewChat": "開始新聊天",
|
"startNewChat": "開始新聊天",
|
||||||
"startNewConversation": "開始新對話",
|
"startNewConversation": "開始新對話",
|
||||||
|
|||||||
2
packages/i18n/src/resources.d.ts
vendored
2
packages/i18n/src/resources.d.ts
vendored
@@ -1365,8 +1365,6 @@ export default interface Resources {
|
|||||||
"sendMessageFailed": "Failed to send message",
|
"sendMessageFailed": "Failed to send message",
|
||||||
"sendRoomMessageFailed": "Failed to send room message",
|
"sendRoomMessageFailed": "Failed to send room message",
|
||||||
"sessionsGroupLabel": "Sessions",
|
"sessionsGroupLabel": "Sessions",
|
||||||
"showPlainText": "Show all messages as plain text",
|
|
||||||
"showRenderedMarkdown": "Show all messages as rendered Markdown",
|
|
||||||
"skillSuggestions": "Skill suggestions",
|
"skillSuggestions": "Skill suggestions",
|
||||||
"startNewChat": "Start a new chat",
|
"startNewChat": "Start a new chat",
|
||||||
"startNewConversation": "Start a new conversation",
|
"startNewConversation": "Start a new conversation",
|
||||||
|
|||||||
Reference in New Issue
Block a user