Skip to content

Commit 54bdb1d

Browse files
committed
fix(sidebar): suppress collapse->expand transition flash on fresh load
The pre-paint script sets the correct --sidebar-width, but store rehydration re-applies it during hydration and the always-on width/slide transitions animate that re-apply, reading as a collapse->expand flash. Suppress sidebar transitions during the boot window via a new html.sidebar-booting class (mirroring html.sidebar-resizing), lifted after the first paint via double-rAF so user toggles and the fullscreen slide still animate.
1 parent 7662ecc commit 54bdb1d

2 files changed

Lines changed: 36 additions & 1 deletion

File tree

apps/sim/app/_styles/globals.css

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,18 @@ html.sidebar-resizing .sidebar-shell-inner {
110110
transition: none !important;
111111
}
112112

113+
/* Suppress sidebar transitions during the initial hydration window. The
114+
pre-paint script sets the correct --sidebar-width, but store rehydration
115+
re-applies it a tick later; without this guard that re-apply animates the
116+
rail, reading as a collapse -> expand flash on a fresh page load. Removed
117+
after the first paint (see workspace-chrome.tsx) so user-driven toggles and
118+
the fullscreen slide still animate. */
119+
html.sidebar-booting .sidebar-container,
120+
html.sidebar-booting .sidebar-shell-outer,
121+
html.sidebar-booting .sidebar-shell-inner {
122+
transition: none !important;
123+
}
124+
113125
.panel-container {
114126
width: var(--panel-width);
115127
}

apps/sim/app/workspace/[workspaceId]/components/workspace-chrome/workspace-chrome.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { useEffect, useLayoutEffect } from 'react'
3+
import { useEffect, useLayoutEffect, useRef } from 'react'
44
import { cn } from '@sim/emcn'
55
import { usePathname } from 'next/navigation'
66
import { Sidebar } from '@/app/workspace/[workspaceId]/w/components/sidebar/sidebar'
@@ -47,6 +47,8 @@ export function WorkspaceChrome({
4747
children,
4848
initialSidebarCollapsed = false,
4949
}: WorkspaceChromeProps) {
50+
const rafRef = useRef(0)
51+
5052
const pathname = usePathname()
5153
const isFullscreen = isFullscreenPath(pathname)
5254

@@ -66,6 +68,27 @@ export function WorkspaceChrome({
6668
*/
6769
const isCollapsed = hasHydrated ? storeIsCollapsed : initialSidebarCollapsed
6870

71+
// Suppress sidebar transitions across the initial hydration window. The
72+
// pre-paint script already set the correct `--sidebar-width`, but the store
73+
// rehydration below re-applies it a tick later; without this guard that
74+
// re-apply animates the rail, reading as a collapse -> expand flash on a
75+
// fresh load. Runs before the rehydrate effect so the class is in place
76+
// ahead of the width mutation, then lifts after the first paint so
77+
// user-driven collapse toggles and the fullscreen slide still animate.
78+
useLayoutEffect(() => {
79+
const root = document.documentElement
80+
root.classList.add('sidebar-booting')
81+
const raf1 = requestAnimationFrame(() => {
82+
const raf2 = requestAnimationFrame(() => root.classList.remove('sidebar-booting'))
83+
rafRef.current = raf2
84+
})
85+
rafRef.current = raf1
86+
return () => {
87+
cancelAnimationFrame(rafRef.current)
88+
root.classList.remove('sidebar-booting')
89+
}
90+
}, [])
91+
6992
// Hydrate the persisted width before paint (collapse comes from the cookie/prop).
7093
useLayoutEffect(() => {
7194
void useSidebarStore.persist.rehydrate()

0 commit comments

Comments
 (0)