What happens
#123 stopped the contents panel flashing open on static-build loads. The button that drives it has the identical failure mode, and is untouched.
app/components/toolbar/SidebarToggle.tsx renders both lucide icons unconditionally with absolute transition-all duration-300 ease-in-out, visibility driven by opacity-0 / opacity-100. On the pre-app.css frame none of .absolute, .opacity-0, .opacity-100 exist, and lucide emits real width="24" height="24" attributes — so both icons paint in flow, side by side, at full opacity. When app.css lands, position snaps (not transitionable) but opacity 1 → 0 animates, because transition-all duration-300 has been on the element since that first paint.
Net effect on every static-build navigation: a close (X) icon fading out over ~300 ms in the toolbar. Same mechanism as the panel — a correction being animated because the transition predates the stylesheet. Measured in WebKit: opacity walks 1 → 0.978 → 0.860 → ... → 0, animating on 18 of 32 sampled frames.
app/components/Outline.tsx (BackToTop) has the same shape — transition-opacity ease-in-out duration-300 with an opacity toggle — so this is a small class of sites rather than one.
Suggested direction
The useMounted() hook added in #123 is the general guard for this. Hoisting it to app/hooks/ next to useScroll and gating the toggle's transition on it would cover both sites. Worth narrowing transition-all to transition-opacity at the same time — today it also animates the hover:scale-110 transform and the position change.
Worth noting the toolbar is broadly unstyled on that frame anyway (fixed top-0 left-0 right-0 h-[50px] is not in CRITICAL_CSS), so the fade is the tail of a larger snap. Whether to extend CRITICAL_CSS to cover the toolbar is a bigger, separate question.
Turned up while reviewing #123; pre-existing, not caused by it.
What happens
#123 stopped the contents panel flashing open on static-build loads. The button that drives it has the identical failure mode, and is untouched.
app/components/toolbar/SidebarToggle.tsxrenders both lucide icons unconditionally withabsolute transition-all duration-300 ease-in-out, visibility driven byopacity-0/opacity-100. On the pre-app.cssframe none of.absolute,.opacity-0,.opacity-100exist, and lucide emits realwidth="24" height="24"attributes — so both icons paint in flow, side by side, at full opacity. Whenapp.csslands,positionsnaps (not transitionable) butopacity1 → 0 animates, becausetransition-all duration-300has been on the element since that first paint.Net effect on every static-build navigation: a close (X) icon fading out over ~300 ms in the toolbar. Same mechanism as the panel — a correction being animated because the transition predates the stylesheet. Measured in WebKit: opacity walks 1 → 0.978 → 0.860 → ... → 0, animating on 18 of 32 sampled frames.
app/components/Outline.tsx(BackToTop) has the same shape —transition-opacity ease-in-out duration-300with an opacity toggle — so this is a small class of sites rather than one.Suggested direction
The
useMounted()hook added in #123 is the general guard for this. Hoisting it toapp/hooks/next touseScrolland gating the toggle's transition on it would cover both sites. Worth narrowingtransition-alltotransition-opacityat the same time — today it also animates thehover:scale-110transform and the position change.Worth noting the toolbar is broadly unstyled on that frame anyway (
fixed top-0 left-0 right-0 h-[50px]is not inCRITICAL_CSS), so the fade is the tail of a larger snap. Whether to extendCRITICAL_CSSto cover the toolbar is a bigger, separate question.Turned up while reviewing #123; pre-existing, not caused by it.