fix(mobile): navigation back behavior, scroll button position

- 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) <noreply@anthropic.com>
This commit is contained in:
kuannnn
2026-03-29 09:50:38 +08:00
parent 10c38ad2e4
commit e2bf3512c9
4 changed files with 28 additions and 14 deletions
+6 -2
View File
@@ -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';
}
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]);
+4 -2
View File
@@ -253,16 +253,18 @@ export function ChatBody({
)}
</div>
{/* Scroll-to-bottom button */}
{/* Scroll-to-bottom button — sits just above the footer area */}
<div className="relative shrink-0">
{userScrolled && (
<button
onClick={() => { setUserScrolled(false); scrollToBottom(true); }}
className="absolute bottom-20 right-4 w-8 h-8 rounded-full bg-surface border border-border flex items-center justify-center shadow-lg hover:bg-surface-light transition-colors z-10"
className="absolute -top-10 right-4 w-8 h-8 rounded-full bg-surface border border-border flex items-center justify-center shadow-lg hover:bg-surface-light transition-colors z-10"
aria-label="Scroll to bottom"
>
<ArrowDownToLine className="w-4 h-4 text-text-secondary" />
</button>
)}
</div>
{renderAboveInput?.()}
+8 -1
View File
@@ -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<any[]>([]);
const fetchAll = useCallback(async () => {
+1
View File
@@ -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;