this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

web: LRU screen cache to bound memory in long sessions (#10063)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

authored by

Samuel Newman
Claude Opus 4.6 (1M context)
and committed by
GitHub
530afe87 317ce2b3

+59 -14
+5 -1
src/lib/hooks/useWebScrollRestoration.ts
··· 42 42 () => ({ 43 43 focus(e: EventArg<'focus', boolean | undefined, unknown>) { 44 44 const scrollY = state.scrollYs.get(e.target) ?? 0 45 - window.scrollTo(0, scrollY) 45 + // Deferred so that screens re-mounted by LRU eviction have 46 + // time to render their content before we scroll. 47 + requestAnimationFrame(() => { 48 + window.scrollTo(0, scrollY) 49 + }) 46 50 state.focusedKey = e.target ?? null 47 51 }, 48 52 }),
+54 -13
src/view/shell/createNativeStackNavigatorWithAuth.tsx
··· 42 42 import {DesktopLeftNav} from './desktop/LeftNav' 43 43 import {DesktopRightNav} from './desktop/RightNav' 44 44 45 + // On web, only this many screens (beyond Home + focused) stay mounted. 46 + // Older screens are unmounted to prevent memory growth during long sessions. 47 + const WEB_MAX_CACHED_SCREENS = 5 48 + 45 49 type NativeStackNavigationOptionsWithAuth = NativeStackNavigationOptions & { 46 50 requireAuth?: boolean 47 51 } ··· 105 109 ) 106 110 107 111 // --- our custom logic starts here --- 112 + // Web LRU: tracks route keys in most-recently-focused order 113 + const lruKeysRef = React.useRef<string[]>([]) 108 114 const {hasSession, currentAccount} = useSession() 109 115 const activeRoute = state.routes[state.index] 110 116 const activeDescriptor = descriptors[activeRoute.key] ··· 126 132 if (onboardingState.isActive) { 127 133 return <Onboarding /> 128 134 } 129 - const newDescriptors: typeof descriptors = {} 130 - for (let key in descriptors) { 131 - const descriptor = descriptors[key] 132 - const requireAuth = descriptor.options.requireAuth ?? false 133 - newDescriptors[key] = { 134 - ...descriptor, 135 - render() { 136 - if (requireAuth && !hasSession) { 137 - return <View /> 138 - } else { 139 - return descriptor.render() 135 + // On web, limit how many screens stay mounted to prevent memory growth. 136 + // Home is always pinned, the focused screen is always mounted, and the 137 + // most recently visited screens are kept up to WEB_MAX_CACHED_SCREENS. 138 + // Evicted screens render a lightweight placeholder — the route stays in 139 + // state so browser back/forward still works; the component just re-mounts. 140 + let finalDescriptors = descriptors 141 + if (IS_WEB) { 142 + const focusedKey = activeRoute.key 143 + 144 + // Update LRU: move focused key to front 145 + const lru = lruKeysRef.current 146 + const idx = lru.indexOf(focusedKey) 147 + if (idx > 0) { 148 + lru.splice(idx, 1) 149 + lru.unshift(focusedKey) 150 + } else if (idx === -1) { 151 + lru.unshift(focusedKey) 152 + } 153 + 154 + // Remove keys for routes no longer in the stack 155 + const routeKeySet = new Set(state.routes.map(r => r.key)) 156 + lruKeysRef.current = lruKeysRef.current.filter(k => routeKeySet.has(k)) 157 + 158 + // Build mount set: Home (pinned) + focused + N most recent 159 + const mountSet = new Set<string>() 160 + mountSet.add(focusedKey) 161 + const homeKey = state.routes.find(r => r.name === 'Home')?.key 162 + if (homeKey) mountSet.add(homeKey) 163 + let cached = 0 164 + for (const key of lruKeysRef.current) { 165 + if (cached >= WEB_MAX_CACHED_SCREENS) break 166 + if (!mountSet.has(key)) { 167 + mountSet.add(key) 168 + cached++ 169 + } 170 + } 171 + 172 + // Evicted screens get a lightweight placeholder instead of their full tree 173 + finalDescriptors = {} as typeof descriptors 174 + for (const key in descriptors) { 175 + if (mountSet.has(key)) { 176 + finalDescriptors[key] = descriptors[key] 177 + } else { 178 + finalDescriptors[key] = { 179 + ...descriptors[key], 180 + render: () => <View />, 140 181 } 141 - }, 182 + } 142 183 } 143 184 } 144 185 ··· 153 194 {...rest} 154 195 state={state} 155 196 navigation={navigation} 156 - descriptors={descriptors} 197 + descriptors={finalDescriptors} 157 198 describe={describe} 158 199 /> 159 200 </View>