diff --git a/docs/solutions/ui-bugs/tablet-keyboard-viewport-mode-flip.md b/docs/solutions/ui-bugs/tablet-keyboard-viewport-mode-flip.md index e1b80a123d..a4f679863e 100644 --- a/docs/solutions/ui-bugs/tablet-keyboard-viewport-mode-flip.md +++ b/docs/solutions/ui-bugs/tablet-keyboard-viewport-mode-flip.md @@ -44,6 +44,8 @@ This preserves landscape-phone behavior while preventing keyboard-driven height ChatView also keeps a defense-in-depth CSS guard from FN-6210: `.chat-sidebar` has a non-mobile `max-width` matching `CHAT_SIDEBAR_MAX_WIDTH`, with the mobile media rule overriding it back to `100%`. That guard bounds the sidebar even if viewport-mode state is temporarily wrong and the inline sidebar width is removed. +FN-6516 refined the FN-6494 keyboard-open behavior: tablet chat sidebars remain visible at the user's current/persisted width while the software keyboard is open, rather than narrowing to the minimum width. Resize controls still stay disabled while typing, collapsed sidebars remain collapsed, and the FN-6210 `max-width` CSS guard remains the upper bound. + ## Regression coverage Cover the invariant rather than the single repro: diff --git a/packages/dashboard/app/components/ChatView.tsx b/packages/dashboard/app/components/ChatView.tsx index ce29f5a1a9..cba6226ce7 100644 --- a/packages/dashboard/app/components/ChatView.tsx +++ b/packages/dashboard/app/components/ChatView.tsx @@ -3132,11 +3132,12 @@ export function ChatView({ projectId, addToast, experimentalFeatures }: ChatView /** * FNXC:ChatTabletKeyboard 2026-06-16-17:46: - * FN-6494 reverses the FN-6178/FN-6210 tablet-keyboard auto-hide: a visible chat sidebar must stay visible while the software keyboard is up, but use the minimum bounded width so the session list is not too wide in the reduced viewport. The user's persisted width remains untouched and returns when the keyboard closes; mobile keeps CSS-driven one-pane sizing. + * FN-6494 reverses the FN-6178/FN-6210 tablet-keyboard auto-hide: a visible chat sidebar must stay visible while the software keyboard is up. The user's persisted width remains untouched and returns when the keyboard closes; mobile keeps CSS-driven one-pane sizing. + * + * FNXC:ChatTabletKeyboard 2026-06-16-22:59: + * FN-6516 refines the tablet keyboard behavior: keep the sidebar at the same persisted width while the keyboard is open instead of narrowing to the minimum. The FN-6210 CSS max-width guard remains the upper bound, and resize controls still stay disabled while typing. */ - const sidebarInlineStyle: React.CSSProperties | undefined = isMobile - ? undefined - : { width: `${tabletKeyboardOpen ? Math.min(sidebarWidth, CHAT_SIDEBAR_MIN_WIDTH) : sidebarWidth}px` }; + const sidebarInlineStyle: React.CSSProperties | undefined = isMobile ? undefined : { width: `${sidebarWidth}px` }; return (
diff --git a/packages/dashboard/app/components/__tests__/ChatView.mobile-render.test.tsx b/packages/dashboard/app/components/__tests__/ChatView.mobile-render.test.tsx index 920720bfb1..f2dd713b80 100644 --- a/packages/dashboard/app/components/__tests__/ChatView.mobile-render.test.tsx +++ b/packages/dashboard/app/components/__tests__/ChatView.mobile-render.test.tsx @@ -347,7 +347,7 @@ describe("FN-5997 mobile chat message pane rendering", () => { } }); - it("keeps the tablet sidebar visible but narrower while the software keyboard is open, and restores width when closed", async () => { + it("keeps the tablet sidebar at the same width while the software keyboard is open", async () => { const restoreMatchMedia = mockViewportMode("tablet"); const visualViewport = mockVisualViewport({ width: 900, height: 1112 }); try { @@ -369,20 +369,57 @@ describe("FN-5997 mobile chat message pane rendering", () => { }); await setVisualViewportHeight(visualViewport, 560); - await waitFor(() => expect(sidebar.style.width).toBe("180px")); + await waitFor(() => expect(screen.queryByRole("separator", { name: "Resize chat sidebar" })).toBeNull()); + expect(sidebar.style.width).toBe("280px"); expect(sidebar).not.toHaveClass("chat-sidebar--hidden"); - expect(Number.parseInt(sidebar.style.width, 10)).toBeLessThan(280); - expect(Number.parseInt(sidebar.style.width, 10)).toBeLessThanOrEqual(280); - expect(screen.queryByRole("separator", { name: "Resize chat sidebar" })).toBeNull(); await act(async () => { input.blur(); }); await setVisualViewportHeight(visualViewport, 1112); - await waitFor(() => expect(sidebar.style.width).toBe("280px")); + await waitFor(() => expect(screen.getByRole("separator", { name: "Resize chat sidebar" })).toBeInTheDocument()); + expect(sidebar.style.width).toBe("280px"); + expect(sidebar).not.toHaveClass("chat-sidebar--hidden"); + } finally { + restoreMatchMedia.mockRestore(); + } + }); + + it("keeps a persisted custom tablet sidebar width while the software keyboard is open", async () => { + const restoreMatchMedia = mockViewportMode("tablet"); + const visualViewport = mockVisualViewport({ width: 900, height: 1112 }); + localStorage.setItem("fusion:chat-sidebar-width", "360"); + try { + setupChat({ + sessions: [activeSession], + filteredSessions: [activeSession], + activeSession, + }); + await renderWithCss(); + + const sidebar = getSidebar(); + await waitFor(() => expect(sidebar.style.width).toBe("360px")); + expect(sidebar).not.toHaveClass("chat-sidebar--hidden"); + + const input = screen.getByTestId("chat-input") as HTMLTextAreaElement; + await act(async () => { + input.focus(); + }); + await setVisualViewportHeight(visualViewport, 560); + + await waitFor(() => expect(screen.queryByRole("separator", { name: "Resize chat sidebar" })).toBeNull()); + expect(sidebar.style.width).toBe("360px"); + expect(sidebar).not.toHaveClass("chat-sidebar--hidden"); + + await act(async () => { + input.blur(); + }); + await setVisualViewportHeight(visualViewport, 1112); + + await waitFor(() => expect(screen.getByRole("separator", { name: "Resize chat sidebar" })).toBeInTheDocument()); + expect(sidebar.style.width).toBe("360px"); expect(sidebar).not.toHaveClass("chat-sidebar--hidden"); - expect(screen.getByRole("separator", { name: "Resize chat sidebar" })).toBeInTheDocument(); } finally { restoreMatchMedia.mockRestore(); } @@ -429,9 +466,9 @@ describe("FN-5997 mobile chat message pane rendering", () => { }); await setVisualViewportHeight(visualViewport, 560); - await waitFor(() => expect(sidebar.style.width).toBe("180px")); + await waitFor(() => expect(screen.queryByRole("separator", { name: "Resize chat sidebar" })).toBeNull()); + expect(sidebar.style.width).toBe("280px"); expect(sidebar).toHaveClass("chat-sidebar--hidden"); - expect(screen.queryByRole("separator", { name: "Resize chat sidebar" })).toBeNull(); await act(async () => { input.blur();