FN-8558: enlarge Quick Chat tablet drag target

Make the floating Quick Chat header easier to drag on tablet viewports.

- Increase only the floating Chat header touch target in the tablet viewport band.
- Cover floating header controls for empty and populated chats and scope the CSS contract.
- Add a patch changeset for the Quick Chat usability fix.

Files changed:
 .changeset/quick-chat-tablet-drag-header.md        |  7 ++++
 packages/dashboard/app/components/ChatView.css     | 13 +++++++
 packages/dashboard/app/components/__tests__/ChatView.mobile.test.tsx  | 41 ++++++++++++++++++++++
 packages/dashboard/app/components/__tests__/FloatingWindow.test.tsx   | 20 +++++++++++
 4 files changed, 81 insertions(+)

Fusion-Task-Id: FN-8558

Fusion-Task-Lineage: 2d2f788b-b417-4fa3-b066-8790858b393a

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-24 00:46:44 -07:00
parent c3cebcd56b
commit e4fc3d2acb
4 changed files with 81 additions and 0 deletions

View File

@@ -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.

View File

@@ -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.

View File

@@ -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(
<ChatView
projectId="proj-123"
addToast={vi.fn()}
floating
onMaximize={onMaximize}
onMinimize={onMinimize}
onClose={onClose}
/>,
);
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;

View File

@@ -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);