web: fix Settings pane unreachable on mobile (push ?pane= atomically)
Opening the settings pane on mobile set activePaneIdx, but the ?pane= URL-sync effect snapped it back to the chat pane on the panes change, so the pane never showed. toggleSettingsPane now returns the new pane id (id generated outside the updater, strict-mode safe); Session's toggleSettingsAndSync pushes ?pane=<id> on mobile when opening (and drops it on close) so the sync effect keeps it active — mirrors the existing addPaneAndSwitch pattern. Desktop unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -50,8 +50,8 @@ export function activePaneChatId(pane: WorkspacePane): string | undefined {
|
||||
// v1.9: settings pane factory. No chats, no state beyond identity — the
|
||||
// SettingsPane component renders Session/Project sections from the
|
||||
// surrounding session/project.
|
||||
function settingsPane(): WorkspacePane {
|
||||
return { id: generateId(), kind: 'settings', chatIds: [], activeChatIdx: -1 };
|
||||
function settingsPane(id: string = generateId()): WorkspacePane {
|
||||
return { id, kind: 'settings', chatIds: [], activeChatIdx: -1 };
|
||||
}
|
||||
|
||||
// v1.14.x-html-artifact-panes: artifact pane factories. Payload travels with
|
||||
@@ -135,7 +135,7 @@ export interface UseWorkspacePanesResult {
|
||||
// Open-on-first-click, close-on-second-click. Singleton — settings panes
|
||||
// don't count toward MAX_PANES. Closing the only remaining pane (edge case)
|
||||
// falls back to an empty pane to preserve the "always one pane" invariant.
|
||||
toggleSettingsPane: () => void;
|
||||
toggleSettingsPane: () => string | null;
|
||||
removePane: (idx: number) => void;
|
||||
removeChatFromPanes: (chatId: string) => void;
|
||||
initializeFirstChatIfEmpty: (chatId: string) => void;
|
||||
@@ -492,14 +492,21 @@ export function useWorkspacePanes(sessionId: string): UseWorkspacePanesResult {
|
||||
return success ? newPaneId : null;
|
||||
}, [seedPaneChat]);
|
||||
|
||||
const toggleSettingsPane = useCallback(() => {
|
||||
// Returns the new settings pane id when one is OPENED (so mobile callers can
|
||||
// push ?pane= atomically — see addPaneAndSwitch), or null when it was closed.
|
||||
// Id generated outside the updater so a strict-mode double-invoke agrees.
|
||||
const toggleSettingsPane = useCallback((): string | null => {
|
||||
const newPaneId = generateId();
|
||||
let openedId: string | null = null;
|
||||
setPanes((prev) => {
|
||||
const existingIdx = prev.findIndex((p) => p.kind === 'settings');
|
||||
if (existingIdx < 0) {
|
||||
const next = [...prev, settingsPane()];
|
||||
const next = [...prev, settingsPane(newPaneId)];
|
||||
setActivePaneIdx(next.length - 1);
|
||||
openedId = newPaneId;
|
||||
return next;
|
||||
}
|
||||
openedId = null;
|
||||
if (prev.length <= 1) {
|
||||
setActivePaneIdx(0);
|
||||
return [emptyPane()];
|
||||
@@ -508,6 +515,7 @@ export function useWorkspacePanes(sessionId: string): UseWorkspacePanesResult {
|
||||
setActivePaneIdx((ai) => Math.min(ai, next.length - 1));
|
||||
return next;
|
||||
});
|
||||
return openedId;
|
||||
}, []);
|
||||
|
||||
const removePane = useCallback((idx: number) => {
|
||||
|
||||
@@ -123,6 +123,20 @@ function SessionInner({ sessionId }: { sessionId: string }) {
|
||||
};
|
||||
}, [sessionId]);
|
||||
|
||||
// v2.3: opening the settings pane on mobile must push ?pane= atomically, or
|
||||
// the URL-sync effect below snaps activePaneIdx back to the chat pane and the
|
||||
// settings pane never shows (same fix as addPaneAndSwitch). toggleSettingsPane
|
||||
// returns the new pane id when it opens (null when it closes → drop ?pane= so
|
||||
// the effect falls back to pane 0). Desktop has no URL pane state — no-op.
|
||||
const toggleSettingsAndSync = useCallback(() => {
|
||||
const openedId = panesHook.toggleSettingsPane();
|
||||
if (!isMobile) return;
|
||||
const params = new URLSearchParams(location.search);
|
||||
if (openedId) params.set('pane', openedId);
|
||||
else params.delete('pane');
|
||||
navigate(`${location.pathname}?${params.toString()}`);
|
||||
}, [panesHook, isMobile, navigate, location.pathname, location.search]);
|
||||
|
||||
useEffect(() => {
|
||||
return sessionEvents.subscribe((event) => {
|
||||
if (event.type === 'session_renamed' && event.session_id === sessionId) {
|
||||
@@ -156,10 +170,10 @@ function SessionInner({ sessionId }: { sessionId: string }) {
|
||||
// Sidebar Settings button broadcasts this when a session is mounted;
|
||||
// toggleSettingsPane opens on first click, closes on second.
|
||||
if (event.type === 'open_settings_pane') {
|
||||
panesHook.toggleSettingsPane();
|
||||
toggleSettingsAndSync();
|
||||
}
|
||||
});
|
||||
}, [sessionId, editingName, navigate, project, panesHook]);
|
||||
}, [sessionId, editingName, navigate, project, toggleSettingsAndSync]);
|
||||
|
||||
// v1.8: URL ?pane= sync (mobile only). Lifted from Workspace.tsx so
|
||||
// MobileTabSwitcher's onSwitchPane can push the same URL state and the
|
||||
|
||||
Reference in New Issue
Block a user