Skip to content

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

Merged
nperez0111 merged 22 commits into
code-block-previewsfrom
math-diagram-exporters
Aug 12, 2026
Merged

Math & diagram exporters: block-package mappings, typed errors, ExportImage contract, email export#2961
nperez0111 merged 22 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 Error Error Aug 12, 2026 3:32pm
blocknote-website Ready Ready Preview Aug 12, 2026 3:32pm

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: 8c0f51c4-b1d3-4963-b77f-8353084724d5

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.
Comment thread docs/content/docs/features/export/docx.mdx
Comment thread docs/content/docs/features/export/email.mdx
… 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.
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: 651dadc

- Docs: the renderDiagram examples return the ExportImage shape (they
  showed the pre-refactor dataURL shape), and the placeholder wording
  matches the source-identifying placeholders.
- Markdown: nested multi-line blocks (math and code fences) indent every
  line including the closing delimiter - an unindented line would end
  the parent list item. Covered by nested toggle-item snapshot tests
  (external HTML flattens other list items' non-list children, so
  toggles are where nesting actually reaches the serializer).
- DOCX diagrams clamp their display width to the page body (aspect
  preserved), like the email mapping; with a scaled-extents test.
- ODT: ODTExporter deduplicates identical automatic styles at the
  source (both registerStyle and styled-text BN_T styles) - documents
  with many alike blocks or styled runs no longer accumulate duplicate
  styles. Unit-tested; snapshots shrink accordingly.
- RTL locales (ar/fa/he) wrap the interpolated LTR source in bidi
  isolates in both block packages.
- Playground build-mode alias map gains @shared; canvas 2D context gets
  an explicit guard in the image test util; testing-skill fence gets a
  language.
The snippets used DOCXExporter & co. without showing their imports
(review feedback).
composite: true,
// The repo-wide alias for the shared test-utils package, for examples
// that depend on it (private, so it only resolves inside the monorepo).
...(project.config.dependencies?.["@blocknote/shared"]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why not just include this in the package.json template by default?

// metadata, so stub images must be actual PNGs.
export const pngBytes = Uint8Array.from(
atob(
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This seems a bit esoteric

} from "@blocknote/core";
import { plainContentToString } from "@blocknote/core";
import { AlignmentType, ImportedXmlComponent, Paragraph, TextRun } from "docx";
import { mml2omml } from "mathml2omml";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is this dep always installed when the math-block package is installed? Probably not a big deal but just checking since I expect not everyone who uses the math block will need docx export.

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.

sub-export so should be fine

import type { ExportImage } from "@blocknote/core";
import { liteAdaptor } from "mathjax-full/js/adaptors/liteAdaptor.js";
import { RegisterHTMLHandler } from "mathjax-full/js/handlers/html.js";
import { TeX } from "mathjax-full/js/input/tex.js";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If we have to use mathjax for this, maybe makes sense to remove the katex dep and use mathjax for HTML too? Both packages do sort of the same thing so it feels like we should just stick to one

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.

Looked into it, it is worth only doing katex on the live editor, and mathjax is only ever used for the export path (if pulled in since it is a sub-export). This tries to pull only the minimal possible. But the import/export that katex does in the browser would be pretty heavy, so I'll stick with this

…iagram-exporters

# Conflicts:
#	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-email-exporter/src/react-email/defaultSchema/blocks.tsx
#	packages/xl-email-exporter/src/react-email/defaultSchema/inlinecontent.tsx
#	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-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
@nperez0111
nperez0111 merged commit 20d74c6 into main Aug 12, 2026
13 of 18 checks passed
@nperez0111
nperez0111 deleted the math-diagram-exporters branch August 12, 2026 15:59
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.

4 participants