Adds a dual-mode Typst preview: a new "Live (browser)" mode compiles and
renders Typst documents entirely in-browser using typst.ts WASM (28 MB
compiler + 1 MB renderer). The existing server-side PDF mode is preserved
and selectable via a new "Preview mode" section in the recompile dropdown,
visible only for Typst projects.
Architecture:
- Web Worker (typst-preview-worker.ts) runs the WASM compiler; queues
compile requests so only the latest compile runs after each keypress
- TypstWasmPreview component initialises the renderer on the main thread,
listens to changedAt from the compile context, debounces at 400 ms, and
renders SVG into a container div via renderToSvg
- typstPreviewMode ('wasm'|'pdf') is persisted per-project in localStorage
- isTypstProject, changedAt, typstPreviewMode, setTypstPreviewMode are
exposed through both LocalCompileContext and DetachCompileContext
- Fonts loaded from jsDelivr CDN (text subset only) on first use
- Phase 1: single-file Typst only (no #include, no images)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Apply the same CSS inversion filter to the HTML iframe as is already
applied to pdfjs PDF pages, so Quarto RevealJS presentations respect
the dark mode toggle. Also show the theme button for HTML outputs and
relax the darkModePdf condition to activate for iframes regardless of
the pdfViewer setting.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Set scale synchronously on pagesinit to prevent 1.333 DPI flash
PDF.js resets its internal scale to 1.0 when setDocument() is called,
causing pages to momentarily render at the default 96/72 DPI scale
(1.333) before the React restore effect can apply the correct value.
Setting currentScaleValue directly in the pagesinit handler eliminates
this one-frame wrong-scale flash.
* Override .page display to block to prevent horizontal jump on recompile
Overleaf's global .loading class sets display:inline-flex, which
collides with PDF.js's transient 'loading' class on .page elements.
When the loading class is applied, inline-flex breaks margin:auto
centering, causing the page to jump horizontally. Forcing display:block
at higher specificity prevents the global rule from taking effect.
* Fix scrollToPosition offset using marginTop instead of borderWidth
scrollPageIntoView aligns the page content edge with the container top,
leaving scrollTop equal to the page's top margin (12px) rather than 0.
The previous correction used borderWidth (effectively 0) so the margin
offset was never compensated. Using marginTop scrolls back the correct
amount so the margin above the first page is visible.
* Prevent PDF viewer collapsing during recompile by preserving height
When setDocument() is called with a new PDF, _resetView() synchronously
clears all page elements, briefly collapsing the .pdfViewer div to the
viewport height. This produces a visible flicker before pagesinit fires
and pages are re-added.
Fix: record the current height and pin it as min-height on the .pdfViewer
element before calling setDocument(). A one-shot pagesinit listener
removes the constraint once the new pages are initialised at the correct
scale, by which point the element is already at its correct final height.
* Suppress PDF.js page-level loading spinner in Overleaf viewer
The PDF.js loadingIcon/loading classes briefly add a ::after pseudo-element
with display:block and contain:strict to each page div. Overleaf has its
own loading state UI so the spinner is redundant, and its activation was
the root cause of the shifts 4-5 height oscillation (the display change
broke CSS margin collapse on .pdfViewer, adding 2x page margins to its
computed height).
The display:block rule already added to .page prevents the direct cause
(Overleaf's .loading{display:inline-flex} colliding with the PDF.js class).
This rule makes the intent explicit by zeroing the ::after entirely.
* Wrap PDF setDocument in startViewTransition
---------
Co-authored-by: Brian Gough <brian.gough@overleaf.com>
GitOrigin-RevId: 353ab865de3c7872363a61592d86390dfc34dacc