Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .claude/skills/docs-skill/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,16 @@ The documentation site lives in `docs/content/docs` (fumadocs + Next.js). These
- **Live examples cap the setup they demonstrate.** Place an `<Example>` embed after the prose has introduced everything the example's code uses — an embed whose `App.tsx` shows APIs the page never mentions teaches by confusion. Introduced means named, given a purpose, and linked — the example itself (and the linked component pages) can carry the full wiring; expanding every integration inline pushes the example too far down the page. Not at the top (a demo without context motivates but doesn't teach) and not at the bottom (readers rarely reach it); at the end of the "getting it working" narrative, before advanced/optional topics. If an example uses more API than the page should cover, simplify the example rather than the rule.

- **Signature snippets tell the truth for one API.** A snippet formatted as a type signature documents exactly that export — never fold variant differences ("only for the X subpath…") into a doc-comment inside it. Show the signature that is true everywhere, and describe variant-specific options as prose in the variant's own section.
- **Docs follow the dependency direction.** When a package builds on another, the base layer's page owns the shared mechanisms (mappings, formats, behaviors) and never points "up" to a consumer page for its own concepts; consumer pages link down. Consumer pages still document their own API surfaces (option lists, signatures) explicitly rather than delegating them, per the signature rule below. Name sections for every layer they serve ("Typst / PDF", not just "PDF") so the base layer isn't erased.
- **Confine a cross-cutting concept to one designated section per page.** A page's headline concept (a conformance standard, an offline guarantee) gets one owning section; the intro may link to it once, and other sections mention it only where the reader must act on it there. Sprinkled re-mentions read as emphasis when written but age as duplication.
- **The hero snippet is the happy path only.** The first usage snippet shows the shortest end-to-end flow and nothing else; auxiliary mechanisms (asset maps, secondary outputs, tuning knobs) move to their own short sections even when genuinely relevant. Same for prose caveats aimed at a niche audience (a live-preview memory note): put them where that audience looks, not in the getting-started flow.
- **Caveats must be actionable.** Only note a limitation if the reader can do something with it (install a package, avoid a pattern, pass an option). Speculative hedges ("rare X may behave differently") and defensive implementation details (what a function guards against internally) erode trust without helping anyone act — cut them.

## Prose style

- Avoid em-dash-heavy prose; prefer commas, colons, semicolons, periods, or parentheses, choosing per sentence rather than substituting mechanically. An em-dash is fine occasionally; several per section reads as filler.
- Before listing something as a _requirement_, verify the reader can actually fail it. A "requirement" the implementation always satisfies automatically (e.g. auto-derived alt text) is at most a quality tip, phrased as one.

## Verifying content

- **Verify snippets against the actual package exports, not memory or existing docs.** APIs drift; grep the package source for every symbol a snippet imports (`export function X` / `export const X`) and check option names and shapes. Content copied forward without this check stays wrong after refactors.
Expand Down
4 changes: 4 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,9 @@
.git
**/test-results
**/blob-report
# The Typst compiler's Rust build tree (GBs) and wasm output: never image
# content - docker-run.sh bind-mounts pkg/ at run time.
**/rust/target
packages/xl-typst-compiler/pkg
**/playwright-report
tests/.vitest-attachments
71 changes: 66 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,16 @@ jobs:
- name: Install Dependencies
run: vp install

# `vp run -r build` includes @blocknote/xl-typst-compiler, whose build
# task compiles its Rust wasm when missing or stale (rustup is
# preinstalled on the runners; the pinned toolchain + wasm32 target
# auto-provision from rust/rust-toolchain.toml).
- name: Build packages
run: vp run -r build

- name: Lint packages
run: vp lint

- name: Install PDF tooling (veraPDF conformance gate + poppler visual snapshots)
run: bash tests/scripts/install-pdf-tooling.sh

- name: Run unit tests
run: vp run test

Expand All @@ -55,18 +56,72 @@ jobs:

- name: Soft release
id: soft-release
run: vp dlx pkg-pr-new publish './packages/*' # TODO disabled only for AI branch--compact
# xl-typst-compiler is excluded: its wasm makes the package ~25MB,
# which pkg.pr.new rejects (413; multipart uploads are whitelisted -
# https://github.com/stackblitz-labs/pkg.pr.new/blob/main/.whitelist).
# Preview installs of the PDF exporter therefore lack the compiler
# until the repo is whitelisted or the wasm ships separately.
# TODO disabled only for AI branch--compact
run: |
packages=()
for dir in ./packages/*/; do
if [[ "$dir" != *"/xl-typst-compiler/"* ]]; then
packages+=("${dir%/}")
fi
done
vp dlx pkg-pr-new publish "${packages[@]}"

build-typst-compiler:
# The one package the e2e suite consumes through build outputs instead of
# src/ aliases: @blocknote/xl-typst-compiler (its /pkg and /wasm subpaths
# are wasm build artifacts - see tests/vite.config.browser.ts). Built once
# on a bare runner - the Playwright container has no C toolchain for the
# Rust build - and shared with the shards as an artifact.
name: Build Typst compiler
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 100
persist-credentials: false

- uses: voidzero-dev/setup-vp@313600b80b104eadebb9111787d37a2e83e014ca # v1.17.0
with:
node-version-file: ".node-version"
cache: true

- name: Install dependencies
run: vp install

# Compiles the Rust wasm when missing/stale (rustup is bootstrapped by
# the build task itself) plus the TS wrapper's dist/types.
- name: Build Typst compiler package
run: vp run --filter @blocknote/xl-typst-compiler build

- name: Upload compiler build outputs
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: typst-compiler-build
path: |
packages/xl-typst-compiler/pkg
packages/xl-typst-compiler/dist
packages/xl-typst-compiler/types
include-hidden-files: true

e2e:
# Vitest Browser Mode runs in the Playwright Linux container — the same
# environment as the local Docker run — so behaviour matches local dev.
# The suite resolves every `@blocknote/*` import to its `src/` via the
# aliases in `tests/vite.config.browser.ts` (vite transpiles the package
# sources on the fly), so the packages do NOT need to be built to `dist`
# first — `vp install` + the checked-out sources are enough.
# first — `vp install` + the checked-out sources are enough (the one
# exception, xl-typst-compiler, arrives prebuilt from the
# build-typst-compiler job).
name: "E2E - ${{ matrix.browser }} (${{ matrix.shardIndex }}/${{ matrix.shardTotal }})"
runs-on: ubuntu-latest
timeout-minutes: 30
needs: build-typst-compiler
container:
image: mcr.microsoft.com/playwright:v1.60.0-noble
strategy:
Expand All @@ -89,6 +144,12 @@ jobs:
- name: Install dependencies
run: vp install

- name: Download Typst compiler build outputs
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: typst-compiler-build
path: packages/xl-typst-compiler

# No preview server: Vitest Browser Mode serves the tests + mounted example
# apps itself. `--browser` selects this matrix job's browser and
# `--shard=<index>/<total>` splits that browser's test files across two
Expand Down
3 changes: 0 additions & 3 deletions .github/workflows/fresh-install-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,6 @@ jobs:
name: Build packages
run: vp run -r build

- name: Install PDF tooling (veraPDF conformance gate + poppler visual snapshots)
run: bash tests/scripts/install-pdf-tooling.sh

- id: run_unit_tests
name: Run unit tests
run: vp run test
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ jobs:
- name: Prebuild
run: cp README.md packages/core/README.md && cp README.md packages/react/README.md

# `vp run -r build` includes @blocknote/xl-typst-compiler, whose build
# task compiles its Rust wasm (published inside the package as pkg/);
# rustup auto-provisions the pinned toolchain from
# rust/rust-toolchain.toml.
- name: Build packages
run: vp run -r build

Expand Down
20 changes: 14 additions & 6 deletions docs/app/demo/_components/DemoEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import {
} from "@blocknote/xl-pdf-exporter";
// Bundle the Typst compiler wasm (resolved to a local asset in
// next.config.ts) instead of loading it from a CDN.
import compilerWasmUrl from "@myriaddreamin/typst-ts-web-compiler/wasm?url";
import compilerWasmUrl from "@blocknote/xl-typst-compiler/wasm?url";
import { DefaultChatTransport } from "ai";
import { useTheme } from "next-themes";
import { useEffect, useMemo, useState } from "react";
Expand Down Expand Up @@ -352,12 +352,20 @@ function DemoEditorInner({
const exporter = new PDFExporter(
editor.schema,
typstDefaultSchemaMappings,
{ wasm: compilerWasmUrl },
);
blob = await exporter.toBlob(
editor.document,
{ getModule: () => compilerWasmUrl },
{ title: "BlockNote demo document" },
);
const result = await exporter.toPDF(editor.document, {
title: "BlockNote demo document",
lang: "en",
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if (result.error) {
throw new Error(
`PDF export failed: ${result.compileErrors
.map((d) => d.message)
.join("; ")}`,
);
}
blob = result.blob;
} else if (format === "docx") {
const exporter = new DOCXExporter(
editor.schema,
Expand Down
4 changes: 2 additions & 2 deletions docs/components/typstCompilerWasmUrl.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// The Typst compiler wasm for the docs build (see next.config.ts): the
// pdf-ua example bundles it via Vite's `?url`; under the docs site the
// import is aliased here. `new URL(..., import.meta.url)` is Turbopack's
// static-asset reference: the ~29MB wasm is emitted as a hashed static
// static-asset reference: the ~25MB wasm is emitted as a hashed static
// file and served by the site itself - no CDN involved, and the version
// can't skew from the installed package because it IS the installed file.
const url = new URL(
"../node_modules/@myriaddreamin/typst-ts-web-compiler/pkg/typst_ts_web_compiler_bg.wasm",
"../node_modules/@blocknote/xl-typst-compiler/pkg/blocknote_typst_wasm_bg.wasm",
import.meta.url,
).href;
export default url;
4 changes: 0 additions & 4 deletions docs/components/typstRendererStub.ts

This file was deleted.

11 changes: 6 additions & 5 deletions docs/content/docs/features/blocks/math.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,13 @@ import {
// Spread into the ODTExporter's mappings exactly as for DOCX above.
```

### PDF
### Typst / PDF

With the [PDF exporter](/docs/features/export/pdf) (or the standalone
[Typst exporter](/docs/features/export/typst) — they share mappings), math
blocks and inline math export as **native Typst equations**: real, selectable
text, no rasterization, so they also work server-side out of the box:
With the [Typst exporter](/docs/features/export/typst) and the
[PDF exporter](/docs/features/export/pdf) built on it (they share mappings),
math blocks and inline math export as **native Typst equations**: real,
selectable text, no rasterization, so they also work server-side out of the
box:

```typescript
import {
Expand Down
Loading
Loading