mirror of
https://github.com/stablyai/orca.git
synced 2026-11-06 03:57:35 +00:00
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:
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user