diff --git a/services/web/frontend/js/features/typst-preview/components/typst-wasm-preview.tsx b/services/web/frontend/js/features/typst-preview/components/typst-wasm-preview.tsx index b3c0a5dada..aab70b3dc5 100644 --- a/services/web/frontend/js/features/typst-preview/components/typst-wasm-preview.tsx +++ b/services/web/frontend/js/features/typst-preview/components/typst-wasm-preview.tsx @@ -39,8 +39,12 @@ const TypstWasmPreview: FC = () => { const rendererRef = useRef(null) const isReadyRef = useRef(false) const compileTimerRef = useRef | null>(null) - // If 'compiled' arrives before renderer is ready, buffer the vector data + // If 'compiled' arrives before renderer is ready, buffer the latest vector data const pendingVectorRef = useRef(null) + // Prevent concurrent runWithSession calls which cause Rust aliasing errors + const isRenderingRef = useRef(false) + // Latest pending render while one is in progress + const renderQueueRef = useRef(null) const [status, setStatus] = useState('initializing') const [errorMsg, setErrorMsg] = useState('') @@ -52,17 +56,42 @@ const TypstWasmPreview: FC = () => { pendingVectorRef.current = vectorData return } - try { - await renderer.runWithSession(async session => { - session.manipulateData({ action: 'reset', data: vectorData }) - await renderer.renderToSvg({ renderSession: session, container }) - }) - pendingVectorRef.current = null - setStatus('ready') - setErrorMsg('') - } catch (e) { - setStatus('error') - setErrorMsg(`Render failed: ${e}`) + + // If a render is in progress, queue this data and return — + // the in-progress render will pick it up when it finishes + if (isRenderingRef.current) { + renderQueueRef.current = vectorData + return + } + + let dataToRender: Uint8Array | null = vectorData + while (dataToRender) { + isRenderingRef.current = true + const data = dataToRender + dataToRender = null + + try { + await renderer.runWithSession(async session => { + session.manipulateData({ action: 'reset', data }) + // Use session.renderToSvg (NOT renderer.renderToSvg with renderSession) + // to avoid double-aliasing the same Rust object inside runWithSession + await session.renderToSvg({ container }) + }) + pendingVectorRef.current = null + setStatus('ready') + setErrorMsg('') + } catch (e) { + setStatus('error') + setErrorMsg(`Render failed: ${e}`) + } finally { + isRenderingRef.current = false + } + + // Pick up any compile result that arrived while we were rendering + if (renderQueueRef.current) { + dataToRender = renderQueueRef.current + renderQueueRef.current = null + } } }, [])