Skip to content

Math & diagram exporters: block-package mappings, typed errors, ExportImage contract, email export - #2961

Open
YousefED wants to merge 18 commits into
code-block-previewsfrom
math-diagram-exporters
Open

Math & diagram exporters: block-package mappings, typed errors, ExportImage contract, email export#2961
YousefED wants to merge 18 commits into
code-block-previewsfrom
math-diagram-exporters

Conversation

@YousefED

@YousefED YousefED commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

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}-exporter and 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. The xl-* exporters no longer ship math/diagram defaults; consumers spread the mappings in (see the updated interoperability examples/docs).

ExportImage is the image contract. Renderers, rasterizers, deliveries and mappings exchange { data: Uint8Array, mimeType, width, height } (display dimensions) instead of SVG strings / data URLs. exportImageToDataURL lives 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), and imageDelivery: ReactEmailImageDelivery (email) plug in via the create*Mapping factories. 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. ExporterOptions gains dictionary (pass a core locale or your editor's dictionary); core's Dictionary gains an exporter section translated in all 24 locales, and math/diagram own their exporter strings in their own locales (invalid_formula/invalid_diagram templates), read via getMathExporterDictionary(exporter) — the same merge-a-section shape as their editor dictionaries, with bundled-English fallback. Also fixes a TexError CJS/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 via createCIDImageDelivery() (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)

  1. Expected failures are values, not exceptions. Invalid LaTeX/Mermaid is user input, so failure is part of the function's contract: it's caught at the lowest adapter around the throwing library and returned as a Result-style union ({ 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.
  2. Make the type system carry the contract — discriminated unions over flags, no 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.
  3. No jsdom in tests. It's a murky middle ground — document exists 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 colocated packages/*/src/**/*.browser.test.ts files, which run in the browser suite.
  4. Test exporters through complete documents, not by calling mappings directly — the browser e2e (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/canvas is excluded workspace-wide).
  5. Screenshots silently blank below the tester iframe's fold (~720px), and 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, the screenshotFull util (tests/src/utils) backports the same mechanism — grow the iframe, neutralize its scale transform during the capture — and screenshotFull.test.tsx guards it on synthetic striped content. Always eyeball regenerated baselines.
  6. WebKit rasterizes SVGs by CSS, not attributes: Mermaid's inline max-width style 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

  • All affected package unit suites green (core 731, diagram-block 26, math-block 25, docx/odt/pdf/email exporters).
  • Scoped browser run green: 13 passed, 2 skipped (the PDF visual test is chromium-only by design — the produced PDF is identical across browsers).
  • Full e2e suite: 4 failures that pre-exist on code-block-previews and are untouched by this branch — dragdrop "Formatting toolbar should not appear when dragging image block" (chromium+webkit) and keyboardhandlers "Delete before shallower block" snapshot (chromium+webkit). Worth a look on the parent PR.

Follow-ups

  • End-user testing of exported files in Word/LibreOffice/email clients (in progress).
  • i18n: exporter placeholder text ("Invalid formula …", "Invalid diagram …") isn't dictionary-backed — exporters have no dictionary yet.
  • Migrate the jsdom-based block-spec tests (createReactMathInlineContentSpec.test.tsx, createReactDiagramBlockSpec.test.tsx) to the browser suite.
  • Email: async/upload-based image delivery (current deliver is sync because react-email rendering is sync).
  • Upstream a latexToSvg export to @react-pdf/renderer's math support so PDF inline math needs less glue.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added math and diagram export support for PDF, DOCX, ODT, email, and Markdown.
    • Math exports support editable document formulas, inline math, SVG or raster images, and localized error placeholders.
    • Diagram exports support rendered images, custom renderers, image attachments, and invalid-source handling.
    • Added localized exporter messages across supported languages.
    • Added full-page screenshot capture for tall content.
  • Bug Fixes

    • Export mappings now provide clearer errors for missing or invalid content.

…, 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.
@vercel

vercel Bot commented Aug 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
blocknote Ready Ready Preview Aug 11, 2026 7:55pm
blocknote-website Ready Ready Preview Aug 11, 2026 7:55pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1791f239-1992-4104-9002-5ec1fa0e5d57

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This 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.

Changes

Exporter foundations

Layer / File(s) Summary
Shared exporter contracts and documentation
packages/core/src/exporter/*, packages/core/src/schema/blocks/types.ts, packages/core/src/api/exporters/markdown/*, docs/content/docs/features/export/*
Exporters now support localized dictionaries, typed image values, descriptive missing-mapping errors, plain-content conversion, and MathML-to-Markdown serialization.
Math export mappings
packages/math-block/src/*
Math content now exports to native DOCX and ODT formulas, PDF formulas or images, and email SVG or raster images. Invalid formulas produce localized placeholders.
Diagram export mappings
packages/diagram-block/src/*
Diagram content now renders through injected or browser renderers for DOCX, ODT, PDF, and email output. Invalid diagrams produce localized placeholders.
Legacy exporter boundaries and email delivery
packages/xl-*/**, packages/xl-email-exporter/src/react-email/*
Math and diagram mappings were removed from legacy XL exporter subpaths. Default schemas use localized file-link labels. Email output supports data URLs and CID attachments.
Examples and package wiring
examples/05-interoperability/*, packages/*/package.json, playground/*, packages/dev-scripts/*
Examples and package builds now use dedicated math and diagram exporter entry points. Shared aliases and optional exporter integrations were added.
Browser validation and testing guidance
tests/src/*, shared/util/*, .claude/skills/testing-skill/SKILL.md, AGENTS.md
Browser tests cover rendered export output and tall screenshots. Testing guidance now separates Node logic tests from browser rendering tests.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested reviewers: matthewlipski, nperez0111

Poem

I’m a rabbit with formulas bright,
Mermaid diagrams take graceful flight.
DOCX and ODT hold images in tune,
PDFs and emails arrive soon.
Localized errors hop into view—
Browser tests keep the garden true.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary math and diagram exporter changes, including package mappings, typed errors, the ExportImage contract, and email export.
Description check ✅ Passed The description thoroughly covers the feature rationale, major changes, impact, testing results, known failures, and follow-ups, but omits the template checklist and explicit screenshots section.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch math-diagram-exporters

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread tests/src/end-to-end/exporters/exporterImages.test.tsx Fixed
…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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is this imported from?

import { createDiagramBlockMapping } from "@blocknote/diagram-block/email-exporter";

const exporter = new ReactEmailExporter(editor.schema, {
...reactEmailDefaultSchemaMappings,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import is missing

… 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.
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://TypeCellOS.github.io/BlockNote/pr-preview/pr-2961/

Built to branch gh-pages at 2026-08-11 19:59 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

…r HTML-escaped attribute decoding. The order of decoding has been adjusted to prevent double-unescaping of sequences.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🧹 Nitpick comments (20)
shared/util/odtTestUtil.ts (1)

22-27: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Model required ZIP entries without unchecked assertions.

as FileEntry and as 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 before getData.

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 win

Use a schema that declares the tested extension blocks.

This test declares only defaultBlockSpecs but supplies math, diagram, and inlineMath fixtures. The as any casts 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 win

Replace untyped PDF-tree inspection with narrowed node types.

any makes collectImages accept arbitrary objects and bypasses React and react-pdf element contracts. Use ReactNode plus an element type guard for image props. Keep the PDF render input typed as the expected document element.

As per coding guidelines, avoid any and 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 win

Use concrete exporter generic types and localize negative-case casts.

Exporter<any, ...>, StyledText<any>, and the mapping as any cast 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 value

Use function declarations for named test helpers.

Change getZIPEntryContent and prettify from arrow-function values to function declarations.

🤖 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 win

Type the invalid-LaTeX fixture against the exporter schema.

Add createReactMathBlockSpec() and createReactInlineMathSpec() to the test schema. Type the fixture as Block<typeof schema.blockSchema, typeof schema.inlineContentSchema, typeof schema.styleSchema>[] instead of using as any. toDocxJsDocument accepts Block<B, I, S>[], so the cast suppresses validation for math and inlineMath.

🤖 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 win

Use explicit result discriminators for export results.

Return { type: "success"; mathML: string } | { type: "invalid-latex"; error: string } from latexToMathML. Apply the same pattern to latexToDocxEquation, then branch on type in 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 win

Prefer a function declaration for the named template factory.

Use function template(project: Project) {} instead of assigning an arrow function to template. Update the closing }); to close the returned object and the function.

As per coding guidelines: “Prefer function name() {} declarations over const 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 value

Use function declarations for the named exported components.

  • packages/math-block/src/block/helpers/render/MathBlockPreviewWithPopup.tsx#L13-L16: Change MathBlockPreviewWithPopup to an exported function declaration.
  • packages/math-block/src/block/helpers/toExternalHTML/BlockMathMLElement.tsx#L8-L11: Change BlockMathMLElement to an exported function declaration.

As per coding guidelines, “Prefer function name() {} declarations over const 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 win

Use a typed schema-backed fixture instead of as any.

Register the math specs in the test schema and convert the partial fixture with partialBlocksToBlocksForTesting before calling toODTDocument. 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 value

Align the errorText parameter order with the sibling exporters.

This errorText takes (source, exporter). The errorText helpers in packages/math-block/src/docx-exporter/index.ts, packages/math-block/src/email-exporter/index.tsx, and packages/math-block/src/pdf-exporter/index.tsx all 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 win

Validate the imported equation without an any cast. docx@9.6.1 drops XML declarations and comments, so they do not shift imported.root[0]. mathml2omml@0.5.0 produces an m:oMath root 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 value

Named 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: convert createExporter to a function declaration.
  • packages/math-block/src/pdf-exporter/pdfExporter.test.tsx#L23-L28: convert rasterize to an async function declaration.

As per coding guidelines: "Prefer function name() {} declarations over const 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 value

Validate the node kind before you read attributes.

Line 121 takes firstChild(node). If the conversion output has no child element, documentAdaptor.getAttribute at lines 122-123 receives null and throws a TypeError instead of the descriptive "No SVG found in MathJax output" error. Move the kind check 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 value

Align the LaTeX package sets.

latexToMathSVG uses curated TEX_PACKAGES, while @react-pdf/math@2.0.1 uses MathJax AllPackages. 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 win

Extract the shared renderer-resolution logic. Both mappings duplicate the same renderer fallback, the same typeof document check, 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 example resolveRenderDiagram(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 win

Replace the unchecked MIME-type cast at line 103. ExportImage.mimeType is string, 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 returns string | undefined and 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 win

Remove unchecked casts from the exporter test utility.

Type diagramDocument from DiagramBlockConfig and the default inline/style schemas. Add the diagram block to each test schema so the fixture satisfies Block<B, I, S>[].

If the matching ZIP entry is absent or entry.directory is true, return "". Otherwise call getData after the directory check narrows the entry to FileEntry.

🤖 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 win

Make the ODT exporter contract explicit.

BlockMapping accepts a generic Exporter, but this mapping uses ODT-only registerStyle and registerPicture through 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 win

Register the diagram block spec in the test schema.

BlockMapping derives its block keys and types from the schema. defaultBlockSpecs excludes diagram, so mappings as any suppresses type checking for this mapping. Add diagram: createReactDiagramBlockSpec() to blockSpecs and 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

📥 Commits

Reviewing files that changed from the base of the PR and between 5291cb3 and 112f1ee.

⛔ Files ignored due to path filters (25)
  • packages/math-block/src/docx-exporter/__snapshots__/withMathMappings/document.xml is excluded by !**/__snapshots__/**
  • packages/math-block/src/email-exporter/__snapshots__/emailExporter.test.tsx.snap is excluded by !**/*.snap, !**/__snapshots__/**
  • packages/math-block/src/odt-exporter/__snapshots__/withMathMappings/content.xml is excluded by !**/__snapshots__/**
  • packages/math-block/src/odt-exporter/__snapshots__/withMathMappings/objects.xml is excluded by !**/__snapshots__/**
  • packages/math-block/src/odt-exporter/__snapshots__/withMathMappings/styles.xml is excluded by !**/__snapshots__/**
  • packages/math-block/src/pdf-exporter/__snapshots__/exampleWithMathMappings.jsx is excluded by !**/__snapshots__/**
  • packages/xl-docx-exporter/src/docx/__snapshots__/basic/document.xml is excluded by !**/__snapshots__/**
  • packages/xl-email-exporter/src/react-email/__snapshots__/reactEmailExporter.test.tsx.snap is excluded by !**/*.snap, !**/__snapshots__/**
  • packages/xl-odt-exporter/src/odt/__snapshots__/basic/content.xml is excluded by !**/__snapshots__/**
  • packages/xl-odt-exporter/src/odt/__snapshots__/withCustomOptions/content.xml is excluded by !**/__snapshots__/**
  • packages/xl-pdf-exporter/src/pdf/__snapshots__/example.jsx is excluded by !**/__snapshots__/**
  • packages/xl-pdf-exporter/src/pdf/__snapshots__/exampleWithHeaderAndFooter.jsx is excluded by !**/__snapshots__/**
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/email-export-chromium-linux.png is excluded by !**/*.png
  • tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/email-export-firefox-linux.png is excluded by !**/*.png
  • tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/email-export-webkit-linux.png is excluded by !**/*.png
  • tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/pdf-export-page-1-chromium-linux.png is excluded by !**/*.png
  • tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/pdf-export-page-2-chromium-linux.png is excluded by !**/*.png
  • tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/pdf-export-page-3-chromium-linux.png is excluded by !**/*.png
  • tests/src/end-to-end/exporters/__screenshots__/exporterImages.test.tsx/pdf-export-page-4-chromium-linux.png is excluded by !**/*.png
  • tests/src/end-to-end/screenshots/__screenshots__/screenshotFull.test.tsx/screenshot-full-tall-element-chromium-linux.png is excluded by !**/*.png
  • tests/src/end-to-end/screenshots/__screenshots__/screenshotFull.test.tsx/screenshot-full-tall-element-firefox-linux.png is excluded by !**/*.png
  • tests/src/end-to-end/screenshots/__screenshots__/screenshotFull.test.tsx/screenshot-full-tall-element-webkit-linux.png is excluded by !**/*.png
  • tests/src/unit/react/formatConversion/export/__snapshots__/markdown/inlineMath/basic.md is excluded by !**/__snapshots__/**
  • tests/src/unit/react/formatConversion/export/__snapshots__/markdown/math/basic.md is excluded by !**/__snapshots__/**
📒 Files selected for processing (187)
  • .claude/skills/testing-skill/SKILL.md
  • AGENTS.md
  • docs/content/docs/features/blocks/code-blocks.mdx
  • docs/content/docs/features/export/docx.mdx
  • docs/content/docs/features/export/email.mdx
  • docs/content/docs/features/export/markdown.mdx
  • docs/content/docs/features/export/odt.mdx
  • docs/content/docs/features/export/pdf.mdx
  • docs/package.json
  • examples/04-theming/07-custom-code-block/.bnexample.json
  • examples/04-theming/07-custom-code-block/package.json
  • examples/05-interoperability/05-converting-blocks-to-pdf/.bnexample.json
  • examples/05-interoperability/05-converting-blocks-to-pdf/package.json
  • examples/05-interoperability/05-converting-blocks-to-pdf/src/App.tsx
  • examples/05-interoperability/06-converting-blocks-to-docx/.bnexample.json
  • examples/05-interoperability/06-converting-blocks-to-docx/src/App.tsx
  • examples/05-interoperability/07-converting-blocks-to-odt/.bnexample.json
  • examples/05-interoperability/07-converting-blocks-to-odt/src/App.tsx
  • examples/05-interoperability/08-converting-blocks-to-react-email/.bnexample.json
  • examples/05-interoperability/08-converting-blocks-to-react-email/package.json
  • examples/05-interoperability/08-converting-blocks-to-react-email/src/App.tsx
  • examples/07-collaboration/14-suggestion-gallery/src/scenarios.ts
  • examples/07-collaboration/14-suggestion-gallery/tsconfig.json
  • examples/07-collaboration/14-suggestion-gallery/vite.config.ts
  • packages/code-block/package.json
  • packages/core/package.json
  • packages/core/src/api/exporters/markdown/htmlToMarkdown.ts
  • packages/core/src/exporter/ExportImage.ts
  • packages/core/src/exporter/Exporter.test.ts
  • packages/core/src/exporter/Exporter.ts
  • packages/core/src/exporter/index.ts
  • packages/core/src/i18n/locales/ar.ts
  • packages/core/src/i18n/locales/de.ts
  • packages/core/src/i18n/locales/en.ts
  • packages/core/src/i18n/locales/es.ts
  • packages/core/src/i18n/locales/fa.ts
  • packages/core/src/i18n/locales/fr.ts
  • packages/core/src/i18n/locales/he.ts
  • packages/core/src/i18n/locales/hr.ts
  • packages/core/src/i18n/locales/is.ts
  • packages/core/src/i18n/locales/it.ts
  • packages/core/src/i18n/locales/ja.ts
  • packages/core/src/i18n/locales/ko.ts
  • packages/core/src/i18n/locales/nl.ts
  • packages/core/src/i18n/locales/no.ts
  • packages/core/src/i18n/locales/pl.ts
  • packages/core/src/i18n/locales/pt.ts
  • packages/core/src/i18n/locales/ru.ts
  • packages/core/src/i18n/locales/sk.ts
  • packages/core/src/i18n/locales/uk.ts
  • packages/core/src/i18n/locales/uz.ts
  • packages/core/src/i18n/locales/vi.ts
  • packages/core/src/i18n/locales/zh-tw.ts
  • packages/core/src/i18n/locales/zh.ts
  • packages/core/src/schema/blocks/types.ts
  • packages/core/src/schema/inlineContent/types.ts
  • packages/dev-scripts/examples/template-react/tsconfig.json.template.tsx
  • packages/dev-scripts/examples/template-react/vite.config.ts.template.tsx
  • packages/diagram-block/package.json
  • packages/diagram-block/src/block/helpers/render/DiagramBlockPreviewWithPopup.tsx
  • packages/diagram-block/src/docx-exporter/docxExporter.test.ts
  • packages/diagram-block/src/docx-exporter/index.ts
  • packages/diagram-block/src/email-exporter/emailExporter.test.tsx
  • packages/diagram-block/src/email-exporter/index.tsx
  • packages/diagram-block/src/exporterTestUtil.ts
  • packages/diagram-block/src/helpers/getDiagramPlainTextContent.ts
  • packages/diagram-block/src/helpers/index.ts
  • packages/diagram-block/src/helpers/renderDiagramToImage.browser.test.ts
  • packages/diagram-block/src/helpers/renderDiagramToImage.ts
  • packages/diagram-block/src/i18n/dictionary.ts
  • packages/diagram-block/src/i18n/locales/ar.ts
  • packages/diagram-block/src/i18n/locales/de.ts
  • packages/diagram-block/src/i18n/locales/en.ts
  • packages/diagram-block/src/i18n/locales/es.ts
  • packages/diagram-block/src/i18n/locales/fa.ts
  • packages/diagram-block/src/i18n/locales/fr.ts
  • packages/diagram-block/src/i18n/locales/he.ts
  • packages/diagram-block/src/i18n/locales/hr.ts
  • packages/diagram-block/src/i18n/locales/is.ts
  • packages/diagram-block/src/i18n/locales/it.ts
  • packages/diagram-block/src/i18n/locales/ja.ts
  • packages/diagram-block/src/i18n/locales/ko.ts
  • packages/diagram-block/src/i18n/locales/nl.ts
  • packages/diagram-block/src/i18n/locales/no.ts
  • packages/diagram-block/src/i18n/locales/pl.ts
  • packages/diagram-block/src/i18n/locales/pt.ts
  • packages/diagram-block/src/i18n/locales/ru.ts
  • packages/diagram-block/src/i18n/locales/sk.ts
  • packages/diagram-block/src/i18n/locales/uk.ts
  • packages/diagram-block/src/i18n/locales/uz.ts
  • packages/diagram-block/src/i18n/locales/vi.ts
  • packages/diagram-block/src/i18n/locales/zh-tw.ts
  • packages/diagram-block/src/i18n/locales/zh.ts
  • packages/diagram-block/src/odt-exporter/index.ts
  • packages/diagram-block/src/odt-exporter/odtExporter.test.ts
  • packages/diagram-block/src/pdf-exporter/index.tsx
  • packages/diagram-block/src/pdf-exporter/pdfExporter.test.tsx
  • packages/diagram-block/tsconfig.json
  • packages/diagram-block/vite.config.ts
  • packages/math-block/package.json
  • packages/math-block/src/block/helpers/render/MathBlockPreviewWithPopup.tsx
  • packages/math-block/src/block/helpers/toExternalHTML/BlockMathMLElement.tsx
  • packages/math-block/src/docx-exporter/docxExporter.test.ts
  • packages/math-block/src/docx-exporter/index.ts
  • packages/math-block/src/email-exporter/emailExporter.test.tsx
  • packages/math-block/src/email-exporter/index.tsx
  • packages/math-block/src/exporterHelpers/latexToMathML.ts
  • packages/math-block/src/exporterHelpers/renderMathToImage.browser.test.ts
  • packages/math-block/src/exporterHelpers/renderMathToImage.test.ts
  • packages/math-block/src/exporterHelpers/renderMathToImage.ts
  • packages/math-block/src/helpers/getMathPlainTextContent.ts
  • packages/math-block/src/helpers/index.ts
  • packages/math-block/src/i18n/dictionary.ts
  • packages/math-block/src/i18n/locales/ar.ts
  • packages/math-block/src/i18n/locales/de.ts
  • packages/math-block/src/i18n/locales/en.ts
  • packages/math-block/src/i18n/locales/es.ts
  • packages/math-block/src/i18n/locales/fa.ts
  • packages/math-block/src/i18n/locales/fr.ts
  • packages/math-block/src/i18n/locales/he.ts
  • packages/math-block/src/i18n/locales/hr.ts
  • packages/math-block/src/i18n/locales/is.ts
  • packages/math-block/src/i18n/locales/it.ts
  • packages/math-block/src/i18n/locales/ja.ts
  • packages/math-block/src/i18n/locales/ko.ts
  • packages/math-block/src/i18n/locales/nl.ts
  • packages/math-block/src/i18n/locales/no.ts
  • packages/math-block/src/i18n/locales/pl.ts
  • packages/math-block/src/i18n/locales/pt.ts
  • packages/math-block/src/i18n/locales/ru.ts
  • packages/math-block/src/i18n/locales/sk.ts
  • packages/math-block/src/i18n/locales/uk.ts
  • packages/math-block/src/i18n/locales/uz.ts
  • packages/math-block/src/i18n/locales/vi.ts
  • packages/math-block/src/i18n/locales/zh-tw.ts
  • packages/math-block/src/i18n/locales/zh.ts
  • packages/math-block/src/inlineContent/helpers/render/MathInlinePreviewWithPopup.tsx
  • packages/math-block/src/inlineContent/helpers/toExternalHTML/InlineMathMLElement.tsx
  • packages/math-block/src/odt-exporter/index.ts
  • packages/math-block/src/odt-exporter/odtExporter.test.ts
  • packages/math-block/src/pdf-exporter/index.tsx
  • packages/math-block/src/pdf-exporter/pdfExporter.test.tsx
  • packages/math-block/tsconfig.json
  • packages/math-block/vite.config.ts
  • packages/xl-docx-exporter/package.json
  • packages/xl-docx-exporter/src/diagram-block/index.ts
  • packages/xl-docx-exporter/src/docx/defaultSchema/blocks.ts
  • packages/xl-docx-exporter/src/docx/defaultSchema/inlinecontent.ts
  • packages/xl-docx-exporter/src/docx/docxExporter.test.ts
  • packages/xl-docx-exporter/src/math-block/index.ts
  • packages/xl-docx-exporter/vite.config.ts
  • packages/xl-email-exporter/src/react-email/defaultSchema/blocks.tsx
  • packages/xl-email-exporter/src/react-email/defaultSchema/inlinecontent.tsx
  • packages/xl-email-exporter/src/react-email/imageDelivery.test.ts
  • packages/xl-email-exporter/src/react-email/imageDelivery.ts
  • packages/xl-email-exporter/src/react-email/index.ts
  • packages/xl-email-exporter/src/react-email/reactEmailExporter.test.tsx
  • packages/xl-odt-exporter/package.json
  • packages/xl-odt-exporter/src/diagram-block/index.ts
  • packages/xl-odt-exporter/src/math-block/index.tsx
  • packages/xl-odt-exporter/src/odt/defaultSchema/blocks.tsx
  • packages/xl-odt-exporter/src/odt/defaultSchema/inlineContent.tsx
  • packages/xl-odt-exporter/src/odt/odtExporter.test.ts
  • packages/xl-odt-exporter/vite.config.ts
  • packages/xl-pdf-exporter/package.json
  • packages/xl-pdf-exporter/src/diagram-block/index.tsx
  • packages/xl-pdf-exporter/src/math-block/index.tsx
  • packages/xl-pdf-exporter/src/pdf/defaultSchema/blocks.tsx
  • packages/xl-pdf-exporter/src/pdf/defaultSchema/inlinecontent.tsx
  • packages/xl-pdf-exporter/src/pdf/pdfExporter.test.tsx
  • packages/xl-pdf-exporter/vite.config.ts
  • playground/src/examples.gen.tsx
  • playground/tsconfig.json
  • playground/vite.config.ts
  • pnpm-workspace.yaml
  • shared/package.json
  • shared/util/browserImageTestUtil.ts
  • shared/util/odtTestUtil.ts
  • shared/vite.config.ts
  • tests/docker-run.sh
  • tests/package.json
  • tests/src/end-to-end/exporters/exporterImages.test.tsx
  • tests/src/end-to-end/screenshots/screenshotFull.test.tsx
  • tests/src/end-to-end/static/static.test.tsx
  • tests/src/unit/react/formatConversion/export/exportTestInstances.ts
  • tests/src/utils/screenshotFull.ts
  • tests/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

Comment on lines 50 to 52
```
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]
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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

Comment on lines +146 to +149
const renderDiagram: RenderDiagram = async (source) => {
// Render the Mermaid source to an image with your renderer of choice.
return { dataURL, width, height };
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Comment on lines +205 to +207
function serializeMathBlock(el: HTMLElement, ctx: SerializeContext): string {
const latex = extractMathLatexSource(el);
return ctx.indent + "$$\n" + latex + "\n$$\n\n";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Suggested change
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.

Comment on lines +110 to +122
return new Paragraph({
alignment: AlignmentType.CENTER,
children: [
new ImageRun({
data: result.image.data,
type: imageType,
transformation: {
width: result.image.width,
height: result.image.height,
},
}),
],
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Suggested change
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.

Comment on lines +61 to +72
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" }),
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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:


🏁 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 || true

Repository: 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:


🌐 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:


🏁 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 500

Repository: 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("---")
PY

Repository: 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("---")
PY

Repository: 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]);
  }
}
JS

Repository: 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.

Comment on lines +31 to +33
exporter: {
invalid_formula: (source: string) => `صيغة غير صالحة "${source}"`,
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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-L33
  • packages/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.

Comment on lines +37 to +50
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",
}),
),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 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/src

Repository: 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/src

Repository: 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.")
PY

Repository: 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.

Comment thread playground/tsconfig.json
Comment on lines +19 to +22
"rootDir": "..",
"paths": {
"@shared/*": ["../shared/*"]
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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 || true

Repository: 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 || true

Repository: 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:


🏁 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`")
PY

Repository: 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

Comment on lines +20 to +24
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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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 -240

Repository: 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.")
PY

Repository: 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:


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.
@pkg-pr-new

pkg-pr-new Bot commented Aug 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

@blocknote/ariakit

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/ariakit@2961

@blocknote/code-block

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/code-block@2961

@blocknote/core

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/core@2961

@blocknote/diagram-block

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/diagram-block@2961

@blocknote/mantine

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/mantine@2961

@blocknote/math-block

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/math-block@2961

@blocknote/react

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/react@2961

@blocknote/server-util

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/server-util@2961

@blocknote/shadcn

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/shadcn@2961

@blocknote/xl-ai

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/xl-ai@2961

@blocknote/xl-docx-exporter

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/xl-docx-exporter@2961

@blocknote/xl-email-exporter

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/xl-email-exporter@2961

@blocknote/xl-multi-column

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/xl-multi-column@2961

@blocknote/xl-odt-exporter

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/xl-odt-exporter@2961

@blocknote/xl-pdf-exporter

npm i https://pkg.pr.new/TypeCellOS/BlockNote/@blocknote/xl-pdf-exporter@2961

commit: 0a23145

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants