Repository navigation
Keep the Gantt tree panel and timeline in lock-step while scrolling - #81
Merged
Merged
Conversation
The two panes are separate vertical scroll containers mirrored by useScrollSync. The follower lagged a frame and stuttered during fast scrolls: its own scroll event arrived after the rAF guard had reset and copied its older position back onto the pane being scrolled. The hook now remembers each value it writes and ignores the follower's matching scroll event, and a non-passive wheel listener moves both panes in the same frame (Ctrl+wheel zoom, Shift+wheel and horizontal trackpad gestures are left to the browser). The last rows also misaligned by up to 8 px: the left panes carried a fixed 8 px bottom padding "to match the horizontal scrollbar", which is 0 px tall with overlay scrollbars. The hook now measures both panes' natural scroll range and pads the shorter one, re-measured one frame after a ResizeObserver notification; the fixed paddings are gone from the five stylesheets. trail check flags one keyword match (vi.stubGlobal against the window.location learning); nothing ruled out is reintroduced. Release 1.10.1. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FPFQEucReHG1Wo9R6PpEpj
Deploying milestone-planner with
|
| Latest commit: |
cf82570
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://92423a89.milestone-planner.pages.dev |
| Branch Preview URL: | https://fix-gantt-scroll-sync.milestone-planner.pages.dev |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Two things you could see in the Gantt (and the Staff, Equipment, Cross-site and Archived views, which share the mechanism): the left tree panel trailed the timeline by a frame and stuttered during fast scrolls, and the last rows misaligned by up to 8 px at the bottom.
Both come from
useScrollSync, which keeps the two separate vertical scroll containers together by mirroringscrollToponscrollevents.scrollevent arrives a frame later, after the rAFisSyncingguard has reset, so it copied its older position back onto the pane the user was scrolling.padding-bottom: 8px"to match the horizontal scrollbar". That only holds for classic scrollbars; with overlay scrollbars (macOS) andscrollbar-gutter: stablethe scrollbar takes 0 px, so the left pane scrolled 8 px further than the right.Release 1.10.1.
What changed
useScrollSynckeeps its(ref1, ref2)signature; none of the five callers change. It now has three behaviours:scrollevent within 1 px of it is consumed, anything else is a real scroll and is mirrored. It only writes when the panes differ by ≥ 1 px.wheellistener on both panes moves bothscrollTops synchronously and prevents the native scroll, so the follower never trails. Ctrl+wheel (the zoom gesture), Shift+wheel, any event with a horizontal component and zero vertical delta are left to the browser, so zoom and sideways trackpad panning keep working; at the end of the range the event is not cancelled so the page can scroll. Line and page delta modes are normalised.scrollHeight - clientHeight(excluding the padding it applied itself) and pads the shorter pane by the difference, on mount and one frame after aResizeObservernotification on either pane or its content (windowresize fallback whereResizeObserveris missing). The fixed 8 px paddings are removed from the five stylesheets.Docs: the hook row in CLAUDE.md, and a UI_GUIDELINES §3.9 rule that no pane gets a fixed scrollbar-height padding.
Tests
useScrollSync.test.tsrewritten (21 cases): mirroring both ways; echo ignored (incl. a fractional one), a real scroll while an echo is pending still propagates, nothing written under 1 px; wheel moves both panes from either side, clamps at both ends, nopreventDefaultat the ends, Ctrl / Shift / horizontal / diagonal / zero-delta events ignored, line and page modes; padding 600 vs 592 →8px/0pxand stable on re-measure, flips to the other pane, returns to 0, merged callbacks, panes without a content child, thewindowresize fallback, panes not laid out; unmount removes listeners, observer, pending frame and paddings.tsc, eslint (0 warnings on the changed files) andnpm run buildare clean.trail checkflags one keyword match (vi.stubGlobalagainst thewindow.locationlearning); nothing a learning ruled out is reintroduced.Visible changes to check
https://claude.ai/code/session_01FPFQEucReHG1Wo9R6PpEpj
Generated by Claude Code