diff --git a/web/CLAUDE.md b/web/CLAUDE.md index 7bd3621..6cc6c84 100644 --- a/web/CLAUDE.md +++ b/web/CLAUDE.md @@ -617,7 +617,10 @@ modal covers the screen anyway, but this can be improved in a future pass - The hamburger button is rendered inside `AppHeader`, which is only present within `ProjectShell`. The `/all` route has no hamburger. Low impact because - the All Projects dashboard itself lists all projects. + the All Projects dashboard itself lists all projects. The `/chat` route + renders its own `MobileChatHeader` (`web/src/pages/Chat/MobileChatHeader.tsx`) + with a hamburger that reuses `useMobileSidebar`, plus the focused chat title + and a "+ new chat" button. - The desktop collapsed sidebar (48 px strip, no `.sidebar` class) does not hide on mobile — pre-existing issue unrelated to this feature. - `MobileSidebarContext.tsx` exports both a component and a hook from the same diff --git a/web/src/pages/Chat/ChatPage.tsx b/web/src/pages/Chat/ChatPage.tsx index 452ff2d..56e5fe3 100644 --- a/web/src/pages/Chat/ChatPage.tsx +++ b/web/src/pages/Chat/ChatPage.tsx @@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useNavigate, useParams, useSearchParams } from 'react-router-dom'; import { NewChatDialog } from './NewChatDialog'; import { ChatThread } from './ChatThread'; +import { MobileChatHeader } from './MobileChatHeader'; import { ChatLayout, ChatLayoutProvider, type AvailableChat, type Slot } from '../../components/ChatLayout'; import { useChatLayout, type ChatLayoutState, type LRUEvictionEvent } from '../../hooks/useChatLayout'; import { useChatSessions, notifyChatSessionsChanged } from '../../hooks/useChatSessions'; @@ -171,28 +172,49 @@ export function ChatPage() { } }, [pendingDelete, layout]); + const focusedChatId = useMemo( + () => (layout.state.focused ? layout.state.panes[layout.state.focused]?.chatId ?? null : null), + [layout.state.focused, layout.state.panes], + ); + const mobileTitle = useMemo( + () => (focusedChatId + ? availableChats.find((c) => c.id === focusedChatId)?.title ?? 'Chat' + : 'Chats'), + [focusedChatId, availableChats], + ); + return ( - setDialogOpen(true)} - onEndSession={handleEndSession} - onReopenChat={handleReopenChat} - onDeleteChat={requestDeleteChat} - renderPaneBody={renderPaneBody} - /> +
+ {isMobile && ( + setDialogOpen(true)} + /> + )} +
+ setDialogOpen(true)} + onEndSession={handleEndSession} + onReopenChat={handleReopenChat} + onDeleteChat={requestDeleteChat} + renderPaneBody={renderPaneBody} + /> +
+
{evictToast && (
diff --git a/web/src/pages/Chat/MobileChatHeader.tsx b/web/src/pages/Chat/MobileChatHeader.tsx new file mode 100644 index 0000000..b7e3ca7 --- /dev/null +++ b/web/src/pages/Chat/MobileChatHeader.tsx @@ -0,0 +1,49 @@ +import { useMobileSidebar } from '../../context/MobileSidebarContext'; + +interface Props { + title: string; + onNewChat: () => void; +} + +export function MobileChatHeader({ title, onNewChat }: Props) { + const { toggle } = useMobileSidebar(); + return ( +
+ +

+ {title} +

+ +
+ ); +}