From 29560021d3b7143ac388a8b675d31615e8a282b9 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Mon, 13 Jul 2026 00:48:02 -0700 Subject: [PATCH] FN-7934: fix chat brain popup clipping in narrow floating windows Fixes the in-chat model/thinking (Brain) popup being cut off inside a narrow floating Chat window or compact dock on a wide desktop viewport. - Key the popover's viewport-fitting inset layout on ChatView's .chat-view--narrow class (chat surface width) instead of only the @media (max-width: 768px) browser-viewport query, so narrow floating/docked chat surfaces get the fitted layout too. - Add narrow-surface CSS rules for .chat-thinking-level-root, .chat-thinking-popover, .chat-thinking-agent-list, and .chat-thinking-popover-list to constrain position/width/max-height to the chat surface. - Add a CSS-contract regression test asserting both the desktop popover sizing and the new narrow-surface rules stay in sync. - Update docs/dashboard-guide.md to describe the popup staying fitted to the chat surface for narrow floating Chat windows/compact docks, not just mobile/tablet viewports. - Add a patch changeset for @runfusion/fusion documenting the fix. Files changed: .changeset/fn-7934-chat-narrow-model-popup.md | 7 +++++ docs/dashboard-guide.md | 3 +- packages/dashboard/app/components/ChatView.css | 22 +++++++++++++++ .../__tests__/ChatThinkingLevelControl.test.tsx | 32 ++++++++++++++++++++++ 4 files changed, 63 insertions(+), 1 deletion(-) Fusion-Task-Id: FN-7934 Fusion-Task-Lineage: 30c461c5-a153-4005-8a8c-24f02916a934 Co-authored-by: Fusion (runfusion.ai) --- .changeset/fn-7934-chat-narrow-model-popup.md | 7 ++++ docs/dashboard-guide.md | 3 +- .../dashboard/app/components/ChatView.css | 22 +++++++++++++ .../ChatThinkingLevelControl.test.tsx | 32 +++++++++++++++++++ 4 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 .changeset/fn-7934-chat-narrow-model-popup.md diff --git a/.changeset/fn-7934-chat-narrow-model-popup.md b/.changeset/fn-7934-chat-narrow-model-popup.md new file mode 100644 index 0000000000..7552e655cc --- /dev/null +++ b/.changeset/fn-7934-chat-narrow-model-popup.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Fix the in-chat model/thinking popup being cut off inside a narrow floating Chat window. +category: fix +dev: The popover's viewport-fitting inset is now keyed on ChatView's .chat-view--narrow class (surface width, incl. floating window / compact dock) instead of only @media (max-width: 768px) (browser viewport), so a narrow floating Chat window on a wide viewport no longer clips the popup. CustomModelDropdown is unchanged. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index 633e696823..c4fda79385 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -523,7 +523,8 @@ Chat view provides project-scoped conversations with agents. -- A small **Brain**-icon button next to the composer's attach button lets you change an already-created direct chat session's target and thinking level mid-conversation, without starting a new chat. Its **Model / Agent** section can switch the session to another model via the shared model picker or to a real agent from the agent list; its **Thinking level** section still lists the six thinking levels plus **Default** (clear/inherit, labeled with the current resolved default such as **Default (medium)**). Each selection persists immediately and applies starting with the session's next send, including on mobile and tablet touch viewports where the popup stays fitted to the screen. This control appears only for non-CLI Direct sessions — it is not shown for CLI-agent-backed sessions or in Chat Rooms, neither of which support this per-session retargeting control. + +- A small **Brain**-icon button next to the composer's attach button lets you change an already-created direct chat session's target and thinking level mid-conversation, without starting a new chat. Its **Model / Agent** section can switch the session to another model via the shared model picker or to a real agent from the agent list; its **Thinking level** section still lists the six thinking levels plus **Default** (clear/inherit, labeled with the current resolved default such as **Default (medium)**). Each selection persists immediately and applies starting with the session's next send, including on mobile/tablet touch viewports and narrow floating Chat windows or compact docks where the popup stays fitted to the chat surface. This control appears only for non-CLI Direct sessions — it is not shown for CLI-agent-backed sessions or in Chat Rooms, neither of which support this per-session retargeting control. - Full Chat and Quick Chat both consume the same streamed `/api/chat/sessions/:id/messages` response contract, and both now prefer the authoritative assistant `message` snapshot on `done` while still accumulating `text` chunks when present (so providers without incremental text streaming still render output immediately) - Final assistant messages with no text, tool calls, thinking output, attachments, or failure details render a muted **No message** placeholder instead of a blank bubble. In-progress responses still use the existing **Working…** / **Thinking…** streaming state until the run finishes. diff --git a/packages/dashboard/app/components/ChatView.css b/packages/dashboard/app/components/ChatView.css index 0c54080b86..e3c1102c52 100644 --- a/packages/dashboard/app/components/ChatView.css +++ b/packages/dashboard/app/components/ChatView.css @@ -2046,6 +2046,28 @@ FN-7908 keeps model/agent retargeting inside the same brain popup. The widened p z-index: 50; } +/* +FNXC:Chat-ModelSwitch 2026-07-13-00:00: +FN-7934: The brain popup must fit the containing chat surface, not only the browser viewport. Key the inset layout on .chat-view--narrow because it reflects surface width for floating Chat windows and compact docks; otherwise a narrow floating Chat window on a wide viewport clips the Model / Agent picker on the right. +*/ +.chat-view--narrow .chat-thinking-level-root { + position: static; +} + +.chat-view--narrow .chat-thinking-popover { + left: var(--space-md); + right: var(--space-md); + width: auto; + max-width: none; + max-inline-size: none; + max-height: min(calc(var(--space-xl) * 20), calc(100vh - (var(--space-xl) * 5))); +} + +.chat-view--narrow .chat-thinking-agent-list, +.chat-view--narrow .chat-thinking-popover-list { + max-height: calc(var(--space-xl) * 7); +} + .chat-thinking-target-section, .chat-thinking-level-section { padding: var(--space-sm); diff --git a/packages/dashboard/app/components/__tests__/ChatThinkingLevelControl.test.tsx b/packages/dashboard/app/components/__tests__/ChatThinkingLevelControl.test.tsx index 9eb0a46fd0..9c6e26cb07 100644 --- a/packages/dashboard/app/components/__tests__/ChatThinkingLevelControl.test.tsx +++ b/packages/dashboard/app/components/__tests__/ChatThinkingLevelControl.test.tsx @@ -1,3 +1,5 @@ +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; import { describe, it, expect, vi, beforeEach } from "vitest"; import { fireEvent, render, screen } from "@testing-library/react"; import { THINKING_LEVELS } from "@fusion/core"; @@ -28,6 +30,13 @@ const agents = [ { id: "agent-002", name: "Beta", role: "reviewer" }, ]; +const chatViewCss = () => readFileSync(resolve(__dirname, "../ChatView.css"), "utf-8"); + +function cssRule(css: string, selector: string) { + const escapedSelector = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\s+/g, "\\s+"); + return css.match(new RegExp(`${escapedSelector}\\s*\\{([^}]*)\\}`))?.[1] ?? ""; +} + describe("ChatThinkingLevelControl", () => { beforeEach(() => { vi.clearAllMocks(); @@ -222,3 +231,26 @@ describe("ChatThinkingLevelControl", () => { expect(screen.queryByRole("listbox")).toBeNull(); }); }); + +describe("ChatThinkingLevelControl CSS contract", () => { + it("keeps the popover fit keyed to narrow chat surfaces while preserving desktop sizing", () => { + const css = chatViewCss(); + const desktopPopoverRule = cssRule(css, ".chat-thinking-popover"); + const narrowRootRule = cssRule(css, ".chat-view--narrow .chat-thinking-level-root"); + const narrowPopoverRule = cssRule(css, ".chat-view--narrow .chat-thinking-popover"); + const narrowListRule = cssRule(css, ".chat-view--narrow .chat-thinking-agent-list,\n.chat-view--narrow .chat-thinking-popover-list"); + + expect(desktopPopoverRule).toContain("left: 0;"); + expect(desktopPopoverRule).toContain("width: min(calc(var(--space-xl) * 15), calc(100vw - (var(--space-lg) * 2)));"); + expect(desktopPopoverRule).toContain("max-width: calc(100vw - (var(--space-lg) * 2));"); + + expect(narrowRootRule).toContain("position: static;"); + expect(narrowPopoverRule).toContain("left: var(--space-md);"); + expect(narrowPopoverRule).toContain("right: var(--space-md);"); + expect(narrowPopoverRule).toContain("width: auto;"); + expect(narrowPopoverRule).toContain("max-width: none;"); + expect(narrowPopoverRule).toContain("max-inline-size: none;"); + expect(narrowPopoverRule).toContain("max-height: min(calc(var(--space-xl) * 20), calc(100vh - (var(--space-xl) * 5)));"); + expect(narrowListRule).toContain("max-height: calc(var(--space-xl) * 7);"); + }); +});