diff --git a/.jules/bolt.md b/.jules/bolt.md
new file mode 100644
index 0000000..4cb965d
--- /dev/null
+++ b/.jules/bolt.md
@@ -0,0 +1,4 @@
+## 2023-10-27 - Excalidraw Component Re-render Performance Penalty
+
+**Learning:** Due to the exceptionally high rendering cost of the `@excalidraw/excalidraw` canvas, inline functions passed as props (like `onChange` and `onExcalidrawAPI`) cause severe performance degradation by defeating `React.memo` and triggering full canvas re-renders when parent state changes.
+**Action:** Always strictly memoize all callbacks passed to the Excalidraw component using `useCallback` to preserve prop stability and prevent unnecessary, expensive re-renders.
diff --git a/src/client/App.tsx b/src/client/App.tsx
index 33ddb41..5fd06e8 100644
--- a/src/client/App.tsx
+++ b/src/client/App.tsx
@@ -307,6 +307,10 @@ function PrivateApp() {
[],
);
+ const handleExcalidrawAPI = useCallback((api: ExcalidrawImperativeAPI) => {
+ excalidrawApiRef.current = api;
+ }, []);
+
return (
{toastMessage && (
@@ -332,9 +336,7 @@ function PrivateApp() {
onSceneChange={handleSceneChange}
onSelectionStateChange={handleCodeBlockSelectionChange}
onEditorActivity={scheduleThemeTokenSync}
- onExcalidrawAPI={(api) => {
- excalidrawApiRef.current = api;
- }}
+ onExcalidrawAPI={handleExcalidrawAPI}
renderEmbeddable={renderCodeBlockEmbeddable}
/>
{
+ onEditorActivity();
+ onSceneChange(sceneFromEditor(elements, appState, files));
+ onSelectionStateChange(getCodeBlockSelectionState(elements, appState));
+ },
+ [onEditorActivity, onSceneChange, onSelectionStateChange],
+ );
+
if (loading || !scene) {
return {error ?? "Loading..."}
;
}
@@ -67,13 +81,7 @@ export const EditorCanvas = memo(function EditorCanvas({
initialData={sceneToInitialData(scene)}
excalidrawAPI={onExcalidrawAPI}
renderEmbeddable={renderEmbeddable}
- onChange={(elements, appState, files) => {
- onEditorActivity();
- onSceneChange(sceneFromEditor(elements, appState, files));
- onSelectionStateChange(
- getCodeBlockSelectionState(elements, appState),
- );
- }}
+ onChange={handleChange}
/>
);