From 0abe4af5f438fe8bf17a3b4cd0fb6225cf903770 Mon Sep 17 00:00:00 2001 From: Kobe Attias Date: Mon, 21 Sep 2026 21:51:50 -0400 Subject: [PATCH] Give phones a full-screen view of conversations and documents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Below the tablet breakpoint the sidebar no longer peeks up as a bottom drawer. A PanelLeft button in the chat header — the same icon the desktop's column suggests — opens the sidebar as a screen of its own, with a header that closes it, so the Documents section has the room its rows need. Verified with type-check, eslint and prettier only. Co-Authored-By: Claude Fable 5.1 --- components/AIMode/AIModeOverlay.tsx | 15 +++---- components/AIMode/chat/ChatPane.tsx | 6 +-- components/AIMode/shell/WorkspacePanes.tsx | 48 ++++++++++++++-------- 3 files changed, 43 insertions(+), 26 deletions(-) diff --git a/components/AIMode/AIModeOverlay.tsx b/components/AIMode/AIModeOverlay.tsx index 554e87944..a7f3bf826 100644 --- a/components/AIMode/AIModeOverlay.tsx +++ b/components/AIMode/AIModeOverlay.tsx @@ -37,9 +37,10 @@ export function AIModeOverlay() { const { close, layout, setLayout } = useAIMode(); const state = useAIModeChat(); const { target } = state; - // Below the tablet breakpoint the sidebar lives in a bottom drawer. - const [listDrawerOpen, setListDrawerOpen] = useState(false); - const closeListDrawer = useCallback(() => setListDrawerOpen(false), []); + // Below the tablet breakpoint the sidebar is a screen of its own, opened + // from the chat header. + const [listOpen, setListOpen] = useState(false); + const closeList = useCallback(() => setListOpen(false), []); const doc = useAIModeDocument({ note: state.note, @@ -175,15 +176,15 @@ export function AIModeOverlay() { isBelowTablet={isBelowTablet} sidebarWidth={{ ...listWidth, min: LIST_MIN_WIDTH, max: LIST_MAX_WIDTH }} sideWidth={{ ...sideWidth, min: sideMinWidth, max: sideMaxWidth }} - listDrawerOpen={listDrawerOpen} - onCloseListDrawer={closeListDrawer} + listOpen={listOpen} + onCloseList={closeList} onCloseDocumentDrawer={closeDocument} container={rootEl} - sidebar={} + sidebar={} chat={ setListDrawerOpen(true)} + onOpenConversations={() => setListOpen(true)} documentCard={documentCard} documentCardExecutionId={documentCardExecutionId} headerActions={ diff --git a/components/AIMode/chat/ChatPane.tsx b/components/AIMode/chat/ChatPane.tsx index 3c9bf8ed9..74e18983f 100644 --- a/components/AIMode/chat/ChatPane.tsx +++ b/components/AIMode/chat/ChatPane.tsx @@ -1,7 +1,7 @@ 'use client'; import { useEffect, useRef, useState, type ReactNode } from 'react'; -import { Menu } from 'lucide-react'; +import { PanelLeft } from 'lucide-react'; import { ChatComposer } from '@/components/AgentChat/ChatComposer'; import { ChatTranscript } from '@/components/AgentChat/ChatTranscript'; import { JumpToLatestButton } from '@/components/AgentChat/JumpToLatestButton'; @@ -132,10 +132,10 @@ export function ChatPane({ )} {renaming && chatId != null ? ( diff --git a/components/AIMode/shell/WorkspacePanes.tsx b/components/AIMode/shell/WorkspacePanes.tsx index 15af510e2..ae05fe773 100644 --- a/components/AIMode/shell/WorkspacePanes.tsx +++ b/components/AIMode/shell/WorkspacePanes.tsx @@ -1,6 +1,7 @@ 'use client'; import type { ReactNode } from 'react'; +import { PanelLeft } from 'lucide-react'; import { ResizeHandle } from '@/components/ui/ResizeHandle'; import { SwipeableDrawer } from '@/components/ui/SwipeableDrawer'; import type { useResizableWidth } from '@/hooks/useResizableWidth'; @@ -23,8 +24,9 @@ interface WorkspacePanesProps { readonly isBelowTablet: boolean; readonly sidebarWidth: ResizableWidth & { readonly min: number; readonly max: number }; readonly sideWidth: ResizableWidth & { readonly min: number; readonly max: number }; - readonly listDrawerOpen: boolean; - readonly onCloseListDrawer: () => void; + /** Below the tablet breakpoint the sidebar is a screen of its own; this shows it. */ + readonly listOpen: boolean; + readonly onCloseList: () => void; readonly onCloseDocumentDrawer: () => void; /** The overlay's root, which the drawers render inside so they stay live. */ readonly container: HTMLElement | null; @@ -35,7 +37,8 @@ interface WorkspacePanesProps { * their places in the tree whichever is the main pane — swapping them would * remount the editor and the transcript — and trade places on screen with * flex order. Both side panes drag; the main pane takes the rest. Below the - * tablet breakpoint the sidebar and the document live in bottom drawers. + * tablet breakpoint the sidebar takes the whole screen when asked for and + * the document lives in a bottom drawer. */ export function WorkspacePanes({ layout, @@ -46,8 +49,8 @@ export function WorkspacePanes({ isBelowTablet, sidebarWidth, sideWidth, - listDrawerOpen, - onCloseListDrawer, + listOpen, + onCloseList, onCloseDocumentDrawer, container, }: WorkspacePanesProps) { @@ -98,19 +101,32 @@ export function WorkspacePanes({ {document} )} + + {listOpen && isBelowTablet && ( +
+
+ +

Conversations and documents

+
+
{sidebar}
+
+ )} - {/* Drawers portal to the body, so they need to stack above this overlay - (z-9500) while staying under BaseModal (9999). */} - - {sidebar} - + {/* The drawer portals to the body, so it needs to stack above this + overlay (z-9500) while staying under BaseModal (9999). */}