diff --git a/.claude/skills/create-example/SKILL.md b/.claude/skills/create-example/SKILL.md
index dbf95af451..47f91486d0 100644
--- a/.claude/skills/create-example/SKILL.md
+++ b/.claude/skills/create-example/SKILL.md
@@ -45,7 +45,7 @@ Contains all of the example's configuration. Here's an annotated example (from `
"Slash Menu",
"Appearance & Styling"
],
-
+
"dependencies": {
"@mui/icons-material": "^5.16.1",
"@mui/material": "^5.16.1"
@@ -54,6 +54,11 @@ Contains all of the example's configuration. Here's an annotated example (from `
"pro": true
```
+Two constraints on dependencies:
+
+- `"uiLib"` (optional; `"mantine"` | `"ariakit"` | `"shadcn"`, default `"mantine"`) selects which single BlockNote UI package the generated `package.json` depends on. The mantine variant also carries `@mantine/core`/`@mantine/hooks` (peer dependencies of `@blocknote/mantine`). Do not add UI packages to `"dependencies"` manually; set `uiLib` instead.
+- Examples with `"docs": true` are all compiled into the docs site's single dependency context, so `vp run gen` fails if two docs-embedded examples declare incompatible version ranges of the same package (e.g. `lib0` for yjs 13 vs 14). Reconcile the ranges or set `"docs": false` on one of them.
+
`README.md`
A Markdown description of the example. Made of four parts:
diff --git a/.claude/skills/knip-skill/SKILL.md b/.claude/skills/knip-skill/SKILL.md
new file mode 100644
index 0000000000..42fa1ef87d
--- /dev/null
+++ b/.claude/skills/knip-skill/SKILL.md
@@ -0,0 +1,43 @@
+---
+name: knip-skill
+description: How Knip is configured in this repo and how to act on its findings. Should be used automatically whenever Knip is run, mentioned, configured, or reports unused files/dependencies/exports — including when deciding whether a flagged item is safe to delete.
+---
+
+Knip checks for unused files, dependencies, and exports. Config lives in `knip.json` at the repo root. Several parts of this repo look "unused" to static analysis but are load-bearing, so **verify every finding before deleting** — the config encodes a lot of hard-won exceptions.
+
+# Running Knip
+
+1. **Always regenerate first**: `vp run --filter @blocknote/dev-scripts gen`. Example `package.json`s, `playground/src/examples.gen.tsx`, and part of `docs/package.json`'s dependencies are generated from `.bnexample.json` manifests. Running Knip against stale generated files produces phantom findings.
+2. Run via the root script: `pnpm knip` (this is `knip --no-config-hints`). Don't run bare `knip` from a subdirectory.
+3. Knip respects `.gitignore` for entry/project file discovery — gitignored files (e.g. `docs/components/example/generated/`) are never picked up as project files, so they don't appear in reports. That is expected, not a coverage bug.
+4. Knip is **not wired into CI**. CI only checks that `gen` output is committed and current (`build.yml` "Check generated files are up to date"). Keeping `pnpm knip` at zero findings is a manual discipline — run it before finishing any branch that touches dependencies or exports.
+
+# Triaging findings — check these before deleting anything
+
+A flagged item is a **false positive** (fix the config, not the code) when it is:
+
+- **Referenced by a string path**, not an import — e.g. `docs/components/typstCompilerWasmUrl.ts` via turbopack aliases in `docs/next.config.ts`. Add it as an `entry` if it's real source that should stay analyzed; use the root `ignore` list only when suppressing all issue types for it is acceptable (`ignore` silences everything about a file, not just "unused file" — `ignoreFiles` is the narrower option that only suppresses the unused-file report).
+- **A binary invoked from `node_modules/.bin` in a script** — e.g. `wasm-pack` in `packages/xl-typst-compiler/scripts/ensure-wasm.mjs`. Add to that workspace's `ignoreDependencies`.
+- **A subpath entry of a package's exports map** — packages with entries beyond `"."` need them listed in the workspace `entry` config (see `packages/math-block`, `packages/diagram-block` with `src/*-exporter/index.{ts,tsx}`, and `packages/xl-pdf-exporter` with `src/react-pdf/index.ts`). Check the package.json `exports` field before deleting an "unused file" that is an `index.ts`.
+- **A workspace dep that exists for build ordering, not imports** — e.g. `@blocknote/shared` and `@blocknote/xl-typst-compiler` in root `ignoreDependencies`, and all of playground's `@blocknote/*` deps (see below). Removing them breaks topological build order (locally and on Vercel) even though no source file imports them.
+- **Consumed via a namespace import** (`import * as x` then `x.fn(...)` passed around) — Knip can miss this. Keep the export and mark it with a `@public` JSDoc tag, which exempts it (see `getLLMText` in `docs/lib/source/examples.ts`).
+
+Only after ruling those out is a finding a **true positive**: delete the file, remove the dep, or un-export the symbol.
+
+**Be very careful with `knip --fix`.** It once un-exported `getLLMText`, which broke the docs production build because only `next build` (not `types:check`) runs the full route typecheck. Prefer manual fixes; if you use `--fix`, review every hunk.
+
+# Non-standard workspace configs (the "why" behind knip.json)
+
+- **`docs`**: `ignoreDependencies: [".+"]` — dependency checking is disabled **by design**. `gen` syncs deps from docs-embedded example manifests into `docs/package.json`, and docs consumes things through paths Knip can't follow. Do not "improve" this by narrowing the pattern; do not hand-prune docs deps that gen will re-add.
+- **`playground`**: `@blocknote/.+` deps drive Vercel's `--filter '@blocknote/example-editor...'` build ordering — never remove them. `katex` and `tex2typst` are resolved from the playground's own `node_modules` by build-mode vite aliases in `playground/vite.config.ts` (a Vercel filtered-install workaround) — keep the ignore list in sync with those aliases: if an alias is added/removed there, mirror it in the dep list and `knip.json`. Everything else was deliberately pruned; playground examples resolve their own deps via per-example `node_modules` (relative imports).
+- **`examples/*/*`**: `.bnexample.json` lists only what the example's source imports; the generator auto-injects `@blocknote/core`, `@blocknote/react`, the `uiLib` package (+ `@mantine/core`/`@mantine/hooks` for mantine), `react`, `react-dom` — hence the shared `ignoreDependencies`. A few examples have per-example configs extending it. **Never edit a generated example `package.json` to satisfy Knip** — fix `.bnexample.json` and rerun `gen`.
+- Example `"@blocknote/*": "latest"` version specs are deliberate (the StackBlitz open-a-directory repro contract, guarded by `fresh-install-tests.yml`); pnpm's workspace settings resolve them to workspace packages locally. Knip flagging or you pinning these is wrong.
+- **`tests`**: has `paths` for `@examples/*`/`@shared/*`; the ambient `declare module` for `@examples` is deliberate (avoids TS6059) — don't convert it to tsconfig paths. `@blocknote/ariakit`/`@blocknote/shadcn` are used by e2e fixtures in ways Knip misses.
+- **`packages/dev-scripts`**: templates (`examples/template-react/*.template.tsx`) are entries; `vite-env.d.ts.template.tsx` is ignored (it's emitted verbatim, never imported).
+
+# When adding new packages or deps
+
+- New package with subpath exports → add an `entry` config for it in `knip.json`.
+- New workspace dep added only for build ordering → add it to root `ignoreDependencies` with the others, don't leave Knip red.
+- New string-referenced source file → prefer an `entry` for its workspace; fall back to the root `ignore` list (accepting the blind spot: `ignore` suppresses all issue types for that file). Generated files should be gitignored, which keeps them out of analysis entirely.
+- After any of the above, `pnpm knip` must exit 0 before the branch is done.
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000..eb1fb001f4
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,3 @@
+# Committed output of `pnpm run gen` — collapsed in GitHub diffs and excluded
+# from language stats. CI (build.yml) verifies it stays in sync with examples/.
+playground/src/examples.gen.tsx linguist-generated=true
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index cc5b7941bc..212748ecc9 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -31,6 +31,12 @@ jobs:
- name: Install Dependencies
run: vp install
+ - name: Check generated files are up to date
+ run: |
+ vp run --filter @blocknote/dev-scripts gen
+ git diff --exit-code || (echo "Generated files are out of date. Run 'pnpm run gen' and commit the result." && exit 1)
+ test -z "$(git ls-files --others --exclude-standard)" || (echo "Generated files are missing from the commit. Run 'pnpm run gen' and commit the result." && git ls-files --others --exclude-standard && exit 1)
+
# `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
diff --git a/docs/app/(home)/_components/FAQ.tsx b/docs/app/(home)/_components/FAQ.tsx
deleted file mode 100644
index 5fbe0bda40..0000000000
--- a/docs/app/(home)/_components/FAQ.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import React from "react";
-
-const faqs = [
- {
- question: "Isn't it easier to use a Headless editor framework?",
- answer:
- "There are a number of really powerful headless text editor frameworks available. In fact, BlockNote is built on Prosemirror and TipTap. However, even when using a headless library, it takes several months and requires deep expertise to build a fully-featured editor with a polished UI that your users expect.",
- },
- {
- question: "Is BlockNote ready for production use?",
- answer:
- "BlockNote is used by dozens of companies in production, ranging from startups to large enterprises and public institutions. Also, we didn't reinvent the wheel. The core editor is built on top of Prosemirror - a battle tested framework that powers software from Atlassian, Gitlab, the New York Times, and many others.",
- },
- {
- question: "Can I add my own extensions to BlockNote?",
- answer:
- "BlockNote comes with lot of functionality out-of-the-box, but we understand that every use case is different. You can easily customize the built-in UI Components, or create your own custom Blocks, Inline Content, and Styles. If you want to go even further, you can extend the core editor with additional Prosemirror or TipTap plugins.",
- },
- {
- question: "Is BlockNote really free?",
- answer:
- "100% of BlockNote is open source. We offer consultancy, support services and commercial licenses for specific XL packages to help sustain BlockNote. Explore our pricing page for more details.",
- },
-];
-
-export const FAQ: React.FC = () => {
- return (
-
- {faq.answer}
-
- Questions?
-
-
- {faq.question}
-
-
- Building a rich text editor is one of the hardest engineering - challenges on the web. It used to take months of specialized - work. -
-- We believe that great tools should be{" "} - sovereign by default. You shouldn't have - to choose between a cohesive UX and owning your - infrastructure. -
-- That's why we built BlockNote. A{" "} - batteries-included editor that gives you a - Notion-quality experience in minutes, while staying grounded - in open standards like{" "} - - ProseMirror - {" "} - and Yjs. -
-- Whether you're a startup or a public institution, you - deserve software that lasts. Join us to{" "} - - shape the future - - {" "} - of the open web. -
-- Forget low-level details. Work with a strongly typed API. - Get modern UI components out-of-the-box. -
-- "{testimonial.quote}" -
-- From startups to enterprises, teams choose BlockNote to build their - document experiences. -
-- {props.children} -
- ); -} - -export function SidebarItem({ - icon, - children, - ...props -}: LinkProps & { - icon?: ReactNode; -}) { - const pathname = usePathname(); - const ref = useRef{selected.title}
-- {selected.description} -
-{item.title}
-- {item.description} -
-BlockNote is an open source block-based editor:
", - "