fix: preserve per-session scroll position on session switch (#1083)
* fix: restore scroll position when switching chat sessions When switching between chat sessions, scroll position now restores to where the user left off instead of always jumping to the bottom. - Save pixel-level scrollPosition (scrollTop/scrollHeight/clientHeight) in viewport store on every scroll event - Add restoreSavedScrollPosition to timeline controller for ratio-based restoration (handles content size changes between visits) - Suppress intermediate scroll events during session transition with an explicit flag, cleared deterministically after restore completes - Cancel in-flight animations/follow-loops on session switch - Preserve scrollPosition when session-ui-store rebuilds SessionMemoryState * fix: keep streaming sessions pinned on restore --------- Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
Bohdan Triapitsyn
parent
a23f5e7545
commit
03c9065c90
@@ -819,10 +819,11 @@ export const useSessionUIStore = create<SessionUIState>()((set, get) => ({
|
||||
if (!memState || !memState.lastUserMessageAt) {
|
||||
const newMemState = new Map(viewportState.sessionMemoryState)
|
||||
newMemState.set(currentSessionId, {
|
||||
viewportAnchor: memState?.viewportAnchor ?? 0,
|
||||
isStreaming: memState?.isStreaming ?? false,
|
||||
viewportAnchor: 0,
|
||||
isStreaming: false,
|
||||
lastAccessedAt: Date.now(),
|
||||
backgroundMessageCount: memState?.backgroundMessageCount ?? 0,
|
||||
backgroundMessageCount: 0,
|
||||
...memState,
|
||||
lastUserMessageAt: Date.now(),
|
||||
})
|
||||
useViewportStore.setState({ sessionMemoryState: newMemState })
|
||||
|
||||
@@ -7,6 +7,12 @@ import { create } from "zustand"
|
||||
|
||||
export type SessionMemoryState = {
|
||||
viewportAnchor: number
|
||||
/** Last known scrollbar pixel state — saved on every scroll event. */
|
||||
scrollPosition?: {
|
||||
scrollTop: number
|
||||
scrollHeight: number
|
||||
clientHeight: number
|
||||
}
|
||||
isStreaming: boolean
|
||||
streamStartTime?: number
|
||||
lastAccessedAt: number
|
||||
@@ -27,14 +33,14 @@ export type ViewportState = {
|
||||
sessionMemoryState: Map<string, SessionMemoryState>
|
||||
isSyncing: boolean
|
||||
|
||||
updateViewportAnchor: (sessionId: string, anchor: number) => void
|
||||
updateViewportAnchor: (sessionId: string, anchor: number, scrollPosition?: SessionMemoryState['scrollPosition']) => void
|
||||
}
|
||||
|
||||
export const useViewportStore = create<ViewportState>()((set) => ({
|
||||
sessionMemoryState: new Map(),
|
||||
isSyncing: false,
|
||||
|
||||
updateViewportAnchor: (sessionId, anchor) =>
|
||||
updateViewportAnchor: (sessionId, anchor, scrollPosition) =>
|
||||
set((s) => {
|
||||
const map = new Map(s.sessionMemoryState)
|
||||
const existing = map.get(sessionId) ?? {
|
||||
@@ -43,7 +49,12 @@ export const useViewportStore = create<ViewportState>()((set) => ({
|
||||
lastAccessedAt: Date.now(),
|
||||
backgroundMessageCount: 0,
|
||||
}
|
||||
map.set(sessionId, { ...existing, viewportAnchor: anchor, lastAccessedAt: Date.now() })
|
||||
map.set(sessionId, {
|
||||
...existing,
|
||||
viewportAnchor: anchor,
|
||||
...(scrollPosition ? { scrollPosition } : {}),
|
||||
lastAccessedAt: Date.now(),
|
||||
})
|
||||
return { sessionMemoryState: map }
|
||||
}),
|
||||
}))
|
||||
|
||||
Reference in New Issue
Block a user