fix: keep selected session header details stable
- Prevent header flicker to Untitled during session refresh - Resolve header session data from global sessions first - Pass session directory hints on more session-switch paths
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { RiArrowLeftLine } from '@remixicon/react';
|
||||
import type { Message, Part } from '@opencode-ai/sdk/v2';
|
||||
import type { Message, Part, Session } from '@opencode-ai/sdk/v2';
|
||||
|
||||
import { ChatInput } from './ChatInput';
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
@@ -191,7 +191,8 @@ export const ChatContainer: React.FC = () => {
|
||||
|
||||
const handleReturnToParentSession = React.useCallback(() => {
|
||||
if (!parentSession) return;
|
||||
setCurrentSession(parentSession.id);
|
||||
const parentDirectory = (parentSession as Session & { directory?: string | null }).directory ?? null;
|
||||
setCurrentSession(parentSession.id, parentDirectory);
|
||||
}, [parentSession, setCurrentSession]);
|
||||
|
||||
const returnToParentButton = parentSession ? (
|
||||
|
||||
@@ -29,6 +29,7 @@ import { useDirectoryStore } from '@/stores/useDirectoryStore';
|
||||
import { useFilesViewTabsStore } from '@/stores/useFilesViewTabsStore';
|
||||
import { useGitStore } from '@/stores/useGitStore';
|
||||
import { useGitHubPrStatusStore } from '@/stores/useGitHubPrStatusStore';
|
||||
import { useGlobalSessionsStore } from '@/stores/useGlobalSessionsStore';
|
||||
|
||||
import { useGitHubAuthStore } from '@/stores/useGitHubAuthStore';
|
||||
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
|
||||
@@ -65,6 +66,7 @@ import { ProjectActionsButton } from '@/components/layout/ProjectActionsButton';
|
||||
import { isDesktopShell, isVSCodeRuntime } from '@/lib/desktop';
|
||||
import { desktopHostsGet, locationMatchesHost, redactSensitiveUrl } from '@/lib/desktopHosts';
|
||||
import { resolveSessionDiffStats } from '@/components/session/sidebar/utils';
|
||||
import type { Session } from '@opencode-ai/sdk/v2/client';
|
||||
|
||||
|
||||
const isSameContextUsage = (
|
||||
@@ -250,6 +252,7 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
const currentSessionMessageRecords = useSessionMessageRecords(currentSessionId ?? '');
|
||||
const currentSessionMessages = currentSessionId ? (currentSessionMessageRecords.length > 0 ? currentSessionMessageRecords : undefined) : undefined;
|
||||
const sessions = useSessions();
|
||||
const globalActiveSessions = useGlobalSessionsStore((state) => state.activeSessions);
|
||||
const activeProject = useProjectsStore((state) => {
|
||||
if (!state.activeProjectId) {
|
||||
return null;
|
||||
@@ -561,16 +564,82 @@ export const Header: React.FC<HeaderProps> = ({
|
||||
});
|
||||
}, [fetchAllQuotas, isUsageRefreshSpinning]);
|
||||
|
||||
const currentSession = React.useMemo(() => {
|
||||
const currentSessionLive = React.useMemo(() => {
|
||||
if (!currentSessionId) return null;
|
||||
// Try current directory's store first, then fall back to all child stores.
|
||||
// The sidebar loads sessions globally via SDK, but the header uses
|
||||
// useSessions() which only has the current directory. This fallback
|
||||
// ensures the title/directory show when the session lives elsewhere.
|
||||
return sessions.find((s) => s.id === currentSessionId)
|
||||
// Resolve from the global sessions snapshot first (same source as sidebar).
|
||||
// Child-store lists are intentionally partial/truncated during bootstrap.
|
||||
return globalActiveSessions.find((s) => s.id === currentSessionId)
|
||||
?? sessions.find((s) => s.id === currentSessionId)
|
||||
?? getAllSyncSessions().find((s) => s.id === currentSessionId)
|
||||
?? null;
|
||||
}, [currentSessionId, sessions]);
|
||||
}, [currentSessionId, globalActiveSessions, sessions]);
|
||||
|
||||
const lastResolvedSessionRef = React.useRef<{
|
||||
sessionId: string;
|
||||
session: Session;
|
||||
expiresAt: number;
|
||||
} | null>(null);
|
||||
const [sessionFallbackVersion, setSessionFallbackVersion] = React.useState(0);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!currentSessionId) {
|
||||
if (lastResolvedSessionRef.current) {
|
||||
lastResolvedSessionRef.current = null;
|
||||
setSessionFallbackVersion((value) => value + 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentSessionLive) {
|
||||
lastResolvedSessionRef.current = {
|
||||
sessionId: currentSessionId,
|
||||
session: currentSessionLive,
|
||||
expiresAt: Date.now() + 2000,
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const cached = lastResolvedSessionRef.current;
|
||||
if (!cached || cached.sessionId !== currentSessionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const remainingMs = cached.expiresAt - Date.now();
|
||||
if (remainingMs <= 0) {
|
||||
lastResolvedSessionRef.current = null;
|
||||
setSessionFallbackVersion((value) => value + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
if (lastResolvedSessionRef.current?.sessionId === currentSessionId) {
|
||||
lastResolvedSessionRef.current = null;
|
||||
}
|
||||
setSessionFallbackVersion((value) => value + 1);
|
||||
}, remainingMs);
|
||||
|
||||
return () => {
|
||||
window.clearTimeout(timeoutId);
|
||||
};
|
||||
}, [currentSessionId, currentSessionLive]);
|
||||
|
||||
void sessionFallbackVersion;
|
||||
const currentSession = (() => {
|
||||
if (currentSessionLive) {
|
||||
return currentSessionLive;
|
||||
}
|
||||
|
||||
if (!currentSessionId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cached = lastResolvedSessionRef.current;
|
||||
if (cached && cached.sessionId === currentSessionId && cached.expiresAt > Date.now()) {
|
||||
return cached.session;
|
||||
}
|
||||
|
||||
return null;
|
||||
})();
|
||||
|
||||
const worktreePath = useSessionUIStore((state) => {
|
||||
if (!currentSessionId) return '';
|
||||
|
||||
@@ -24,7 +24,7 @@ type Args = {
|
||||
setDirectory: (directory: string, options?: { showOverlay?: boolean }) => void;
|
||||
setActiveMainTab: (tab: 'chat' | 'plan' | 'git' | 'diff' | 'terminal' | 'files') => void;
|
||||
setSessionSwitcherOpen: (open: boolean) => void;
|
||||
setCurrentSession: (sessionId: string | null) => void;
|
||||
setCurrentSession: (sessionId: string | null, directoryHint?: string | null) => void;
|
||||
updateSessionTitle: (id: string, title: string) => Promise<void>;
|
||||
shareSession: (id: string) => Promise<Session | null>;
|
||||
unshareSession: (id: string) => Promise<Session | null>;
|
||||
@@ -93,7 +93,7 @@ export const useSessionActions = (args: Args) => {
|
||||
resetSessionSearch();
|
||||
return;
|
||||
}
|
||||
args.setCurrentSession(sessionId);
|
||||
args.setCurrentSession(sessionId, sessionDirectory ?? null);
|
||||
args.onSessionSelected?.(sessionId);
|
||||
resetSessionSearch();
|
||||
},
|
||||
|
||||
@@ -55,8 +55,8 @@ export const CommandPalette: React.FC = () => {
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const handleOpenSession = (sessionId: string) => {
|
||||
setCurrentSession(sessionId);
|
||||
const handleOpenSession = (sessionId: string, directoryHint?: string | null) => {
|
||||
setCurrentSession(sessionId, directoryHint ?? null);
|
||||
handleClose();
|
||||
};
|
||||
|
||||
@@ -289,7 +289,7 @@ export const CommandPalette: React.FC = () => {
|
||||
{currentSessions.map((session) => (
|
||||
<CommandItem
|
||||
key={session.id}
|
||||
onSelect={() => handleOpenSession(session.id)}
|
||||
onSelect={() => handleOpenSession(session.id, currentDirectory ?? null)}
|
||||
>
|
||||
<RiChatAi3Line className="mr-2 h-4 w-4" />
|
||||
<span className="truncate">
|
||||
|
||||
Reference in New Issue
Block a user