Skip to content

fix(dashboard): persist sidebar scroll position across navigation#4163

Open
chandan-1427 wants to merge 4 commits into
hatchet-dev:mainfrom
chandan-1427:fix/sidebar-scroll-position-persistence
Open

fix(dashboard): persist sidebar scroll position across navigation#4163
chandan-1427 wants to merge 4 commits into
hatchet-dev:mainfrom
chandan-1427:fix/sidebar-scroll-position-persistence

Conversation

@chandan-1427

Copy link
Copy Markdown
Contributor

Description

When navigating between routes using the sidebar (especially Settings items),
the sidebar scroll position resets to the top instead of maintaining its
previous position.

Steps to Reproduce

  1. Open the dashboard with enough sidebar items to require scrolling
  2. Scroll down in the sidebar to the Settings section
  3. Click any Settings nav item (General, API Tokens, etc.)
  4. Observe sidebar jumps back to top

Expected

Sidebar maintains its scroll position after navigation

Root Cause

The sidebar scroll container has no scroll position persistence. On re-render
triggered by route change, the scroll position resets to 0.

Fix

  • Add scrollPosition and setScrollPosition to useSidebar hook using
    existing useLocalStorageState pattern (consistent with how collapsed
    and expandedWidth are persisted)
  • Restore scroll position on mount via useEffect
  • Save scroll position on scroll with 100ms debounce to avoid excessive
    localStorage writes

Files Changed

  • frontend/app/src/components/hooks/use-sidebar.tsx
  • frontend/app/src/components/v1/nav/side-nav.tsx
🤖 AI Disclosure
  • I acknowledge that an LLM was used in the creation of this Pull Request,
    in accordance with Hatchet's AI_POLICY.md.
  • Details: Claude was used to help identify root cause and suggest fixes.

@vercel

vercel Bot commented Jun 12, 2026

Copy link
Copy Markdown

@chandan-1427 is attempting to deploy a commit to the Hatchet Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions Bot added the dashboard Related to the Hatchet dashboard label Jun 12, 2026

@BloggerBust BloggerBust left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this @chandan-1427,

I left a couple of comments. The main issue is that restoring whenever scrollPosition changes can create unnecessary churn because scrollPosition is updated by the scroll handler itself. Otherwise, your fix looks correct.

setIsResizing(false);
}, [setStoredCollapsed, storedCollapsed]);

useEffect(() => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this restoration effect should avoid depending on scrollPosition. Since scrollPosition is updated by the scroll handler, this effect runs after every debounced scroll save. It also sets scrollTop programmatically, and can trigger another scroll event. The value may stabilize, but it creates unnecessary save -> render -> restore -> scroll-event churn.

Could we restore the scroll position on mount or after navigation instead of every time the saved scroll position changes? Also, useLayoutEffect would be better for restoring scroll position because it runs before paint and avoids a visible jump from the top back to the saved position.

if (scrollSaveTimer.current) {
clearTimeout(scrollSaveTimer.current);
}
scrollSaveTimer.current = setTimeout(() => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add cleanup for this timer on unmount? that would avoid calling setScrollPosition after unmount if a debounce is still pending.

setCollapsed(!collapsed);
}, [collapsed, setCollapsed]);

const [scrollPosition, setScrollPosition] = useLocalStorageState(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need localStorage for this? The sidebar appears to stay mounted across these route changes, so a ref-only approach may be enough to preserve scroll position during navigation. That would avoid React state updates and localStorage writes while the user scrolls.

@chandan-1427

chandan-1427 commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for working on this @chandan-1427,

I left a couple of comments. The main issue is that restoring whenever scrollPosition changes can create unnecessary churn because scrollPosition is updated by the scroll handler itself. Otherwise, your fix looks correct.

Thanks for the detailed review @BloggerBust , all your points are valid. The scroll position is naturally preserved by the DOM and no explicit save/restore is needed at all. I'll verify this is the case and if so, remove the scrollPosition/setScrollPosition state and localStorage key entirely, along with the restore effect and the debounced save. If there's a remount edge case where a ref fallback is needed, I'll make sure to add proper timer cleanup too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dashboard Related to the Hatchet dashboard

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants