diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..404cb8b --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,4 @@ +## 2024-06-02 - Eliminate Layout Thrashing in Theme Sync + +**Learning:** Interleaving `window.getComputedStyle().getPropertyValue()` and `element.style.setProperty()` inside a loop causes layout thrashing (forced synchronous layout) multiple times per animation frame during drawing. +**Action:** When synchronizing many DOM styles based on computed styles, always collect the computed values in a first pass, and only then apply `setProperty` updates in a separate second pass, preferably also skipping updates where the value hasn't actually changed. diff --git a/src/client/components/EditorCanvas.tsx b/src/client/components/EditorCanvas.tsx index c5f4844..7bb9117 100644 --- a/src/client/components/EditorCanvas.tsx +++ b/src/client/components/EditorCanvas.tsx @@ -28,7 +28,8 @@ function sceneFromEditor( files: BinaryFiles, ): ScenePayload { return { - elements: [...elements], + // ⚡ Bolt: Excalidraw passes immutable arrays, avoid copying it on every onChange event + elements: elements as unknown[], appState: appState as unknown as Record, files: files as unknown as Record, }; diff --git a/src/client/hooks/useThemeTokenSync.ts b/src/client/hooks/useThemeTokenSync.ts index 2e48ea7..5a031f7 100644 --- a/src/client/hooks/useThemeTokenSync.ts +++ b/src/client/hooks/useThemeTokenSync.ts @@ -33,10 +33,20 @@ export function useThemeTokenSync( return; } + // ⚡ Bolt: Read all tokens first to avoid layout thrashing const computedStyles = window.getComputedStyle(excalidrawRoot); + const updates: Array<[string, string]> = []; + for (const [sourceToken, targetToken] of EXCALIDRAW_THEME_TOKEN_MAP) { const value = computedStyles.getPropertyValue(sourceToken).trim(); if (value) { + updates.push([targetToken, value]); + } + } + + // ⚡ Bolt: Write in a separate pass, skipping unchanged values + for (const [targetToken, value] of updates) { + if (appShell.style.getPropertyValue(targetToken) !== value) { appShell.style.setProperty(targetToken, value); } }