From e2bf3512c96cda87b8bbe9ddac0e752bc67aadca Mon Sep 17 00:00:00 2001 From: kuannnn Date: Sun, 29 Mar 2026 09:50:38 +0800 Subject: [PATCH] fix(mobile): navigation back behavior, scroll button position MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New Chat → Chat uses replaceState so back skips new chat screen - Active tab persisted to sessionStorage, restored on back navigation - Scroll-to-bottom button positioned relative to footer (not hardcoded) - overscroll-behavior: none to prevent iOS rubber-band Co-Authored-By: Claude Opus 4.6 (1M context) --- src/App.tsx | 10 +++++++--- src/components/ChatBody.tsx | 22 ++++++++++++---------- src/hooks/useSessions.ts | 9 ++++++++- src/lib/storage-keys.ts | 1 + 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 8fb82ef..51ae123 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -36,7 +36,7 @@ function persistView(view: View) { sessionStorage.setItem('currentView', JSON.stringify(view)); } -function navigateTo(view: View) { +function navigateTo(view: View, replace = false) { persistView(view); let url = '/'; if (view.name === 'chat' && view.sessionId) { @@ -45,7 +45,11 @@ function navigateTo(view: View) { } else if (view.name === 'settings') { url = '/?view=settings'; } - window.history.pushState({ view }, '', url); + if (replace) { + window.history.replaceState({ view }, '', url); + } else { + window.history.pushState({ view }, '', url); + } } export function App() { @@ -225,7 +229,7 @@ export function App() { // Navigate to chat view with cwd — ChatView will pick up globals and send the prompt const chatCwd = view.name === 'newchat' ? view.cwd : undefined; const v: View = { name: 'chat', cwd: chatCwd, initialPrompt: options.prompt, adapter: options.adapter }; - navigateTo(v); + navigateTo(v, true); // replace newchat → chat so back goes to session list setView(v); }, [view]); diff --git a/src/components/ChatBody.tsx b/src/components/ChatBody.tsx index 3d3d9b6..b67eaad 100644 --- a/src/components/ChatBody.tsx +++ b/src/components/ChatBody.tsx @@ -253,16 +253,18 @@ export function ChatBody({ )} - {/* Scroll-to-bottom button */} - {userScrolled && ( - - )} + {/* Scroll-to-bottom button — sits just above the footer area */} +
+ {userScrolled && ( + + )} +
{renderAboveInput?.()} diff --git a/src/hooks/useSessions.ts b/src/hooks/useSessions.ts index 8fc9989..849b220 100644 --- a/src/hooks/useSessions.ts +++ b/src/hooks/useSessions.ts @@ -8,7 +8,14 @@ export function useSessions() { () => localStorage.getItem(STORAGE.PROJECT_DIR) ); const [loading, setLoading] = useState(true); - const [activeTab, setActiveTab] = useState<'projects' | 'active'>('projects'); + const [activeTab, _setActiveTab] = useState<'projects' | 'active'>(() => { + const stored = sessionStorage.getItem(STORAGE.SESSIONS_TAB); + return stored === 'active' ? 'active' : 'projects'; + }); + const setActiveTab = useCallback((tab: 'projects' | 'active') => { + _setActiveTab(tab); + sessionStorage.setItem(STORAGE.SESSIONS_TAB, tab); + }, []); const [activeSessions, setActiveSessions] = useState([]); const fetchAll = useCallback(async () => { diff --git a/src/lib/storage-keys.ts b/src/lib/storage-keys.ts index 0fb8bd1..f48dc2b 100644 --- a/src/lib/storage-keys.ts +++ b/src/lib/storage-keys.ts @@ -5,6 +5,7 @@ export const STORAGE = { PROJECT_DIR: 'clawtap:projectDir', DRAFT: 'clawtap:draft', INSTALL_DISMISSED: 'clawtap:install-dismissed', + SESSIONS_TAB: 'clawtap:sessionsTab', adapterPrefs: (id: string) => `clawtap:adapterPrefs:${id}` as const, } as const;