From 54bdb1dcd352cc9ed6ecbae06a08aa3c999bae4c Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 30 Jun 2026 18:07:45 -0700 Subject: [PATCH 1/2] 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. --- apps/sim/app/_styles/globals.css | 12 +++++++++ .../workspace-chrome/workspace-chrome.tsx | 25 ++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) 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..f140dde5748 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,27 @@ export function WorkspaceChrome({ */ const isCollapsed = hasHydrated ? storeIsCollapsed : initialSidebarCollapsed + // Suppress 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. Runs before the rehydrate effect so the class is in place + // ahead of the width mutation, then lifts 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() From ab441b9260c5de626cccd4816e1d63f1c26a6f28 Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 30 Jun 2026 18:08:57 -0700 Subject: [PATCH 2/2] chore(sidebar): convert boot-guard comment to TSDoc --- .../workspace-chrome/workspace-chrome.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) 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 f140dde5748..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 @@ -68,13 +68,15 @@ export function WorkspaceChrome({ */ const isCollapsed = hasHydrated ? storeIsCollapsed : initialSidebarCollapsed - // Suppress 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. Runs before the rehydrate effect so the class is in place - // ahead of the width mutation, then lifts after the first paint so - // user-driven collapse toggles and the fullscreen slide still animate. + /** + * 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')