diff --git a/.changeset/quick-chat-tablet-drag-header.md b/.changeset/quick-chat-tablet-drag-header.md new file mode 100644 index 0000000000..304abb64a8 --- /dev/null +++ b/.changeset/quick-chat-tablet-drag-header.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Make the Quick Chat header easier to drag on tablets. +category: fix +dev: Enlarges only the floating Chat tablet drag target while preserving phone and desktop layouts. diff --git a/packages/dashboard/app/components/ChatView.css b/packages/dashboard/app/components/ChatView.css index 2780d01f8f..2e5d67940f 100644 --- a/packages/dashboard/app/components/ChatView.css +++ b/packages/dashboard/app/components/ChatView.css @@ -910,6 +910,19 @@ The old Quick Chat panel is replaced by the full ChatView inside a movable Float cursor: grabbing; } +/* +FNXC:QuickChat 2026-07-24-00:00: +Tablet Quick Chat keeps its delegated ViewHeader drag handle but needs a modestly taller touch target +than canonical desktop chrome. Scope the token-based height to the movable tablet band so phone sheets, +desktop geometry, and every other ViewHeader consumer remain unchanged. +*/ +@media (min-width: 769px) and (max-width: 1024px) and (min-height: 481px) { + .chat-view--floating .view-header { + min-height: calc(var(--view-header-min-height) + var(--space-sm)); + height: calc(var(--view-header-min-height) + var(--space-sm)); + } +} + /* FNXC:ChatModal 2026-06-22-14:38: Floating Chat can become narrow while the browser viewport remains desktop-sized. Mirror the mobile one-pane layout with a class driven by the modal ResizeObserver so a narrow pop-out hides the sidebar and shows the mobile thread/list surfaces. diff --git a/packages/dashboard/app/components/__tests__/ChatView.mobile.test.tsx b/packages/dashboard/app/components/__tests__/ChatView.mobile.test.tsx index 41bba5b7f8..69468e13ff 100644 --- a/packages/dashboard/app/components/__tests__/ChatView.mobile.test.tsx +++ b/packages/dashboard/app/components/__tests__/ChatView.mobile.test.tsx @@ -803,6 +803,47 @@ describe("ChatView mobile behavior", () => { } }); + it("keeps floating Quick Chat header controls for empty and populated content", async () => { + const restoreMatchMedia = mockDesktopViewport(); + const renderFloatingChat = async () => { + const onMaximize = vi.fn(); + const onMinimize = vi.fn(); + const onClose = vi.fn(); + const rendered = await renderWithAct( + , + ); + + expect(document.querySelector(".chat-view--floating .view-header")).toBeInTheDocument(); + for (const testId of ["chat-modal-maximize", "chat-modal-minimize", "chat-modal-close"]) { + expect(screen.getByTestId(testId)).toBeInTheDocument(); + } + return rendered; + }; + + try { + setupMockChat({ sessions: [], filteredSessions: [], activeSession: null }); + const emptyRender = await renderFloatingChat(); + emptyRender.unmount(); + + setupMockChat({ + sessions: [activeSessionFixture], + filteredSessions: [activeSessionFixture], + activeSession: activeSessionFixture, + messages: [{ id: "message-001", sessionId: "session-001", role: "assistant", content: "Populated response", createdAt: "2026-04-08T00:00:00.000Z" }], + }); + await renderFloatingChat(); + } finally { + restoreMatchMedia.mockRestore(); + } + }); + it("floating narrow mode: quick session switcher includes New Chat", async () => { const restoreMatchMedia = mockDesktopViewport(); const originalResizeObserver = globalThis.ResizeObserver; diff --git a/packages/dashboard/app/components/__tests__/FloatingWindow.test.tsx b/packages/dashboard/app/components/__tests__/FloatingWindow.test.tsx index d86269dc37..0071380b67 100644 --- a/packages/dashboard/app/components/__tests__/FloatingWindow.test.tsx +++ b/packages/dashboard/app/components/__tests__/FloatingWindow.test.tsx @@ -5,6 +5,7 @@ import { loadAllAppCss, loadStylesCss } from "../../test/cssFixture"; import { FLOATING_WINDOW_GEOMETRY_CHANGE_EVENT, FloatingWindow } from "../FloatingWindow"; const floatingWindowCss = readFileSync("app/components/FloatingWindow.css", "utf8"); +const chatViewCss = readFileSync("app/components/ChatView.css", "utf8"); const allAppCss = loadAllAppCss(); const stylesCss = loadStylesCss(); @@ -313,6 +314,25 @@ describe("FloatingWindow", () => { } }); + it("gives only delegated Quick Chat headers a larger tablet touch target", () => { + const tabletRule = mediaBlockFor( + chatViewCss, + "(min-width: 769px) and (max-width: 1024px) and (min-height: 481px)", + ); + const floatingHeaderRule = cssRuleFor(chatViewCss, ".chat-view--floating .view-header"); + + expect(tabletRule).toContain(".chat-view--floating .view-header"); + expect(tabletRule).toContain("min-height: calc(var(--view-header-min-height) + var(--space-sm));"); + expect(tabletRule).toContain("height: calc(var(--view-header-min-height) + var(--space-sm));"); + expect(floatingHeaderRule).toContain("cursor: grab;"); + expect(floatingHeaderRule).toContain("user-select: none;"); + expect(floatingHeaderRule).toContain("touch-action: none;"); + + // The explicit tablet query leaves the ≤768px sheet and >1024px desktop header geometry canonical. + expect(chatViewCss).not.toMatch(/@media \(max-width: 768px\)\s*\{\s*\.chat-view--floating \.view-header\s*\{/); + expect(chatViewCss).not.toMatch(/@media \(min-width: 1025px\)[\s\S]*\.chat-view--floating \.view-header/); + }); + it("keeps every tablet movable-modal drag handle on the explicit touch-action none contract", () => { const tabletStylesStart = stylesCss.indexOf("@media (min-width: 769px) and (max-width: 1024px)"); const mobileStylesStart = stylesCss.indexOf("@media (max-width: 768px)", tabletStylesStart);