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:
+7
-3
@@ -36,7 +36,7 @@ function persistView(view: View) {
|
|||||||
sessionStorage.setItem('currentView', JSON.stringify(view));
|
sessionStorage.setItem('currentView', JSON.stringify(view));
|
||||||
}
|
}
|
||||||
|
|
||||||
function navigateTo(view: View) {
|
function navigateTo(view: View, replace = false) {
|
||||||
persistView(view);
|
persistView(view);
|
||||||
let url = '/';
|
let url = '/';
|
||||||
if (view.name === 'chat' && view.sessionId) {
|
if (view.name === 'chat' && view.sessionId) {
|
||||||
@@ -45,7 +45,11 @@ function navigateTo(view: View) {
|
|||||||
} else if (view.name === 'settings') {
|
} else if (view.name === 'settings') {
|
||||||
url = '/?view=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() {
|
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
|
// Navigate to chat view with cwd — ChatView will pick up globals and send the prompt
|
||||||
const chatCwd = view.name === 'newchat' ? view.cwd : undefined;
|
const chatCwd = view.name === 'newchat' ? view.cwd : undefined;
|
||||||
const v: View = { name: 'chat', cwd: chatCwd, initialPrompt: options.prompt, adapter: options.adapter };
|
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);
|
setView(v);
|
||||||
}, [view]);
|
}, [view]);
|
||||||
|
|
||||||
|
|||||||
+12
-10
@@ -253,16 +253,18 @@ export function ChatBody({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Scroll-to-bottom button */}
|
{/* Scroll-to-bottom button — sits just above the footer area */}
|
||||||
{userScrolled && (
|
<div className="relative shrink-0">
|
||||||
<button
|
{userScrolled && (
|
||||||
onClick={() => { setUserScrolled(false); scrollToBottom(true); }}
|
<button
|
||||||
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"
|
onClick={() => { setUserScrolled(false); scrollToBottom(true); }}
|
||||||
aria-label="Scroll to bottom"
|
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>
|
<ArrowDownToLine className="w-4 h-4 text-text-secondary" />
|
||||||
)}
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{renderAboveInput?.()}
|
{renderAboveInput?.()}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,14 @@ export function useSessions() {
|
|||||||
() => localStorage.getItem(STORAGE.PROJECT_DIR)
|
() => localStorage.getItem(STORAGE.PROJECT_DIR)
|
||||||
);
|
);
|
||||||
const [loading, setLoading] = useState(true);
|
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 [activeSessions, setActiveSessions] = useState<any[]>([]);
|
||||||
|
|
||||||
const fetchAll = useCallback(async () => {
|
const fetchAll = useCallback(async () => {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export const STORAGE = {
|
|||||||
PROJECT_DIR: 'clawtap:projectDir',
|
PROJECT_DIR: 'clawtap:projectDir',
|
||||||
DRAFT: 'clawtap:draft',
|
DRAFT: 'clawtap:draft',
|
||||||
INSTALL_DISMISSED: 'clawtap:install-dismissed',
|
INSTALL_DISMISSED: 'clawtap:install-dismissed',
|
||||||
|
SESSIONS_TAB: 'clawtap:sessionsTab',
|
||||||
adapterPrefs: (id: string) => `clawtap:adapterPrefs:${id}` as const,
|
adapterPrefs: (id: string) => `clawtap:adapterPrefs:${id}` as const,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user