diff --git a/studio/frontend/src/features/rag/components/preview-pdf-view.tsx b/studio/frontend/src/features/rag/components/preview-pdf-view.tsx index 7786792d26..fa2d1b5d39 100644 --- a/studio/frontend/src/features/rag/components/preview-pdf-view.tsx +++ b/studio/frontend/src/features/rag/components/preview-pdf-view.tsx @@ -252,6 +252,8 @@ export const PreviewPdfView: FC = ({ target, file }) => { const [searchTerm, setSearchTerm] = useState(""); const [copied, setCopied] = useState(false); const containerRef = useRef(null); + const observerRef = useRef(null); + const resizeTimeoutRef = useRef(null); const lastMeasuredWidthRef = useRef(null); const lastResetKeyRef = useRef(null); const [width, setWidth] = useState(null); @@ -280,35 +282,54 @@ export const PreviewPdfView: FC = ({ target, file }) => { setCopied(false); }, [resetKey, target.targetPage]); - useEffect(() => { + const measureWidth = useCallback(() => { const el = containerRef.current; if (!el) { return; } - let timeoutId: number | null = null; - const updateWidth = () => { - const next = Math.max(MIN_PDF_WIDTH, el.clientWidth - PDF_BODY_GUTTER_PX); - if (lastMeasuredWidthRef.current === next) { + const next = Math.max(MIN_PDF_WIDTH, el.clientWidth - PDF_BODY_GUTTER_PX); + if (lastMeasuredWidthRef.current === next) { + return; + } + lastMeasuredWidthRef.current = next; + setWidth(next); + }, []); + + // Callback ref instead of useRef + mount effect: the scroll container + // lives INSIDE , so it only enters the DOM after the PDF + // loads. Attaching the ResizeObserver the instant the node mounts + // (rather than on the component's mount effect, when the node is still + // absent) is what keeps the main page from rendering at width 0 — the + // thin white strip regression. + const attachContainer = useCallback( + (node: HTMLDivElement | null) => { + if (observerRef.current) { + observerRef.current.disconnect(); + observerRef.current = null; + } + if (resizeTimeoutRef.current !== null) { + window.clearTimeout(resizeTimeoutRef.current); + resizeTimeoutRef.current = null; + } + containerRef.current = node; + if (!node) { return; } - lastMeasuredWidthRef.current = next; - setWidth(next); - }; - updateWidth(); - const observer = new ResizeObserver(() => { - if (timeoutId !== null) { - window.clearTimeout(timeoutId); - } - timeoutId = window.setTimeout(updateWidth, RESIZE_DEBOUNCE_MS); - }); - observer.observe(el); - return () => { - observer.disconnect(); - if (timeoutId !== null) { - window.clearTimeout(timeoutId); - } - }; - }, []); + measureWidth(); + const observer = new ResizeObserver(() => { + if (resizeTimeoutRef.current !== null) { + window.clearTimeout(resizeTimeoutRef.current); + } + resizeTimeoutRef.current = window.setTimeout( + measureWidth, + RESIZE_DEBOUNCE_MS, + ); + }); + observer.observe(node); + observerRef.current = observer; + }, + [measureWidth], + ); useEffect(() => { if (!copied) { @@ -531,7 +552,7 @@ export const PreviewPdfView: FC = ({ target, file }) => { ))}