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
This commit is contained in:
Waleed
2026-06-30 18:13:57 -07:00
committed by GitHub
parent 48752c6024
commit fbcdcd0b92
2 changed files with 38 additions and 1 deletions
+12
View File
@@ -110,6 +110,18 @@ html.sidebar-resizing .sidebar-shell-inner {
transition: none !important;
}
/* Suppress sidebar transitions during the initial hydration window. The
pre-paint script sets the correct --sidebar-width, but store rehydration
re-applies it a tick later; without this guard that re-apply animates the
rail, reading as a collapse -> expand flash on a fresh page load. Removed
after the first paint (see workspace-chrome.tsx) so user-driven toggles and
the fullscreen slide still animate. */
html.sidebar-booting .sidebar-container,
html.sidebar-booting .sidebar-shell-outer,
html.sidebar-booting .sidebar-shell-inner {
transition: none !important;
}
.panel-container {
width: var(--panel-width);
}
@@ -1,6 +1,6 @@
'use client'
import { useEffect, useLayoutEffect } from 'react'
import { useEffect, useLayoutEffect, useRef } from 'react'
import { cn } from '@sim/emcn'
import { usePathname } from 'next/navigation'
import { Sidebar } from '@/app/workspace/[workspaceId]/w/components/sidebar/sidebar'
@@ -47,6 +47,8 @@ export function WorkspaceChrome({
children,
initialSidebarCollapsed = false,
}: WorkspaceChromeProps) {
const rafRef = useRef(0)
const pathname = usePathname()
const isFullscreen = isFullscreenPath(pathname)
@@ -66,6 +68,29 @@ export function WorkspaceChrome({
*/
const isCollapsed = hasHydrated ? storeIsCollapsed : initialSidebarCollapsed
/**
* Suppresses sidebar transitions across the initial hydration window. The
* pre-paint script already set the correct `--sidebar-width`, but the store
* rehydration below re-applies it a tick later; without this guard that
* re-apply animates the rail, reading as a collapse -> expand flash on a
* fresh load. Applied before the rehydrate effect so the class is in place
* ahead of the width mutation, then lifted after the first paint so
* user-driven collapse toggles and the fullscreen slide still animate.
*/
useLayoutEffect(() => {
const root = document.documentElement
root.classList.add('sidebar-booting')
const raf1 = requestAnimationFrame(() => {
const raf2 = requestAnimationFrame(() => root.classList.remove('sidebar-booting'))
rafRef.current = raf2
})
rafRef.current = raf1
return () => {
cancelAnimationFrame(rafRef.current)
root.classList.remove('sidebar-booting')
}
}, [])
// Hydrate the persisted width before paint (collapse comes from the cookie/prop).
useLayoutEffect(() => {
void useSidebarStore.persist.rehydrate()