Math & diagram exporters: block-package mappings, typed errors, ExportImage contract, email export - #2961
Math & diagram exporters: block-package mappings, typed errors, ExportImage contract, email export#2961YousefED wants to merge 18 commits into
Conversation
…, ExportImage contract, email export
- Move the math/diagram exporter mappings out of the (GPL) xl-* exporter
packages into the (MPL) block packages as subpath exports:
@blocknote/{math,diagram}-block/{docx,odt,pdf,email}-exporter. The xl
exporters no longer ship math/diagram defaults.
- Expected failures are typed results: invalid LaTeX/Mermaid is caught at
the lowest adapter around the throwing library and returned as
{ error: string }, propagating through the type system. Placeholders
render the source's first line plus the typed message - never a caught
exception's message. Environment problems still throw, naming the
option to pass (renderDiagram / rasterize).
- ExportImage (bytes + mime + display dimensions) is the image contract
between renderers, rasterizers, deliveries and mappings; rasterization
scale is owned by the rasterizer implementation.
- Email exporter support for math & diagrams: data-URL images by
default, createCIDImageDelivery() for nodemailer-style inline
attachments. PDF inline math rasterizes at asset resolution. Markdown
exports math as $...$ / $$...$$.
- Fix WebKit rasterizing Mermaid SVGs letterboxed at half size (its
intrinsic sizing honors Mermaid's inline max-width style over the
explicit width/height attributes - strip the style).
- Tests: per-module matrices (valid/invalid/capability/empty/CID) in
node with stubs; colocated .browser.test files for the browser-only
implementations; a browser e2e exporting the complete shared test
document through the real exporters with visual baselines (email
captured in window-sized pages, each PDF page rendered via pdf.js -
element screenshots blank out below the tester iframe's fold, so tall
captures must be paged).
- Docs for the export formats and interoperability examples updated;
xl-pdf-exporter tests run without jsdom; stale pdf-image snapshot
experiments and their dependencies removed.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis change adds package-specific math and diagram exporters for DOCX, ODT, PDF, and email output. It adds exporter localization, image contracts, Markdown math serialization, browser rendering tests, screenshot utilities, updated examples, and revised exporter package boundaries. ChangesExporter foundations
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…ckfile - Capture the email export as one full-resolution baseline instead of page slices, and PDF pages at their natural size: grow the tester iframe past the content and neutralize the harness's fit-to-window scale transform during the capture (screenshotFull). A harness DOM change fails the baseline dimension check loudly. - Ignore pdfjs-dist's optional @napi-rs/canvas dependency workspace-wide: it exists for Node-side rendering, which we never do - pdf.js only runs in the browser suite, where the browser is the canvas. - Testing skill: document the -u-after-filters requirement (before the filters it swallows them and the whole suite runs in update mode), the full-resolution capture pattern, and that end-to-end/ hosts browser integration tests beyond UI-interaction e2e.
- screenshotFull moves to tests/src/utils: grows the tester iframe past the content and neutralizes the harness's fit-to-window scale transform during the capture - the same mechanism upstream Vitest adopted in vitest-dev/vitest#9745 (milestone 5.0.0) to fix #9124 and #9363, so the util can be deleted once vite-plus ships it. - New screenshotFull.test.tsx guards the harness-internals dependency on synthetic striped content: a plain capture blanks below the ~712px iframe fold, a viewport-only capture downscales to ~0.14x - the 600x2000 baselines prove the full-resolution render. - static.test.tsx: document that scale: "css" is load-bearing - dropping it pushes the chromium equality diff past the pixel budget under the iframe's scale transform.
| import { createDiagramBlockMapping } from "@blocknote/diagram-block/docx-exporter"; | ||
|
|
||
| const exporter = new DOCXExporter(editor.schema, { | ||
| ...docxDefaultSchemaMappings, |
There was a problem hiding this comment.
where is this imported from?
| import { createDiagramBlockMapping } from "@blocknote/diagram-block/email-exporter"; | ||
|
|
||
| const exporter = new ReactEmailExporter(editor.schema, { | ||
| ...reactEmailDefaultSchemaMappings, |
… i18n - ExporterOptions gains `dictionary` (a core locale or an editor dictionary); Exporter exposes the `exporter` string section with English defaults. Mappings already receive the exporter, so every render site reads from it. - Core Dictionary gains an `exporter` section (open_file, open_video_file, open_audio_file) - translated in all 24 locales, and unified on "Open video"/"Open audio" wording across exporters (docx/ odt snapshots regenerated). - math-block/diagram-block own their exporter strings: their locales gain an `exporter` sub-section (invalid_formula/invalid_diagram templates, function-valued like the core dictionary) with getMathExporterDictionary/getDiagramExporterDictionary reading them from the exporter's dictionary - the same shape hosts merge into editor dictionaries, bundled-English fallback. - All hardcoded literals replaced (10 sites across the four exporters, 8 math/diagram modules); dictionary tests prove the seams (German file links, custom diagram placeholder); export docs document the option.
…sufficient
- mathjax-full ships CommonJS and vite's interop can resolve the
default TexError import as a { default: class } namespace, making the
instanceof boundary throw on the first invalid formula. isTexError
resolves the constructor defensively; the e2e document now includes an
invalid formula (a structural error - MathJax's noundefined package
renders unknown commands as text, not errors), which exercises the
real error class through vite's bundling and would have caught this.
- shared's build task now declares its dist output - without it the
cache replayed nothing on hits, leaving consumers to type-check
against missing or stale declarations (the 'pnpm build' failure).
- The playground's build-mode diagram-block alias points at src/: the
prefix replace bypasses the exports map, so the package root broke
every subpath import in production builds.
- Testing skill: -u only rewrites baselines whose comparison fails;
changes within the 2% tolerance leave baselines silently stale -
delete the file to force a fresh capture.
Only ParseError messages (invalid user LaTeX) become typed errors safe to render to readers; any other throw is a bug and propagates. ParseError is read off the same katex object whose renderToString just ran, so unlike a separately imported class it can't diverge under bundler interop.
Declaring @blocknote/shared in an example's .bnexample.json dependencies now emits the @shared vite alias and tsconfig paths into its generated configs (the package is private, so it only resolves inside the monorepo - which is also why the previous package-name import never worked standalone). The tests browser config is down to the single @shared alias all consumers use. Also syncs the custom-code-block example's .bnexample.json with its hand-edited shiki version pins, so gen stops reverting them.
- The ODT math/diagram mappings typed their exporter parameter as ODTExporter, but mapping signatures are contravariant in it - a function requiring the subclass isn't assignable to the mapping type, which surfaced once the interoperability example spread these mappings in. They now take the base Exporter and cast internally (only the ODTExporter ever invokes them). - The playground type-checks ../examples but had no paths mapping for the @shared alias the suggestion-gallery example uses.
|
…r HTML-escaped attribute decoding. The order of decoding has been adjusted to prevent double-unescaping of sequences.
There was a problem hiding this comment.
Actionable comments posted: 12
🧹 Nitpick comments (20)
shared/util/odtTestUtil.ts (1)
22-27: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winModel required ZIP entries without unchecked assertions.
as FileEntryandas FileEntry[]hide missing or non-file entry states.expect(...).toBeDefined()does not narrow TypeScript types. Use a checked helper or type guard that throws a clear test-invariant error beforegetData.As per coding guidelines, avoid “casts that hide a case a caller should handle.”
Also applies to: 38-40
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@shared/util/odtTestUtil.ts` around lines 22 - 27, Replace the unchecked FileEntry assertions in the styles.xml and content.xml lookup logic with a checked helper or type guard that verifies each matching ZIP entry exists and is a file, throwing a clear test-invariant error otherwise. Ensure the narrowed FileEntry values are validated before getData is called, including the additional affected lookup.Source: Coding guidelines
tests/src/end-to-end/exporters/exporterImages.test.tsx (2)
45-63: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winUse a schema that declares the tested extension blocks.
This test declares only
defaultBlockSpecsbut suppliesmath,diagram, andinlineMathfixtures. Theas anycasts then suppress verification of the schema, mappings, and export inputs. Define the test schema with the math and diagram specs, and type the fixtures and mappings from that schema.As per coding guidelines, use the type system so unsupported states surface at compile time.
Also applies to: 93-106, 150-173
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/src/end-to-end/exporters/exporterImages.test.tsx` around lines 45 - 63, Update the schema factory to include the math and diagram block specs, then derive the fixture and exporter mapping types from that schema instead of using as any. Apply the same typed schema and mappings to the fixtures in the affected sections, including inlineMath, so unsupported blocks or invalid inputs are caught at compile time.Source: Coding guidelines
178-200: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReplace untyped PDF-tree inspection with narrowed node types.
anymakescollectImagesaccept arbitrary objects and bypasses React and react-pdf element contracts. UseReactNodeplus an element type guard for image props. Keep the PDF render input typed as the expected document element.As per coding guidelines, avoid
anyand casts that hide a case a caller should handle.Also applies to: 227-230
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/src/end-to-end/exporters/exporterImages.test.tsx` around lines 178 - 200, Replace the any-based traversal in collectImages with ReactNode narrowing and a React/react-pdf element type guard that safely identifies IMAGE nodes and accesses typed image props. Remove the any cast from the pdf render call and pass transformed as the expected document element type, handling unsupported node shapes through the existing traversal guards.Source: Coding guidelines
packages/core/src/exporter/Exporter.test.ts (1)
12-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse concrete exporter generic types and localize negative-case casts.
Exporter<any, ...>,StyledText<any>, and the mappingas anycast disable contract checking. Use concrete schema types and typed fixtures. Keep any unavoidable cast only at the intentionally invalid"math","inlineMath", and missing-"bold"mapping test inputs.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/exporter/Exporter.test.ts` around lines 12 - 16, Replace the any-based generic parameters and mapping fixture cast in TestExporter with the concrete schema, block, inline-content, style, and output types used by Exporter, and type the StyledText fixtures accordingly. Keep casts localized only on the intentionally invalid “math”, “inlineMath”, and missing-“bold” mapping test inputs.Source: Coding guidelines
packages/math-block/src/docx-exporter/docxExporter.test.ts (2)
25-42: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUse function declarations for named test helpers.
Change
getZIPEntryContentandprettifyfrom arrow-function values tofunctiondeclarations.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/math-block/src/docx-exporter/docxExporter.test.ts` around lines 25 - 42, Convert the named test helpers getZIPEntryContent and prettify from arrow-function assignments to function declarations, preserving their parameters, return behavior, and existing implementation logic.Source: Coding guidelines
114-133: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winType the invalid-LaTeX fixture against the exporter schema.
Add
createReactMathBlockSpec()andcreateReactInlineMathSpec()to the test schema. Type the fixture asBlock<typeof schema.blockSchema, typeof schema.inlineContentSchema, typeof schema.styleSchema>[]instead of usingas any.toDocxJsDocumentacceptsBlock<B, I, S>[], so the cast suppresses validation formathandinlineMath.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/math-block/src/docx-exporter/docxExporter.test.ts` around lines 114 - 133, Add createReactMathBlockSpec() and createReactInlineMathSpec() to the test schema, then type the invalid-LaTeX fixture passed to exporter.toDocxJsDocument as Block<typeof schema.blockSchema, typeof schema.inlineContentSchema, typeof schema.styleSchema>[]; remove the as any cast so math and inlineMath are validated against the exporter schema.Source: Coding guidelines
packages/math-block/src/exporterHelpers/latexToMathML.ts (1)
8-11: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse explicit result discriminators for export results.
Return
{ type: "success"; mathML: string } | { type: "invalid-latex"; error: string }fromlatexToMathML. Apply the same pattern tolatexToDocxEquation, then branch ontypein the DOCX and ODT mappings with exhaustive switches.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/math-block/src/exporterHelpers/latexToMathML.ts` around lines 8 - 11, Update latexToMathML and latexToDocxEquation to return explicitly discriminated results: { type: "success"; mathML: string } or { type: "invalid-latex"; error: string }. Revise the DOCX and ODT mappings to branch on each result’s type using exhaustive switches, preserving current success and invalid-LaTeX behavior.Source: Coding guidelines
packages/dev-scripts/examples/template-react/tsconfig.json.template.tsx (1)
3-3: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPrefer a function declaration for the named template factory.
Use
function template(project: Project) {}instead of assigning an arrow function totemplate. Update the closing});to close the returned object and the function.As per coding guidelines: “Prefer
function name() {}declarations overconst name = () => {}for named functions.”Proposed refactor
-const template = (project: Project) => ({ +function template(project: Project) { + return { ... -}); + }; +}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/dev-scripts/examples/template-react/tsconfig.json.template.tsx` at line 3, Convert the named template factory from the const-assigned arrow function to a function declaration named template, while preserving the Project parameter type and returned object. Adjust the closing syntax so it closes the object return and function body correctly.Source: Coding guidelines
packages/math-block/src/block/helpers/render/MathBlockPreviewWithPopup.tsx (1)
13-16: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUse function declarations for the named exported components.
packages/math-block/src/block/helpers/render/MathBlockPreviewWithPopup.tsx#L13-L16: ChangeMathBlockPreviewWithPopupto an exported function declaration.packages/math-block/src/block/helpers/toExternalHTML/BlockMathMLElement.tsx#L8-L11: ChangeBlockMathMLElementto an exported function declaration.As per coding guidelines, “Prefer
function name() {}declarations overconst name = () => {}for named functions.”🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/math-block/src/block/helpers/render/MathBlockPreviewWithPopup.tsx` around lines 13 - 16, Convert the named exported component MathBlockPreviewWithPopup in packages/math-block/src/block/helpers/render/MathBlockPreviewWithPopup.tsx:13-16 to an exported function declaration, preserving its props and behavior. Apply the same conversion to BlockMathMLElement in packages/math-block/src/block/helpers/toExternalHTML/BlockMathMLElement.tsx:8-11; no other changes are needed.Source: Coding guidelines
packages/math-block/src/odt-exporter/odtExporter.test.ts (1)
68-68: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse a typed schema-backed fixture instead of
as any.Register the math specs in the test schema and convert the partial fixture with
partialBlocksToBlocksForTestingbefore callingtoODTDocument. This preserves type checking for block and inline math content.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/math-block/src/odt-exporter/odtExporter.test.ts` at line 68, Replace the `as any` fixture cast in the ODT exporter test with a typed schema-backed fixture: register the math specs in the test schema, then convert the partial fixture using `partialBlocksToBlocksForTesting` before passing it to `toODTDocument`, preserving type checking for block and inline math content.Source: Coding guidelines
packages/math-block/src/odt-exporter/index.ts (1)
69-69: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAlign the
errorTextparameter order with the sibling exporters.This
errorTexttakes(source, exporter). TheerrorTexthelpers inpackages/math-block/src/docx-exporter/index.ts,packages/math-block/src/email-exporter/index.tsx, andpackages/math-block/src/pdf-exporter/index.tsxall take(exporter, source). Both parameters are structurally distinct, so a transposed call would fail to compile, but the inconsistency slows reading and invites mistakes during future edits.Swap the parameters here to match the other three exporters.
♻️ Proposed change
-function errorText(source: string, exporter: ODTExporter<any, any, any>) { +function errorText(exporter: ODTExporter<any, any, any>, source: string) {Update the two call sites at lines 120 and 176:
return createElement("text:p", null, errorText(odtExporter, source)); // ... return errorText(odtExporter, source);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/math-block/src/odt-exporter/index.ts` at line 69, Update the errorText helper in the ODT exporter to accept parameters in the order (exporter, source), matching the sibling exporter implementations. Adjust both errorText call sites in the ODT export flow to pass odtExporter first and source second.packages/math-block/src/docx-exporter/index.ts (1)
33-39: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winValidate the imported equation without an
anycast.docx@9.6.1drops XML declarations and comments, so they do not shiftimported.root[0].mathml2omml@0.5.0produces anm:oMathroot for valid MathML. Use a narrow typed adapter that rejects a missing child or a child with an unexpected element name before returning it.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/math-block/src/docx-exporter/index.ts` around lines 33 - 39, Update the imported equation handling in the fromXmlString flow to remove the any cast and use a narrowly typed adapter for imported.root. Validate that the first child exists and has the expected m:oMath element name, rejecting missing or unexpected children before returning the equation.packages/math-block/src/email-exporter/emailExporter.test.tsx (1)
25-28: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueNamed test helpers use arrow consts. Both files declare named functions as
const name = () => {}, which the coding guidelines disallow.
packages/math-block/src/email-exporter/emailExporter.test.tsx#L25-L28: convertcreateExporterto afunctiondeclaration.packages/math-block/src/pdf-exporter/pdfExporter.test.tsx#L23-L28: convertrasterizeto anasync functiondeclaration.As per coding guidelines: "Prefer
function name() {}declarations overconst name = () => {}for named functions".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/math-block/src/email-exporter/emailExporter.test.tsx` around lines 25 - 28, Replace the named arrow-const helpers with function declarations: convert createExporter in packages/math-block/src/email-exporter/emailExporter.test.tsx (lines 25-28) to a function declaration, and convert rasterize in packages/math-block/src/pdf-exporter/pdfExporter.test.tsx (lines 23-28) to an async function declaration, preserving their existing parameters and behavior.Source: Coding guidelines
packages/math-block/src/exporterHelpers/renderMathToImage.ts (1)
121-137: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueValidate the node kind before you read attributes.
Line 121 takes
firstChild(node). If the conversion output has no child element,documentAdaptor.getAttributeat lines 122-123 receivesnulland throws aTypeErrorinstead of the descriptive"No SVG found in MathJax output"error. Move thekindcheck ahead of the attribute reads.Also note that lines 136-137 set the ceiled dimensions as intrinsic size, while lines 143-144 return the unrounded values. Renderers then scale the SVG by a sub-pixel factor.
♻️ Proposed reorder
const svgNode = documentAdaptor.firstChild(node as any) as any; + if (!svgNode || documentAdaptor.kind(svgNode) !== "svg") { + throw new Error("No SVG found in MathJax output"); + } const widthEx = parseFloat(documentAdaptor.getAttribute(svgNode, "width")); const heightEx = parseFloat(documentAdaptor.getAttribute(svgNode, "height")); - if ( - documentAdaptor.kind(svgNode) !== "svg" || - isNaN(widthEx) || - isNaN(heightEx) - ) { + if (isNaN(widthEx) || isNaN(heightEx)) { throw new Error("No SVG found in MathJax output"); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/math-block/src/exporterHelpers/renderMathToImage.ts` around lines 121 - 137, Update the svgNode validation to check documentAdaptor.kind(svgNode) before calling getAttribute, so missing children produce the descriptive SVG error. In the dimension-return path, use the same ceiled width and height assigned to the SVG attributes so intrinsic and reported sizes remain consistent.packages/math-block/src/pdf-exporter/index.tsx (1)
82-98: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueAlign the LaTeX package sets.
latexToMathSVGuses curatedTEX_PACKAGES, while@react-pdf/math@2.0.1uses MathJaxAllPackages. The validator can reject formulas that<Math>accepts. Use the same package configuration, or document the supported subset.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/math-block/src/pdf-exporter/index.tsx` around lines 82 - 98, Align the validation configuration in the math rendering flow around latexToMathSVG with the package set used by the Math component, preferably by configuring both to use the same package collection. Ensure formulas accepted by <Math> are not rejected by validation; if identical configuration is unavailable, explicitly document and enforce the supported LaTeX subset.packages/diagram-block/src/docx-exporter/index.ts (2)
78-85: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the shared renderer-resolution logic. Both mappings duplicate the same renderer fallback, the same
typeof documentcheck, and a byte-identical error message. The strings will drift as more exporter targets are added in this stack.
packages/diagram-block/src/docx-exporter/index.ts#L78-L85: replace the inline resolution with a call to a shared helper, for exampleresolveRenderDiagram(options?.renderDiagram)exported from../helpers/renderDiagramToImage.js.packages/diagram-block/src/email-exporter/index.tsx#L67-L74: call the same helper instead of repeating the check and the error text.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/diagram-block/src/docx-exporter/index.ts` around lines 78 - 85, The renderer fallback and browser validation are duplicated in createDiagramBlockMapping-related logic. Add or reuse a shared resolveRenderDiagram helper exported from ../helpers/renderDiagramToImage.js, then replace the inline logic at packages/diagram-block/src/docx-exporter/index.ts:78-85 and packages/diagram-block/src/email-exporter/index.tsx:67-74 with calls passing options?.renderDiagram; preserve the shared browser check and identical error message in the helper.
96-108: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReplace the unchecked MIME-type cast at line 103.
ExportImage.mimeTypeisstring, so the cast does not hide a current literal-union member. It still asserts that every MIME type is a supported key and prevents compiler help when the mapping changes. Use a lookup that returnsstring | undefinedand retain the existing error guard.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/diagram-block/src/docx-exporter/index.ts` around lines 96 - 108, Update the imageType lookup in the DOCX image-export flow to avoid casting result.image.mimeType to keyof typeof imageTypes; use a type-safe lookup that yields string | undefined for arbitrary MIME strings. Preserve the existing imageTypes mapping and !imageType error guard.Source: Coding guidelines
packages/diagram-block/src/exporterTestUtil.ts (1)
5-15: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove unchecked casts from the exporter test utility.
Type
diagramDocumentfromDiagramBlockConfigand the default inline/style schemas. Add the diagram block to each test schema so the fixture satisfiesBlock<B, I, S>[].If the matching ZIP entry is absent or
entry.directoryis true, return"". Otherwise callgetDataafter the directory check narrows the entry toFileEntry.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/diagram-block/src/exporterTestUtil.ts` around lines 5 - 15, Replace the unchecked cast on diagramDocument with the DiagramBlockConfig type and the default inline/style schemas, adding the diagram block to each test schema so it satisfies Block<B, I, S>[]. In the ZIP export lookup, return an empty string when the matching entry is absent or directory-valued; only call getData after the directory check narrows it to FileEntry.Source: Coding guidelines
packages/diagram-block/src/odt-exporter/index.ts (1)
21-25: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winMake the ODT exporter contract explicit.
BlockMappingaccepts a genericExporter, but this mapping uses ODT-onlyregisterStyleandregisterPicturethrough an unchecked cast. Add a narrow ODT adapter that validates these capabilities before calling ODT helpers. Otherwise, reuse by a non-ODT exporter can fail at runtime.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/diagram-block/src/odt-exporter/index.ts` around lines 21 - 25, Make the ODT exporter contract explicit around the DiagramBlock mapping: add a narrow adapter that accepts the generic Exporter, validates the required registerStyle and registerPicture capabilities, and only then delegates to the ODT helpers. Replace the unchecked cast in the BlockMapping setup with this adapter so non-ODT exporters fail through the validation path rather than at helper invocation.Source: Coding guidelines
packages/diagram-block/src/odt-exporter/odtExporter.test.ts (1)
22-33: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRegister the
diagramblock spec in the test schema.
BlockMappingderives its block keys and types from the schema.defaultBlockSpecsexcludesdiagram, somappings as anysuppresses type checking for this mapping. Adddiagram: createReactDiagramBlockSpec()toblockSpecsand remove the cast.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/diagram-block/src/odt-exporter/odtExporter.test.ts` around lines 22 - 33, Add the diagram block spec via createReactDiagramBlockSpec() to the blockSpecs passed to BlockNoteSchema.create in createExporter, and remove the mappings as any cast so the diagram mapping is type-checked against the schema.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.claude/skills/testing-skill/SKILL.md:
- Around line 50-52: Update the command fence containing tests/docker-run.sh to
specify bash as its language identifier, preserving the existing command
content.
In `@docs/content/docs/features/export/docx.mdx`:
- Around line 146-149: Update the renderDiagram example’s success return to
match the RenderDiagram result shape: return an image object containing the
rendered image bytes in data, its MIME type, and width and height. Ensure the
returned data is embeddable by the DOCX mapping, while preserving the existing
renderer flow.
In `@docs/content/docs/features/export/email.mdx`:
- Line 144: Update the export mapping descriptions to state that invalid LaTeX
or Mermaid sources render a localized invalid-source placeholder, not the
parser’s error message. Apply the same wording in
docs/content/docs/features/export/email.mdx at lines 144-144 and
docs/content/docs/features/export/docx.mdx at lines 138-138.
In `@packages/core/src/api/exporters/markdown/htmlToMarkdown.ts`:
- Around line 205-207: Update serializeMathBlock so ctx.indent prefixes every
generated block-math line: the opening delimiter, each LaTeX line, and the
closing delimiter. Preserve the existing spacing and trailing newline while
ensuring multiline LaTeX remains nested within lists or blockquotes.
In `@packages/diagram-block/src/docx-exporter/index.ts`:
- Around line 110-122: Update the DOCX image transformation in the diagram
export flow to clamp oversized images to the established MAX_WIDTH_PIXELS value,
scaling height by the same factor to preserve aspect ratio. Keep intrinsic
dimensions unchanged when the image width is within the limit, and reuse the
existing email-mapping width constant or helper rather than introducing a
duplicate.
In `@packages/diagram-block/src/helpers/renderDiagramToImage.ts`:
- Around line 16-18: Update the RenderDiagram type to an explicit discriminated
union with success and invalid-source variants, then revise all four exporter
mappings, test stubs, and browser tests to branch via exhaustive switch handling
on the discriminator. Remove result.error checks and ensure each variant is
handled explicitly.
In `@packages/diagram-block/src/i18n/dictionary.ts`:
- Around line 28-36: Update getDiagramExporterDictionary so diagram.exporter is
treated as a partial dictionary and merged field-by-field with en.exporter,
ensuring missing custom strings such as invalid_diagram fall back to the English
values while preserving provided overrides.
In `@packages/math-block/src/exporterHelpers/renderMathToImage.ts`:
- Around line 61-72: Update the MathJax document configuration in the
mathDocument initialization to provide a compileError handler that rethrows the
received error, ensuring compile failures propagate to the latexToMathSVG catch
path. Keep the existing formatError behavior unchanged.
In `@packages/math-block/src/i18n/locales/ar.ts`:
- Around line 31-33: Update the invalid_formula templates to bidi-isolate the
interpolated source with U+2068 and U+2069:
packages/math-block/src/i18n/locales/ar.ts lines 31-33,
packages/math-block/src/i18n/locales/fa.ts lines 31-33, and
packages/math-block/src/i18n/locales/he.ts lines 31-33. Preserve each locale’s
existing wording and quote placement.
In `@packages/math-block/src/odt-exporter/index.ts`:
- Around line 37-50: Cache the style registrations used by formulaFrame,
errorText, and mathBlockMapping within each ODT export instead of calling
ODTExporter.registerStyle for every occurrence. Reuse the cached style names for
identical definitions, while keeping separate styles where definitions differ,
so valid and invalid formulas do not create duplicate automatic styles.
In `@playground/tsconfig.json`:
- Around line 19-22: Update the playground TypeScript/build resolver
configuration so `@shared/`* resolves during vp build as it already does through
devAliases in vp dev. Add the matching `@shared` alias to the build alias map, or
enable resolve.tsconfigPaths, and verify an `@shared/`* import works in both
development and production builds.
In `@shared/util/browserImageTestUtil.ts`:
- Around line 20-24: Update the canvas setup in the browser image utility to
explicitly validate the result of canvas.getContext("2d") instead of using a
non-null assertion. If the context is unavailable, throw a descriptive error
before invoking context.drawImage; preserve the existing drawing behavior when a
context is returned.
---
Nitpick comments:
In `@packages/core/src/exporter/Exporter.test.ts`:
- Around line 12-16: Replace the any-based generic parameters and mapping
fixture cast in TestExporter with the concrete schema, block, inline-content,
style, and output types used by Exporter, and type the StyledText fixtures
accordingly. Keep casts localized only on the intentionally invalid “math”,
“inlineMath”, and missing-“bold” mapping test inputs.
In `@packages/dev-scripts/examples/template-react/tsconfig.json.template.tsx`:
- Line 3: Convert the named template factory from the const-assigned arrow
function to a function declaration named template, while preserving the Project
parameter type and returned object. Adjust the closing syntax so it closes the
object return and function body correctly.
In `@packages/diagram-block/src/docx-exporter/index.ts`:
- Around line 78-85: The renderer fallback and browser validation are duplicated
in createDiagramBlockMapping-related logic. Add or reuse a shared
resolveRenderDiagram helper exported from ../helpers/renderDiagramToImage.js,
then replace the inline logic at
packages/diagram-block/src/docx-exporter/index.ts:78-85 and
packages/diagram-block/src/email-exporter/index.tsx:67-74 with calls passing
options?.renderDiagram; preserve the shared browser check and identical error
message in the helper.
- Around line 96-108: Update the imageType lookup in the DOCX image-export flow
to avoid casting result.image.mimeType to keyof typeof imageTypes; use a
type-safe lookup that yields string | undefined for arbitrary MIME strings.
Preserve the existing imageTypes mapping and !imageType error guard.
In `@packages/diagram-block/src/exporterTestUtil.ts`:
- Around line 5-15: Replace the unchecked cast on diagramDocument with the
DiagramBlockConfig type and the default inline/style schemas, adding the diagram
block to each test schema so it satisfies Block<B, I, S>[]. In the ZIP export
lookup, return an empty string when the matching entry is absent or
directory-valued; only call getData after the directory check narrows it to
FileEntry.
In `@packages/diagram-block/src/odt-exporter/index.ts`:
- Around line 21-25: Make the ODT exporter contract explicit around the
DiagramBlock mapping: add a narrow adapter that accepts the generic Exporter,
validates the required registerStyle and registerPicture capabilities, and only
then delegates to the ODT helpers. Replace the unchecked cast in the
BlockMapping setup with this adapter so non-ODT exporters fail through the
validation path rather than at helper invocation.
In `@packages/diagram-block/src/odt-exporter/odtExporter.test.ts`:
- Around line 22-33: Add the diagram block spec via
createReactDiagramBlockSpec() to the blockSpecs passed to BlockNoteSchema.create
in createExporter, and remove the mappings as any cast so the diagram mapping is
type-checked against the schema.
In `@packages/math-block/src/block/helpers/render/MathBlockPreviewWithPopup.tsx`:
- Around line 13-16: Convert the named exported component
MathBlockPreviewWithPopup in
packages/math-block/src/block/helpers/render/MathBlockPreviewWithPopup.tsx:13-16
to an exported function declaration, preserving its props and behavior. Apply
the same conversion to BlockMathMLElement in
packages/math-block/src/block/helpers/toExternalHTML/BlockMathMLElement.tsx:8-11;
no other changes are needed.
In `@packages/math-block/src/docx-exporter/docxExporter.test.ts`:
- Around line 25-42: Convert the named test helpers getZIPEntryContent and
prettify from arrow-function assignments to function declarations, preserving
their parameters, return behavior, and existing implementation logic.
- Around line 114-133: Add createReactMathBlockSpec() and
createReactInlineMathSpec() to the test schema, then type the invalid-LaTeX
fixture passed to exporter.toDocxJsDocument as Block<typeof schema.blockSchema,
typeof schema.inlineContentSchema, typeof schema.styleSchema>[]; remove the as
any cast so math and inlineMath are validated against the exporter schema.
In `@packages/math-block/src/docx-exporter/index.ts`:
- Around line 33-39: Update the imported equation handling in the fromXmlString
flow to remove the any cast and use a narrowly typed adapter for imported.root.
Validate that the first child exists and has the expected m:oMath element name,
rejecting missing or unexpected children before returning the equation.
In `@packages/math-block/src/email-exporter/emailExporter.test.tsx`:
- Around line 25-28: Replace the named arrow-const helpers with function
declarations: convert createExporter in
packages/math-block/src/email-exporter/emailExporter.test.tsx (lines 25-28) to a
function declaration, and convert rasterize in
packages/math-block/src/pdf-exporter/pdfExporter.test.tsx (lines 23-28) to an
async function declaration, preserving their existing parameters and behavior.
In `@packages/math-block/src/exporterHelpers/latexToMathML.ts`:
- Around line 8-11: Update latexToMathML and latexToDocxEquation to return
explicitly discriminated results: { type: "success"; mathML: string } or { type:
"invalid-latex"; error: string }. Revise the DOCX and ODT mappings to branch on
each result’s type using exhaustive switches, preserving current success and
invalid-LaTeX behavior.
In `@packages/math-block/src/exporterHelpers/renderMathToImage.ts`:
- Around line 121-137: Update the svgNode validation to check
documentAdaptor.kind(svgNode) before calling getAttribute, so missing children
produce the descriptive SVG error. In the dimension-return path, use the same
ceiled width and height assigned to the SVG attributes so intrinsic and reported
sizes remain consistent.
In `@packages/math-block/src/odt-exporter/index.ts`:
- Line 69: Update the errorText helper in the ODT exporter to accept parameters
in the order (exporter, source), matching the sibling exporter implementations.
Adjust both errorText call sites in the ODT export flow to pass odtExporter
first and source second.
In `@packages/math-block/src/odt-exporter/odtExporter.test.ts`:
- Line 68: Replace the `as any` fixture cast in the ODT exporter test with a
typed schema-backed fixture: register the math specs in the test schema, then
convert the partial fixture using `partialBlocksToBlocksForTesting` before
passing it to `toODTDocument`, preserving type checking for block and inline
math content.
In `@packages/math-block/src/pdf-exporter/index.tsx`:
- Around line 82-98: Align the validation configuration in the math rendering
flow around latexToMathSVG with the package set used by the Math component,
preferably by configuring both to use the same package collection. Ensure
formulas accepted by <Math> are not rejected by validation; if identical
configuration is unavailable, explicitly document and enforce the supported
LaTeX subset.
In `@shared/util/odtTestUtil.ts`:
- Around line 22-27: Replace the unchecked FileEntry assertions in the
styles.xml and content.xml lookup logic with a checked helper or type guard that
verifies each matching ZIP entry exists and is a file, throwing a clear
test-invariant error otherwise. Ensure the narrowed FileEntry values are
validated before getData is called, including the additional affected lookup.
In `@tests/src/end-to-end/exporters/exporterImages.test.tsx`:
- Around line 45-63: Update the schema factory to include the math and diagram
block specs, then derive the fixture and exporter mapping types from that schema
instead of using as any. Apply the same typed schema and mappings to the
fixtures in the affected sections, including inlineMath, so unsupported blocks
or invalid inputs are caught at compile time.
- Around line 178-200: Replace the any-based traversal in collectImages with
ReactNode narrowing and a React/react-pdf element type guard that safely
identifies IMAGE nodes and accesses typed image props. Remove the any cast from
the pdf render call and pass transformed as the expected document element type,
handling unsupported node shapes through the existing traversal guards.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: bbb4a2f7-7982-4127-a5b2-503372947fc4
⛔ Files ignored due to path filters (25)
packages/math-block/src/docx-exporter/__snapshots__/withMathMappings/document.xmlis excluded by!**/__snapshots__/**packages/math-block/src/email-exporter/__snapshots__/emailExporter.test.tsx.snapis excluded by!**/*.snap,!**/__snapshots__/**packages/math-block/src/odt-exporter/__snapshots__/withMathMappings/content.xmlis excluded by!**/__snapshots__/**packages/math-block/src/odt-exporter/__snapshots__/withMathMappings/objects.xmlis excluded by!**/__snapshots__/**packages/math-block/src/odt-exporter/__snapshots__/withMathMappings/styles.xmlis excluded by!**/__snapshots__/**packages/math-block/src/pdf-exporter/__snapshots__/exampleWithMathMappings.jsxis excluded by!**/__snapshots__/**packages/xl-docx-exporter/src/docx/__snapshots__/basic/document.xmlis excluded by!**/__snapshots__/**packages/xl-email-exporter/src/react-email/__snapshots__/reactEmailExporter.test.tsx.snapis excluded by!**/*.snap,!**/__snapshots__/**packages/xl-odt-exporter/src/odt/__snapshots__/basic/content.xmlis excluded by!**/__snapshots__/**packages/xl-odt-exporter/src/odt/__snapshots__/withCustomOptions/content.xmlis excluded by!**/__snapshots__/**packages/xl-pdf-exporter/src/pdf/__snapshots__/example.jsxis excluded by!**/__snapshots__/**packages/xl-pdf-exporter/src/pdf/__snapshots__/exampleWithHeaderAndFooter.jsxis excluded by!**/__snapshots__/**pnpm-lock.yamlis excluded by!**/pnpm-lock.yamltests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/email-export-chromium-linux.pngis excluded by!**/*.pngtests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/email-export-firefox-linux.pngis excluded by!**/*.pngtests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/email-export-webkit-linux.pngis excluded by!**/*.pngtests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/pdf-export-page-1-chromium-linux.pngis excluded by!**/*.pngtests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/pdf-export-page-2-chromium-linux.pngis excluded by!**/*.pngtests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/pdf-export-page-3-chromium-linux.pngis excluded by!**/*.pngtests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/pdf-export-page-4-chromium-linux.pngis excluded by!**/*.pngtests/src/end-to-end/screenshots/__screenshots__/screenshotFull.test.tsx/screenshot-full-tall-element-chromium-linux.pngis excluded by!**/*.pngtests/src/end-to-end/screenshots/__screenshots__/screenshotFull.test.tsx/screenshot-full-tall-element-firefox-linux.pngis excluded by!**/*.pngtests/src/end-to-end/screenshots/__screenshots__/screenshotFull.test.tsx/screenshot-full-tall-element-webkit-linux.pngis excluded by!**/*.pngtests/src/unit/react/formatConversion/export/__snapshots__/markdown/inlineMath/basic.mdis excluded by!**/__snapshots__/**tests/src/unit/react/formatConversion/export/__snapshots__/markdown/math/basic.mdis excluded by!**/__snapshots__/**
📒 Files selected for processing (187)
.claude/skills/testing-skill/SKILL.mdAGENTS.mddocs/content/docs/features/blocks/code-blocks.mdxdocs/content/docs/features/export/docx.mdxdocs/content/docs/features/export/email.mdxdocs/content/docs/features/export/markdown.mdxdocs/content/docs/features/export/odt.mdxdocs/content/docs/features/export/pdf.mdxdocs/package.jsonexamples/04-theming/07-custom-code-block/.bnexample.jsonexamples/04-theming/07-custom-code-block/package.jsonexamples/05-interoperability/05-converting-blocks-to-pdf/.bnexample.jsonexamples/05-interoperability/05-converting-blocks-to-pdf/package.jsonexamples/05-interoperability/05-converting-blocks-to-pdf/src/App.tsxexamples/05-interoperability/06-converting-blocks-to-docx/.bnexample.jsonexamples/05-interoperability/06-converting-blocks-to-docx/src/App.tsxexamples/05-interoperability/07-converting-blocks-to-odt/.bnexample.jsonexamples/05-interoperability/07-converting-blocks-to-odt/src/App.tsxexamples/05-interoperability/08-converting-blocks-to-react-email/.bnexample.jsonexamples/05-interoperability/08-converting-blocks-to-react-email/package.jsonexamples/05-interoperability/08-converting-blocks-to-react-email/src/App.tsxexamples/07-collaboration/14-suggestion-gallery/src/scenarios.tsexamples/07-collaboration/14-suggestion-gallery/tsconfig.jsonexamples/07-collaboration/14-suggestion-gallery/vite.config.tspackages/code-block/package.jsonpackages/core/package.jsonpackages/core/src/api/exporters/markdown/htmlToMarkdown.tspackages/core/src/exporter/ExportImage.tspackages/core/src/exporter/Exporter.test.tspackages/core/src/exporter/Exporter.tspackages/core/src/exporter/index.tspackages/core/src/i18n/locales/ar.tspackages/core/src/i18n/locales/de.tspackages/core/src/i18n/locales/en.tspackages/core/src/i18n/locales/es.tspackages/core/src/i18n/locales/fa.tspackages/core/src/i18n/locales/fr.tspackages/core/src/i18n/locales/he.tspackages/core/src/i18n/locales/hr.tspackages/core/src/i18n/locales/is.tspackages/core/src/i18n/locales/it.tspackages/core/src/i18n/locales/ja.tspackages/core/src/i18n/locales/ko.tspackages/core/src/i18n/locales/nl.tspackages/core/src/i18n/locales/no.tspackages/core/src/i18n/locales/pl.tspackages/core/src/i18n/locales/pt.tspackages/core/src/i18n/locales/ru.tspackages/core/src/i18n/locales/sk.tspackages/core/src/i18n/locales/uk.tspackages/core/src/i18n/locales/uz.tspackages/core/src/i18n/locales/vi.tspackages/core/src/i18n/locales/zh-tw.tspackages/core/src/i18n/locales/zh.tspackages/core/src/schema/blocks/types.tspackages/core/src/schema/inlineContent/types.tspackages/dev-scripts/examples/template-react/tsconfig.json.template.tsxpackages/dev-scripts/examples/template-react/vite.config.ts.template.tsxpackages/diagram-block/package.jsonpackages/diagram-block/src/block/helpers/render/DiagramBlockPreviewWithPopup.tsxpackages/diagram-block/src/docx-exporter/docxExporter.test.tspackages/diagram-block/src/docx-exporter/index.tspackages/diagram-block/src/email-exporter/emailExporter.test.tsxpackages/diagram-block/src/email-exporter/index.tsxpackages/diagram-block/src/exporterTestUtil.tspackages/diagram-block/src/helpers/getDiagramPlainTextContent.tspackages/diagram-block/src/helpers/index.tspackages/diagram-block/src/helpers/renderDiagramToImage.browser.test.tspackages/diagram-block/src/helpers/renderDiagramToImage.tspackages/diagram-block/src/i18n/dictionary.tspackages/diagram-block/src/i18n/locales/ar.tspackages/diagram-block/src/i18n/locales/de.tspackages/diagram-block/src/i18n/locales/en.tspackages/diagram-block/src/i18n/locales/es.tspackages/diagram-block/src/i18n/locales/fa.tspackages/diagram-block/src/i18n/locales/fr.tspackages/diagram-block/src/i18n/locales/he.tspackages/diagram-block/src/i18n/locales/hr.tspackages/diagram-block/src/i18n/locales/is.tspackages/diagram-block/src/i18n/locales/it.tspackages/diagram-block/src/i18n/locales/ja.tspackages/diagram-block/src/i18n/locales/ko.tspackages/diagram-block/src/i18n/locales/nl.tspackages/diagram-block/src/i18n/locales/no.tspackages/diagram-block/src/i18n/locales/pl.tspackages/diagram-block/src/i18n/locales/pt.tspackages/diagram-block/src/i18n/locales/ru.tspackages/diagram-block/src/i18n/locales/sk.tspackages/diagram-block/src/i18n/locales/uk.tspackages/diagram-block/src/i18n/locales/uz.tspackages/diagram-block/src/i18n/locales/vi.tspackages/diagram-block/src/i18n/locales/zh-tw.tspackages/diagram-block/src/i18n/locales/zh.tspackages/diagram-block/src/odt-exporter/index.tspackages/diagram-block/src/odt-exporter/odtExporter.test.tspackages/diagram-block/src/pdf-exporter/index.tsxpackages/diagram-block/src/pdf-exporter/pdfExporter.test.tsxpackages/diagram-block/tsconfig.jsonpackages/diagram-block/vite.config.tspackages/math-block/package.jsonpackages/math-block/src/block/helpers/render/MathBlockPreviewWithPopup.tsxpackages/math-block/src/block/helpers/toExternalHTML/BlockMathMLElement.tsxpackages/math-block/src/docx-exporter/docxExporter.test.tspackages/math-block/src/docx-exporter/index.tspackages/math-block/src/email-exporter/emailExporter.test.tsxpackages/math-block/src/email-exporter/index.tsxpackages/math-block/src/exporterHelpers/latexToMathML.tspackages/math-block/src/exporterHelpers/renderMathToImage.browser.test.tspackages/math-block/src/exporterHelpers/renderMathToImage.test.tspackages/math-block/src/exporterHelpers/renderMathToImage.tspackages/math-block/src/helpers/getMathPlainTextContent.tspackages/math-block/src/helpers/index.tspackages/math-block/src/i18n/dictionary.tspackages/math-block/src/i18n/locales/ar.tspackages/math-block/src/i18n/locales/de.tspackages/math-block/src/i18n/locales/en.tspackages/math-block/src/i18n/locales/es.tspackages/math-block/src/i18n/locales/fa.tspackages/math-block/src/i18n/locales/fr.tspackages/math-block/src/i18n/locales/he.tspackages/math-block/src/i18n/locales/hr.tspackages/math-block/src/i18n/locales/is.tspackages/math-block/src/i18n/locales/it.tspackages/math-block/src/i18n/locales/ja.tspackages/math-block/src/i18n/locales/ko.tspackages/math-block/src/i18n/locales/nl.tspackages/math-block/src/i18n/locales/no.tspackages/math-block/src/i18n/locales/pl.tspackages/math-block/src/i18n/locales/pt.tspackages/math-block/src/i18n/locales/ru.tspackages/math-block/src/i18n/locales/sk.tspackages/math-block/src/i18n/locales/uk.tspackages/math-block/src/i18n/locales/uz.tspackages/math-block/src/i18n/locales/vi.tspackages/math-block/src/i18n/locales/zh-tw.tspackages/math-block/src/i18n/locales/zh.tspackages/math-block/src/inlineContent/helpers/render/MathInlinePreviewWithPopup.tsxpackages/math-block/src/inlineContent/helpers/toExternalHTML/InlineMathMLElement.tsxpackages/math-block/src/odt-exporter/index.tspackages/math-block/src/odt-exporter/odtExporter.test.tspackages/math-block/src/pdf-exporter/index.tsxpackages/math-block/src/pdf-exporter/pdfExporter.test.tsxpackages/math-block/tsconfig.jsonpackages/math-block/vite.config.tspackages/xl-docx-exporter/package.jsonpackages/xl-docx-exporter/src/diagram-block/index.tspackages/xl-docx-exporter/src/docx/defaultSchema/blocks.tspackages/xl-docx-exporter/src/docx/defaultSchema/inlinecontent.tspackages/xl-docx-exporter/src/docx/docxExporter.test.tspackages/xl-docx-exporter/src/math-block/index.tspackages/xl-docx-exporter/vite.config.tspackages/xl-email-exporter/src/react-email/defaultSchema/blocks.tsxpackages/xl-email-exporter/src/react-email/defaultSchema/inlinecontent.tsxpackages/xl-email-exporter/src/react-email/imageDelivery.test.tspackages/xl-email-exporter/src/react-email/imageDelivery.tspackages/xl-email-exporter/src/react-email/index.tspackages/xl-email-exporter/src/react-email/reactEmailExporter.test.tsxpackages/xl-odt-exporter/package.jsonpackages/xl-odt-exporter/src/diagram-block/index.tspackages/xl-odt-exporter/src/math-block/index.tsxpackages/xl-odt-exporter/src/odt/defaultSchema/blocks.tsxpackages/xl-odt-exporter/src/odt/defaultSchema/inlineContent.tsxpackages/xl-odt-exporter/src/odt/odtExporter.test.tspackages/xl-odt-exporter/vite.config.tspackages/xl-pdf-exporter/package.jsonpackages/xl-pdf-exporter/src/diagram-block/index.tsxpackages/xl-pdf-exporter/src/math-block/index.tsxpackages/xl-pdf-exporter/src/pdf/defaultSchema/blocks.tsxpackages/xl-pdf-exporter/src/pdf/defaultSchema/inlinecontent.tsxpackages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsxpackages/xl-pdf-exporter/vite.config.tsplayground/src/examples.gen.tsxplayground/tsconfig.jsonplayground/vite.config.tspnpm-workspace.yamlshared/package.jsonshared/util/browserImageTestUtil.tsshared/util/odtTestUtil.tsshared/vite.config.tstests/docker-run.shtests/package.jsontests/src/end-to-end/exporters/exporterImages.test.tsxtests/src/end-to-end/screenshots/screenshotFull.test.tsxtests/src/end-to-end/static/static.test.tsxtests/src/unit/react/formatConversion/export/exportTestInstances.tstests/src/utils/screenshotFull.tstests/vite.config.browser.ts
💤 Files with no reviewable changes (13)
- packages/math-block/src/helpers/getMathPlainTextContent.ts
- packages/math-block/src/helpers/index.ts
- packages/diagram-block/src/helpers/getDiagramPlainTextContent.ts
- packages/diagram-block/src/helpers/index.ts
- packages/xl-pdf-exporter/src/diagram-block/index.tsx
- packages/xl-odt-exporter/src/diagram-block/index.ts
- packages/xl-docx-exporter/src/math-block/index.ts
- packages/xl-docx-exporter/src/diagram-block/index.ts
- packages/xl-odt-exporter/src/math-block/index.tsx
- packages/xl-pdf-exporter/src/math-block/index.tsx
- packages/xl-docx-exporter/vite.config.ts
- packages/xl-odt-exporter/vite.config.ts
- packages/core/src/schema/inlineContent/types.ts
| ``` | ||
| docker run --rm -e RUN_IN_DOCKER=true --network host -v $(pwd)/..:/work/ -w /work/tests -it mcr.microsoft.com/playwright:v1.51.1-noble npx playwright test | ||
| bash tests/docker-run.sh -e CI=1 -- --run [filters] | ||
| ``` |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add a language identifier to the command fence.
Set the fence language to bash. This resolves MD040 and enables shell syntax highlighting.
🧰 Tools
🪛 markdownlint-cli2 (0.23.2)
[warning] 50-50: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.claude/skills/testing-skill/SKILL.md around lines 50 - 52, Update the
command fence containing tests/docker-run.sh to specify bash as its language
identifier, preserving the existing command content.
Source: Linters/SAST tools
| const renderDiagram: RenderDiagram = async (source) => { | ||
| // Render the Mermaid source to an image with your renderer of choice. | ||
| return { dataURL, width, height }; | ||
| }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Return the RenderDiagram success result shape.
Line 148 returns { dataURL, width, height }. The DOCX mapping reads result.image.data, result.image.mimeType, result.image.width, and result.image.height after it checks result.error. This example does not satisfy RenderDiagram and cannot provide an embeddable image. Return the typed success variant with an image object, image bytes, and a MIME type.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/content/docs/features/export/docx.mdx` around lines 146 - 149, Update
the renderDiagram example’s success return to match the RenderDiagram result
shape: return an image object containing the rendered image bytes in data, its
MIME type, and width and height. Ensure the returned data is embeddable by the
DOCX mapping, while preserving the existing renderer flow.
| }); | ||
| ``` | ||
|
|
||
| Invalid LaTeX or Mermaid sources render an error placeholder with the error message, mirroring the editor. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Describe the localized placeholder, not a parser error message.
The email diagram mapping deliberately does not render the Mermaid parser message. It renders a localized invalid-source placeholder instead. Update both documents to describe that behavior.
docs/content/docs/features/export/email.mdx#L144-L144: Replace “with the error message” with wording that describes a localized invalid-source placeholder.docs/content/docs/features/export/docx.mdx#L138-L138: Use the same wording for DOCX mappings.
📍 Affects 2 files
docs/content/docs/features/export/email.mdx#L144-L144(this comment)docs/content/docs/features/export/docx.mdx#L138-L138
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/content/docs/features/export/email.mdx` at line 144, Update the export
mapping descriptions to state that invalid LaTeX or Mermaid sources render a
localized invalid-source placeholder, not the parser’s error message. Apply the
same wording in docs/content/docs/features/export/email.mdx at lines 144-144 and
docs/content/docs/features/export/docx.mdx at lines 138-138.
| function serializeMathBlock(el: HTMLElement, ctx: SerializeContext): string { | ||
| const latex = extractMathLatexSource(el); | ||
| return ctx.indent + "$$\n" + latex + "\n$$\n\n"; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Preserve indentation for every block-math line.
ctx.indent applies only to the opening $$ delimiter. If math occurs in a list item or blockquote, the LaTeX and closing delimiter leave the parent container. The generated Markdown is then invalid or changes structure.
Prefix each LaTeX line and the closing delimiter with ctx.indent.
Proposed fix
function serializeMathBlock(el: HTMLElement, ctx: SerializeContext): string {
const latex = extractMathLatexSource(el);
- return ctx.indent + "$$\n" + latex + "\n$$\n\n";
+ const indentedLatex = latex
+ .split("\n")
+ .map((line) => ctx.indent + line)
+ .join("\n");
+ return ctx.indent + "$$\n" + indentedLatex + "\n" + ctx.indent + "$$\n\n";
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function serializeMathBlock(el: HTMLElement, ctx: SerializeContext): string { | |
| const latex = extractMathLatexSource(el); | |
| return ctx.indent + "$$\n" + latex + "\n$$\n\n"; | |
| function serializeMathBlock(el: HTMLElement, ctx: SerializeContext): string { | |
| const latex = extractMathLatexSource(el); | |
| const indentedLatex = latex | |
| .split("\n") | |
| .map((line) => ctx.indent + line) | |
| .join("\n"); | |
| return ctx.indent + "$$\n" + indentedLatex + "\n" + ctx.indent + "$$\n\n"; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/core/src/api/exporters/markdown/htmlToMarkdown.ts` around lines 205
- 207, Update serializeMathBlock so ctx.indent prefixes every generated
block-math line: the opening delimiter, each LaTeX line, and the closing
delimiter. Preserve the existing spacing and trailing newline while ensuring
multiline LaTeX remains nested within lists or blockquotes.
| return new Paragraph({ | ||
| alignment: AlignmentType.CENTER, | ||
| children: [ | ||
| new ImageRun({ | ||
| data: result.image.data, | ||
| type: imageType, | ||
| transformation: { | ||
| width: result.image.width, | ||
| height: result.image.height, | ||
| }, | ||
| }), | ||
| ], | ||
| }); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Clamp the embedded image width to the page width.
transformation uses the intrinsic image dimensions. renderDiagramToImage falls back to 800×600, and Mermaid diagrams are frequently wider than the DOCX body area (about 624px at 96dpi with default one-inch margins). Word clips such images at the right margin. The email mapping already clamps to MAX_WIDTH_PIXELS; apply the same scaling here.
📐 Proposed fix to scale down oversized diagrams
+ // DOCX body width with default one-inch margins on US Letter, in px at 96dpi.
+ const maxWidthPixels = 624;
+ const displayWidth = Math.min(result.image.width, maxWidthPixels);
+ const displayHeight = Math.round(
+ (displayWidth / result.image.width) * result.image.height,
+ );
+
return new Paragraph({
alignment: AlignmentType.CENTER,
children: [
new ImageRun({
data: result.image.data,
type: imageType,
transformation: {
- width: result.image.width,
- height: result.image.height,
+ width: displayWidth,
+ height: displayHeight,
},
}),
],
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return new Paragraph({ | |
| alignment: AlignmentType.CENTER, | |
| children: [ | |
| new ImageRun({ | |
| data: result.image.data, | |
| type: imageType, | |
| transformation: { | |
| width: result.image.width, | |
| height: result.image.height, | |
| }, | |
| }), | |
| ], | |
| }); | |
| // DOCX body width with default one-inch margins on US Letter, in px at 96dpi. | |
| const maxWidthPixels = 624; | |
| const displayWidth = Math.min(result.image.width, maxWidthPixels); | |
| const displayHeight = Math.round( | |
| (displayWidth / result.image.width) * result.image.height, | |
| ); | |
| return new Paragraph({ | |
| alignment: AlignmentType.CENTER, | |
| children: [ | |
| new ImageRun({ | |
| data: result.image.data, | |
| type: imageType, | |
| transformation: { | |
| width: displayWidth, | |
| height: displayHeight, | |
| }, | |
| }), | |
| ], | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/diagram-block/src/docx-exporter/index.ts` around lines 110 - 122,
Update the DOCX image transformation in the diagram export flow to clamp
oversized images to the established MAX_WIDTH_PIXELS value, scaling height by
the same factor to preserve aspect ratio. Keep intrinsic dimensions unchanged
when the image width is within the limit, and reuse the existing email-mapping
width constant or helper rather than introducing a duplicate.
| mathDocument = mathjax.document("", { | ||
| InputJax: new TeX({ | ||
| packages: TEX_PACKAGES, | ||
| // MathJax renders TeX errors as error text by default - throw | ||
| // instead, so the conversion boundary in `latexToMathSVG` can return | ||
| // them as typed errors. | ||
| formatError: (_jax: unknown, err: TexError) => { | ||
| throw err; | ||
| }, | ||
| }), | ||
| OutputJax: new SVG({ fontCache: "none" }), | ||
| }); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
mathjax-full TeX input option formatError throw TexError propagate convert
💡 Result:
In MathJax, the formatError option and TexError class are part of the TeX input processor's error handling mechanism. formatError Option The formatError option is a function in the TeX input processor configuration (within the tex block) that is called whenever the processor encounters a syntax or processing error [1][2]. Its signature is formatError(jax, err), where jax is the TeX input jax instance and err is the error object [3][4]. The default behavior is to generate an merror MathML element containing the error message [1][5]. You can override this function to perform custom actions, such as logging the error, replacing it with alternative content, or throwing a JavaScript error to stop processing and catch it elsewhere [1][3][4]. Throwing TexError TexError is the internal MathJax class used to report TeX-related errors [6][7]. When writing custom extensions or macros, you can throw a new TexError instance to signal a failure (e.g., for undefined control sequences or environments) [8][7]. The constructor typically takes two arguments: an error code string (e.g., 'UndefinedControlSequence') and a message string, optionally with placeholders for additional information [8][7]. Propagating and Converting To propagate TeX errors so they can be caught during an html.convert call or other rendering operations, you can override formatError to throw an Error [9]. Since MathJax often operates asynchronously (using promises), you can use try-catch blocks or Promise catch handlers to trap these errors when invoking convert [9]. Example of throwing a custom error for trapping: class MyTeX extends TeX { formatError(error) { throw Error('TeX error: ' + error.message); } } // You can then wrap html.convert in a try-catch or.catch block [9].
Citations:
- 1: https://docs.mathjax.org/en/stable/options/input/tex.html
- 2: https://docs.mathjax.org/en/v3.2/options/input/tex.html
- 3: https://docs.mathjax.org/en/stable/web/errors.html
- 4: https://docs.mathjax.org/en/latest/web/errors.html
- 5: https://docs.mathjax.org/en/latest/options/input/tex.html
- 6: https://github.com/mathjax/MathJax-src/blob/master/ts/input/tex/TexParser.ts
- 7: https://git.dailysh.it/public/malta-slides/raw/commit/e5f219507fa6ba4e8791269567893d2aecac39a2/node_modules/mathjax-full/ts/input/tex/base/BaseConfiguration.ts
- 8: when Handle undefined control sequence, how to catch error and use fallback way, for example server render mathjax/MathJax#2469
- 9: Error handling when html.convert() is used mathjax/MathJax-demos-node#3
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- target file ---'
cat -n packages/math-block/src/exporterHelpers/renderMathToImage.ts | sed -n '1,130p'
printf '%s\n' '--- dependency declarations and lock entries ---'
rg -n -C 3 '"mathjax-full"|mathjax-full@|mathjax-full:' \
package.json packages pnpm-lock.yaml yarn.lock package-lock.json 2>/dev/null || true
printf '%s\n' '--- MathJax-related repository references ---'
rg -n -C 4 'formatError|TexError|mathDocument\.convert|latexToMathSVG' packages --glob '*.{ts,tsx,js,mjs,cjs,json}' 2>/dev/null || trueRepository: TypeCellOS/BlockNote
Length of output: 29058
🌐 Web query:
mathjax-full 3.2.2 source TeX formatError process convert TexError catch
💡 Result:
In MathJax 3.2.2, the TeX input processor provides a formatError configuration option to handle syntax or processing errors encountered during TeX typesetting [1][2]. By default, MathJax catches these errors and renders them as an merror element (typically displaying red text on a yellow background) [3][4]. To intercept, process, or prevent these default error messages, you can override the formatError function within your MathJax configuration [1][3]. Configuring formatError The formatError function receives two arguments: jax (the TeX input jax instance) and err (the error object) [3][4]. Example: Logging Errors You can log errors to the console while still allowing the default rendering behavior to occur [3][4]: MathJax = { tex: { formatError(jax, err) { console.log(TeX error in "${jax.latex}": ${err.message}); return jax.formatError(err); } } }; Example: Throwing Errors to be Caught If you prefer to stop processing and handle the error using try-catch blocks or promise rejection handlers, you can configure formatError to throw the error [1][5][6]: // Configuration to throw the error MathJax = { tex: { formatError(jax, err) { throw err; } } }; // Usage with promise-based conversion try { const node = html.convert(texEquation, {... }); } catch (error) { console.error("Caught error:", error.message); } Alternatively, you can subclass the TeX input processor and override the formatError method to achieve the same result [6]. This is often the most robust approach for controlling how errors propagate through your application's pipeline [6].
Citations:
- 1: https://dokk.org/documentation/mathjax/v3.2-latest/options/input/tex/
- 2: https://docs.mathjax.org/en/v3.2/options/input/tex.html
- 3: https://docs.mathjax.org/en/latest/web/errors.html
- 4: https://docs.mathjax.org/en/stable/web/errors.html
- 5: https://docs.mathjax.org/en/stable/server/direct.html
- 6: Error handling when html.convert() is used mathjax/MathJax-demos-node#3
🌐 Web query:
site:github.com/mathjax/MathJax-src v3.2.2 formatError TexParser compile convert
💡 Result:
In MathJax (including version 3.2.2), formatError is a configurable function used by the TeX input jax to handle errors that occur during the parsing of TeX expressions [1]. When the TexParser encounters a problem during compilation, it can throw errors (such as missing braces or undefined dimensions) [2]. The formatError function allows developers to intercept these run-time errors and determine how they are reported, rather than relying on the default behavior of generating an merror element in the output [1][3]. Key technical points regarding this mechanism include: Configuration: You can define formatError as an option when configuring the TeX input jax. This provides a way to trap errors globally or log them to the console, which is especially useful for debugging or integrating MathJax into server-side environments where you might want to stop processing on failure [4]. Integration with Compile Process: The error handling process is integrated into the MathDocument compile phase [3]. When MathJax compiles math, it wraps the process in a try-catch block; if an error occurs, it triggers the registered error handlers (like compileError or formatError) [3]. Error Reporting: By default, if a parsing error occurs, MathJax creates an merror node in the MathML output, often including the error message in a data-mjx-error attribute or as a tooltip, making the error visible to users or accessible to the contextual menu [5][3][6]. For developers working with MathJax v3.2.2, if you are attempting to handle specific LaTeX parsing failures, you should provide a custom formatError function in your TeX input jax configuration object [4]. This function typically receives the jax instance and the error object as arguments, allowing you to handle the error message programmatically [4].
Citations:
- 1: Make formatError a configuration option for tex input jax mathjax/MathJax-src#483
- 2: https://github.com/mathjax/MathJax-src/blob/master/ts/input/tex/TexParser.ts
- 3: https://github.com/mathjax/MathJax-src/blob/master/ts/core/MathDocument.ts
- 4: https://github.com/mathjax/MathJax-src/releases/tag/4.0.0-rc.4
- 5: General improvements to input/output errors mathjax/MathJax-src#491
- 6: Add TeX error as data-mjx-error, similar to compile/typeset errors mathjax/MathJax-src#490
🏁 Script executed:
#!/bin/bash
set -eu
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
curl -fsSL https://registry.npmjs.org/mathjax-full/3.2.2 \
| jq -r '.dist.tarball' \
| xargs curl -fsSL -o "$tmpdir/mathjax-full.tgz"
tar -xzf "$tmpdir/mathjax-full.tgz" -C "$tmpdir"
printf '%s\n' '--- relevant package files ---'
find "$tmpdir/package" -type f \( -path '*/input/tex.js' -o -path '*/input/tex/*' -o -path '*/core/MathDocument.js' -o -path '*/mathjax.js' \) -print
printf '%s\n' '--- formatError and conversion call paths ---'
rg -n -C 8 'formatError|compileError|compile\(|convert\(' "$tmpdir/package" \
-g '*.js' -g '*.ts' | head -n 500Repository: TypeCellOS/BlockNote
Length of output: 50377
🏁 Script executed:
#!/bin/bash
set -eu
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
tarball="$(curl -fsSL https://registry.npmjs.org/mathjax-full/3.2.2 | jq -r '.dist.tarball')"
curl -fsSL "$tarball" -o "$tmpdir/mathjax-full.tgz"
tar -xzf "$tmpdir/mathjax-full.tgz" -C "$tmpdir"
printf '%s\n' '--- exact implementation files ---'
find "$tmpdir/package" -type f \
\( -path '*/js/input/tex.js' -o -path '*/js/input/tex/TexError.js' \
-o -path '*/js/core/MathDocument.js' -o -path '*/js/mathjax.js' \) -print
printf '%s\n' '--- targeted implementation excerpts ---'
python3 - "$tmpdir/package" <<'PY'
import pathlib, re, sys
root = pathlib.Path(sys.argv[1])
files = list(root.glob("**/js/input/tex.js"))
files += list(root.glob("**/js/core/MathDocument.js"))
files += list(root.glob("**/js/input/tex/TexError.js"))
for path in files:
text = path.read_text(errors="replace")
print(f"\n### {path.relative_to(root)}")
lines = text.splitlines()
hits = [i for i, line in enumerate(lines)
if re.search(r"formatError|compileError|compile\\(|convert\\(|TexError", line)]
shown = set()
for i in hits:
start, end = max(0, i - 5), min(len(lines), i + 8)
for j in range(start, end):
if j not in shown:
print(f"{j+1}: {lines[j]}")
shown.add(j)
print("---")
PYRepository: TypeCellOS/BlockNote
Length of output: 1561
🏁 Script executed:
#!/bin/bash
set -eu
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
tarball="$(curl -fsSL https://registry.npmjs.org/mathjax-full/3.2.2 | jq -r '.dist.tarball')"
curl -fsSL "$tarball" -o "$tmpdir/mathjax-full.tgz"
tar -xzf "$tmpdir/mathjax-full.tgz" -C "$tmpdir"
python3 - "$tmpdir/package" <<'PY'
import pathlib, re, sys
root = pathlib.Path(sys.argv[1])
files = [
root / "js/input/tex.js",
root / "js/input/tex/TexError.js",
root / "js/core/MathDocument.js",
root / "js/mathjax.js",
]
pattern = re.compile(r"formatError|compileError|compile\(|convert\(|TexError")
for path in files:
text = path.read_text(errors="replace")
lines = text.splitlines()
print(f"\n### {path.relative_to(root)} ({len(lines)} lines)")
hits = [i for i, line in enumerate(lines) if pattern.search(line)]
shown = set()
for i in hits:
start, end = max(0, i - 4), min(len(lines), i + 7)
for j in range(start, end):
if j not in shown:
print(f"{j+1}: {lines[j]}")
shown.add(j)
print("---")
PYRepository: TypeCellOS/BlockNote
Length of output: 5467
🏁 Script executed:
#!/bin/bash
set -eu
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
tarball="$(curl -fsSL https://registry.npmjs.org/mathjax-full/3.2.2 | jq -r '.dist.tarball')"
curl -fsSL "$tarball" -o "$tmpdir/mathjax-full.tgz"
tar -xzf "$tmpdir/mathjax-full.tgz" -C "$tmpdir"
printf '%s\n' '--- package metadata and bundled entry points ---'
cat "$tmpdir/package/package.json" | jq '{main, dependencies, files}'
find "$tmpdir/package/es5" -maxdepth 1 -type f -printf '%f\n' | sort | head -n 30
printf '%s\n' '--- standalone runtime probe availability ---'
node --version
node - <<'JS' "$tmpdir/package"
const path = require("node:path");
const root = process.argv[2];
const candidates = ["es5/tex-svg.js", "es5/tex.js", "es5/tex-chtml.js"];
for (const relative of candidates) {
const file = path.join(root, relative);
try {
const mod = require(file);
console.log(relative, "loaded", Object.keys(mod).slice(0, 10));
} catch (error) {
console.log(relative, "failed", error.code || error.name, error.message.split("\n")[0]);
}
}
JSRepository: TypeCellOS/BlockNote
Length of output: 1083
Re-throw compile errors from MathDocument. MathDocument.compileMath catches the TexError after formatError throws it and invokes compileError, so mathDocument.convert does not reach the latexToMathSVG catch. Configure compileError to rethrow err; otherwise invalid LaTeX produces MathJax error output instead of a typed error.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/math-block/src/exporterHelpers/renderMathToImage.ts` around lines 61
- 72, Update the MathJax document configuration in the mathDocument
initialization to provide a compileError handler that rethrows the received
error, ensuring compile failures propagate to the latexToMathSVG catch path.
Keep the existing formatError behavior unchanged.
| exporter: { | ||
| invalid_formula: (source: string) => `صيغة غير صالحة "${source}"`, | ||
| }, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Interpolated LaTeX needs bidi isolation in the RTL locales. The invalid_formula message interpolates source, which holds left-to-right LaTeX, into a right-to-left sentence. The Unicode bidi algorithm can move the ASCII quotes and neighbouring characters away from the formula when a renderer displays the exported placeholder. The shared fix is to wrap the interpolation in first-strong isolate characters, U+2068 and U+2069.
packages/math-block/src/i18n/locales/ar.ts#L31-L33: change the template to`صيغة غير صالحة "\u2068${source}\u2069"`.packages/math-block/src/i18n/locales/fa.ts#L31-L33: change the template to`فرمول نامعتبر "\u2068${source}\u2069"`.packages/math-block/src/i18n/locales/he.ts#L31-L33: change the template to`נוסחה לא חוקית "\u2068${source}\u2069"`.
📍 Affects 3 files
packages/math-block/src/i18n/locales/ar.ts#L31-L33(this comment)packages/math-block/src/i18n/locales/fa.ts#L31-L33packages/math-block/src/i18n/locales/he.ts#L31-L33
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/math-block/src/i18n/locales/ar.ts` around lines 31 - 33, Update the
invalid_formula templates to bidi-isolate the interpolated source with U+2068
and U+2069: packages/math-block/src/i18n/locales/ar.ts lines 31-33,
packages/math-block/src/i18n/locales/fa.ts lines 31-33, and
packages/math-block/src/i18n/locales/he.ts lines 31-33. Preserve each locale’s
existing wording and quote placement.
| const styleName = exporter.registerStyle((name) => | ||
| createElement( | ||
| "style:style", | ||
| { | ||
| "style:family": "graphic", | ||
| "style:name": name, | ||
| "style:parent-style-name": "Formula", | ||
| }, | ||
| createElement("style:graphic-properties", { | ||
| "style:vertical-pos": "middle", | ||
| "style:vertical-rel": "text", | ||
| }), | ||
| ), | ||
| ); |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect registerStyle and registerObject in the ODT exporter.
fd -t f -a 'ODTExporter*' packages/xl-odt-exporter/src | while IFS= read -r f; do
echo "=== $f ==="
rg -n -C 15 'registerStyle|registerObject' "$f"
done
# Fall back to a broad search if the file name differs.
rg -n -C 12 'registerStyle\s*[(<]' packages/xl-odt-exporter/srcRepository: TypeCellOS/BlockNote
Length of output: 19846
🏁 Script executed:
#!/bin/bash
set -e
echo '=== math-block ODT exporter outline ==='
ast-grep outline packages/math-block/src/odt-exporter/index.ts
echo '=== relevant implementation ==='
cat -n packages/math-block/src/odt-exporter/index.ts | sed -n '1,180p'
echo '=== registerStyle definition and style storage ==='
cat -n packages/xl-odt-exporter/src/odt/odtExporter.tsx | sed -n '1,80p;330,375p'
echo '=== all math exporter call sites ==='
rg -n -C 8 'formulaFrame|errorText|mathBlockMapping|registerStyle' packages/math-block/src/odt-exporter packages/math-block/srcRepository: TypeCellOS/BlockNote
Length of output: 50378
🏁 Script executed:
#!/bin/bash
set -e
echo '=== ODT style serialization path ==='
cat -n packages/xl-odt-exporter/src/odt/odtExporter.tsx | sed -n '250,335p'
echo '=== focused math exporter calls ==='
rg -n 'registerStyle|registerObject|function formulaFrame|function errorText|export function mathBlockMapping|export function inlineMathMapping' packages/math-block/src/odt-exporter/index.ts
echo '=== read-only invariant check ==='
python3 - <<'PY'
from pathlib import Path
import re
math = Path("packages/math-block/src/odt-exporter/index.ts").read_text()
odt = Path("packages/xl-odt-exporter/src/odt/odtExporter.tsx").read_text()
implementation = re.search(
r"public registerStyle\(style:.*?\n\s*}\n",
odt,
re.S,
)
assert implementation, "registerStyle implementation not found"
body = implementation.group(0)
assert "++this.styleCounter" in body
assert "automaticStyles.set(styleName" in body
assert "automaticStyles.get" not in body
assert "automaticStyles.values" not in body
for name in ("formulaFrame", "errorText", "mathBlockMapping"):
assert name in math, f"{name} not found"
# Count the style registrations in each helper/mapping without executing repository code.
ranges = {
"formulaFrame": math[math.index("function formulaFrame"):math.index("function errorText")],
"errorText": math[math.index("function errorText"):math.index("export function mathBlockMapping")],
"mathBlockMapping": math[math.index("export function mathBlockMapping"):math.index("/**", math.index("export function mathBlockMapping") + 1)],
}
for name, text in ranges.items():
count = text.count("registerStyle(")
print(f"{name}: registerStyle calls = {count}")
assert count >= 1, f"{name} has no registerStyle call"
print("registerStyle allocates a new style name per call and has no lookup-based deduplication.")
PYRepository: TypeCellOS/BlockNote
Length of output: 4156
Cache the repeated ODT styles per export. ODTExporter.registerStyle creates a new BN_S* style for every call and does not deduplicate identical definitions. Cache or hoist the styles used by formulaFrame, errorText, and mathBlockMapping so valid and invalid formulas do not add duplicate automatic styles.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/math-block/src/odt-exporter/index.ts` around lines 37 - 50, Cache
the style registrations used by formulaFrame, errorText, and mathBlockMapping
within each ODT export instead of calling ODTExporter.registerStyle for every
occurrence. Reuse the cached style names for identical definitions, while
keeping separate styles where definitions differ, so valid and invalid formulas
do not create duplicate automatic styles.
| "rootDir": "..", | ||
| "paths": { | ||
| "@shared/*": ["../shared/*"] | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -u
rg -n -C 3 --glob '*.ts' --glob '*.tsx' '`@shared/`' playground shared tests || true
rg -n -C 5 'tsconfigPaths|`@shared`' playground/vite.config.ts || trueRepository: TypeCellOS/BlockNote
Length of output: 3147
🏁 Script executed:
#!/usr/bin/env bash
set -u
printf '%s\n' '--- playground/tsconfig.json ---'
cat -n playground/tsconfig.json
printf '%s\n' '--- playground/vite.config.ts ---'
cat -n playground/vite.config.ts
printf '%s\n' '--- `@shared` references in playground and config files ---'
rg -n -C 3 '`@shared`|tsconfigPaths|resolve:' playground --glob '*.ts' --glob '*.tsx' --glob '*.json' || true
printf '%s\n' '--- relevant package/config declarations ---'
rg -n -C 3 '"vite"|vite-plus|tsconfigPaths|typescript' package.json playground/package.json pnpm-workspace.yaml vite.config.* playground/vite.config.ts 2>/dev/null || trueRepository: TypeCellOS/BlockNote
Length of output: 12298
🌐 Web query:
Vite 8 resolve.tsconfigPaths official documentation Vite Plus
💡 Result:
In Vite 8, resolve.tsconfigPaths is a built-in configuration option that enables native support for resolving TypeScript path aliases defined in your tsconfig.json file [1][2][3]. By setting resolve.tsconfigPaths to true in your vite.config.ts (or equivalent configuration file), Vite will automatically use the paths defined in your tsconfig to resolve imports, eliminating the need for third-party plugins like vite-tsconfig-paths [1][4][3]. Key details regarding this feature: - Configuration: The option is disabled by default [5][1]. You can enable it by adding resolve: { tsconfigPaths: true } to your Vite configuration [5][2]. - Performance: Enabling this feature comes with a small performance cost [1][2]. - Official Guidance: While Vite provides this built-in support, the TypeScript team generally discourages using the tsconfig paths feature to change the behavior of external tools [6]. - Migration: Because Vite now supports this natively, using the legacy vite-tsconfig-paths or vite-plugin-tsconfig-paths plugins is no longer necessary [4][3]. Vite includes logic to detect these plugins and may emit a warning suggesting you switch to the built-in option instead [4]. As of recent Vite 8 releases, the feature has seen ongoing improvements, including expanded support for CSS and Sass imports [7] and fixes for various edge cases related to path resolution [8][9][10]. The @experimental tag previously associated with this option was removed in version 8.2.1 [7].
Citations:
- 1: https://vite.dev/blog/announcing-vite8
- 2: https://vite.dev/blog/announcing-vite8-beta
- 3: https://listiak.dev/blog/vite-8-one-bundler-to-rule-them-all
- 4: feat: warn when
vite-tsconfig-pathsplugin is detected vitejs/vite#21781 - 5: https://github.com/vitejs/vite/blob/v8.0.8/docs/config/shared-options.md
- 6: https://github.com/vitejs/vite/blob/2eae24e1/docs/guide/features.md
- 7: https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md
- 8:
resolve.tsconfigPathsconfiguration not working in Vite 8 vitejs/vite#22047 - 9:
resolve.tsconfigPathsmisresolves paths from a symlinked shared tsconfig package vitejs/vite#21856 - 10: tsconfig paths are not correctly recognized / working vitejs/vite#22139
🏁 Script executed:
#!/usr/bin/env bash
set -u
printf '%s\n' '--- change to playground/tsconfig.json ---'
git diff -- playground/tsconfig.json
printf '%s\n' '--- relevant alias branch ---'
sed -n '100,136p' playground/vite.config.ts
printf '%s\n' '--- all `@shared` imports and configuration aliases ---'
rg -n -C 2 '`@shared`(?:/|["'\''])|tsconfigPaths' --glob '*.ts' --glob '*.tsx' --glob '*.json' . || true
printf '%s\n' '--- read-only branch verifier ---'
python3 - <<'PY'
from pathlib import Path
config = Path("playground/vite.config.ts").read_text()
build_start = config.index('conf.command === "build"')
build_end = config.index("\n : devAliases", build_start)
build_branch = config[build_start:build_end]
assert '"`@shared`"' in config[:build_start], "The development alias map lacks `@shared`"
assert '"`@shared`"' not in build_branch, "The build alias map unexpectedly contains `@shared`"
assert ": devAliases" in config[build_end:], "The development branch does not use devAliases"
print("vp dev: uses devAliases, which contains `@shared`")
print("vp build: uses a separate alias object, which omits `@shared`")
PYRepository: TypeCellOS/BlockNote
Length of output: 30948
Add @shared to the build resolver.
vp dev already resolves @shared through devAliases. vp build uses a separate alias map that omits @shared. Add the alias to that map or enable resolve.tsconfigPaths: true, then test an @shared/* import in both modes.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@playground/tsconfig.json` around lines 19 - 22, Update the playground
TypeScript/build resolver configuration so `@shared/`* resolves during vp build as
it already does through devAliases in vp dev. Add the matching `@shared` alias to
the build alias map, or enable resolve.tsconfigPaths, and verify an `@shared/`*
import works in both development and production builds.
Source: MCP tools
| const canvas = document.createElement("canvas"); | ||
| canvas.width = image.naturalWidth || image.width; | ||
| canvas.height = image.naturalHeight || image.height; | ||
| const context = canvas.getContext("2d")!; | ||
| context.drawImage(image, 0, 0); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- target file ---'
cat -n shared/util/browserImageTestUtil.ts
printf '%s\n' '--- related symbols and callers ---'
rg -n -C 3 'decodeAndSample|getContext\("2d"\)|drawImage' shared . \
-g '*.ts' -g '*.tsx' -g '!node_modules' 2>/dev/null | head -240Repository: TypeCellOS/BlockNote
Length of output: 11595
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- TypeScript configuration ---'
fd -i 'tsconfig*.json' . -t f -E node_modules -x sh -c 'echo "--- $1"; cat "$1"' sh {} \; \
| rg -n -C 2 'strict|strictNullChecks|lib|dom' | head -160
printf '%s\n' '--- local nullable canvas handling ---'
rg -n -C 4 'getContext\(["'\'']2d["'\'']\)|if\s*\([^)]*getContext|context\s*\?' . \
-g '*.ts' -g '*.tsx' -g '!node_modules' | head -260
printf '%s\n' '--- standalone API-shape check ---'
python3 - <<'PY'
from pathlib import Path
source = Path("shared/util/browserImageTestUtil.ts").read_text()
assert 'canvas.getContext("2d")!' in source
assert "getContext" in source
print("The target uses a non-null assertion on getContext('2d').")
print("The platform API permits a null 2D context; callers currently receive no explicit handling.")
PYRepository: TypeCellOS/BlockNote
Length of output: 13378
🌐 Web query:
MDN HTMLCanvasElement getContext 2d returns null if context type is not supported
💡 Result:
The HTMLCanvasElement.getContext method returns null when the requested context type is not supported [1][2]. According to the official HTML specification and MDN documentation, this behavior occurs in the following scenarios [1][2]: 1. The provided context identifier is not supported by the browser or the platform (e.g., hardware limitations) [2][3]. 2. The canvas element has already been initialized with a different, incompatible context type [1][2]. In these cases, the method does not throw an error, but instead returns null, which developers should check for before proceeding with canvas operations [1][4]. Note that in specific edge cases involving advanced features like proxy transfers (e.g., using transferControlToProxy), an InvalidStateError exception may be thrown instead of returning null [5][4].
Citations:
- 1: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext
- 2: https://html.spec.whatwg.org/multipage/canvas.html
- 3: https://dev.w3.org/html5/spec-LC/the-canvas-element.html
- 4: https://stackoverflow.com/questions/36687045/can-browsers-throw-errors-when-trying-to-create-a-rendering-context
- 5: https://whatpr.org/html/3752/canvas.html
Handle an unavailable 2D canvas context.
getContext("2d") can return null. Replace the non-null assertion with an explicit guard and throw a descriptive error before calling drawImage.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@shared/util/browserImageTestUtil.ts` around lines 20 - 24, Update the canvas
setup in the browser image utility to explicitly validate the result of
canvas.getContext("2d") instead of using a non-null assertion. If the context is
unavailable, throw a descriptive error before invoking context.drawImage;
preserve the existing drawing behavior when a context is returned.
Source: Coding guidelines
fumadocs-core still declares @shikijs/rehype ^3 while everything else (code-block, docs, fumadocs-twoslash) is on shiki 4.4.3 - two shiki type identities in one graph fail the docs build on the rehype transformer types. Overriding shiki/@shikijs/rehype/@shikijs/types to ^4.4.3 moves the one v3 straggler; shiki's v3->v4 migration is documented as a direct bump, and the full docs build (static prerender of every highlighted code block + twoslash) passes with it. Drop the override once fumadocs bumps its own rehype to ^4.
The merged SourceWithPreview UI renders the empty diagram preview with a CSS-driven data-placeholder instead of the old static placeholder DOM; the snapshot predated it (it fails identically on the parent branch).
An undestroyed EditorView leaves ProseMirror DOMObserver debounce timers behind; on slow CI they fire after the jsdom environment is torn down and fail the run with an unhandled "document is not defined". The same mount-without-destroy pattern exists in other jsdom test files, but only this one mutates the editor in its last test right before teardown - which is the window the flake needs.
@blocknote/ariakit
@blocknote/code-block
@blocknote/core
@blocknote/diagram-block
@blocknote/mantine
@blocknote/math-block
@blocknote/react
@blocknote/server-util
@blocknote/shadcn
@blocknote/xl-ai
@blocknote/xl-docx-exporter
@blocknote/xl-email-exporter
@blocknote/xl-multi-column
@blocknote/xl-odt-exporter
@blocknote/xl-pdf-exporter
commit: |
Stacked on #2857 (
code-block-previews). Rounds off the math & diagram exporter work: architecture, error handling, email support, and browser-verified visual coverage.What changed
Mappings live in the block packages. The math/diagram exporter mappings moved out of the (GPL)
xl-*exporter packages into the (MPL) block packages as subpath exports —@blocknote/math-block/{docx,odt,pdf,email}-exporterand the same for@blocknote/diagram-block. Keeps MPL block code out of the GPL exporters and puts each mapping next to the block it maps. Thexl-*exporters no longer ship math/diagram defaults; consumers spread the mappings in (see the updated interoperability examples/docs).ExportImageis the image contract. Renderers, rasterizers, deliveries and mappings exchange{ data: Uint8Array, mimeType, width, height }(display dimensions) instead of SVG strings / data URLs.exportImageToDataURLlives in core; rasterization scale is owned by the rasterizer implementation, not sprinkled through call sites.Pluggable seams with browser defaults.
rasterize: RasterizeSVG(math),renderDiagram: RenderDiagram(Mermaid), andimageDelivery: ReactEmailImageDelivery(email) plug in via thecreate*Mappingfactories. In the browser the defaults just work; headless exports throw a capability error that names the exact option to pass (e.g. mermaid-cli / Kroki for diagrams).Exporters never hardcode language strings.
ExporterOptionsgainsdictionary(pass a core locale or your editor's dictionary); core'sDictionarygains anexportersection translated in all 24 locales, and math/diagram own their exporter strings in their own locales (invalid_formula/invalid_diagramtemplates), read viagetMathExporterDictionary(exporter)— the same merge-a-section shape as their editor dictionaries, with bundled-English fallback. Also fixes aTexErrorCJS/ESM interop crash on invalid formulas under vite bundling, now covered by an invalid formula in the browser e2e document.Email export embeds math & diagrams as data-URL images by default, or as
cid:inline attachments viacreateCIDImageDelivery()(nodemailer-shaped) for clients that don't render data URLs. PDF inline math works now (rasterized during react-pdf asset resolution). Markdown exports math as$…$/$$…$$.Takeaways for the team (now in AGENTS.md / the testing skill)
{ error: string } | { …data }). The compiler then forces every caller to handle it. Corollary: never render a caught exception's message into a document — a catch-all can capture anything and leak internals. Only messages carried by typed results are known-safe to show; placeholders render the source's first line plus that typed message. Environment problems (no browser, nothing plugged in) still throw loudly.any/casts hiding cases, exhaustive switches. When an image format reaches DOCX embedding that the renderer contract doesn't allow, we throw instead of silently mislabeling bytes.documentexists but rendering doesn't — so browser-capability checks pass while the capability is broken. Node with pluggable stubs for logic; the Docker browser suite for real rendering. Browser-only implementations get colocatedpackages/*/src/**/*.browser.test.tsfiles, which run in the browser suite.tests/src/end-to-end/exporters/exporterImages.test.tsx) exports the full shared test document through the real exporters and screenshots the results: the email as one full-resolution capture, and each page of an actually-produced PDF rendered with pdf.js (a real browser needs no native canvas — which is what blocked the old Node attempt; pdf.js itself is a single pure-JS devDep, and its optional@napi-rs/canvasis excluded workspace-wide).page.viewport()alone makes the harness downscale the iframe to fit the window. This is known and fixed upstream (page.screenshot is extremely low resolution with a large viewport vitest-dev/vitest#9124, #9363, fixed by fix(browser)!: iframe scale vitest-dev/vitest#9745 in the Vitest 5.0.0 milestone); until vite-plus ships that, thescreenshotFullutil (tests/src/utils) backports the same mechanism — grow the iframe, neutralize its scale transform during the capture — andscreenshotFull.test.tsxguards it on synthetic striped content. Always eyeball regenerated baselines.max-widthstyle overrode our explicit width/height and letterboxed diagrams at half size. The renderer now strips the style, and a browser test asserts ink coverage so this class of bug can't pass as "non-blank image".Test status
code-block-previewsand are untouched by this branch —dragdrop"Formatting toolbar should not appear when dragging image block" (chromium+webkit) andkeyboardhandlers"Delete before shallower block" snapshot (chromium+webkit). Worth a look on the parent PR.Follow-ups
createReactMathInlineContentSpec.test.tsx,createReactDiagramBlockSpec.test.tsx) to the browser suite.deliveris sync because react-email rendering is sync).latexToSvgexport to@react-pdf/renderer's math support so PDF inline math needs less glue.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes