diff --git a/apps/sim/app/_styles/globals.css b/apps/sim/app/_styles/globals.css index 0c1b9fe923d..64fa865241d 100644 --- a/apps/sim/app/_styles/globals.css +++ b/apps/sim/app/_styles/globals.css @@ -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); } diff --git a/apps/sim/app/workspace/[workspaceId]/components/workspace-chrome/workspace-chrome.tsx b/apps/sim/app/workspace/[workspaceId]/components/workspace-chrome/workspace-chrome.tsx index 3f6a9fa9fe5..57a2b69ed52 100644 --- a/apps/sim/app/workspace/[workspaceId]/components/workspace-chrome/workspace-chrome.tsx +++ b/apps/sim/app/workspace/[workspaceId]/components/workspace-chrome/workspace-chrome.tsx @@ -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()