From a0d72d1776a49ee7ace71999fafc716cbf28cd09 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 15 Jun 2026 20:46:54 +0000 Subject: [PATCH] perf(cuesheet): add result capture + candidate lazy-autosize (Exp B, under measurement) - Scaffold: snapshot()/getResults() so the benchmark returns structured metrics for automated before/after capture (PERF-METRICS). - Candidate Exp B: defer AutoTextarea autosize() off the mount path to focus-time, removing the per-row forced reflows during scroll. Under evaluation against the metrics. --- .../input/auto-textarea/AutoTextarea.tsx | 43 ++++++++++++++----- .../devtools/cuesheet-metrics/benchmark.ts | 9 ++-- .../devtools/cuesheet-metrics/perfStore.ts | 41 +++++++++++++----- .../cuesheet-metrics/useCuesheetPerf.ts | 3 +- 4 files changed, 70 insertions(+), 26 deletions(-) diff --git a/apps/client/src/common/components/input/auto-textarea/AutoTextarea.tsx b/apps/client/src/common/components/input/auto-textarea/AutoTextarea.tsx index e0f91039c..1c52a8126 100644 --- a/apps/client/src/common/components/input/auto-textarea/AutoTextarea.tsx +++ b/apps/client/src/common/components/input/auto-textarea/AutoTextarea.tsx @@ -1,6 +1,6 @@ // @ts-expect-error no types from library import autosize from 'autosize/dist/autosize'; -import { RefObject, useEffect } from 'react'; +import { FocusEvent, RefObject, useCallback, useEffect } from 'react'; import { timeSync } from '../../../devtools/cuesheet-metrics/usePerfMark'; // PERF-METRICS import Textarea, { type TextareaProps } from '../textarea/Textarea'; @@ -10,18 +10,41 @@ interface AutoTextAreaProps extends TextareaProps { } /** - * A textarea that automatically resizes based on its content + * A textarea that automatically resizes based on its content. + * + * `autosize()` forces a synchronous reflow (reads scrollHeight) and installs a MutationObserver. + * Doing that on mount makes it expensive when many of these are mounted at once (e.g. virtualised + * table rows during scroll), so we only attach autosize while the field is focused for editing and + * keep it in sync with the value during that time. */ -export function AutoTextarea({ value, inputref, ...textAreaProps }: AutoTextAreaProps) { - // when the value changes, we use the ref to reapply autosize +export function AutoTextarea({ value, inputref, onFocus, onBlur, ...textAreaProps }: AutoTextAreaProps) { + const handleFocus = useCallback( + (event: FocusEvent) => { + timeSync('cell.autosize', () => autosize(inputref.current)); // PERF-METRICS + onFocus?.(event); + }, + [inputref, onFocus], + ); + + const handleBlur = useCallback( + (event: FocusEvent) => { + if (inputref.current) { + autosize.destroy(inputref.current); + } + onBlur?.(event); + }, + [inputref, onBlur], + ); + + // while focused, keep the height in sync as the value changes useEffect(() => { const node = inputref.current; - timeSync('cell.autosize', () => autosize(inputref.current)); // PERF-METRICS - - return () => { - autosize.destroy(node); - }; + if (node && document.activeElement === node) { + autosize(node); + } }, [inputref, value]); - return