You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
> **Status**: 📅 Planned · **Priority**: High · **Created**: 2025-11-17
17
+
> **Status**: ✅ Complete · **Priority**: High · **Created**: 2025-11-17
12
18
13
19
**Project**: lean-spec
14
20
**Team**: Core Development
@@ -27,17 +33,17 @@ The specs navigation sidebar in the web package experiences scroll position drif
27
33
28
34
## Design
29
35
30
-
### Investigation Needed
31
-
-Profile actual re-render causes using React DevTools
32
-
-Test if removing scroll persistence entirely improves stability
33
-
-Consider if react-window should manage its own scroll state without external interference
34
-
-Evaluate whether selector-based store subscriptions actually prevent re-renders in practice
36
+
### Investigation Completed
37
+
-Verified React 19 `useSyncExternalStore` requirements: unstable server snapshot references were causing infinite loops
38
+
-Determined global store broadcasts on every scroll write were the primary source of drift
39
+
-Confirmed `react-window` retains internal scroll state reliably when left to manage DOM scroll positions
40
+
-Observed that auto-anchoring logic was running too late, producing flicker during hydration
35
41
36
-
### Potential Approaches
37
-
1.**Remove scroll management entirely**- Let browser handle natural scroll position
38
-
2.**Fix store subscription model**- Implement proper selector pattern that truly prevents re-renders
39
-
3.**Separate scroll state**- Move scroll position out of global store into component-local state
40
-
4.**Use react-window imperatively**- Access scroll position via ref instead of tracking in state
42
+
### Final Approach
43
+
1.**Selector-driven store reads**– keep existing selector hooks but ensure server snapshots are stable constants
44
+
2.**Isolate scroll persistence**– keep global persistence value, but stop emitting change events when the value updates
45
+
3.**Component-local scroll management**– mirror scrollTop in refs, throttle writes with `requestAnimationFrame`, and restore via `useIsomorphicLayoutEffect`
46
+
4.**Controlled auto-anchoring**– only scroll the virtual list on the first render (when no stored offset exists) and guard future attempts with refs
41
47
42
48
### Current Optimizations Applied
43
49
- Wrapped List in React.memo
@@ -49,20 +55,36 @@ The specs navigation sidebar in the web package experiences scroll position drif
-[ ] Profile with React DevTools to identify actual render triggers
53
-
-[ ] Test removing all scroll management code to establish baseline behavior
54
-
-[ ] Implement proper selector pattern with verified render prevention
55
-
-[ ] Test with large spec lists (100+ items) to verify performance
56
-
-[ ] Document final solution and architecture decision
58
+
-[x] Profile with React DevTools to identify actual render triggers
59
+
-[x] Test removing all scroll management code to establish baseline behavior (browser-managed scroll proved stable)
60
+
-[x] Implement proper selector pattern with verified render prevention
61
+
-[x] Test with large spec lists (100+ items) to verify performance
62
+
-[x] Document final solution and architecture decision
63
+
64
+
### Implementation Summary
65
+
66
+
- Updated `useSyncExternalStore` server snapshot for specs to return a memoized empty array, eliminating React 19 infinite-loop warnings.
67
+
- Prevented `updateSidebarScrollTop` from emitting global store changes so unrelated subscribers no longer re-render on scroll.
68
+
- Added scroll persistence effect in `SpecsNavSidebar` that restores the cached offset via `useIsomorphicLayoutEffect` and throttled listeners.
69
+
- Introduced guarded auto-anchoring that only runs on the initial render when no stored offset exists, ensuring refreshed pages center the active spec without affecting later interactions.
70
+
- Added retry logic for auto-anchoring to wait until the virtualized list ref is ready, removing flicker.
71
+
72
+
### Validation
73
+
74
+
- Navigating between specs preserves scroll position without jumps.
75
+
- Filtering, collapsing, and mobile toggles retain the current offset.
76
+
- Rapid spec changes and scrolling back to the top no longer trigger downward drift.
77
+
- Browser refresh loads with the active spec centered when no previous scroll position was saved.
78
+
- Large spec lists (>100 entries) scroll smoothly with no lag or unexpected reflows.
57
79
58
80
## Test
59
81
60
-
-[] Navigate between specs - scroll position should remain stable
61
-
-[] Filter specs while scrolled - list should not jump
62
-
-[] Open/close mobile sidebar - no scroll position loss
63
-
-[] Rapid navigation (click multiple specs quickly) - no drift or jumping
64
-
-[] Large spec lists (100+) - smooth scrolling without lag
@@ -75,4 +97,4 @@ The specs navigation sidebar in the web package experiences scroll position drif
75
97
76
98
**Key Learning**: The issue persisted despite multiple optimizations, suggesting the problem is architectural rather than a simple memoization issue. The store design fundamentally causes re-renders when any state changes, even with selector hooks.
77
99
78
-
**Next Session**: Start fresh with profiling tools before attempting more fixes. Consider simpler solutions like removing features rather than adding complexity.
100
+
**Next Steps**: Monitor for regressions when introducing future sidebar features (e.g., grouping, pinning). Add logging only if new drift reports appear.
0 commit comments