Skip to content

Commit fbcdcd0

Browse files
authored
fix(sidebar): suppress collapse->expand transition flash on fresh load (#5306)
* 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. * chore(sidebar): convert boot-guard comment to TSDoc
1 parent 48752c6 commit fbcdcd0

2 files changed

Lines changed: 38 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: 26 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,29 @@ export function WorkspaceChrome({
6668
*/
6769
const isCollapsed = hasHydrated ? storeIsCollapsed : initialSidebarCollapsed
6870

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

0 commit comments

Comments
 (0)