perf(sidebar): stop restyling the whole document on every sidebar-resize frame (#36071)

* perf(sidebar): scope live sidebar width to the drawers that read it

Writing --workspace-sidebar-live-width on :root every drag frame restyled the
whole document. Only the workspace board and agent dashboard sheets read it,
so write it to their SheetContent elements instead; a newly mounted drawer
picks up the current live width so opening mid-drag stays aligned.

* perf(sidebar): keep the live-width registry across HMR and stop it inheriting into drawer subtrees

- Registrations survive module re-evaluation; Radix keeps open drawers' refs stable, so they never re-register.
- @property inherits:false: only the drawer element itself reads the width, so its subtree no longer restyles per drag frame.
- Refresh the plugin-panel theme comment, which still described root writes during drags.
This commit is contained in:
Neil
2026-10-29 14:31:41 -07:00
committed by GitHub
parent ab674d13e5
commit 2da0aaae61
7 changed files with 82 additions and 6 deletions
+6
View File
@@ -168,6 +168,12 @@
--radius-4xl: calc(var(--radius) * 2.6);
}
/* Why: only the drawer that carries it reads the live sidebar width; don't restyle its subtree per drag frame. */
@property --workspace-sidebar-live-width {
syntax: '*';
inherits: false;
}
:root {
--app-font-family: 'Geist', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--font-sans: var(--app-font-family);
@@ -18,6 +18,7 @@ import { AgentDashboardSettingsMenu } from './AgentDashboardSettingsMenu'
import { useLiveDashboardSnapshot } from './useLiveDashboardSnapshot'
import { useAgentDashboardTelemetry } from '../dashboard-popout/use-agent-dashboard-telemetry'
import { translate } from '@/i18n/i18n'
import { trackSidebarLiveWidth } from '@/components/sidebar/sidebar-live-width'
// Nested interactive overlays own Escape before the companion sheet; exclude the sheet’s own role=dialog marker.
const AGENT_BOARD_ESCAPE_BLOCKING_OVERLAY_SELECTOR = [
@@ -213,6 +214,7 @@ export function AgentDashboardDrawer({
<SheetContent
side="left"
showCloseButton={false}
ref={trackSidebarLiveWidth}
aria-describedby={undefined}
className="workspace-kanban-sheet-content bg-worktree-sidebar p-0 sm:max-w-none"
overlayStyle={{
@@ -11,7 +11,7 @@ function readPanelThemeSnapshot(): string {
export function usePluginPanelThemeRevision(): number {
const [revision, setRevision] = useState(0)
// Compare baked shell values: rAF root-style writes during sidebar drags must not remount iframes and destroy panel state.
// Compare baked shell values: unrelated root-style writes must not remount iframes and destroy panel state.
const snapshotRef = useRef<string | null>(null)
useEffect(() => {
snapshotRef.current ??= readPanelThemeSnapshot()
@@ -2,6 +2,7 @@ import React from 'react'
import { Sheet, SheetContent } from '@/components/ui/sheet'
import { isWorkspaceBoardKeepOpenTarget } from './use-workspace-kanban-outside-dismiss'
import { STATUS_BAR_RESERVE_HEIGHT, WORKSPACE_TOP_CHROME_HEIGHT } from './workspace-chrome-metrics'
import { trackSidebarLiveWidth } from './sidebar-live-width'
type WorkspaceKanbanSheetProps = {
boardRef: React.RefObject<HTMLDivElement | null>
@@ -68,6 +69,7 @@ export default function WorkspaceKanbanSheet({
<SheetContent
side="left"
showCloseButton={false}
ref={trackSidebarLiveWidth}
className="workspace-kanban-sheet-content bg-worktree-sidebar p-0 sm:max-w-none"
overlayStyle={{
top: WORKSPACE_TOP_CHROME_HEIGHT,
@@ -4,6 +4,7 @@ import { TooltipProvider } from '@/components/ui/tooltip'
import { useSidebarResize } from '@/hooks/useSidebarResize'
import SidebarHeader from './SidebarHeader'
import SidebarNav from './SidebarNav'
import { setSidebarLiveWidth } from './sidebar-live-width'
import SetupScriptPromptCard from './SetupScriptPromptCard'
import WorktreeList from './WorktreeList'
import WslMovedProjectsSection from './WslMovedProjectsSection'
@@ -112,10 +113,6 @@ function Sidebar({
cancelWorkspaceBoardDragPreview
} = useWorkspaceBoardPanel()
const setLiveSidebarWidth = React.useCallback((width: number) => {
document.documentElement.style.setProperty('--workspace-sidebar-live-width', `${width}px`)
}, [])
const repoCount = repos.length
const previousRepoCountRef = React.useRef(repoCount)
useEffect(() => {
@@ -146,7 +143,7 @@ function Sidebar({
maxWidth: MAX_WIDTH,
deltaSign: 1,
setWidth: setSidebarWidth,
onDraftWidthChange: setLiveSidebarWidth
onDraftWidthChange: setSidebarLiveWidth
})
useWorkspaceRevealBodyRedirect(sidebarOpen && sidebarBody === 'agents')
@@ -0,0 +1,28 @@
// @vitest-environment happy-dom
import { afterEach, describe, expect, it } from 'vitest'
import { setSidebarLiveWidth, trackSidebarLiveWidth } from './sidebar-live-width'
const read = (el: HTMLElement): string =>
el.style.getPropertyValue('--workspace-sidebar-live-width')
describe('sidebar live width', () => {
const cleanups: (() => void)[] = []
afterEach(() => cleanups.splice(0).forEach((cleanup) => cleanup()))
it('scopes drag width to mounted drawers instead of the document root', () => {
setSidebarLiveWidth(300)
const drawer = document.createElement('div')
cleanups.push(trackSidebarLiveWidth(drawer)!)
// A drawer opened mid-drag starts at the live edge, not the persisted width.
expect(read(drawer)).toBe('300px')
setSidebarLiveWidth(312)
expect(read(drawer)).toBe('312px')
expect(read(document.documentElement)).toBe('')
cleanups.pop()!()
setSidebarLiveWidth(330)
expect(read(drawer)).toBe('312px')
})
})
@@ -0,0 +1,41 @@
const LIVE_WIDTH_PROPERTY = '--workspace-sidebar-live-width'
type SidebarLiveWidthState = { width: string | null; targets: Set<HTMLElement> }
type SidebarLiveWidthHotData = { sidebarLiveWidth?: SidebarLiveWidthState }
// Why: Radix keeps open drawers' refs stable across HMR, so registrations must survive module re-evaluation.
function sharedState(hotData?: SidebarLiveWidthHotData): SidebarLiveWidthState {
const state = hotData?.sidebarLiveWidth ?? { width: null, targets: new Set<HTMLElement>() }
if (hotData) {
hotData.sidebarLiveWidth = state
}
return state
}
const state = sharedState(import.meta.hot?.data)
// Why: a custom property on :root restyles the whole document every drag frame; only the drawers read it.
export function setSidebarLiveWidth(width: number): void {
const next = `${width}px`
if (next === state.width) {
return
}
state.width = next
for (const target of state.targets) {
target.style.setProperty(LIVE_WIDTH_PROPERTY, next)
}
}
// Ref callback: applies the current width on mount so a drawer opened mid-drag starts at the sidebar edge.
export function trackSidebarLiveWidth(element: HTMLElement | null): (() => void) | undefined {
if (!element) {
return undefined
}
if (state.width !== null) {
element.style.setProperty(LIVE_WIDTH_PROPERTY, state.width)
}
state.targets.add(element)
return () => {
state.targets.delete(element)
}
}