From 11f08f7b2105da85bf8c826410d6987392a26c76 Mon Sep 17 00:00:00 2001 From: zbeyens Date: Wed, 20 May 2026 18:46:40 +0200 Subject: [PATCH 1/2] ci: add release docs workflow --- .agents/AGENTS.md | 24 +- .agents/rules/agent-browser-issue.mdc | 2 +- .agents/rules/browser-debug-setup.mdc | 124 - .agents/rules/dev-browser.mdc | 130 + .agents/rules/gpt-pro.mdc | 170 ++ .agents/rules/major-task.mdc | 2 +- .agents/rules/task.mdc | 8 +- .agents/skiller.toml | 5 +- .agents/skills/agent-browser-issue/SKILL.md | 2 +- .agents/skills/browser-debug-setup/SKILL.md | 128 - .agents/skills/codex-review/SKILL.md | 139 ++ .../skills/codex-review/scripts/codex-review | 314 +++ .agents/skills/dev-browser/SKILL.md | 123 +- .agents/skills/gpt-pro/SKILL.md | 174 ++ .agents/skills/major-task/SKILL.md | 2 +- .agents/skills/task/SKILL.md | 8 +- .claude/prompt.yml | 2 +- .claude/skills/browser-debug-setup | 1 - .claude/skills/codex-review | 1 + .claude/skills/gpt-pro | 1 + .codex/config.toml | 4 + .github/prompts/release-notes-rewrite.md | 41 + .github/workflows/changeset-auto-release.yml | 20 +- .github/workflows/release.yml | 156 +- .mcp.json | 5 + AGENTS.md | 24 +- apps/www/next-env.d.ts | 2 +- apps/www/next.config.ts | 10 + apps/www/package.json | 7 +- apps/www/src/app/layout.tsx | 2 + apps/www/src/components/mdx-components.tsx | 15 +- apps/www/src/components/release-index.tsx | 383 +++ apps/www/src/config/docs.ts | 10 +- apps/www/src/generated/release-index.json | 2178 +++++++++++++++++ .../fumadocs/fumadocs-mdx-components.tsx | 1 + content/migration/index.mdx | 1062 -------- content/migration/v48.cn.mdx | 4 +- content/migration/v48.mdx | 2 +- content/releases/index.mdx | 5 + .../2026-03-30-table-multi-cell-selection.md | 2 +- .../2026-04-04-footnote-inline-void-fix.md | 2 +- ...te-stale-duplicate-warning-after-delete.md | 2 +- .../2026-04-06-toc-interaction-polish.md | 4 +- ...-09-date-media-expansion-consensus-plan.md | 2 +- ...04-10-dnd-missing-context-runtime-guard.md | 2 +- ...h-delimiter-trigger-implementation-plan.md | 2 +- .../plans/2026-04-17-code-block-demo-debug.md | 4 +- docs/plans/2026-04-17-codeblock-regression.md | 4 +- ...-selection-coverage-and-list-regression.md | 2 +- ...2026-04-27-major-release-migration-sync.md | 226 ++ ...4527-ai-menu-streaming-anchor-undefined.md | 2 +- .../plans/4637-verify-space-overflow-repro.md | 4 +- ...ix-table-left-border-multirow-selection.md | 2 +- ...nistic-ids-and-timestamps-for-hydration.md | 2 +- ...generated-markers-must-use-jsx-comments.md | 70 + ...elease-docs-need-index-and-detail-pages.md | 98 + ...ent-controller-for-active-heading-state.md | 2 +- ...es-and-preserve-unparseable-legacy-text.md | 2 +- ...wser-highlight-must-match-server-output.md | 2 +- ...t-rules-must-delete-matched-marker-text.md | 2 +- package.json | 2 + packages/table/CHANGELOG.md | 2 + pnpm-lock.yaml | 19 + skills-lock.json | 10 +- tooling/config/test-suites.mjs | 1 + tooling/preset/.agents/AGENTS.md | 16 +- tooling/preset/.claude/prompt.yml | 2 +- tooling/preset/preset.toml | 2 +- tooling/scripts/published-package-tags.mjs | 44 + .../scripts/published-package-tags.test.mjs | 31 + tooling/scripts/release-notes.mjs | 500 ++++ tooling/scripts/release-notes.test.mjs | 251 ++ tooling/scripts/release-workflow.test.mjs | 72 + .../scripts/sync-version-package-releases.mjs | 544 ++++ .../sync-version-package-releases.test.mjs | 226 ++ 75 files changed, 6019 insertions(+), 1432 deletions(-) delete mode 100644 .agents/rules/browser-debug-setup.mdc create mode 100644 .agents/rules/dev-browser.mdc create mode 100644 .agents/rules/gpt-pro.mdc delete mode 100644 .agents/skills/browser-debug-setup/SKILL.md create mode 100644 .agents/skills/codex-review/SKILL.md create mode 100755 .agents/skills/codex-review/scripts/codex-review create mode 100644 .agents/skills/gpt-pro/SKILL.md delete mode 120000 .claude/skills/browser-debug-setup create mode 120000 .claude/skills/codex-review create mode 120000 .claude/skills/gpt-pro create mode 100644 .github/prompts/release-notes-rewrite.md create mode 100644 apps/www/src/components/release-index.tsx create mode 100644 apps/www/src/generated/release-index.json delete mode 100644 content/migration/index.mdx create mode 100644 content/releases/index.mdx create mode 100644 docs/plans/2026-04-27-major-release-migration-sync.md create mode 100644 docs/solutions/developer-experience/2026-04-27-mdx-generated-markers-must-use-jsx-comments.md create mode 100644 docs/solutions/developer-experience/2026-04-27-monorepo-release-docs-need-index-and-detail-pages.md create mode 100644 tooling/scripts/published-package-tags.mjs create mode 100644 tooling/scripts/published-package-tags.test.mjs create mode 100644 tooling/scripts/release-notes.mjs create mode 100644 tooling/scripts/release-notes.test.mjs create mode 100644 tooling/scripts/release-workflow.test.mjs create mode 100644 tooling/scripts/sync-version-package-releases.mjs create mode 100644 tooling/scripts/sync-version-package-releases.test.mjs diff --git a/.agents/AGENTS.md b/.agents/AGENTS.md index 3d1e8a06f2..2b61856690 100644 --- a/.agents/AGENTS.md +++ b/.agents/AGENTS.md @@ -61,25 +61,17 @@ When using the following skills, override the default behavior. - For issue-backed work, start the filename with the ticket number instead of `date+ticket`. Example: `docs/plans/4510-fix-schema.md` - For non-ticket work, keep the date-based format. Example: `docs/plans/2026-02-07-fix-schema.md` -`dev-browser`: - -- Use `dev-browser --connect http://127.0.0.1:9222` by default for browser work. Do not preflight `9222` first. -- Only inspect `9222` or use `browser-debug-setup` after a direct `dev-browser --connect http://127.0.0.1:9222` attempt fails. -- Reuse one persistent debug Chrome on `127.0.0.1:9222`. Do not spin up disposable browser instances unless the user asks. -- Use a dedicated Chrome `--user-data-dir` for that debug browser, not the user's normal daily Chrome data dir. -- Clone the signed-in Chrome profile into the dedicated debug dir, then launch the debug browser from that clone. -- On macOS, launch the debug browser with `open -na "Google Chrome" --args ... --remote-debugging-port=9222` so it opens as a separate Chrome instance without hijacking the user's normal window. -- Do not close or stop the user's connected debug browser. Leave that debug window open and reuse it. Close named pages only when needed. -- Keep scripts small and direct. Prefer `browser.getPage("persistent-main")` for the main app. -- Use `dev-browser` instead of `agent-browser` or next-devtools `browser_eval`. -- If `dev-browser` gets blocked by a human prompt or loops on the same step, stop and ask the user to unblock. After the unblock works: - - For Plate registry/browser proof, prefer `/blocks/[id]-demo` over docs wrappers when that standalone demo route exists. +Browser usage: + +- Always try `[@browser-use](plugin://browser-use@openai-bundled)` first for browser usage. +- Do not substitute Puppeteer, standalone Playwright, or raw Chrome DevTools for browser usage. +- For Plate registry/browser proof, prefer `/blocks/[id]-demo` over docs wrappers when that standalone demo route exists. `ce-*`: -- **plan:** Include `dev-browser` in acceptance criteria for browser features +- **plan:** Include Browser Use in acceptance criteria for browser features - **deepen-plan:** Context7 only when not covered by skills -- **work:** UI tasks require `dev-browser` BEFORE marking complete. Never guess. +- **work:** UI tasks require Browser Use BEFORE marking complete. Never guess. ## Commands @@ -185,7 +177,7 @@ pnpm --filter @platejs/core lint:fix - [ ] Typecheck (IF updated `.ts` files or typed test/build config): For package-scoped verification, follow the build-first sequence: `pnpm install` -> `pnpm turbo build --filter=...` -> `pnpm turbo typecheck --filter=...`. If unresolved workspace imports remain, run `pnpm build` at repo root, then rerun the package typecheck. For full repo verification, use `pnpm typecheck`. Do not default to `pnpm typecheck` for package verification. - [ ] Lint: Run `lint:fix` - [ ] PR gate (IF creating/updating a PR): Run `check` -- [ ] Browser verification (IF a browser surface changed): verify with `dev-browser` before done +- [ ] Browser verification (IF a browser surface changed): verify with Browser Use before done - [ ] ce-compound (SKIP if trivial): CRITICAL: After completing this request, you MUST evaluate whether it produced extractable knowledge. EVALUATION PROTOCOL (NON-NEGOTIABLE): (1) COMPLETE the user's request first (2) EVALUATE - Did this require non-obvious investigation or debugging? Was the solution something that would help in future similar situations? Did I discover something not immediately obvious from documentation? (3) IF YES to any: load `ce-compound` after the fix is verified and follow its workflow to capture the solution in `docs/solutions/` (4) IF NO to all: Skip - no extraction needed This is NOT optional. Failing to evaluate = valuable knowledge lost. ### Post Compact Recovery diff --git a/.agents/rules/agent-browser-issue.mdc b/.agents/rules/agent-browser-issue.mdc index 501597cd82..85cd25ee6c 100644 --- a/.agents/rules/agent-browser-issue.mdc +++ b/.agents/rules/agent-browser-issue.mdc @@ -1,5 +1,5 @@ --- -description: Open a concise GitHub follow-up for reusable dev-browser or agent-browser limitations. Use when browser automation is blocked by a likely tool-side issue that is worth fixing separately, especially for clicks, dropdowns, file inputs, focus traps, or other repeatable agent/browser failures. +description: Open a concise GitHub follow-up for reusable browser-use limitations. Use when browser automation is blocked by a likely tool-side issue that is worth fixing separately, especially for clicks, dropdowns, file inputs, focus traps, or other repeatable agent/browser failures. argument-hint: '[browser block summary]' disable-model-invocation: true --- diff --git a/.agents/rules/browser-debug-setup.mdc b/.agents/rules/browser-debug-setup.mdc deleted file mode 100644 index a1c98dac61..0000000000 --- a/.agents/rules/browser-debug-setup.mdc +++ /dev/null @@ -1,124 +0,0 @@ ---- -description: One-time setup for a persistent debug browser on `127.0.0.1:9222` for `dev-browser --connect`. Use when browser work is needed but no reusable debug browser is running yet. ---- - -# Browser Debug Setup - -Use this skill when `dev-browser --connect http://127.0.0.1:9222` fails because -no persistent debug browser is running yet. - -## Goal - -Get the user onto one persistent browser/profile that both the human and the -agent reuse. Minimize the `Allow remote debugging?` popup by keeping one -dedicated debug browser/profile alive. - -## Rules - -- Prefer one permanent debug browser/profile over disposable automation - browsers. -- Treat a custom `--user-data-dir` as mandatory, not optional. Chrome 136+ - basically wants remote debugging to happen from a dedicated profile. -- Keep auth in that profile. Do not fall back to cookie dumps or state files - unless the user asks. -- Use a separate signed-in Chrome profile for browser work, like `dev`. Do not - use the user's normal daily `Default` profile as the source profile. -- Clone that separate signed-in Chrome profile into the dedicated debug - `--user-data-dir`; do not point `9222` straight at the user's daily Chrome - data dir. -- On macOS, use `open -na "Google Chrome" --args ...` for the debug browser. - That starts a separate Chrome instance with the dedicated debug profile - without touching the user's normal Chrome window. - -## Preferred Shape - -Use a dedicated browser/profile with: - -- `--remote-debugging-address=127.0.0.1` -- `--remote-debugging-port=9222` -- a persistent `--user-data-dir=` - -Sign in once in that dedicated browser and keep reusing it for agent work. - -Quick sanity check: - -```bash -curl -sS http://127.0.0.1:9222/json/version -``` - -Healthy output includes a JSON object with `webSocketDebuggerUrl`. Empty output -or `404` means the wrong process owns `9222`. - -Then verify `dev-browser`: - -```bash -dev-browser --connect http://127.0.0.1:9222 <<'EOF' -const page = await browser.getPage("persistent-main"); -console.log(await page.title()); -EOF -``` - -If `dev-browser --connect http://127.0.0.1:9222` still cannot resolve CDP even -though `/json/version` is healthy, connect with the exact websocket URL: - -```bash -WS=$(curl -sS http://127.0.0.1:9222/json/version | jq -r '.webSocketDebuggerUrl') - -dev-browser --connect "$WS" <<'EOF' -const page = await browser.getPage("persistent-main"); -console.log(await page.title()); -EOF -``` - -## Google Chrome Path - -Default setup on macOS: - -1. Pick a separate signed-in Chrome profile for agent work, like `dev`, not - the daily `Default` profile. -2. Map that human-facing Chrome profile name to the real folder in `Local State`. -3. Clone that profile into the dedicated debug dir. -4. Launch a separate Chrome instance on `9222`. -5. Leave that debug window open and reuse it. - -```bash -python3 - <<'PY' -import json, pathlib -p = pathlib.Path('~/Library/Application Support/Google/Chrome/Local State').expanduser() -obj = json.loads(p.read_text()) -for key, val in obj.get('profile', {}).get('info_cache', {}).items(): - print(f"{key}\tname={val.get('name')}\tgaia_name={val.get('gaia_name')}") -PY - -# Example: if `dev` maps to `Profile 1`, clone `Profile 1`. -mkdir -p "$HOME/.config/google-chrome-debug-profile/Default" -rsync -a --delete \ - --exclude='Singleton*' \ - --exclude='DevToolsActivePort' \ - --exclude='lockfile' \ - "$HOME/Library/Application Support/Google/Chrome/Profile 1/" \ - "$HOME/.config/google-chrome-debug-profile/Default/" -cp "$HOME/Library/Application Support/Google/Chrome/Local State" \ - "$HOME/.config/google-chrome-debug-profile/Local State" - -open -na "Google Chrome" --args \ - --user-data-dir="$HOME/.config/google-chrome-debug-profile" \ - --profile-directory="Default" \ - --remote-debugging-address=127.0.0.1 \ - --remote-debugging-port=9222 -``` - -That keeps the signed-in identity while still satisfying Chrome's dedicated -`--user-data-dir` requirement. - -Then keep reusing that exact debug browser. Do not point `9222` at your normal -daily `Default` Chrome profile. - -## After Setup - -- Use `dev-browser --connect http://127.0.0.1:9222` for browser work. -- Reuse named pages like `persistent-main`. -- Do not stop the user's debug browser unless they ask. -- If the wrong Chrome steals `9222`, identify it with `lsof -nP -iTCP:9222 -sTCP:LISTEN`, - kill that listener, and relaunch the dedicated debug browser. Do not keep - debugging against a stale `404` or empty `/json/version` owner. diff --git a/.agents/rules/dev-browser.mdc b/.agents/rules/dev-browser.mdc new file mode 100644 index 0000000000..5818256f08 --- /dev/null +++ b/.agents/rules/dev-browser.mdc @@ -0,0 +1,130 @@ +--- +description: Fallback browser automation with persistent Chrome state. Use only when Browser Use is unavailable or blocked. +--- + +# Dev Browser + +Use this only as the fallback browser path when `[@browser-use](plugin://browser-use@openai-bundled)` is unavailable or blocked. + +Do not substitute Puppeteer, standalone Playwright, or raw Chrome DevTools for this fallback path. + +## Installation + +```bash +npm install -g dev-browser +dev-browser install +``` + +Run `dev-browser --help` to learn more. + +## Plate Defaults + +- Use `dev-browser --connect http://127.0.0.1:9222` by default. Do not preflight `9222` first. +- Only inspect `9222` after a direct `dev-browser --connect http://127.0.0.1:9222` attempt fails. +- Reuse one persistent debug Chrome on `127.0.0.1:9222`. Do not spin up disposable browser instances unless the user asks. +- Use a dedicated Chrome `--user-data-dir` for that debug browser, not the user's normal daily Chrome data dir. +- Clone the signed-in Chrome profile into the dedicated debug dir, then launch the debug browser from that clone. +- On macOS, launch the debug browser with `open -na "Google Chrome" --args ... --remote-debugging-port=9222` so it opens as a separate Chrome instance without hijacking the user's normal window. +- Do not close or stop the user's connected debug browser. Leave that debug window open and reuse it. Close named pages only when needed. +- Keep scripts small and direct. Prefer `browser.getPage("persistent-main")` for the main app. +- Use `dev-browser` instead of `agent-browser` or next-devtools `browser_eval`. +- For Plate registry/browser proof, prefer `/blocks/[id]-demo` over docs wrappers when that standalone demo route exists. +- If `dev-browser` gets blocked by a human prompt or loops on the same step, stop and ask the user to unblock. + +## Fallback Setup + +Use this only after `dev-browser --connect http://127.0.0.1:9222` fails because no reusable debug Chrome is available or the CDP endpoint is broken. + +## Rules + +- Prefer one permanent debug browser/profile over disposable automation browsers. +- Treat a custom `--user-data-dir` as mandatory, not optional. Chrome 136+ expects remote debugging to happen from a dedicated profile. +- Keep auth in that profile. Do not fall back to cookie dumps or state files unless the user asks. +- Use a separate signed-in Chrome profile for browser work, like `dev`. Do not use the user's normal daily `Default` profile as the source profile. +- Clone that separate signed-in Chrome profile into the dedicated debug `--user-data-dir`; do not point `9222` straight at the user's daily Chrome data dir. +- On macOS, use `open -na "Google Chrome" --args ...` for the debug browser. That starts a separate Chrome instance with the dedicated debug profile without touching the user's normal Chrome window. + +## Preferred Shape + +Use a dedicated browser/profile with: + +- `--remote-debugging-address=127.0.0.1` +- `--remote-debugging-port=9222` +- a persistent `--user-data-dir=` + +Sign in once in that dedicated browser and keep reusing it for agent work. + +Quick sanity check: + +```bash +curl -sS http://127.0.0.1:9222/json/version +``` + +Healthy output includes a JSON object with `webSocketDebuggerUrl`. Empty output or `404` means the wrong process owns `9222`. + +Then verify: + +```bash +dev-browser --connect http://127.0.0.1:9222 <<'EOF' +const page = await browser.getPage("persistent-main"); +console.log(await page.title()); +EOF +``` + +If direct connect still cannot resolve CDP even though `/json/version` is healthy, connect with the exact websocket URL: + +```bash +WS=$(curl -sS http://127.0.0.1:9222/json/version | jq -r '.webSocketDebuggerUrl') + +dev-browser --connect "$WS" <<'EOF' +const page = await browser.getPage("persistent-main"); +console.log(await page.title()); +EOF +``` + +## Google Chrome Path + +Default setup on macOS: + +1. Pick a separate signed-in Chrome profile for agent work, like `dev`, not the daily `Default` profile. +2. Map that human-facing Chrome profile name to the real folder in `Local State`. +3. Clone that profile into the dedicated debug dir. +4. Launch a separate Chrome instance on `9222`. +5. Leave that debug window open and reuse it. + +```bash +python3 - <<'PY' +import json, pathlib +p = pathlib.Path('~/Library/Application Support/Google/Chrome/Local State').expanduser() +obj = json.loads(p.read_text()) +for key, val in obj.get('profile', {}).get('info_cache', {}).items(): + print(f"{key}\tname={val.get('name')}\tgaia_name={val.get('gaia_name')}") +PY + +# Example: if `dev` maps to `Profile 1`, clone `Profile 1`. +mkdir -p "$HOME/.config/google-chrome-debug-profile/Default" +rsync -a --delete \ + --exclude='Singleton*' \ + --exclude='DevToolsActivePort' \ + --exclude='lockfile' \ + "$HOME/Library/Application Support/Google/Chrome/Profile 1/" \ + "$HOME/.config/google-chrome-debug-profile/Default/" +cp "$HOME/Library/Application Support/Google/Chrome/Local State" \ + "$HOME/.config/google-chrome-debug-profile/Local State" + +open -na "Google Chrome" --args \ + --user-data-dir="$HOME/.config/google-chrome-debug-profile" \ + --profile-directory="Default" \ + --remote-debugging-address=127.0.0.1 \ + --remote-debugging-port=9222 +``` + +Do not point `9222` at the normal daily `Default` Chrome profile. + +If the wrong Chrome steals `9222`, identify it with: + +```bash +lsof -nP -iTCP:9222 -sTCP:LISTEN +``` + +Kill that listener and relaunch the dedicated debug browser. Do not keep debugging against a stale `404` or empty `/json/version` owner. diff --git a/.agents/rules/gpt-pro.mdc b/.agents/rules/gpt-pro.mdc new file mode 100644 index 0000000000..141b76b04f --- /dev/null +++ b/.agents/rules/gpt-pro.mdc @@ -0,0 +1,170 @@ +--- +description: Create a self-contained GPT Pro or external-review prompt with full repo context, current state, evidence, and pointed review questions because the reviewer has no local file access. +argument-hint: '[topic | plan path | review target | prompt request]' +disable-model-invocation: true +--- + +# GPT Pro + +Handle $ARGUMENTS. + +Use this when the user wants a paste-ready prompt for ChatGPT Pro, GPT Pro, or +another external reviewer/model. The external reviewer has no repo, terminal, +browser, or local file access, so the prompt must include enough current, +source-backed context to reason independently. + +This skill borrows the rigor of `ralplan-creator`, but its output is a prompt, +not a local execution plan. + +## Use When + +- The user says `gpt-pro`, GPT Pro, ChatGPT Pro, external review, ask another + model, or send this to ChatGPT. +- The user wants "full current state", "review the direction", "ask questions", + "best solution", or "harsh review" from an external model. +- The task needs source-backed context, benchmark evidence, API shape, or + architecture skeleton that the external model cannot inspect. +- The user wants a review prompt for a plan, API rewrite, architecture decision, + benchmark result, research direction, migration, or public-facing design. + +## Do Not Use When + +- The user asks to implement the plan locally. +- The user wants a normal `ralplan`, `ralph`, code review, or bug fix. +- A short answer in chat is enough. +- The external reviewer already has direct repo access. + +## Hard Policy + +- Output a prompt, not an implementation plan, unless the user explicitly asks + for a local plan file. +- Assume the reviewer has zero local access. Never write "read this file", + "inspect the repo", "run the benchmark", or "see the branch" as a required + step for them. +- Include all necessary local context inline: source paths, API skeletons, + behavior flow, docs claims, test/benchmark results, prior decisions, + constraints, known gaps, and the exact question to answer. +- Do not invent current state. Read live files, docs, tests, benchmarks, plans, + and sibling repos before summarizing them. +- Treat pasted old prompts and previous model answers as context, not truth. + Refresh against the current repo when feasible. +- Label facts clearly: `confirmed`, `benchmarked`, `inferred`, `stale`, or + `gap`. +- Prefer exact paths plus concise summaries over huge code dumps. +- Quote only the smallest snippets needed to prove API shape or behavior. +- If evidence is missing, say that in the prompt and ask the reviewer what would + change their verdict. +- Force a decision. Ask for a harsh verdict, rejected alternatives, red flags, + pass/fail gates, and the evidence that would overturn the recommendation. +- Latest user intent wins over pasted context. + +## Read First + +Read only what the prompt needs, but the prompt must be self-contained. + +1. The latest user request. +2. Any named plan, state file, doc, source file, issue, benchmark, or sibling + checkout. +3. Current local source that owns the API/behavior. +4. Current tests, examples, benchmark runner, or proof artifact for the claim. +5. Existing active plan or completion file when the prompt is about active work. +6. Relevant research docs or local sibling repos when ecosystem comparison is + part of the ask. +7. Previous external answer only after local state has been grounded. + +If the prompt concerns performance or current behavior, prefer fresh benchmark +or test output when practical. If fresh proof is too expensive, say exactly +which numbers are stale or memory-derived. + +## Workflow + +1. Restate the decision or review question in one sentence. +2. Gather the minimum local evidence needed for the reviewer to reason without + repo access. +3. Separate confirmed facts from assumptions, gaps, and previous opinions. +4. Extract the API or architecture skeleton: + - public types or props + - runtime flow + - data model + - extension points + - source paths + - proof/test/benchmark ownership +5. Summarize the strongest evidence, including numbers when relevant. +6. Name candidate directions and the one the local analysis currently favors. +7. Add pointed review questions that force tradeoffs, not generic advice. +8. Produce one paste-ready prompt. + +## Prompt Contract + +The prompt should usually contain these sections, adapted to the task: + +1. Role and review standard. +2. Decision to make. +3. Current repo state. +4. Source-backed API or architecture skeleton. +5. Evidence: benchmarks, tests, docs, examples, or observed failures. +6. Prior decisions already accepted. +7. Constraints and non-goals. +8. Known gaps and red flags. +9. Candidate directions. +10. The direction we currently favor, if any. +11. Exact output requested from GPT Pro. +12. Review questions. + +For architecture/API/performance prompts, request these outputs when relevant: + +- harsh verdict +- recommended default or decision +- how to win the critical benchmark or quality lane +- what to steal and reject from comparable systems +- risk table +- benchmark or proof matrix +- implementation phases with hard gates +- maintainer objections and answers +- red flags that could invalidate the conclusion +- exact evidence that would change the decision + +## Context Packing Rules + +- Include full current-state context, not just links. +- Keep paths in the prompt so the answer can be mapped back to the repo. +- Use tables for metrics and side-by-side tradeoffs. +- Use snippets for API shapes and before/after examples. +- Do not paste large source files. Summarize behavior and include only the + smallest decisive snippet. +- Include source-backed contradictions, especially stale docs versus fresh + benchmarks. +- If sibling repos matter, include their behavior skeleton too. Do not ask the + reviewer to inspect them. +- If the prompt uses a previous GPT Pro answer, include it as "previous answer" + and ask the reviewer to critique or refine it against the new state. + +## Quality Gates + +Before finalizing the prompt, check: + +- Can the external model answer without repo access? +- Does the prompt include the current API/behavior skeleton, not just goals? +- Are benchmarks/tests labeled with dates, commands, or source paths when known? +- Are stale claims marked stale? +- Are open questions sharp enough to produce a decision-grade answer? +- Does the prompt ask for tradeoffs and red flags, not encouragement? +- Is the local favored direction stated clearly enough to be challenged? + +## Output + +If the user names a target file, write the prompt there. Otherwise paste the +prompt in chat. + +Use a short wrapper before the prompt only when helpful: + +```md +Prompt below. Sources grounded from: + +- ... +``` + +Then provide the prompt, usually in a fenced markdown block. + +Do not create or update implementation files while using this skill unless the +user separately asks for that work. diff --git a/.agents/rules/major-task.mdc b/.agents/rules/major-task.mdc index 7f8a754372..9f40d47643 100644 --- a/.agents/rules/major-task.mdc +++ b/.agents/rules/major-task.mdc @@ -156,7 +156,7 @@ Apply this section only when the task source is a tracker item. Use only when major work actually turns into risky code-changing execution or architecture-sensitive diffs. - `agent-native-reviewer` Use only when the change touches `.agents/**`, `.claude/**`, AI/tooling surfaces, commands, or user actions that an agent should also be able to perform. -- `dev-browser` +- `browser-use` Use only when there is a real browser surface to verify. - `agent-browser-issue` Use when browser automation is blocked by a likely reusable tool-side issue that deserves a separate GitHub follow-up. diff --git a/.agents/rules/task.mdc b/.agents/rules/task.mdc index 6b3290e08f..366a8e5637 100644 --- a/.agents/rules/task.mdc +++ b/.agents/rules/task.mdc @@ -204,7 +204,7 @@ Apply this section only when the task source is a tracker item. Use when requirements are still ambiguous after reading the source of truth and nearby code. - `framework-docs-researcher` Use when touching unfamiliar, version-sensitive, or unstable third-party APIs after checking local clones and docs per AGENTS. -- `dev-browser` +- `browser-use` Use only when there is a real browser surface to verify. Require real browser proof only for browser or UI tasks. - `agent-browser-issue` @@ -356,11 +356,11 @@ Every final response must include: ### UI Or Browser Tasks - Include at least one real browser proof screenshot in the final response. -- The screenshot must come from `dev-browser` or the real browser workflow used for verification. +- The screenshot must come from `browser-use` or the real browser workflow used for verification. - When `**🌐 Browser Check**` is present, put the screenshot immediately after that section. - Otherwise, put the screenshot immediately after the metadata lines + flow table, before the completion summary. - If no real browser proof exists, the task is not done unless the user explicitly waived it. -- If `dev-browser` is blocked on a likely reusable tool-side issue and the product task is still otherwise fixable, load `agent-browser-issue`. +- If `browser-use` is blocked on a likely reusable tool-side issue and the product task is still otherwise fixable, load `agent-browser-issue`. - If that follow-up issue is opened, mention it in the caveat or handoff. - `**🌐 Browser Check**` must be a flat bullet list: - keep it short and concrete @@ -427,7 +427,7 @@ Apply this section only when the task came from a tracker item and reached a mea - upload the image immediately - replace the placeholder with the real hosted proof before handoff - If the PR description includes a local image path for proof, do not leave it that way on GitHub. -- Use `dev-browser --connect http://127.0.0.1:9222` to upload the image through the PR comment file input as a staging area, then replace the local proof path in the PR body with the hosted GitHub attachment URL. +- Use `browser-use` to upload the image through the PR comment file input as a staging area, then replace the local proof path in the PR body with the hosted GitHub attachment URL. - Use the PR comment textarea only as staging: - upload image - read generated markdown or URL from the textarea diff --git a/.agents/skiller.toml b/.agents/skiller.toml index ab3a4b57a7..9970a29331 100644 --- a/.agents/skiller.toml +++ b/.agents/skiller.toml @@ -28,10 +28,13 @@ merge_strategy = "merge" command = "npx" args = [ "shadcn@latest", "mcp" ] - [mcp_servers.plate.env] REGISTRY_URL = "http://localhost:3000/rd/registry.json" +[mcp_servers.agentation] +command = "npx" +args = [ "-y", "agentation-mcp@latest", "server" ] + # --- Agent-Specific Configuration --- [agents.claude-code] diff --git a/.agents/skills/agent-browser-issue/SKILL.md b/.agents/skills/agent-browser-issue/SKILL.md index 58b6aab694..b8eba24251 100644 --- a/.agents/skills/agent-browser-issue/SKILL.md +++ b/.agents/skills/agent-browser-issue/SKILL.md @@ -1,5 +1,5 @@ --- -description: Open a concise GitHub follow-up for reusable dev-browser or agent-browser limitations. Use when browser automation is blocked by a likely tool-side issue that is worth fixing separately, especially for clicks, dropdowns, file inputs, focus traps, or other repeatable agent/browser failures. +description: Open a concise GitHub follow-up for reusable browser-use limitations. Use when browser automation is blocked by a likely tool-side issue that is worth fixing separately, especially for clicks, dropdowns, file inputs, focus traps, or other repeatable agent/browser failures. argument-hint: '[browser block summary]' disable-model-invocation: true name: agent-browser-issue diff --git a/.agents/skills/browser-debug-setup/SKILL.md b/.agents/skills/browser-debug-setup/SKILL.md deleted file mode 100644 index 59a7fad7a3..0000000000 --- a/.agents/skills/browser-debug-setup/SKILL.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -description: One-time setup for a persistent debug browser on `127.0.0.1:9222` for `dev-browser --connect`. Use when browser work is needed but no reusable debug browser is running yet. -name: browser-debug-setup -metadata: - skiller: - source: .agents/rules/browser-debug-setup.mdc ---- - -# Browser Debug Setup - -Use this skill when `dev-browser --connect http://127.0.0.1:9222` fails because -no persistent debug browser is running yet. - -## Goal - -Get the user onto one persistent browser/profile that both the human and the -agent reuse. Minimize the `Allow remote debugging?` popup by keeping one -dedicated debug browser/profile alive. - -## Rules - -- Prefer one permanent debug browser/profile over disposable automation - browsers. -- Treat a custom `--user-data-dir` as mandatory, not optional. Chrome 136+ - basically wants remote debugging to happen from a dedicated profile. -- Keep auth in that profile. Do not fall back to cookie dumps or state files - unless the user asks. -- Use a separate signed-in Chrome profile for browser work, like `dev`. Do not - use the user's normal daily `Default` profile as the source profile. -- Clone that separate signed-in Chrome profile into the dedicated debug - `--user-data-dir`; do not point `9222` straight at the user's daily Chrome - data dir. -- On macOS, use `open -na "Google Chrome" --args ...` for the debug browser. - That starts a separate Chrome instance with the dedicated debug profile - without touching the user's normal Chrome window. - -## Preferred Shape - -Use a dedicated browser/profile with: - -- `--remote-debugging-address=127.0.0.1` -- `--remote-debugging-port=9222` -- a persistent `--user-data-dir=` - -Sign in once in that dedicated browser and keep reusing it for agent work. - -Quick sanity check: - -```bash -curl -sS http://127.0.0.1:9222/json/version -``` - -Healthy output includes a JSON object with `webSocketDebuggerUrl`. Empty output -or `404` means the wrong process owns `9222`. - -Then verify `dev-browser`: - -```bash -dev-browser --connect http://127.0.0.1:9222 <<'EOF' -const page = await browser.getPage("persistent-main"); -console.log(await page.title()); -EOF -``` - -If `dev-browser --connect http://127.0.0.1:9222` still cannot resolve CDP even -though `/json/version` is healthy, connect with the exact websocket URL: - -```bash -WS=$(curl -sS http://127.0.0.1:9222/json/version | jq -r '.webSocketDebuggerUrl') - -dev-browser --connect "$WS" <<'EOF' -const page = await browser.getPage("persistent-main"); -console.log(await page.title()); -EOF -``` - -## Google Chrome Path - -Default setup on macOS: - -1. Pick a separate signed-in Chrome profile for agent work, like `dev`, not - the daily `Default` profile. -2. Map that human-facing Chrome profile name to the real folder in `Local State`. -3. Clone that profile into the dedicated debug dir. -4. Launch a separate Chrome instance on `9222`. -5. Leave that debug window open and reuse it. - -```bash -python3 - <<'PY' -import json, pathlib -p = pathlib.Path('~/Library/Application Support/Google/Chrome/Local State').expanduser() -obj = json.loads(p.read_text()) -for key, val in obj.get('profile', {}).get('info_cache', {}).items(): - print(f"{key}\tname={val.get('name')}\tgaia_name={val.get('gaia_name')}") -PY - -# Example: if `dev` maps to `Profile 1`, clone `Profile 1`. -mkdir -p "$HOME/.config/google-chrome-debug-profile/Default" -rsync -a --delete \ - --exclude='Singleton*' \ - --exclude='DevToolsActivePort' \ - --exclude='lockfile' \ - "$HOME/Library/Application Support/Google/Chrome/Profile 1/" \ - "$HOME/.config/google-chrome-debug-profile/Default/" -cp "$HOME/Library/Application Support/Google/Chrome/Local State" \ - "$HOME/.config/google-chrome-debug-profile/Local State" - -open -na "Google Chrome" --args \ - --user-data-dir="$HOME/.config/google-chrome-debug-profile" \ - --profile-directory="Default" \ - --remote-debugging-address=127.0.0.1 \ - --remote-debugging-port=9222 -``` - -That keeps the signed-in identity while still satisfying Chrome's dedicated -`--user-data-dir` requirement. - -Then keep reusing that exact debug browser. Do not point `9222` at your normal -daily `Default` Chrome profile. - -## After Setup - -- Use `dev-browser --connect http://127.0.0.1:9222` for browser work. -- Reuse named pages like `persistent-main`. -- Do not stop the user's debug browser unless they ask. -- If the wrong Chrome steals `9222`, identify it with `lsof -nP -iTCP:9222 -sTCP:LISTEN`, - kill that listener, and relaunch the dedicated debug browser. Do not keep - debugging against a stale `404` or empty `/json/version` owner. diff --git a/.agents/skills/codex-review/SKILL.md b/.agents/skills/codex-review/SKILL.md new file mode 100644 index 0000000000..b987d0e90d --- /dev/null +++ b/.agents/skills/codex-review/SKILL.md @@ -0,0 +1,139 @@ +--- +name: codex-review +description: "Codex code review closeout: local dirty changes, PR branch vs main, parallel tests." +--- + +# Codex Review + +Run Codex's built-in code review as a closeout check. This is code review (`codex review`), not Guardian `auto_review` approval routing. + +Use when: +- user asks for Codex review / autoreview / second-model review +- after non-trivial code edits, before final/commit/ship +- reviewing a local branch or PR branch after fixes + +## Contract + +- Treat review output as advisory. Never blindly apply it. +- Verify every finding by reading the real code path and adjacent files. +- Read dependency docs/source/types when the finding depends on external behavior. +- Reject unrealistic edge cases, speculative risks, broad rewrites, and fixes that over-complicate the codebase. +- Prefer small fixes at the right ownership boundary; no refactor unless it clearly improves the bug class. +- Keep going until Codex review returns no accepted/actionable findings. +- If a review-triggered fix changes code, rerun focused tests and rerun Codex review. +- For security-audit suppression changes, verify accepted findings remain auditable: suppressed findings stay in structured output, active output keeps an unsuppressible suppression notice, and aggregate findings cannot hide unrelated active risk. +- Never switch or override the review model. If the review hits model capacity, retry the same command a few times with the same model. The helper runs nested review in yolo/full-access mode by default; use `--no-yolo` only when intentionally testing sandbox behavior. +- Stop as soon as the review command/helper exits 0 with no accepted/actionable findings. Do not run an extra direct `codex review` just to get a nicer "clean" line, a second opinion, or clearer closeout wording. +- Treat the helper's successful exit plus absence of actionable findings as the clean review result, even if the underlying Codex CLI output is terse. +- If rejecting a finding as intentional/not worth fixing, add a brief inline code comment only when it explains a real invariant or ownership decision that future reviewers should know. +- If `gh`/Gitcrawl reports `database disk image is malformed`, run `gitcrawl doctor --json` once to let the portable cache repair before retrying review; do not bypass the shim unless repair fails and freshness requires live GitHub. +- If Gitcrawl reports a portable manifest mismatch, source/runtime DB health error, or stale portable-store checkout, run `gitcrawl doctor --json` and inspect `source_db_health`, `runtime_db_health`, and `portable_store_status` before falling back to live GitHub. +- Do not push just to review. Push only when the user requested push/ship/PR update. + +## Pick Target + +Dirty local work: + +```bash +codex review --uncommitted +``` + +Use this only when the patch is actually unstaged/staged/untracked in the +current checkout. For committed, pushed, or PR work, point Codex at the commit +or branch diff instead; do not force `--mode local` / `--uncommitted` just +because the helper docs mention dirty work first. A clean `--uncommitted` review +only proves there is no local patch. + +Branch/PR work: + +```bash +git fetch origin +codex review --base origin/main +``` + +Do not pass any prompt with `--base`. Some Codex CLI versions reject both inline +and stdin prompt forms, including helper commands shaped like +`codex review --base -`, with `--base cannot be used with +[PROMPT]`. If the helper hits this error, run plain `codex review --base ` +and report that helper prompt injection was skipped. + +If an open PR exists, use its actual base: + +```bash +base=$(gh pr view --json baseRefName --jq .baseRefName) +codex review --base "origin/$base" +``` + +Committed single change: + +```bash +codex review --commit HEAD +``` + +or with the helper: + +```bash +/Users/steipete/Projects/agent-scripts/skills/codex-review/scripts/codex-review --mode commit --commit HEAD +``` + +Use commit review for already-landed or already-pushed work on `main`. Reviewing +clean `main` against `origin/main` is usually an empty diff after push. For a +small stack, review each commit explicitly or review the branch before merging +with `--base`. + +## Parallel Closeout + +Format first if formatting can change line locations. Then it is OK to run tests and review in parallel: + +```bash +scripts/codex-review --parallel-tests "" +``` + +Tradeoff: tests may force code changes that stale the review. If tests or review lead to code edits, rerun the affected tests and rerun review until no accepted/actionable findings remain. Once that rerun exits cleanly, stop; do not spend another long review cycle on redundant confirmation. + +## Context Efficiency + +Codex review is usually noisy. Default to a subagent filter when subagents are available. Ask it to run the review and return only: +- actionable findings it accepts +- findings it rejects, with one-line reason +- exact files/tests to rerun + +Run inline only for tiny changes or when subagents are unavailable. + +## Helper + +Bundled helper: + +```bash +~/.codex/skills/codex-review/scripts/codex-review --help +``` + +If installed from `agent-scripts`, path is: + +```bash +/Users/steipete/Projects/agent-scripts/skills/codex-review/scripts/codex-review --help +``` + +The helper: +- chooses dirty `--uncommitted` first +- otherwise uses current PR base if `gh pr view` works +- otherwise uses `origin/main` for non-main branches +- auto-runs `PNPM_CONFIG_PM_ON_FAIL=ignore PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN=false PNPM_CONFIG_OFFLINE=true pnpm run check` in parallel when a repo has `package.json`, `pnpm-lock.yaml`, `node_modules`, and a `check` script; disable with `CODEX_REVIEW_AUTO_TESTS=0` +- use `--mode commit --commit ` for already-committed work, especially clean `main` after landing +- should be left in `--mode auto` or forced to `--mode branch` for PR/branch work; do not force `--mode local` after committing +- writes only to stdout unless `--output` or `CODEX_REVIEW_OUTPUT` is set +- supports `--dry-run`, `--parallel-tests`, and commit refs +- runs nested review with `--dangerously-bypass-approvals-and-sandbox` by default +- branch mode may fail on Codex CLI versions that reject `--base` plus the helper's stdin prompt; on that exact parser error, rerun plain `codex review --base ` instead of falling back to a non-Codex reviewer +- keeps accepting `--full-access`; use `--no-yolo` or `CODEX_REVIEW_YOLO=0` to opt out +- prints `codex-review clean: no accepted/actionable findings reported` when the selected review command exits 0 + +## Final Report + +Include: +- review command used +- tests/proof run +- findings accepted/rejected, briefly why +- the clean review result from the final helper/review run, or why a remaining finding was consciously rejected + +Do not run another Codex review solely to improve the final report wording. If the final helper run exited 0 and produced no accepted/actionable findings, report that exact run as clean. diff --git a/.agents/skills/codex-review/scripts/codex-review b/.agents/skills/codex-review/scripts/codex-review new file mode 100755 index 0000000000..9392a21c2f --- /dev/null +++ b/.agents/skills/codex-review/scripts/codex-review @@ -0,0 +1,314 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: codex-review [options] + +Options: + --mode auto|local|branch|commit + Target selection. Default: auto. + --base REF Base ref for branch review. Default: PR base or origin/main. + --commit REF Commit ref for commit review. Default: HEAD. + --codex-bin PATH Codex binary. Default: codex. + --full-access Keep yolo/full-access mode enabled. Default. + --no-yolo Run nested Codex review with normal sandbox/approval prompts. + --output FILE Also save output to file. + --parallel-tests CMD Run review and test command concurrently. + Default: PNPM_CONFIG_PM_ON_FAIL=ignore PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN=false PNPM_CONFIG_OFFLINE=true pnpm run check when available. + --dry-run Print selected commands, do not run. + -h, --help Show help. + +Modes: + local codex review --uncommitted + branch codex review --base + commit codex review --commit + auto dirty tree -> local, else PR/current branch -> branch +EOF +} + +mode=auto +base_ref= +commit_ref=HEAD +codex_bin=${CODEX_BIN:-codex} +codex_args=() +yolo=${CODEX_REVIEW_YOLO:-1} +output=${CODEX_REVIEW_OUTPUT:-} +parallel_tests= +parallel_tests_auto=false +dry_run=false + +while [[ $# -gt 0 ]]; do + case "$1" in + --mode) + mode=${2:-} + shift 2 + ;; + --base) + base_ref=${2:-} + shift 2 + ;; + --commit) + commit_ref=${2:-} + shift 2 + ;; + --codex-bin) + codex_bin=${2:-} + shift 2 + ;; + --full-access) + yolo=1 + shift + ;; + --no-yolo) + yolo=0 + shift + ;; + --output) + output=${2:-} + shift 2 + ;; + --parallel-tests) + parallel_tests=${2:-} + shift 2 + ;; + --dry-run) + dry_run=true + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + usage >&2 + exit 2 + ;; + esac +done + +case "$yolo" in + 0|false|False|FALSE|no|No|NO|off|Off|OFF) ;; + *) codex_args+=(--dangerously-bypass-approvals-and-sandbox) ;; +esac + +case "$mode" in + auto|local|branch|commit) ;; + *) + echo "invalid --mode: $mode" >&2 + exit 2 + ;; +esac + +repo_root=$(git rev-parse --show-toplevel) +printf -v quoted_repo_root '%q' "$repo_root" + +has_package_check_script() { + command -v node >/dev/null 2>&1 || return 1 + node -e 'const { readFileSync } = require("node:fs"); const p = JSON.parse(readFileSync(process.argv[1], "utf8")); process.exit(p.scripts?.check ? 0 : 1)' \ + "$repo_root/package.json" \ + >/dev/null 2>&1 +} + +auto_tests_disabled() { + case "${CODEX_REVIEW_AUTO_TESTS:-1}" in + 0|false|False|FALSE|no|No|NO|off|Off|OFF) return 0 ;; + *) return 1 ;; + esac +} + +if [[ -z "$parallel_tests" ]] && ! auto_tests_disabled; then + if [[ -f "$repo_root/package.json" && -f "$repo_root/pnpm-lock.yaml" && -d "$repo_root/node_modules" ]] && + command -v pnpm >/dev/null 2>&1 && + has_package_check_script; then + parallel_tests="cd $quoted_repo_root && PNPM_CONFIG_PM_ON_FAIL=ignore PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN=false PNPM_CONFIG_OFFLINE=true pnpm run check" + parallel_tests_auto=true + fi +fi + +current_branch=$(git branch --show-current 2>/dev/null || true) +dirty=false +if [[ -n "$(git status --porcelain)" ]]; then + dirty=true +fi + +pr_url= +if [[ -z "$base_ref" && "$mode" != local ]] && command -v gh >/dev/null 2>&1; then + if pr_lines=$(gh pr view --json baseRefName,url --jq '[.baseRefName, .url] | @tsv' 2>/dev/null); then + base_name=${pr_lines%%$'\t'*} + pr_url=${pr_lines#*$'\t'} + if [[ -n "$base_name" ]]; then + base_ref="origin/$base_name" + fi + fi +fi + +if [[ -z "$base_ref" ]]; then + base_ref=origin/main +fi + +review_kind= +if [[ "$mode" == local || ( "$mode" == auto && "$dirty" == true ) ]]; then + review_kind=local +elif [[ "$mode" == commit ]]; then + review_kind=commit +elif [[ "$mode" == branch || ( "$mode" == auto && -n "$current_branch" && "$current_branch" != "main" ) ]]; then + review_kind=branch +else + echo "no review target: clean main checkout and no forced mode" >&2 + exit 1 +fi + +if [[ "$review_kind" == local ]]; then + review_cmd=("$codex_bin" "${codex_args[@]}" review --uncommitted) +elif [[ "$review_kind" == commit ]]; then + review_cmd=("$codex_bin" "${codex_args[@]}" review --commit "$commit_ref") +else + review_cmd=("$codex_bin" "${codex_args[@]}" review --base "$base_ref") +fi + +printf 'codex-review target: %s\n' "$review_kind" +printf 'branch: %s\n' "${current_branch:-detached}" +if [[ -n "$pr_url" ]]; then + printf 'pr: %s\n' "$pr_url" +fi +printf 'review:' +printf ' %q' "${review_cmd[@]}" +printf '\n' +if [[ -n "$parallel_tests" ]]; then + printf 'tests: %s' "$parallel_tests" + if [[ "$parallel_tests_auto" == true ]]; then + printf ' (auto)' + fi + printf '\n' +fi +if [[ "$review_kind" == branch ]]; then + printf 'fetch: git fetch origin --quiet\n' +fi +if [[ -n "$output" ]]; then + printf 'output: %s\n' "$output" +fi + +if [[ "$dry_run" == true ]]; then + exit 0 +fi + +if [[ "$review_kind" == branch ]]; then + git fetch origin --quiet || { + echo "warning: git fetch origin failed; reviewing with existing refs" >&2 + } +fi + +review_output=$output +review_output_is_temp=false +if [[ -z "$review_output" ]]; then + review_output=$(mktemp) + review_output_is_temp=true +fi + +cleanup() { + if [[ "${review_output_is_temp:-false}" == true && -n "${review_output:-}" ]]; then + rm -f "$review_output" + fi +} +trap cleanup EXIT + +run_review() { + mkdir -p "$(dirname "$review_output")" + "${review_cmd[@]}" 2>&1 | tee "$review_output" +} + +elapsed_since() { + local started_at=$1 + local finished_at + finished_at=$(date +%s) + printf '%s\n' "$((finished_at - started_at))" +} + +format_elapsed() { + local seconds=$1 + if (( seconds < 60 )); then + printf '%ss\n' "$seconds" + else + printf '%sm%ss\n' "$((seconds / 60))" "$((seconds % 60))" + fi +} + +review_output_empty() { + [[ ! -s "$review_output" ]] || ! grep -q '[^[:space:]]' "$review_output" +} + +review_output_has_findings() { + grep -Eq '\[P[0-3]\]' "$review_output" +} + +report_clean_review_or_fail() { + local elapsed_text + elapsed_text=$(format_elapsed "${review_elapsed_seconds:-0}") + + if review_output_has_findings; then + printf 'codex-review complete after %s\n' "$elapsed_text" + printf 'codex-review findings: accepted/actionable findings reported\n' + return 1 + fi + if review_output_empty; then + printf 'codex-review complete after %s; no output\n' "$elapsed_text" + return 1 + fi + printf 'codex-review complete after %s\n' "$elapsed_text" + printf 'codex-review clean: no accepted/actionable findings reported\n' +} + +if [[ -z "$parallel_tests" ]]; then + review_started_at=$(date +%s) + set +e + run_review + review_status=$? + review_elapsed_seconds=$(elapsed_since "$review_started_at") + set -e + if [[ "$review_status" == 0 ]]; then + report_clean_review_or_fail + exit $? + fi + exit "$review_status" +fi + +review_status_file=$(mktemp) +review_elapsed_file=$(mktemp) +tests_status_file=$(mktemp) + +( + set +e + review_started_at=$(date +%s) + run_review + status=$? + elapsed=$(elapsed_since "$review_started_at") + printf '%s\n' "$status" > "$review_status_file" + printf '%s\n' "$elapsed" > "$review_elapsed_file" +) & +review_pid=$! + +( + set +e + bash -lc "$parallel_tests" + status=$? + printf '%s\n' "$status" > "$tests_status_file" +) & +tests_pid=$! + +wait "$review_pid" || true +wait "$tests_pid" || true + +review_status=$(cat "$review_status_file") +review_elapsed_seconds=$(cat "$review_elapsed_file") +tests_status=$(cat "$tests_status_file") +rm -f "$review_status_file" "$review_elapsed_file" "$tests_status_file" + +printf 'codex-review exit: %s\n' "$review_status" +printf 'tests exit: %s\n' "$tests_status" + +if [[ "$review_status" != 0 || "$tests_status" != 0 ]]; then + exit 1 +fi + +report_clean_review_or_fail diff --git a/.agents/skills/dev-browser/SKILL.md b/.agents/skills/dev-browser/SKILL.md index 0ce87d9a3c..9284ffd245 100644 --- a/.agents/skills/dev-browser/SKILL.md +++ b/.agents/skills/dev-browser/SKILL.md @@ -1,11 +1,16 @@ --- +description: Fallback browser automation with persistent Chrome state. Use only when Browser Use is unavailable or blocked. name: dev-browser -description: Browser automation with persistent page state. Use when users ask to navigate websites, fill forms, take screenshots, extract web data, test web apps, or automate browser workflows. Trigger phrases include "go to [url]", "click on", "fill out the form", "take a screenshot", "scrape", "automate", "test the website", "log into", or any browser interaction request. +metadata: + skiller: + source: .agents/rules/dev-browser.mdc --- # Dev Browser -A CLI for controlling browsers with sandboxed JavaScript scripts. +Use this only as the fallback browser path when `[@browser-use](plugin://browser-use@openai-bundled)` is unavailable or blocked. + +Do not substitute Puppeteer, standalone Playwright, or raw Chrome DevTools for this fallback path. ## Installation @@ -14,6 +19,116 @@ npm install -g dev-browser dev-browser install ``` -## Usage - Run `dev-browser --help` to learn more. + +## Plate Defaults + +- Use `dev-browser --connect http://127.0.0.1:9222` by default. Do not preflight `9222` first. +- Only inspect `9222` after a direct `dev-browser --connect http://127.0.0.1:9222` attempt fails. +- Reuse one persistent debug Chrome on `127.0.0.1:9222`. Do not spin up disposable browser instances unless the user asks. +- Use a dedicated Chrome `--user-data-dir` for that debug browser, not the user's normal daily Chrome data dir. +- Clone the signed-in Chrome profile into the dedicated debug dir, then launch the debug browser from that clone. +- On macOS, launch the debug browser with `open -na "Google Chrome" --args ... --remote-debugging-port=9222` so it opens as a separate Chrome instance without hijacking the user's normal window. +- Do not close or stop the user's connected debug browser. Leave that debug window open and reuse it. Close named pages only when needed. +- Keep scripts small and direct. Prefer `browser.getPage("persistent-main")` for the main app. +- Use `dev-browser` instead of `agent-browser` or next-devtools `browser_eval`. +- For Plate registry/browser proof, prefer `/blocks/[id]-demo` over docs wrappers when that standalone demo route exists. +- If `dev-browser` gets blocked by a human prompt or loops on the same step, stop and ask the user to unblock. + +## Fallback Setup + +Use this only after `dev-browser --connect http://127.0.0.1:9222` fails because no reusable debug Chrome is available or the CDP endpoint is broken. + +## Rules + +- Prefer one permanent debug browser/profile over disposable automation browsers. +- Treat a custom `--user-data-dir` as mandatory, not optional. Chrome 136+ expects remote debugging to happen from a dedicated profile. +- Keep auth in that profile. Do not fall back to cookie dumps or state files unless the user asks. +- Use a separate signed-in Chrome profile for browser work, like `dev`. Do not use the user's normal daily `Default` profile as the source profile. +- Clone that separate signed-in Chrome profile into the dedicated debug `--user-data-dir`; do not point `9222` straight at the user's daily Chrome data dir. +- On macOS, use `open -na "Google Chrome" --args ...` for the debug browser. That starts a separate Chrome instance with the dedicated debug profile without touching the user's normal Chrome window. + +## Preferred Shape + +Use a dedicated browser/profile with: + +- `--remote-debugging-address=127.0.0.1` +- `--remote-debugging-port=9222` +- a persistent `--user-data-dir=` + +Sign in once in that dedicated browser and keep reusing it for agent work. + +Quick sanity check: + +```bash +curl -sS http://127.0.0.1:9222/json/version +``` + +Healthy output includes a JSON object with `webSocketDebuggerUrl`. Empty output or `404` means the wrong process owns `9222`. + +Then verify: + +```bash +dev-browser --connect http://127.0.0.1:9222 <<'EOF' +const page = await browser.getPage("persistent-main"); +console.log(await page.title()); +EOF +``` + +If direct connect still cannot resolve CDP even though `/json/version` is healthy, connect with the exact websocket URL: + +```bash +WS=$(curl -sS http://127.0.0.1:9222/json/version | jq -r '.webSocketDebuggerUrl') + +dev-browser --connect "$WS" <<'EOF' +const page = await browser.getPage("persistent-main"); +console.log(await page.title()); +EOF +``` + +## Google Chrome Path + +Default setup on macOS: + +1. Pick a separate signed-in Chrome profile for agent work, like `dev`, not the daily `Default` profile. +2. Map that human-facing Chrome profile name to the real folder in `Local State`. +3. Clone that profile into the dedicated debug dir. +4. Launch a separate Chrome instance on `9222`. +5. Leave that debug window open and reuse it. + +```bash +python3 - <<'PY' +import json, pathlib +p = pathlib.Path('~/Library/Application Support/Google/Chrome/Local State').expanduser() +obj = json.loads(p.read_text()) +for key, val in obj.get('profile', {}).get('info_cache', {}).items(): + print(f"{key}\tname={val.get('name')}\tgaia_name={val.get('gaia_name')}") +PY + +# Example: if `dev` maps to `Profile 1`, clone `Profile 1`. +mkdir -p "$HOME/.config/google-chrome-debug-profile/Default" +rsync -a --delete \ + --exclude='Singleton*' \ + --exclude='DevToolsActivePort' \ + --exclude='lockfile' \ + "$HOME/Library/Application Support/Google/Chrome/Profile 1/" \ + "$HOME/.config/google-chrome-debug-profile/Default/" +cp "$HOME/Library/Application Support/Google/Chrome/Local State" \ + "$HOME/.config/google-chrome-debug-profile/Local State" + +open -na "Google Chrome" --args \ + --user-data-dir="$HOME/.config/google-chrome-debug-profile" \ + --profile-directory="Default" \ + --remote-debugging-address=127.0.0.1 \ + --remote-debugging-port=9222 +``` + +Do not point `9222` at the normal daily `Default` Chrome profile. + +If the wrong Chrome steals `9222`, identify it with: + +```bash +lsof -nP -iTCP:9222 -sTCP:LISTEN +``` + +Kill that listener and relaunch the dedicated debug browser. Do not keep debugging against a stale `404` or empty `/json/version` owner. diff --git a/.agents/skills/gpt-pro/SKILL.md b/.agents/skills/gpt-pro/SKILL.md new file mode 100644 index 0000000000..9f448999e7 --- /dev/null +++ b/.agents/skills/gpt-pro/SKILL.md @@ -0,0 +1,174 @@ +--- +description: Create a self-contained GPT Pro or external-review prompt with full repo context, current state, evidence, and pointed review questions because the reviewer has no local file access. +argument-hint: '[topic | plan path | review target | prompt request]' +disable-model-invocation: true +name: gpt-pro +metadata: + skiller: + source: .agents/rules/gpt-pro.mdc +--- + +# GPT Pro + +Handle $ARGUMENTS. + +Use this when the user wants a paste-ready prompt for ChatGPT Pro, GPT Pro, or +another external reviewer/model. The external reviewer has no repo, terminal, +browser, or local file access, so the prompt must include enough current, +source-backed context to reason independently. + +This skill borrows the rigor of `ralplan-creator`, but its output is a prompt, +not a local execution plan. + +## Use When + +- The user says `gpt-pro`, GPT Pro, ChatGPT Pro, external review, ask another + model, or send this to ChatGPT. +- The user wants "full current state", "review the direction", "ask questions", + "best solution", or "harsh review" from an external model. +- The task needs source-backed context, benchmark evidence, API shape, or + architecture skeleton that the external model cannot inspect. +- The user wants a review prompt for a plan, API rewrite, architecture decision, + benchmark result, research direction, migration, or public-facing design. + +## Do Not Use When + +- The user asks to implement the plan locally. +- The user wants a normal `ralplan`, `ralph`, code review, or bug fix. +- A short answer in chat is enough. +- The external reviewer already has direct repo access. + +## Hard Policy + +- Output a prompt, not an implementation plan, unless the user explicitly asks + for a local plan file. +- Assume the reviewer has zero local access. Never write "read this file", + "inspect the repo", "run the benchmark", or "see the branch" as a required + step for them. +- Include all necessary local context inline: source paths, API skeletons, + behavior flow, docs claims, test/benchmark results, prior decisions, + constraints, known gaps, and the exact question to answer. +- Do not invent current state. Read live files, docs, tests, benchmarks, plans, + and sibling repos before summarizing them. +- Treat pasted old prompts and previous model answers as context, not truth. + Refresh against the current repo when feasible. +- Label facts clearly: `confirmed`, `benchmarked`, `inferred`, `stale`, or + `gap`. +- Prefer exact paths plus concise summaries over huge code dumps. +- Quote only the smallest snippets needed to prove API shape or behavior. +- If evidence is missing, say that in the prompt and ask the reviewer what would + change their verdict. +- Force a decision. Ask for a harsh verdict, rejected alternatives, red flags, + pass/fail gates, and the evidence that would overturn the recommendation. +- Latest user intent wins over pasted context. + +## Read First + +Read only what the prompt needs, but the prompt must be self-contained. + +1. The latest user request. +2. Any named plan, state file, doc, source file, issue, benchmark, or sibling + checkout. +3. Current local source that owns the API/behavior. +4. Current tests, examples, benchmark runner, or proof artifact for the claim. +5. Existing active plan or completion file when the prompt is about active work. +6. Relevant research docs or local sibling repos when ecosystem comparison is + part of the ask. +7. Previous external answer only after local state has been grounded. + +If the prompt concerns performance or current behavior, prefer fresh benchmark +or test output when practical. If fresh proof is too expensive, say exactly +which numbers are stale or memory-derived. + +## Workflow + +1. Restate the decision or review question in one sentence. +2. Gather the minimum local evidence needed for the reviewer to reason without + repo access. +3. Separate confirmed facts from assumptions, gaps, and previous opinions. +4. Extract the API or architecture skeleton: + - public types or props + - runtime flow + - data model + - extension points + - source paths + - proof/test/benchmark ownership +5. Summarize the strongest evidence, including numbers when relevant. +6. Name candidate directions and the one the local analysis currently favors. +7. Add pointed review questions that force tradeoffs, not generic advice. +8. Produce one paste-ready prompt. + +## Prompt Contract + +The prompt should usually contain these sections, adapted to the task: + +1. Role and review standard. +2. Decision to make. +3. Current repo state. +4. Source-backed API or architecture skeleton. +5. Evidence: benchmarks, tests, docs, examples, or observed failures. +6. Prior decisions already accepted. +7. Constraints and non-goals. +8. Known gaps and red flags. +9. Candidate directions. +10. The direction we currently favor, if any. +11. Exact output requested from GPT Pro. +12. Review questions. + +For architecture/API/performance prompts, request these outputs when relevant: + +- harsh verdict +- recommended default or decision +- how to win the critical benchmark or quality lane +- what to steal and reject from comparable systems +- risk table +- benchmark or proof matrix +- implementation phases with hard gates +- maintainer objections and answers +- red flags that could invalidate the conclusion +- exact evidence that would change the decision + +## Context Packing Rules + +- Include full current-state context, not just links. +- Keep paths in the prompt so the answer can be mapped back to the repo. +- Use tables for metrics and side-by-side tradeoffs. +- Use snippets for API shapes and before/after examples. +- Do not paste large source files. Summarize behavior and include only the + smallest decisive snippet. +- Include source-backed contradictions, especially stale docs versus fresh + benchmarks. +- If sibling repos matter, include their behavior skeleton too. Do not ask the + reviewer to inspect them. +- If the prompt uses a previous GPT Pro answer, include it as "previous answer" + and ask the reviewer to critique or refine it against the new state. + +## Quality Gates + +Before finalizing the prompt, check: + +- Can the external model answer without repo access? +- Does the prompt include the current API/behavior skeleton, not just goals? +- Are benchmarks/tests labeled with dates, commands, or source paths when known? +- Are stale claims marked stale? +- Are open questions sharp enough to produce a decision-grade answer? +- Does the prompt ask for tradeoffs and red flags, not encouragement? +- Is the local favored direction stated clearly enough to be challenged? + +## Output + +If the user names a target file, write the prompt there. Otherwise paste the +prompt in chat. + +Use a short wrapper before the prompt only when helpful: + +```md +Prompt below. Sources grounded from: + +- ... +``` + +Then provide the prompt, usually in a fenced markdown block. + +Do not create or update implementation files while using this skill unless the +user separately asks for that work. diff --git a/.agents/skills/major-task/SKILL.md b/.agents/skills/major-task/SKILL.md index 6251225aa9..6674a0f156 100644 --- a/.agents/skills/major-task/SKILL.md +++ b/.agents/skills/major-task/SKILL.md @@ -160,7 +160,7 @@ Apply this section only when the task source is a tracker item. Use only when major work actually turns into risky code-changing execution or architecture-sensitive diffs. - `agent-native-reviewer` Use only when the change touches `.agents/**`, `.claude/**`, AI/tooling surfaces, commands, or user actions that an agent should also be able to perform. -- `dev-browser` +- `browser-use` Use only when there is a real browser surface to verify. - `agent-browser-issue` Use when browser automation is blocked by a likely reusable tool-side issue that deserves a separate GitHub follow-up. diff --git a/.agents/skills/task/SKILL.md b/.agents/skills/task/SKILL.md index 13c016db17..b073270d27 100644 --- a/.agents/skills/task/SKILL.md +++ b/.agents/skills/task/SKILL.md @@ -208,7 +208,7 @@ Apply this section only when the task source is a tracker item. Use when requirements are still ambiguous after reading the source of truth and nearby code. - `framework-docs-researcher` Use when touching unfamiliar, version-sensitive, or unstable third-party APIs after checking local clones and docs per AGENTS. -- `dev-browser` +- `browser-use` Use only when there is a real browser surface to verify. Require real browser proof only for browser or UI tasks. - `agent-browser-issue` @@ -360,11 +360,11 @@ Every final response must include: ### UI Or Browser Tasks - Include at least one real browser proof screenshot in the final response. -- The screenshot must come from `dev-browser` or the real browser workflow used for verification. +- The screenshot must come from `browser-use` or the real browser workflow used for verification. - When `**🌐 Browser Check**` is present, put the screenshot immediately after that section. - Otherwise, put the screenshot immediately after the metadata lines + flow table, before the completion summary. - If no real browser proof exists, the task is not done unless the user explicitly waived it. -- If `dev-browser` is blocked on a likely reusable tool-side issue and the product task is still otherwise fixable, load `agent-browser-issue`. +- If `browser-use` is blocked on a likely reusable tool-side issue and the product task is still otherwise fixable, load `agent-browser-issue`. - If that follow-up issue is opened, mention it in the caveat or handoff. - `**🌐 Browser Check**` must be a flat bullet list: - keep it short and concrete @@ -431,7 +431,7 @@ Apply this section only when the task came from a tracker item and reached a mea - upload the image immediately - replace the placeholder with the real hosted proof before handoff - If the PR description includes a local image path for proof, do not leave it that way on GitHub. -- Use `dev-browser --connect http://127.0.0.1:9222` to upload the image through the PR comment file input as a staging area, then replace the local proof path in the PR body with the hosted GitHub attachment URL. +- Use `browser-use` to upload the image through the PR comment file input as a staging area, then replace the local proof path in the PR body with the hosted GitHub attachment URL. - Use the PR comment textarea only as staging: - upload image - read generated markdown or URL from the textarea diff --git a/.claude/prompt.yml b/.claude/prompt.yml index 30a05944bc..3f1ab30bda 100644 --- a/.claude/prompt.yml +++ b/.claude/prompt.yml @@ -30,7 +30,7 @@ beforeComplete: - NEVER git commit unless explicitly asked - 'NEVER `pnpm dev` or `pnpm run build` unless explicitly asked' todos: - - 'Test Browser (IF new features, styling, visual bugs, state changes. SKIP trivial markup, non-UI): Skill(dev-browser)' + - 'Test Browser (IF new features, styling, visual bugs, state changes. SKIP trivial markup, non-UI): Browser Use' - 'Typecheck (IF updated .ts files): Bash `pnpm typecheck`' - 'Lint: Bash `pnpm lint:fix`' - | diff --git a/.claude/skills/browser-debug-setup b/.claude/skills/browser-debug-setup deleted file mode 120000 index 328e6d7678..0000000000 --- a/.claude/skills/browser-debug-setup +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/browser-debug-setup \ No newline at end of file diff --git a/.claude/skills/codex-review b/.claude/skills/codex-review new file mode 120000 index 0000000000..90131f7c7b --- /dev/null +++ b/.claude/skills/codex-review @@ -0,0 +1 @@ +../../.agents/skills/codex-review \ No newline at end of file diff --git a/.claude/skills/gpt-pro b/.claude/skills/gpt-pro new file mode 120000 index 0000000000..38532ecf8e --- /dev/null +++ b/.claude/skills/gpt-pro @@ -0,0 +1 @@ +../../.agents/skills/gpt-pro \ No newline at end of file diff --git a/.codex/config.toml b/.codex/config.toml index 4626c7bfaf..1f12b3f271 100644 --- a/.codex/config.toml +++ b/.codex/config.toml @@ -4,3 +4,7 @@ args = [ "shadcn@latest", "mcp" ] [mcp_servers.plate.env] REGISTRY_URL = "http://localhost:3000/rd/registry.json" + +[mcp_servers.agentation] +command = "npx" +args = [ "-y", "agentation-mcp@latest", "server" ] diff --git a/.github/prompts/release-notes-rewrite.md b/.github/prompts/release-notes-rewrite.md new file mode 100644 index 0000000000..8be2b5a9ff --- /dev/null +++ b/.github/prompts/release-notes-rewrite.md @@ -0,0 +1,41 @@ + + +You are rewriting release notes for Plate, an open-source rich-text editor +framework for React. + +## Input + +**Raw changelog:** __RAW_CHANGELOG_PATH__ + +The raw changelog is generated from Changesets package changelogs after publish. +It is grouped by npm package and change type. + +## Job + +Rewrite each entry into a polished, user-focused release note while preserving +the exact release structure. Describe what changed for Plate users, not just the +internal implementation. + +## Writing Rules + +- Keep every entry as one clear sentence unless the raw entry already contains a + migration block or code example. +- Keep code identifiers in backticks. +- Keep PR links, author links, package names, and package `CHANGELOG` links. +- Keep migration notes, especially under `### Major Changes`. +- Do not add repo compare links. +- Do not invent package summaries. +- Do not add or remove release entries. +- Do not use em dashes. + +## Structural Rules + +- Do not modify `## \`package-name\`` headings or their order. +- Do not modify `### Major Changes`, `### Minor Changes`, or + `### Patch Changes` headings or their order. +- Do not modify `For detailed changes, see [\`CHANGELOG\`](...)` links. +- Do not remove `## Contributors` when it exists. +- Preserve all PR links in the raw changelog. +- Preserve all migration-note blocks. + +Write the final release notes to: __RAW_CHANGELOG_PATH__.final diff --git a/.github/workflows/changeset-auto-release.yml b/.github/workflows/changeset-auto-release.yml index 3db29555f1..61fe888f48 100644 --- a/.github/workflows/changeset-auto-release.yml +++ b/.github/workflows/changeset-auto-release.yml @@ -1,6 +1,8 @@ name: Changeset Auto Release Checkbox on: + pull_request: + types: [opened, reopened, synchronize, edited] pull_request_target: types: [opened, reopened, synchronize, edited] @@ -12,13 +14,25 @@ jobs: sync-auto-release-checkbox: name: Sync auto-release checkbox runs-on: ubuntu-latest - if: ${{ github.repository == 'udecode/plate' }} + if: >- + github.repository == 'udecode/plate' && + ( + (github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name == github.repository) || + (github.event_name == 'pull_request_target' && + github.event.pull_request.head.repo.full_name != github.repository) + ) steps: - - name: 📥 Checkout base + - name: 📥 Checkout workflow helper uses: actions/checkout@v4 with: - ref: ${{ github.event.repository.default_branch }} + ref: >- + ${{ + github.event_name == 'pull_request' + && github.event.pull_request.head.sha + || github.event.repository.default_branch + }} persist-credentials: false - name: 🔁 Sync auto-release checkbox diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e7112830f5..d584381fdd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,29 +4,46 @@ on: push: branches: [main] -permissions: - contents: write - pull-requests: write +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: false jobs: release: name: Release and changelog runs-on: ubuntu-latest if: ${{ github.repository == 'udecode/plate' && github.ref == 'refs/heads/main' && !contains(github.event.head_commit.message, '[skip release]') }} + permissions: + contents: write + pull-requests: write + id-token: write outputs: published: ${{ steps.changesets.outputs.published }} publishedPackages: ${{ steps.changesets.outputs.publishedPackages }} steps: + - name: Generate App Token + id: app-token + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 + if: vars.RELEASE_APP_ID != '' + with: + app-id: ${{ vars.RELEASE_APP_ID }} + private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + - name: 📥 Checkout Repo uses: actions/checkout@v4 with: fetch-depth: 0 + persist-credentials: false + token: ${{ steps.app-token.outputs.token || secrets.API_TOKEN_GITHUB || secrets.GITHUB_TOKEN }} - name: 🔎 Detect auto-release opt-in id: auto_release uses: actions/github-script@v7 with: + github-token: ${{ steps.app-token.outputs.token || secrets.API_TOKEN_GITHUB || secrets.GITHUB_TOKEN }} script: | const { pathToFileURL } = await import('node:url'); const helperUrl = pathToFileURL( @@ -121,22 +138,144 @@ jobs: with: cwd: ${{ github.workspace }} title: '[Release] Version packages' - publish: pnpm release + commit: '[Release] Version packages' + version: pnpm ci:version + publish: pnpm ci:release + createGithubReleases: false env: HOME: ${{ github.workspace }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - GITHUB_TOKEN: ${{ secrets.API_TOKEN_GITHUB || secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ steps.app-token.outputs.token || secrets.API_TOKEN_GITHUB || secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: 🏷️ Push package tags + if: ${{ steps.changesets.outputs.published == 'true' }} + env: + GH_TOKEN: ${{ steps.app-token.outputs.token || secrets.API_TOKEN_GITHUB || secrets.GITHUB_TOKEN }} + PUBLISHED_PACKAGES: ${{ steps.changesets.outputs.publishedPackages }} + run: | + mapfile -t TAGS < <(node tooling/scripts/published-package-tags.mjs) + + for tag in "${TAGS[@]}"; do + if ! git rev-parse -q --verify "refs/tags/${tag}" >/dev/null; then + echo "Missing expected package tag ${tag}." + exit 1 + fi + + git push "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "refs/tags/${tag}:refs/tags/${tag}" + done + + - name: 📝 Sync release docs from Version Packages PR + if: ${{ steps.changesets.outputs.published != 'true' && steps.changesets.outputs.pullRequestNumber != '' }} + env: + GH_TOKEN: ${{ steps.app-token.outputs.token || secrets.API_TOKEN_GITHUB || secrets.GITHUB_TOKEN }} + RELEASE_PR: ${{ steps.changesets.outputs.pullRequestNumber }} + run: | + HEAD_REF="$(gh pr view "$RELEASE_PR" --json headRefName --jq .headRefName)" + + gh pr checkout "$RELEASE_PR" + node tooling/scripts/sync-version-package-releases.mjs --pr "$RELEASE_PR" --from v49 + + if [[ -z "$(git status --porcelain --untracked-files=all -- apps/www/src/generated/release-index.json)" ]]; then + echo "Release docs already up to date." + exit 0 + fi + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add apps/www/src/generated/release-index.json + git commit -m "[Release] Sync release docs" + git push "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "HEAD:${HEAD_REF}" + + - name: 📝 Generate raw release notes + id: raw-notes + if: ${{ steps.changesets.outputs.published == 'true' }} + env: + PUBLISHED_PACKAGES: ${{ steps.changesets.outputs.publishedPackages }} + run: node tooling/scripts/release-notes.mjs + + - name: 🧠 Build AI prompt from template + id: ai-prompt + if: ${{ steps.raw-notes.outcome == 'success' }} + continue-on-error: true + env: + RAW_PATH: ${{ steps.raw-notes.outputs.raw_changelog_path }} + run: | + PROMPT=$(sed \ + -e "s|__RAW_CHANGELOG_PATH__|${RAW_PATH}|g" \ + .github/prompts/release-notes-rewrite.md) + EOF_DELIM="PROMPT_EOF_$(openssl rand -hex 8)" + echo "prompt<<${EOF_DELIM}" >> "$GITHUB_OUTPUT" + echo "$PROMPT" >> "$GITHUB_OUTPUT" + echo "${EOF_DELIM}" >> "$GITHUB_OUTPUT" + + - name: 🤖 Rewrite release notes with AI + id: ai-notes + if: ${{ steps.ai-prompt.outcome == 'success' }} + continue-on-error: true + uses: anthropics/claude-code-action/base-action@2ff1acb3ee319fa302837dad6e17c2f36c0d98ea # v1.0.91 + env: + GH_TOKEN: ${{ steps.app-token.outputs.token || secrets.API_TOKEN_GITHUB || secrets.GITHUB_TOKEN }} + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + prompt: ${{ steps.ai-prompt.outputs.prompt }} + claude_args: --max-turns 100 --allowedTools "Read Write Bash(gh pr diff*) Bash(gh pr view*)" + + - name: ✅ Validate AI release notes + if: ${{ steps.ai-notes.outcome == 'success' }} + continue-on-error: true + env: + RAW_PATH: ${{ steps.raw-notes.outputs.raw_changelog_path }} + run: | + node tooling/scripts/release-notes.mjs validate "$RAW_PATH" "${RAW_PATH}.final" + touch "${RAW_PATH}.final.validated" + + - name: 📝 Create GitHub Release + if: ${{ steps.changesets.outputs.published == 'true' }} + env: + GH_TOKEN: ${{ steps.app-token.outputs.token || secrets.API_TOKEN_GITHUB || secrets.GITHUB_TOKEN }} + VERSION: ${{ steps.raw-notes.outputs.version }} + RAW_PATH: ${{ steps.raw-notes.outputs.raw_changelog_path }} + run: | + if [[ -z "$VERSION" ]]; then + echo "::error::Could not determine release version." + exit 1 + fi + + TAG="v${VERSION}" + + if [[ -f "${RAW_PATH}.final" && -f "${RAW_PATH}.final.validated" ]]; then + NOTES_FILE="${RAW_PATH}.final" + echo "Using AI-rewritten release notes." + else + NOTES_FILE="${RAW_PATH}" + if [[ -f "${RAW_PATH}.final" ]]; then + echo "::warning::Ignoring unvalidated AI-rewritten release notes." + fi + echo "Using raw release notes." + fi + + gh api "repos/${GITHUB_REPOSITORY}/git/refs" \ + -f ref="refs/tags/${TAG}" -f sha="$GITHUB_SHA" 2>/dev/null \ + || echo "Tag $TAG already exists." + + if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + gh release edit "$TAG" --repo "$GITHUB_REPOSITORY" --title "$TAG" --notes-file "$NOTES_FILE" + echo "Updated release $TAG." + else + gh release create "$TAG" --repo "$GITHUB_REPOSITORY" --title "$TAG" --notes-file "$NOTES_FILE" --target "$GITHUB_SHA" + echo "Created release $TAG." + fi + - name: 🤖 Merge Version Packages PR if: ${{ steps.auto_release.outputs.enabled == 'true' && steps.changesets.outputs.pullRequestNumber != '' }} env: - GH_TOKEN: ${{ secrets.API_TOKEN_GITHUB }} + GH_TOKEN: ${{ steps.app-token.outputs.token || secrets.API_TOKEN_GITHUB }} RELEASE_PR: ${{ steps.changesets.outputs.pullRequestNumber }} SOURCE_PR: ${{ steps.auto_release.outputs.source_pr }} run: | if [[ -z "${GH_TOKEN}" ]]; then - echo "API_TOKEN_GITHUB is required so the merged release PR can trigger publish workflows." + echo "A GitHub App token or API_TOKEN_GITHUB is required so the merged release PR can trigger publish workflows." exit 1 fi @@ -148,6 +287,9 @@ jobs: runs-on: ubuntu-latest needs: release if: ${{ needs.release.result == 'success' && needs.release.outputs.published == 'true' }} + permissions: + contents: write + pull-requests: write steps: - name: 📥 Checkout Repo diff --git a/.mcp.json b/.mcp.json index 36d9f01b39..a2f497c9df 100644 --- a/.mcp.json +++ b/.mcp.json @@ -7,6 +7,11 @@ "REGISTRY_URL": "http://localhost:3000/rd/registry.json" }, "type": "stdio" + }, + "agentation": { + "command": "npx", + "args": ["-y", "agentation-mcp@latest", "server"], + "type": "stdio" } } } diff --git a/AGENTS.md b/AGENTS.md index ccf1e3f8a7..ec6990d8ac 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -66,25 +66,17 @@ When using the following skills, override the default behavior. - For issue-backed work, start the filename with the ticket number instead of `date+ticket`. Example: `docs/plans/4510-fix-schema.md` - For non-ticket work, keep the date-based format. Example: `docs/plans/2026-02-07-fix-schema.md` -`dev-browser`: - -- Use `dev-browser --connect http://127.0.0.1:9222` by default for browser work. Do not preflight `9222` first. -- Only inspect `9222` or use `browser-debug-setup` after a direct `dev-browser --connect http://127.0.0.1:9222` attempt fails. -- Reuse one persistent debug Chrome on `127.0.0.1:9222`. Do not spin up disposable browser instances unless the user asks. -- Use a dedicated Chrome `--user-data-dir` for that debug browser, not the user's normal daily Chrome data dir. -- Clone the signed-in Chrome profile into the dedicated debug dir, then launch the debug browser from that clone. -- On macOS, launch the debug browser with `open -na "Google Chrome" --args ... --remote-debugging-port=9222` so it opens as a separate Chrome instance without hijacking the user's normal window. -- Do not close or stop the user's connected debug browser. Leave that debug window open and reuse it. Close named pages only when needed. -- Keep scripts small and direct. Prefer `browser.getPage("persistent-main")` for the main app. -- Use `dev-browser` instead of `agent-browser` or next-devtools `browser_eval`. -- If `dev-browser` gets blocked by a human prompt or loops on the same step, stop and ask the user to unblock. After the unblock works: - - For Plate registry/browser proof, prefer `/blocks/[id]-demo` over docs wrappers when that standalone demo route exists. +Browser usage: + +- Always try `[@browser-use](plugin://browser-use@openai-bundled)` first for browser usage. +- Do not substitute Puppeteer, standalone Playwright, or raw Chrome DevTools for browser usage. +- For Plate registry/browser proof, prefer `/blocks/[id]-demo` over docs wrappers when that standalone demo route exists. `ce-*`: -- **plan:** Include `dev-browser` in acceptance criteria for browser features +- **plan:** Include Browser Use in acceptance criteria for browser features - **deepen-plan:** Context7 only when not covered by skills -- **work:** UI tasks require `dev-browser` BEFORE marking complete. Never guess. +- **work:** UI tasks require Browser Use BEFORE marking complete. Never guess. ## Commands @@ -190,7 +182,7 @@ pnpm --filter @platejs/core lint:fix - [ ] Typecheck (IF updated `.ts` files or typed test/build config): For package-scoped verification, follow the build-first sequence: `pnpm install` -> `pnpm turbo build --filter=...` -> `pnpm turbo typecheck --filter=...`. If unresolved workspace imports remain, run `pnpm build` at repo root, then rerun the package typecheck. For full repo verification, use `pnpm typecheck`. Do not default to `pnpm typecheck` for package verification. - [ ] Lint: Run `lint:fix` - [ ] PR gate (IF creating/updating a PR): Run `check` -- [ ] Browser verification (IF a browser surface changed): verify with `dev-browser` before done +- [ ] Browser verification (IF a browser surface changed): verify with Browser Use before done - [ ] ce-compound (SKIP if trivial): CRITICAL: After completing this request, you MUST evaluate whether it produced extractable knowledge. EVALUATION PROTOCOL (NON-NEGOTIABLE): (1) COMPLETE the user's request first (2) EVALUATE - Did this require non-obvious investigation or debugging? Was the solution something that would help in future similar situations? Did I discover something not immediately obvious from documentation? (3) IF YES to any: load `ce-compound` after the fix is verified and follow its workflow to capture the solution in `docs/solutions/` (4) IF NO to all: Skip - no extraction needed This is NOT optional. Failing to evaluate = valuable knowledge lost. ### Post Compact Recovery diff --git a/apps/www/next-env.d.ts b/apps/www/next-env.d.ts index 2d5420ebae..0c7fad710c 100644 --- a/apps/www/next-env.d.ts +++ b/apps/www/next-env.d.ts @@ -1,7 +1,7 @@ /// /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/www/next.config.ts b/apps/www/next.config.ts index 5c70c575cf..ae70dccdaa 100644 --- a/apps/www/next.config.ts +++ b/apps/www/next.config.ts @@ -135,6 +135,16 @@ const nextConfig = async (_phase: string) => { async redirects() { return [ + { + destination: '/docs/releases', + permanent: true, + source: '/docs/migration', + }, + { + destination: '/cn/docs/releases', + permanent: true, + source: '/cn/docs/migration', + }, { destination: '/r/:path.json', permanent: true, diff --git a/apps/www/package.json b/apps/www/package.json index 1363600275..43831d5bb6 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -73,8 +73,8 @@ "@platejs/emoji": "workspace:^", "@platejs/excalidraw": "workspace:^", "@platejs/find-replace": "workspace:^", - "@platejs/footnote": "workspace:^", "@platejs/floating": "workspace:^", + "@platejs/footnote": "workspace:^", "@platejs/indent": "workspace:^", "@platejs/juice": "workspace:^", "@platejs/layout": "workspace:^", @@ -88,8 +88,8 @@ "@platejs/playwright": "workspace:^", "@platejs/resizable": "workspace:^", "@platejs/selection": "workspace:^", - "@platejs/slate": "workspace:^", "@platejs/slash-command": "workspace:^", + "@platejs/slate": "workspace:^", "@platejs/suggestion": "workspace:^", "@platejs/tabbable": "workspace:^", "@platejs/table": "workspace:^", @@ -189,8 +189,9 @@ "devDependencies": { "@shikijs/compat": "1.1.7", "@tailwindcss/cli": "4.1.8", - "@types/unist": "^3.0.3", "@types/react-syntax-highlighter": "15.5.13", + "@types/unist": "^3.0.3", + "agentation": "^3.0.2", "autoprefixer": "10.4.21", "concurrently": "9.1.2", "glob": "11.0.2", diff --git a/apps/www/src/app/layout.tsx b/apps/www/src/app/layout.tsx index 88b525dc29..5da8a81be1 100644 --- a/apps/www/src/app/layout.tsx +++ b/apps/www/src/app/layout.tsx @@ -1,5 +1,6 @@ import React from 'react'; +import { Agentation } from 'agentation'; import type { Metadata, Viewport } from 'next'; import { NuqsAdapter } from 'nuqs/adapters/next/app'; @@ -114,6 +115,7 @@ export default function RootLayout({ children }: RootLayoutProps) { + {process.env.NODE_ENV === 'development' && } diff --git a/apps/www/src/components/mdx-components.tsx b/apps/www/src/components/mdx-components.tsx index e6167ed10e..55d6f5ac72 100644 --- a/apps/www/src/components/mdx-components.tsx +++ b/apps/www/src/components/mdx-components.tsx @@ -6,6 +6,7 @@ import type { HTMLAttributes, ReactNode } from 'react'; import { cva } from 'class-variance-authority'; import { Provider } from 'jotai'; import { CircleCheck, CircleX, Info, TriangleAlert } from 'lucide-react'; +import dynamic from 'next/dynamic'; import { useMDXComponent } from 'next-contentlayer2/hooks'; import Image from 'next/image'; @@ -61,6 +62,10 @@ import { } from './ui/table'; import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs'; +const ReleaseIndex = dynamic(() => + import('./release-index').then((module) => module.ReleaseIndex) +); + const components = { a: Link, Accordion, @@ -113,6 +118,7 @@ const components = { p: Typography.P, PackageInfo, pre: Typography.Pre, + ReleaseIndex, Step: Typography.Step, Steps: Typography.Steps, table: Typography.Table, @@ -288,7 +294,6 @@ function Accordion({ const calloutVariants = cva( cn( 'my-4 flex gap-2 rounded-lg border border-s-2 bg-neutral-50 p-3 text-sm shadow-md first:mt-0 dark:bg-neutral-900', - '*:[svg]:text-neutral-50 dark:*:[svg]:text-neutral-900', '**:[[data-slot="mdx-link"]]:hover:after:bottom-0' ), { @@ -341,10 +346,10 @@ function Callout({ > {icon ?? { - error: , - info: , - success: , - warn: , + error: , + info: , + success: , + warn: , }[type]}
{title ?

{title}

: null} diff --git a/apps/www/src/components/release-index.tsx b/apps/www/src/components/release-index.tsx new file mode 100644 index 0000000000..d11b5792d3 --- /dev/null +++ b/apps/www/src/components/release-index.tsx @@ -0,0 +1,383 @@ +'use client'; + +import type { HTMLAttributes, ReactElement, ReactNode } from 'react'; + +import { useRef, useState } from 'react'; +import { ChevronDown } from 'lucide-react'; +import ReactMarkdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; + +import { CodeBlock } from '@/components/ui/codeblock'; +import releaseIndexData from '@/generated/release-index.json'; +import { cn } from '@/lib/utils'; + +export type ReleaseIndexRelease = { + content: string; + date: string; + packageTag?: string; + tag: string; + title: string; + url: string; + versionPackagePrUrl?: string; +}; + +export type ReleaseIndexMessage = ReleaseIndexRelease & { + expandable: boolean; +}; + +const githubReleasesUrl = 'https://github.com/udecode/plate/releases'; +const expandableLineThreshold = 15; +const languageClassNamePattern = /language-(\w+)/; +const trailingNewlinePattern = /\n$/; +const releaseHeadingLabels: Record = { + 'Major Changes': 'Breaking Changes', + 'Minor Changes': 'Features', + 'Patch Changes': 'Bug Fixes', +}; + +export function ReleaseIndex({ + className, + releases = releaseIndexData, +}: HTMLAttributes & { + releases?: ReleaseIndexRelease[]; +}) { + const messages = releases.map((release) => ({ + ...release, + date: formatReleaseDate(release.date), + expandable: getContentLineCount(release.content) > expandableLineThreshold, + })); + + if (messages.length === 0) { + return ( +
+

+ No generated release entries yet. +

+ + View all releases on GitHub + +
+ ); + } + + return ( +
+ + + {messages.map((release) => ( + + ))} + + +
+ ); +} + +function getContentLineCount(content: string) { + return content.split('\n').filter((line) => line.trim().length > 0).length; +} + +function ReleaseRow({ release }: { release: ReleaseIndexMessage }) { + return ( +
+
+ + {release.title} + + {release.title !== release.tag ? ( + + {release.tag} + + ) : null} + {release.date ? ( + + ) : null} +
+ + + + +
+ ); +} + +function ReleaseSeparator({ className }: { className?: string }) { + return ( +
+ ); +} + +function ReleaseBody({ + content, + expandable, +}: { + content: string; + expandable: boolean; +}) { + const containerRef = useRef(null); + const [isExpanded, setIsExpanded] = useState(false); + + return ( +
+
+
+ +
+ {expandable && !isExpanded ? ( +
+ ) : null} +
+ + {expandable ? ( + + ) : null} +
+ ); +} + +function MarkdownContent({ content }: { content: string }) { + return ( + ( + + ), + blockquote: ({ className, ...props }) => ( +
p:first-child>code]:!text-xs my-5 border-border border-l-2 py-0.5 pl-4 text-muted-foreground text-sm [&>p:first-child>code]:bg-muted/80 [&>p:first-child>code]:text-foreground [&>p:first-child]:mt-0', + className + )} + {...props} + /> + ), + code: ({ className, children, ...props }) => { + if (className?.includes('language-')) { + return ( + + {children} + + ); + } + + return ( + + {children} + + ); + }, + h2: ({ className, ...props }) => ( +

+ ), + h3: ({ children, className, ...props }) => ( +

+ {formatReleaseHeading(children)} +

+ ), + li: ({ className, ...props }) => ( +
  • + ), + ol: ({ className, ...props }) => ( +
      + ), + p: ({ className, ...props }) => ( +

      + ), + pre: ({ children }) => { + const codeElement = children as ReactElement<{ + children?: string; + className?: string; + }>; + const className = codeElement.props.className ?? ''; + const language = + languageClassNamePattern.exec(className)?.[1] ?? 'text'; + const value = + typeof codeElement.props.children === 'string' + ? codeElement.props.children.replace(trailingNewlinePattern, '') + : ''; + + return ( +

      + +
      + ); + }, + strong: ({ className, ...props }) => ( + + ), + table: ({ className, ...props }) => ( +
      + + + ), + td: ({ className, ...props }) => ( +
      + ), + th: ({ className, ...props }) => ( + + ), + ul: ({ className, ...props }) => ( +
        li]:relative [&>li]:pl-5 [&>li]:before:absolute [&>li]:before:left-0 [&>li]:before:text-muted-foreground [&>li]:before:content-["-"]', + className + )} + {...props} + /> + ), + }} + remarkPlugins={[remarkGfm]} + > + {content} + + ); +} + +function formatReleaseDate(date: string) { + return new Intl.DateTimeFormat('en-US', { + day: 'numeric', + month: 'short', + timeZone: 'UTC', + year: 'numeric', + }).format(new Date(date)); +} + +function formatReleaseHeading(children: ReactNode) { + const text = reactNodeToText(children); + + return text ? (releaseHeadingLabels[text] ?? text) : children; +} + +function reactNodeToText(node: ReactNode): string { + if (typeof node === 'string' || typeof node === 'number') { + return String(node); + } + + if (Array.isArray(node)) { + return node.map(reactNodeToText).join(''); + } + + if (node && typeof node === 'object' && 'props' in node) { + return reactNodeToText( + (node as ReactElement<{ children?: ReactNode }>).props.children + ); + } + + return ''; +} diff --git a/apps/www/src/config/docs.ts b/apps/www/src/config/docs.ts index b2a9d9f9d0..ca24b61e44 100644 --- a/apps/www/src/config/docs.ts +++ b/apps/www/src/config/docs.ts @@ -47,6 +47,11 @@ export const gettingStartedNavItems: SidebarNavItem[] = [ title: 'Installation', titleCn: '安装', }, + { + href: '/docs/releases', + title: 'Releases', + titleCn: '版本发布', + }, ]; export const installationNavItems: SidebarNavItem[] = [ @@ -291,11 +296,6 @@ export const docsConfig: DocsConfig = { }, { items: [ - { - href: '/docs/migration', - title: 'Latest', - titleCn: '最新', - }, { href: '/docs/migration/v48', title: 'v48', diff --git a/apps/www/src/generated/release-index.json b/apps/www/src/generated/release-index.json new file mode 100644 index 0000000000..6962878d07 --- /dev/null +++ b/apps/www/src/generated/release-index.json @@ -0,0 +1,2178 @@ +[ + { + "content": "`platejs`\n\n### Bug Fixes\n\n- Updated `@platejs/core`, `@platejs/slate`, `@platejs/utils`.\n\n`@platejs/slate`\n\n### Bug Fixes\n\n- Updated `slate-react`. ([`ce9ec87`](https://github.com/udecode/plate/commit/ce9ec871c9547a8a3c78ded13a93049ef9fe049c))\n\n`@platejs/utils`\n\n### Bug Fixes\n\n- Updated `@platejs/core`, `@platejs/slate`.\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4978) · [`v53.0.4...v53.0.5`](https://github.com/udecode/plate/compare/%40platejs%2Fai%4053.0.4...platejs%4053.0.5) · By [@github-actions\\[bot\\]](https://github.com/github-actions%5Bbot%5D)", + "contributors": [ + { + "url": "https://github.com/github-actions%5Bbot%5D", + "username": "@github-actions\\[bot\\]" + } + ], + "date": "2026-05-14", + "packageTag": "platejs@53.0.5", + "tag": "v53.0.5", + "title": "v53.0.5", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4978", + "url": "https://github.com/udecode/plate/pull/4978" + }, + { + "content": "`@platejs/ai`\n\n### Bug Fixes\n\n- Updated `@platejs/markdown`.\n\n`@platejs/markdown`\n\n### Bug Fixes\n\n- Respect `resourceLink` when serializing bare autolink literals ([#4972](https://github.com/udecode/plate/pull/4972))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4975) · [`v53.0.3...v53.0.4`](https://github.com/udecode/plate/compare/platejs%4053.0.3...%40platejs%2Fai%4053.0.4) · By [@ajmnz](https://github.com/ajmnz)", + "contributors": [ + { + "url": "https://github.com/ajmnz", + "username": "@ajmnz" + } + ], + "date": "2026-05-12", + "packageTag": "@platejs/ai@53.0.4", + "tag": "v53.0.4", + "title": "v53.0.4", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4975", + "url": "https://github.com/udecode/plate/pull/4975" + }, + { + "content": "`@platejs/ai`\n\n### Bug Fixes\n\n- Clear block streaming state when `aiChat.stop()` stops generation ([#4945](https://github.com/udecode/plate/pull/4945))\n\n`@platejs/link`\n\n### Bug Fixes\n\n- Fix empty link normalization when suggestion acceptance removes the last link character ([#4945](https://github.com/udecode/plate/pull/4945))\n\n`platejs`\n\n### Bug Fixes\n\n- Updated `@platejs/utils`.\n\n`@platejs/suggestion`\n\n### Bug Fixes\n\n- Fix inline-void delete and replace suggestions around mentions and paragraph boundaries ([#4945](https://github.com/udecode/plate/pull/4945))\n\n`@platejs/utils`\n\n### Bug Fixes\n\n- Add a trailing-block insert hook for normalization-driven insert behavior ([#4945](https://github.com/udecode/plate/pull/4945))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4969) · [`v53.0.2...v53.0.3`](https://github.com/udecode/plate/compare/%40platejs%2Flist%4053.0.2...platejs%4053.0.3) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2026-04-29", + "packageTag": "platejs@53.0.3", + "tag": "v53.0.3", + "title": "v53.0.3", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4969", + "url": "https://github.com/udecode/plate/pull/4969" + }, + { + "content": "`@platejs/list`\n\n### Bug Fixes\n\n- Fix `normalizeListStart` to skip unordered list items and resume ordered list numbering past same-indent unordered siblings ([#4954](https://github.com/udecode/plate/pull/4954))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4964) · [`v53.0.1...v53.0.2`](https://github.com/udecode/plate/compare/%40platejs%2Fmedia%4053.0.1...%40platejs%2Flist%4053.0.2) · By [@dylans](https://github.com/dylans)", + "contributors": [ + { + "url": "https://github.com/dylans", + "username": "@dylans" + } + ], + "date": "2026-04-25", + "packageTag": "@platejs/list@53.0.2", + "tag": "v53.0.2", + "title": "v53.0.2", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4964", + "url": "https://github.com/udecode/plate/pull/4964" + }, + { + "content": "`@platejs/media`\n\n### Bug Fixes\n\n- Fix video URL parsing to avoid ReDoS on crafted time parameters ([#4957](https://github.com/udecode/plate/pull/4957))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4961) · [`v53.0.0...v53.0.1`](https://github.com/udecode/plate/compare/platejs%4053.0.0...%40platejs%2Fmedia%4053.0.1) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2026-04-25", + "packageTag": "@platejs/media@53.0.1", + "tag": "v53.0.1", + "title": "v53.0.1", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4961", + "url": "https://github.com/udecode/plate/pull/4961" + }, + { + "content": "`@platejs/autoformat`\n\n### Breaking Changes\n\n- Deprecate `@platejs/autoformat`. Markdown shortcuts and text substitutions are now authored as `inputRules` on each feature plugin, and `AutoformatPlugin` remains only as an inert compatibility export. ([#4941](https://github.com/udecode/plate/pull/4941))\n\n **Migration:**\n\n 1. Remove `AutoformatPlugin` from your plugins and replace `@platejs/autoformat` after migrating rules.\n 2. Replace each old `AutoformatRule` with the matching rule factory on the plugin that owns the feature. See the table below.\n 3. Replace symbol substitutions (arrows, fractions, smart quotes, legal, math operators) with `createTextSubstitutionInputRule` registered on a local `createSlatePlugin`.\n 4. Replace `rules[].query` with `enabled` on the rule factory call. Replace the global code-block guard with a per-plugin `enabled` check.\n 5. Drop `enableUndoOnDelete` — undo-on-delete is the built-in behavior.\n 6. Replace custom `AutoformatRule` definitions with `createRuleFactory` from `platejs`.\n\n ```tsx\n // Before\n import { AutoformatPlugin } from \"@platejs/autoformat\";\n\n const editor = createPlateEditor({\n plugins: [\n AutoformatPlugin.configure({\n options: {\n enableUndoOnDelete: true,\n rules: [\n { match: \"# \", mode: \"block\", type: KEYS.h1 },\n { match: \"**\", mode: \"mark\", type: KEYS.bold },\n {\n match: \"* \",\n mode: \"block\",\n type: \"list\",\n format: (editor) =>\n toggleList(editor, { listStyleType: KEYS.ul }),\n },\n ],\n },\n }),\n ],\n });\n\n // After\n import { BoldRules } from \"@platejs/basic-nodes\";\n import { BoldPlugin } from \"@platejs/basic-nodes/react\";\n import { HeadingRules } from \"@platejs/basic-nodes\";\n import { H1Plugin } from \"@platejs/basic-nodes/react\";\n import { BulletedListRules } from \"@platejs/list\";\n import { ListPlugin } from \"@platejs/list/react\";\n\n const editor = createPlateEditor({\n plugins: [\n H1Plugin.configure({ inputRules: [HeadingRules.markdown()] }),\n BoldPlugin.configure({\n inputRules: [BoldRules.markdown({ variant: \"*\" })],\n }),\n ListPlugin.configure({\n inputRules: [BulletedListRules.markdown({ variant: \"-\" })],\n }),\n ],\n });\n ```\n\n ### Rule Map\n\n #### Basic blocks — `@platejs/basic-nodes`\n\n | Old rule | New rule |\n | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |\n | `{ match: '# '..'###### ', mode: 'block', type: KEYS.h1..h6 }` | `HxPlugin.configure({ inputRules: [HeadingRules.markdown()] })` — register on each `H1Plugin`..`H6Plugin` |\n | `{ match: '> ', mode: 'block', type: KEYS.blockquote }` | `BlockquotePlugin.configure({ inputRules: [BlockquoteRules.markdown()] })` |\n | `{ match: ['---', '—-', '___ '], mode: 'block', type: KEYS.hr }` | `HorizontalRulePlugin.configure({ inputRules: [HorizontalRuleRules.markdown({ variant: '-' }), HorizontalRuleRules.markdown({ variant: '_' })] })` |\n\n #### Basic marks — `@platejs/basic-nodes`\n\n | Old rule | New rule | Owning plugin |\n | -------------------------------------------------------------------- | ------------------------------------------------------------- | --------------------- |\n | `{ match: '**', mode: 'mark', type: KEYS.bold }` | `BoldRules.markdown({ variant: '*' })` | `BoldPlugin` |\n | `{ match: '__', mode: 'mark', type: KEYS.underline }` | `UnderlineRules.markdown()` | `UnderlinePlugin` |\n | `{ match: '*', mode: 'mark', type: KEYS.italic }` | `ItalicRules.markdown({ variant: '*' })` | `ItalicPlugin` |\n | `{ match: '_', mode: 'mark', type: KEYS.italic }` | `ItalicRules.markdown({ variant: '_' })` | `ItalicPlugin` |\n | ``{ match: '`', mode: 'mark', type: KEYS.code }`` | `CodeRules.markdown()` | `CodePlugin` |\n | `{ match: '~~', mode: 'mark', type: KEYS.strikethrough }` | `StrikethroughRules.markdown()` | `StrikethroughPlugin` |\n | `{ match: '~', mode: 'mark', type: KEYS.sub }` | `SubscriptRules.markdown()` | `SubscriptPlugin` |\n | `{ match: '^', mode: 'mark', type: KEYS.sup }` | `SuperscriptRules.markdown()` | `SuperscriptPlugin` |\n | `{ match: '==', mode: 'mark', type: KEYS.highlight }` | `HighlightRules.markdown({ variant: '==' })` | `HighlightPlugin` |\n | `{ match: '≡', mode: 'mark', type: KEYS.highlight }` | `HighlightRules.markdown({ variant: '≡' })` | `HighlightPlugin` |\n | `{ match: '***', mode: 'mark', type: [bold, italic] }` | `MarkComboRules.markdown({ variant: 'boldItalic' })` | `BoldPlugin` |\n | `{ match: '__*', mode: 'mark', type: [underline, italic] }` | `MarkComboRules.markdown({ variant: 'italicUnderline' })` | `BoldPlugin` |\n | `{ match: '__**', mode: 'mark', type: [underline, bold] }` | `MarkComboRules.markdown({ variant: 'boldUnderline' })` | `BoldPlugin` |\n | `{ match: '___***', mode: 'mark', type: [underline, bold, italic] }` | `MarkComboRules.markdown({ variant: 'boldItalicUnderline' })` | `BoldPlugin` |\n\n Register each family on its owning plugin:\n\n ```tsx\n BoldPlugin.configure({\n inputRules: [\n BoldRules.markdown({ variant: \"*\" }),\n BoldRules.markdown({ variant: \"_\" }),\n MarkComboRules.markdown({ variant: \"boldItalic\" }),\n MarkComboRules.markdown({ variant: \"boldUnderline\" }),\n MarkComboRules.markdown({ variant: \"boldItalicUnderline\" }),\n MarkComboRules.markdown({ variant: \"italicUnderline\" }),\n ],\n });\n ```\n\n #### Code block — `@platejs/code-block`\n\n | Old rule | New rule |\n | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |\n | ````{ match: '```', mode: 'block', type: KEYS.codeBlock, format: insertEmptyCodeBlock }```` | `CodeBlockPlugin.configure({ inputRules: [CodeBlockRules.markdown({ on: 'match' })] })` |\n\n #### Lists — `@platejs/list` and `@platejs/list-classic`\n\n | Old rule | New rule |\n | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |\n | `{ match: ['- ', '* '], mode: 'block', format: toggleList(..., { listStyleType: KEYS.ul }) }` | `BulletedListRules.markdown({ variant: '-' })`, `BulletedListRules.markdown({ variant: '*' })` |\n | `{ match: /^\\d+\\.$ \\|^\\d+\\)$ /, matchByRegex: true, format: toggleList(..., { listStyleType: KEYS.ol }) }` | `OrderedListRules.markdown({ variant: '.' })`, `OrderedListRules.markdown({ variant: ')' })` |\n | `{ match: '[] ', mode: 'block', format: toggleList(..., { listStyleType: KEYS.listTodo }) }` | `TaskListRules.markdown({ checked: false })` |\n | `{ match: '[x] ', mode: 'block', format: toggleList + setNodes({ checked: true }) }` | `TaskListRules.markdown({ checked: true })` |\n\n ```tsx\n ListPlugin.configure({\n inputRules: [\n BulletedListRules.markdown({ variant: \"-\" }),\n BulletedListRules.markdown({ variant: \"*\" }),\n OrderedListRules.markdown({ variant: \".\" }),\n OrderedListRules.markdown({ variant: \")\" }),\n TaskListRules.markdown({ checked: false }),\n TaskListRules.markdown({ checked: true }),\n ],\n });\n ```\n\n Replace `@platejs/list` with `@platejs/list-classic` imports when using the classic list model. The factory names are identical.\n\n #### Math — `@platejs/math`\n\n | Old rule | New rule |\n | ---------------------- | ------------------------------------------------------------------------------------------------ |\n | Inline equation `$…$` | `InlineEquationPlugin.configure({ inputRules: [MathRules.markdown({ variant: '$' })] })` |\n | Block equation `$$…$$` | `EquationPlugin.configure({ inputRules: [MathRules.markdown({ on: 'break', variant: '$$' })] })` |\n\n #### Link — `@platejs/link`\n\n | Old behavior | New rule |\n | ---------------------- | ------------------------------------------ |\n | `[text](url)` markdown | `LinkRules.markdown()` |\n | Autolink on paste | `LinkRules.autolink({ variant: 'paste' })` |\n | Autolink on space | `LinkRules.autolink({ variant: 'space' })` |\n | Autolink on Enter | `LinkRules.autolink({ variant: 'break' })` |\n\n ```tsx\n LinkPlugin.configure({\n inputRules: [\n LinkRules.markdown(),\n LinkRules.autolink({ variant: \"paste\" }),\n LinkRules.autolink({ variant: \"space\" }),\n LinkRules.autolink({ variant: \"break\" }),\n ],\n });\n ```\n\n #### Text substitutions (arrows, fractions, legal, math operators, smart quotes)\n\n Move these to a local `createSlatePlugin` with `createTextSubstitutionInputRule`:\n\n ```tsx\n import {\n createSlatePlugin,\n createTextSubstitutionInputRule,\n KEYS,\n } from \"platejs\";\n\n const isTextSubstitutionBlocked = (editor) =>\n editor.api.some({ match: { type: [editor.getType(KEYS.codeBlock)] } });\n\n const ShortcutsPlugin = createSlatePlugin({\n key: \"shortcuts\",\n inputRules: [\n createTextSubstitutionInputRule({\n enabled: ({ editor }) => !isTextSubstitutionBlocked(editor),\n patterns: [\n { format: \"→\", match: \"->\" },\n { format: \"⇒\", match: \"=>\" },\n { format: \"½\", match: \"1/2\" },\n { format: \"™\", match: [\"(tm)\", \"(TM)\"] },\n { format: [\"“\", \"”\"], match: '\"' },\n ],\n }),\n ],\n });\n ```\n\n Each pattern set is just data — `autoformatArrow`, `autoformatLegal`, `autoformatMath`, `autoformatPunctuation`, `autoformatSmartQuotes`, and `autoformatLegalHtml` from the old package map 1:1 onto `patterns` arrays. `AutoformatKit` in the Plate registry is pre-built with all of them.\n\n #### Custom rules\n\n Old `AutoformatRule` objects have no direct replacement. Build a rule family with `createRuleFactory`:\n\n ```tsx\n import { createRuleFactory } from \"platejs\";\n\n const MyRules = {\n markdown: createRuleFactory({\n type: \"blockMatch\",\n match: \"!! \",\n format: \"my-block\",\n }),\n };\n\n MyPlugin.configure({ inputRules: [MyRules.markdown()] });\n ```\n\n ### Option removals\n\n - `enableUndoOnDelete` — removed. Backspace on a rule-inserted node restores the source text by default.\n - `rules[].query` — replaced by `enabled` on the rule factory call.\n - `rules[].preFormat` / `rules[].format` — replaced by rule-family `format` and `resolve` callbacks inside `createRuleFactory`.\n - `rules[].trigger` — rule families set their own trigger. Override it with the `trigger` option on a custom `createRuleFactory` call.\n\n See the [Autoformat](/docs/autoformat) doc for the kit path and the [Plugin Input Rules](/docs/plugin-input-rules) guide for the full runtime.\n\n`@platejs/basic-nodes`\n\n### Breaking Changes\n\n- Store blockquotes as container blocks with block children. Lift every selected nested quoted block one level on `Shift+Tab`. Reset headings to paragraphs on `Backspace` at block start before any merge. ([#4941](https://github.com/udecode/plate/pull/4941))\n\n **Migration:**\n\n 1. Update persisted values, fixtures, and tests to use block children instead of direct text children.\n 2. Expect `editor.tf.blockquote.toggle()` to wrap or unwrap blocks instead of retagging one text block in place.\n 3. Empty later quoted paragraphs delete in place on `Backspace` instead of jumping out of the quote.\n 4. `Backspace` at the start of a heading now resets the heading to a paragraph before any merge.\n 5. Legacy flat blockquote values still normalize on load, but persisted snapshots and fixtures should move to the new shape.\n\n ```tsx\n // Before\n { type: 'blockquote', children: [{ text: 'Quote' }] }\n\n // After\n {\n type: 'blockquote',\n children: [{ type: 'p', children: [{ text: 'Quote' }] }],\n }\n ```\n\n`@platejs/code-block`\n\n### Breaking Changes\n\n- Keep `Backspace` at the start of a non-empty first code line inside the code block. Merge an empty inner code line into the previous code line instead of unwrapping the block. ([#4941](https://github.com/udecode/plate/pull/4941))\n\n`@platejs/markdown`\n\n### Breaking Changes\n\n- Round-trip blockquotes as nested block content instead of flat newline-packed text. Serialize image titles from `node.title` instead of copying the caption into the markdown title slot. Preserve MDX media attribute expressions during markdown serialization instead of stringifying them into JSON text. Serialize plain URL links back to bare URL markdown instead of bracket-link form. Round-trip footnote references and definitions as dedicated footnote nodes instead of collapsing them to plain-text fallback. ([#4941](https://github.com/udecode/plate/pull/4941))\n\n **Migration:**\n\n 1. Update snapshots and direct value assertions to expect `blockquote.children` to contain block nodes such as paragraphs and lists.\n 2. If you generate initial editor values from markdown, hydrate blockquotes with paragraph children instead of flat text.\n 3. If you want markdown output like `![alt](url \"title\")`, set `node.title`. Images without a title now serialize as `![alt](url)`.\n 4. If you serialize MDX media nodes with expression attributes like `width={640}`, expect those expressions to stay as expressions instead of turning into quoted JSON.\n 5. Plain URL links such as `https://platejs.org` now serialize as bare URLs instead of `[https://platejs.org](https://platejs.org)`.\n 6. If you enable footnote-aware markdown input, install `@platejs/footnote` and include `BaseFootnoteReferencePlugin` and `BaseFootnoteDefinitionPlugin` so footnote nodes have real editor semantics instead of falling back to unknown node types.\n\n ```tsx\n // Before\n { type: 'blockquote', children: [{ text: 'Quote\\\\nNext line' }] }\n\n // After\n {\n type: 'blockquote',\n children: [\n { type: 'p', children: [{ text: 'Quote' }] },\n { type: 'p', children: [{ text: 'Next line' }] },\n ],\n }\n ```\n\n### Bug Fixes\n\n- Write canonical date nodes as `` and round-trip normalized media embed metadata ([#4941](https://github.com/udecode/plate/pull/4941))\n\n- Preserve unknown MDX and raw HTML block source more faithfully during markdown deserialization fallback ([#4941](https://github.com/udecode/plate/pull/4941))\n\n`@platejs/table`\n\n### Breaking Changes\n\n- Escalate the second `selectAll` from the current table to the whole document. ([#4941](https://github.com/udecode/plate/pull/4941))\n\n`@platejs/core`\n\n### Features\n\n- Add `lift` as a break and delete rule action for blocks that should leave one ancestor level instead of resetting or exiting. Reset the trailing block to a paragraph when `splitReset` handles selected heading text. ([#4941](https://github.com/udecode/plate/pull/4941))\n\n### Bug Fixes\n\n- Add `createRuleFactory` for building input rule families with overridable defaults and required options ([#4941](https://github.com/udecode/plate/pull/4941))\n\n- Add `useNavigationHighlight(path)` for React node components that need the current navigation-feedback target without reading plugin options directly ([#4941](https://github.com/udecode/plate/pull/4941))\n\n`@platejs/footnote`\n\n### Features\n\n- Add `FootnoteReferencePlugin`, `FootnoteDefinitionPlugin`, and `FootnoteInputPlugin` for real footnote nodes and inline `[^` combobox insertion in Plate editors. ([#4941](https://github.com/udecode/plate/pull/4941))\n\n`@platejs/date`\n\n### Bug Fixes\n\n- Store date nodes as canonical `YYYY-MM-DD` values and preserve unparseable legacy text as fallback data ([#4941](https://github.com/udecode/plate/pull/4941))\n\n`@platejs/link`\n\n### Bug Fixes\n\n- Keep pasted URLs literal inside markdown link source entry by default ([#4941](https://github.com/udecode/plate/pull/4941))\n\n`@platejs/list`\n\n### Bug Fixes\n\n- Allow list markdown rule families to override shared runtime rule fields while keeping semantic `variant` and `checked` options ([#4941](https://github.com/udecode/plate/pull/4941))\n\n`@platejs/list-classic`\n\n### Bug Fixes\n\n- Allow classic list markdown rule families to override shared runtime rule fields while keeping semantic `variant` and `checked` options ([#4941](https://github.com/udecode/plate/pull/4941))\n\n`@platejs/media`\n\n### Bug Fixes\n\n- Support allowlisted Twitter/X embed snippet extraction in media embed URL transforms ([#4941](https://github.com/udecode/plate/pull/4941))\n\n- Normalize supported media embeds into canonical provider metadata and preserve source URLs for embed editing ([#4941](https://github.com/udecode/plate/pull/4941))\n\n`@platejs/slate`\n\n### Bug Fixes\n\n- Updated `slate`, `slate-dom`. ([`081cbe9`](https://github.com/udecode/plate/commit/081cbe92d2284fd6f1b6babc7134405fe7e957c6))\n\n`@platejs/toc`\n\n### Bug Fixes\n\n- Add active section state to `useTocElementState` so TOC elements can mark the current heading while the document scrolls ([#4941](https://github.com/udecode/plate/pull/4941))\n\n- Fix TOC activation to navigate without entering block-selection mode ([#4941](https://github.com/udecode/plate/pull/4941))\n\n`@platejs/utils`\n\n### Bug Fixes\n\n- Add `KEYS.footnoteDefinition`, `KEYS.footnoteReference`, and `KEYS.footnoteInput` ([#4941](https://github.com/udecode/plate/pull/4941))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4948) · [`v52.3.22...v53.0.0`](https://github.com/udecode/plate/compare/%40platejs%2Fai%4052.3.22...platejs%4053.0.0) · By [@zbeyens](https://github.com/zbeyens), [@github-actions\\[bot\\]](https://github.com/github-actions%5Bbot%5D)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + }, + { + "url": "https://github.com/github-actions%5Bbot%5D", + "username": "@github-actions\\[bot\\]" + } + ], + "date": "2026-04-23", + "packageTag": "platejs@53.0.0", + "tag": "v53.0.0", + "title": "v53.0.0", + "type": "major", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4948", + "url": "https://github.com/udecode/plate/pull/4948" + }, + { + "content": "`@platejs/ai`\n\n### Bug Fixes\n\n- Updated `@platejs/markdown`.\n\n`@platejs/markdown`\n\n### Bug Fixes\n\n- Fix classic markdown deserialization for empty list items ([#4938](https://github.com/udecode/plate/pull/4938))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4939) · [`v52.3.21...v52.3.22`](https://github.com/udecode/plate/compare/platejs%4052.3.21...%40platejs%2Fai%4052.3.22) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2026-04-01", + "packageTag": "@platejs/ai@52.3.22", + "tag": "v52.3.22", + "title": "v52.3.22", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4939", + "url": "https://github.com/udecode/plate/pull/4939" + }, + { + "content": "`platejs`\n\n### Bug Fixes\n\n- Updated `@platejs/core`, `@platejs/slate`, `@platejs/utils`.\n\n`@platejs/slate`\n\n### Bug Fixes\n\n- Updated `slate`, `slate-dom`, `slate-react`. ([`0af3236`](https://github.com/udecode/plate/commit/0af323602ab08b2dbe9282c0a8a2db011da10ab6))\n\n`@platejs/utils`\n\n### Bug Fixes\n\n- Updated `@platejs/core`, `@platejs/slate`.\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4933) · [`v52.3.20...v52.3.21`](https://github.com/udecode/plate/compare/%40platejs%2Fai%4052.3.20...platejs%4052.3.21) · By [@github-actions\\[bot\\]](https://github.com/github-actions%5Bbot%5D)", + "contributors": [ + { + "url": "https://github.com/github-actions%5Bbot%5D", + "username": "@github-actions\\[bot\\]" + } + ], + "date": "2026-04-01", + "packageTag": "platejs@52.3.21", + "tag": "v52.3.21", + "title": "v52.3.21", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4933", + "url": "https://github.com/udecode/plate/pull/4933" + }, + { + "content": "`@platejs/ai`\n\n### Bug Fixes\n\n- Updated `@platejs/table`.\n\n`@platejs/table`\n\n### Bug Fixes\n\n- Fix `Shift+Arrow` table selection to switch cells without showing a transient native range ([#4931](https://github.com/udecode/plate/pull/4931))\n\n- Fix merged table border toggles targeting the wrong adjacent cell ([#4930](https://github.com/udecode/plate/pull/4930))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4932) · [`v52.3.19...v52.3.20`](https://github.com/udecode/plate/compare/%40platejs%2Fai%4052.3.19...%40platejs%2Fai%4052.3.20) · By [@hhhjin](https://github.com/hhhjin), [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/hhhjin", + "username": "@hhhjin" + }, + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2026-03-31", + "packageTag": "@platejs/ai@52.3.20", + "tag": "v52.3.20", + "title": "v52.3.20", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4932", + "url": "https://github.com/udecode/plate/pull/4932" + }, + { + "content": "`@platejs/ai`\n\n### Bug Fixes\n\n- Updated `@platejs/markdown`.\n\n`@platejs/markdown`\n\n### Bug Fixes\n\n- Fix ordered markdown lists starting above `1` losing their numbering after `editor.tf.setValue()` ([#4926](https://github.com/udecode/plate/pull/4926))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4929) · [`v52.3.18...v52.3.19`](https://github.com/udecode/plate/compare/%40platejs%2Fai%4052.3.18...%40platejs%2Fai%4052.3.19) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2026-03-30", + "packageTag": "@platejs/ai@52.3.19", + "tag": "v52.3.19", + "title": "v52.3.19", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4929", + "url": "https://github.com/udecode/plate/pull/4929" + }, + { + "content": "`@platejs/ai`\n\n### Bug Fixes\n\n- Updated `@platejs/table`.\n\n`@platejs/table`\n\n### Bug Fixes\n\n- Fixed `ArrowUp` and `ArrowDown` table navigation to avoid the transient caret flash when moving between table cells. ([#4923](https://github.com/udecode/plate/pull/4923))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4924) · [`v52.3.17...v52.3.18`](https://github.com/udecode/plate/compare/%40platejs%2Fai%4052.3.17...%40platejs%2Fai%4052.3.18) · By [@hhhjin](https://github.com/hhhjin)", + "contributors": [ + { + "url": "https://github.com/hhhjin", + "username": "@hhhjin" + } + ], + "date": "2026-03-29", + "packageTag": "@platejs/ai@52.3.18", + "tag": "v52.3.18", + "title": "v52.3.18", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4924", + "url": "https://github.com/udecode/plate/pull/4924" + }, + { + "content": "`@platejs/ai`\n\n### Bug Fixes\n\n- Updated `@platejs/table`.\n\n`@platejs/link`\n\n### Bug Fixes\n\n- Fixed custom `isUrl` handling so it can reject internal paths like `/docs` and anchor links like `#top` instead of those shortcuts always being accepted. ([#4919](https://github.com/udecode/plate/pull/4919))\n\n- Fixed link validation so text starting with `//` is no longer treated as an internal path. This stops comment-style paste content from being autolinked by mistake, including inside code blocks. ([#4917](https://github.com/udecode/plate/pull/4917))\n\n`@platejs/table`\n\n### Bug Fixes\n\n- Fixed table border toggling so left-border updates apply to every selected row in a multi-row cell selection instead of only the topmost row. ([#4922](https://github.com/udecode/plate/pull/4922))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4918) · [`v52.3.16...v52.3.17`](https://github.com/udecode/plate/compare/platejs%4052.3.16...%40platejs%2Fai%4052.3.17) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2026-03-29", + "packageTag": "@platejs/ai@52.3.17", + "tag": "v52.3.17", + "title": "v52.3.17", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4918", + "url": "https://github.com/udecode/plate/pull/4918" + }, + { + "content": "`@platejs/code-block`\n\n### Bug Fixes\n\n- Refresh `@platejs/code-block` release metadata. ([`3465ee1`](https://github.com/udecode/plate/commit/3465ee17a0f2d1557f08656a8d08d3d4a332143b))\n\n`@platejs/core`\n\n### Bug Fixes\n\n- Republish `@platejs/core` to refresh the release graph for downstream packages. ([#4915](https://github.com/udecode/plate/pull/4915))\n\n`@platejs/dnd`\n\n### Bug Fixes\n\n- Refresh `@platejs/dnd` release metadata. ([`3465ee1`](https://github.com/udecode/plate/commit/3465ee17a0f2d1557f08656a8d08d3d4a332143b))\n\n`platejs`\n\n### Bug Fixes\n\n- Updated `@platejs/core`, `@platejs/utils`.\n\n`@platejs/utils`\n\n### Bug Fixes\n\n- Updated `@platejs/core`.\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4916) · [`v52.3.15...v52.3.16`](https://github.com/udecode/plate/compare/%40platejs%2Fcore%4052.3.15...platejs%4052.3.16) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2026-03-28", + "packageTag": "platejs@52.3.16", + "tag": "v52.3.16", + "title": "v52.3.16", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4916", + "url": "https://github.com/udecode/plate/pull/4916" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Added `normalizeStaticValue` to **`@platejs/core`** for normalizing example editor values with deterministic node IDs and stable numeric `createdAt` metadata before SSR hydration. ([#4912](https://github.com/udecode/plate/pull/4912))\n\n ```ts\n import { normalizeStaticValue } from \"@platejs/core\";\n\n const value = normalizeStaticValue(exampleValue);\n ```\n\n`@platejs/dnd`\n\n### Bug Fixes\n\n- Fixed server prerender crashes in **`@platejs/dnd`** by returning inert drag-and-drop connectors when DOM DnD is unavailable, preventing `Expected drag drop context` during SSR builds. ([#4912](https://github.com/udecode/plate/pull/4912))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4913) · [`v52.3.14...v52.3.15`](https://github.com/udecode/plate/compare/%40platejs%2Fcode-block%4052.3.14...%40platejs%2Fcore%4052.3.15) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2026-03-28", + "packageTag": "@platejs/core@52.3.15", + "tag": "v52.3.15", + "title": "v52.3.15", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4913", + "url": "https://github.com/udecode/plate/pull/4913" + }, + { + "content": "`@platejs/code-block`\n\n### Bug Fixes\n\n- Fixed `formatCodeBlock` to rewrite formatted code into real `code_line` nodes and trigger a redecorate pass so syntax highlighting persists after formatting. ([#4907](https://github.com/udecode/plate/pull/4907))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4908) · [`v52.3.13...v52.3.14`](https://github.com/udecode/plate/compare/%40platejs%2Fcore%4052.3.13...%40platejs%2Fcode-block%4052.3.14) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2026-03-27", + "packageTag": "@platejs/code-block@52.3.14", + "tag": "v52.3.14", + "title": "v52.3.14", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4908", + "url": "https://github.com/udecode/plate/pull/4908" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- perf(static): avoid O(n²) findPath in PlateStatic by passing pre-computed path ([#4903](https://github.com/udecode/plate/pull/4903))\n\n Pass pre-computed path through PlateStatic component tree instead of calling `editor.api.findPath()` per node. For 1,872 nodes: paragraph-only 593ms → 68.6ms (8.6x), full plugins 1,661ms → 892ms (1.9x).\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4905) · [`v52.3.12...v52.3.13`](https://github.com/udecode/plate/compare/%40platejs%2Fai%4052.3.12...%40platejs%2Fcore%4052.3.13) · By [@liangzr](https://github.com/liangzr)", + "contributors": [ + { + "url": "https://github.com/liangzr", + "username": "@liangzr" + } + ], + "date": "2026-03-27", + "packageTag": "@platejs/core@52.3.13", + "tag": "v52.3.13", + "title": "v52.3.13", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4905", + "url": "https://github.com/udecode/plate/pull/4905" + }, + { + "content": "`@platejs/ai`\n\n### Bug Fixes\n\n- Fix insert-mode AI preview history so streamed chunks stay out of undo history and selection survives accept, undo, and redo ([#4902](https://github.com/udecode/plate/pull/4902))\n\n`@platejs/slate`\n\n### Bug Fixes\n\n- Fix redo to restore selection after undoing history batches that clear the active selection ([#4902](https://github.com/udecode/plate/pull/4902))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4904) · [`v52.3.11...v52.3.12`](https://github.com/udecode/plate/compare/platejs%4052.3.11...%40platejs%2Fai%4052.3.12) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2026-03-27", + "packageTag": "@platejs/ai@52.3.12", + "tag": "v52.3.12", + "title": "v52.3.12", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4904", + "url": "https://github.com/udecode/plate/pull/4904" + }, + { + "content": "`platejs`\n\n### Bug Fixes\n\n- Use compatible internal dependency ranges so `platejs` can resolve the current `@platejs/*` package graph without nested stale installs. ([`4af5ea4`](https://github.com/udecode/plate/commit/4af5ea4298c0d15f813edd6322bb99cf0a8aaf85))\n\n`@platejs/utils`\n\n### Bug Fixes\n\n- Refresh the published internal core dependency range so consumers can resolve the current Plate package graph without nested stale installs. ([`4af5ea4`](https://github.com/udecode/plate/commit/4af5ea4298c0d15f813edd6322bb99cf0a8aaf85))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4901) · [`v52.3.10...v52.3.11`](https://github.com/udecode/plate/compare/%40platejs%2Fai%4052.3.10...platejs%4052.3.11) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2026-03-26", + "packageTag": "platejs@52.3.11", + "tag": "v52.3.11", + "title": "v52.3.11", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4901", + "url": "https://github.com/udecode/plate/pull/4901" + }, + { + "content": "`@platejs/ai`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/autoformat`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/basic-nodes`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/basic-styles`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/callout`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/caption`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/code-block`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/code-drawing`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/combobox`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/comment`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/csv`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/cursor`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/date`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/diff`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/dnd`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/docx`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/docx-io`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/emoji`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/excalidraw`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/find-replace`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/floating`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/indent`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/juice`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/layout`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/link`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/list`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/list-classic`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/markdown`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/math`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/media`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/mention`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/playwright`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/resizable`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/selection`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/slash-command`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/suggestion`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/tabbable`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/table`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/tag`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/toc`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/toggle`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n`@platejs/yjs`\n\n### Bug Fixes\n\n- Fix declaration bundling by restoring the workspace `platejs` build edge during package builds ([#4897](https://github.com/udecode/plate/pull/4897))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4899) · [`v52.3.9...v52.3.10`](https://github.com/udecode/plate/compare/platejs%4052.3.9...%40platejs%2Fai%4052.3.10) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2026-03-26", + "packageTag": "@platejs/ai@52.3.10", + "tag": "v52.3.10", + "title": "v52.3.10", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4899", + "url": "https://github.com/udecode/plate/pull/4899" + }, + { + "content": "`@platejs/code-block`\n\n### Bug Fixes\n\n- Redecorate code blocks when the language changes to avoid stale highlighting. ([#4893](https://github.com/udecode/plate/pull/4893))\n\n`@platejs/core`\n\n### Bug Fixes\n\n- Typed `editor.api.redecorate()` on the base slate extension API so shared plugins can call it without local casts. ([`d5dfd21`](https://github.com/udecode/plate/commit/d5dfd21e860b4843d5b5da7a211c9c1342860b0d))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4894) · [`v52.3.8...v52.3.9`](https://github.com/udecode/plate/compare/%40platejs%2Fai%4052.3.8...platejs%4052.3.9) · By [@hhhjin](https://github.com/hhhjin), [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/hhhjin", + "username": "@hhhjin" + }, + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2026-03-26", + "packageTag": "platejs@52.3.9", + "tag": "v52.3.9", + "title": "v52.3.9", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4894", + "url": "https://github.com/udecode/plate/pull/4894" + }, + { + "content": "`@platejs/ai`\n\n### Bug Fixes\n\n- Fix `replacePlaceholders` replacing only the first `{prompt}` and markdown placeholder occurrence in AI prompt templates ([#4882](https://github.com/udecode/plate/pull/4882))\n\n`@platejs/csv`\n\n### Bug Fixes\n\n- Fixed Papa Parse interop so native Node ESM runtimes like Vitest can import `@platejs/csv` without failing on a CommonJS named export. ([#4890](https://github.com/udecode/plate/pull/4890))\n\n`@platejs/docx`\n\n### Bug Fixes\n\n- Fix RTF image extraction matching control words like `shp` inside longer tokens such as `shppict` ([#4882](https://github.com/udecode/plate/pull/4882))\n\n`@platejs/docx-io`\n\n### Bug Fixes\n\n- Fix `htmlToDocxBlob` failing TypeScript 6 `BlobPart` checks when wrapping generated `Uint8Array` output. ([#4891](https://github.com/udecode/plate/pull/4891))\n\n`@platejs/layout`\n\n### Bug Fixes\n\n- Fix invalid column group normalization preserving wrapped paragraph content instead of dropping it ([#4882](https://github.com/udecode/plate/pull/4882))\n\n`@platejs/suggestion`\n\n### Bug Fixes\n\n- Fix suggestion metadata lookups using the actual per-suggestion keys and IDs for active descriptions, node matching, and line-break detection ([#4882](https://github.com/udecode/plate/pull/4882))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4888) · [`v52.3.7...v52.3.8`](https://github.com/udecode/plate/compare/%40platejs%2Fmarkdown%4052.3.7...%40platejs%2Fai%4052.3.8) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2026-03-26", + "packageTag": "@platejs/ai@52.3.8", + "tag": "v52.3.8", + "title": "v52.3.8", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4888", + "url": "https://github.com/udecode/plate/pull/4888" + }, + { + "content": "`@platejs/markdown`\n\n### Bug Fixes\n\n- Fix extra blank lines in nested indented list serialization ([#4885](https://github.com/udecode/plate/pull/4885))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4886) · [`v52.3.6...v52.3.7`](https://github.com/udecode/plate/compare/%40platejs%2Fdocx-io%4052.3.6...%40platejs%2Fmarkdown%4052.3.7) · By [@hhhjin](https://github.com/hhhjin)", + "contributors": [ + { + "url": "https://github.com/hhhjin", + "username": "@hhhjin" + } + ], + "date": "2026-03-24", + "packageTag": "@platejs/markdown@52.3.7", + "tag": "v52.3.7", + "title": "v52.3.7", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4886", + "url": "https://github.com/udecode/plate/pull/4886" + }, + { + "content": "`@platejs/docx-io`\n\n### Bug Fixes\n\n- Fixed Mammoth comment preprocessing so block-level comment text keeps spacing instead of collapsing words together during DOCX import. ([#4876](https://github.com/udecode/plate/pull/4876))\n\n- Fixed `decimal-bracket-end` ordered lists to keep decimal DOCX numbering instead of falling back to the package default ordered style. ([#4876](https://github.com/udecode/plate/pull/4876))\n\n- Fixed HTML-to-DOCX list rendering so whitespace-only nodes around list items no longer drop visible list item text in generated documents. ([#4876](https://github.com/udecode/plate/pull/4876))\n\n`@platejs/selection`\n\n### Bug Fixes\n\n- Fixed selection trigger evaluation so multiple configured triggers are checked correctly instead of stopping after the first one. ([#4876](https://github.com/udecode/plate/pull/4876))\n\n`@platejs/table`\n\n### Bug Fixes\n\n- Reduce large-table selection latency by deriving reactive table selection from editor selectors, keeping selected-cell DOM sync at the table root, and avoiding plugin-store writes on every `set_selection`. ([#4872](https://github.com/udecode/plate/pull/4872))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4879) · [`v52.3.5...v52.3.6`](https://github.com/udecode/plate/compare/%40platejs%2Fmarkdown%4052.3.5...%40platejs%2Fdocx-io%4052.3.6) · By [@zbeyens](https://github.com/zbeyens), [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + }, + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2026-03-22", + "packageTag": "@platejs/docx-io@52.3.6", + "tag": "v52.3.6", + "title": "v52.3.6", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4879", + "url": "https://github.com/udecode/plate/pull/4879" + }, + { + "content": "`@platejs/markdown`\n\n### Bug Fixes\n\n- Fix incomplete MDX fallback dropping preceding void blocks during markdown deserialization ([#4870](https://github.com/udecode/plate/pull/4870))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4873) · [`v52.3.4...v52.3.5`](https://github.com/udecode/plate/compare/platejs%4052.3.4...%40platejs%2Fmarkdown%4052.3.5) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2026-03-17", + "packageTag": "@platejs/markdown@52.3.5", + "tag": "v52.3.5", + "title": "v52.3.5", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4873", + "url": "https://github.com/udecode/plate/pull/4873" + }, + { + "content": "`@platejs/autoformat`\n\n### Bug Fixes\n\n- Fixed readonly array support for autoformat rule options like `match`, `trigger`, `type`, and text `format` so `as const` rule definitions typecheck cleanly. ([#4857](https://github.com/udecode/plate/pull/4857))\n\n`@platejs/core`\n\n### Bug Fixes\n\n- Fix `PlateSlate` so it passes the Slate remount key directly instead of spreading `key` through JSX props. ([#4857](https://github.com/udecode/plate/pull/4857))\n\n- Fix `usePlateStore` so it no longer relies on a conditional hook path that breaks React Compiler. ([#4857](https://github.com/udecode/plate/pull/4857))\n\n- Fix `usePluginOption(plugin, 'state')` so it returns the plugin option state instead of reporting an undefined option. ([#4857](https://github.com/udecode/plate/pull/4857))\n\n- Update internal `@udecode/*` dependency ranges to workspace references. ([#4857](https://github.com/udecode/plate/pull/4857))\n\n`platejs`\n\n### Bug Fixes\n\n- Update internal `@platejs/*` and `@udecode/*` dependency ranges to workspace references. ([#4857](https://github.com/udecode/plate/pull/4857))\n\n`@platejs/slate`\n\n### Bug Fixes\n\n- Fixed `editor.tf.duplicateNodes({ block: true })` to duplicate the selected block even when `nodes` is omitted. ([#4857](https://github.com/udecode/plate/pull/4857))\n\n- Fixed `withHistory(createEditor())` legacy method sync so `editor`, `editor.api`, and `editor.tf` all use the history-aware `apply`, `undo`, and `redo` methods. ([#4857](https://github.com/udecode/plate/pull/4857))\n\n- Fixed `queryEditor`, `isAt`, and `editor.api.descendant` so bottom-up location checks, point start/end checks, and non-path descendant searches behave consistently. ([#4857](https://github.com/udecode/plate/pull/4857))\n\n- Update internal `@udecode/*` dependency ranges to workspace references. ([#4857](https://github.com/udecode/plate/pull/4857))\n\n`@udecode/cn`\n\n### Bug Fixes\n\n- Update internal `@udecode/*` dependency ranges to workspace references. ([#4857](https://github.com/udecode/plate/pull/4857))\n\n`@udecode/react-utils`\n\n### Bug Fixes\n\n- Fixed `createPrimitiveComponent` so `setProps` is applied without leaking onto DOM elements. ([#4857](https://github.com/udecode/plate/pull/4857))\n- Fixed `createPrimitiveComponent` to preserve merged hook and consumer `style` props instead of overwriting hook styles when a consumer passes `style`.\n\n- Update internal `@udecode/*` dependency ranges to workspace references. ([#4857](https://github.com/udecode/plate/pull/4857))\n\n`@udecode/utils`\n\n### Bug Fixes\n\n- Fixed `escapeRegExp()` to stop escaping plain `s` characters and only escape actual regular-expression syntax. ([#4857](https://github.com/udecode/plate/pull/4857))\n\n`@platejs/utils`\n\n### Bug Fixes\n\n- Update internal `@udecode/*` dependency ranges to workspace references. ([#4857](https://github.com/udecode/plate/pull/4857))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4858) · [`v52.3.3...v52.3.4`](https://github.com/udecode/plate/compare/platejs%4052.3.3...platejs%4052.3.4) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2026-03-11", + "packageTag": "platejs@52.3.4", + "tag": "v52.3.4", + "title": "v52.3.4", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4858", + "url": "https://github.com/udecode/plate/pull/4858" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Update Slate dependencies to `0.123.0` ([#4849](https://github.com/udecode/plate/pull/4849))\n\n`@platejs/slate`\n\n### Bug Fixes\n\n- Update Slate dependencies to `0.123.0` ([#4849](https://github.com/udecode/plate/pull/4849))\n\n`@platejs/test-utils`\n\n### Bug Fixes\n\n- Update Slate dependencies to `0.123.0` ([#4849](https://github.com/udecode/plate/pull/4849))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4850) · [`v52.3.2...v52.3.3`](https://github.com/udecode/plate/compare/platejs%4052.3.2...platejs%4052.3.3) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2026-03-06", + "packageTag": "platejs@52.3.3", + "tag": "v52.3.3", + "title": "v52.3.3", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4850", + "url": "https://github.com/udecode/plate/pull/4850" + }, + { + "content": "`@platejs/test-utils`\n\n### Bug Fixes\n\n- Support code drawing keys. ([`2f71954`](https://github.com/udecode/plate/commit/2f719540d5201472ceef318f48eee5a44ea4e11e))\n\n`@platejs/utils`\n\n### Bug Fixes\n\n- Support code drawing keys. ([`2f71954`](https://github.com/udecode/plate/commit/2f719540d5201472ceef318f48eee5a44ea4e11e))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4844) · [`v52.3.1...v52.3.2`](https://github.com/udecode/plate/compare/%40platejs%2Fmarkdown%4052.3.1...platejs%4052.3.2) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2026-02-26", + "packageTag": "platejs@52.3.2", + "tag": "v52.3.2", + "title": "v52.3.2", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4844", + "url": "https://github.com/udecode/plate/pull/4844" + }, + { + "content": "`@platejs/markdown`\n\n### Bug Fixes\n\n- fix(markdown): convert void HTML tags to self-closing JSX and preserve image attributes ([#4839](https://github.com/udecode/plate/pull/4839))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4841) · [`v52.3.0...v52.3.1`](https://github.com/udecode/plate/compare/%40platejs%2Fcode-drawing%4052.3.0...%40platejs%2Fmarkdown%4052.3.1) · By [@OmarSalouss](https://github.com/OmarSalouss)", + "contributors": [ + { + "url": "https://github.com/OmarSalouss", + "username": "@OmarSalouss" + } + ], + "date": "2026-02-24", + "packageTag": "@platejs/markdown@52.3.1", + "tag": "v52.3.1", + "title": "v52.3.1", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4841", + "url": "https://github.com/udecode/plate/pull/4841" + }, + { + "content": "`@platejs/code-drawing`\n\n### Features\n\n- Add code drawing plugin with inline editing support for PlantUML, Graphviz, Flowchart, and Mermaid diagrams ([#4811](https://github.com/udecode/plate/pull/4811))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4829) · [`v52.2.0...v52.3.0`](https://github.com/udecode/plate/compare/%40platejs%2Fdocx-io%4052.2.0...%40platejs%2Fcode-drawing%4052.3.0) · By [@electroluxcode](https://github.com/electroluxcode)", + "contributors": [ + { + "url": "https://github.com/electroluxcode", + "username": "@electroluxcode" + } + ], + "date": "2026-01-28", + "packageTag": "@platejs/code-drawing@52.3.0", + "tag": "v52.3.0", + "title": "v52.3.0", + "type": "minor", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4829", + "url": "https://github.com/udecode/plate/pull/4829" + }, + { + "content": "`@platejs/docx-io`\n\n### Features\n\n- Add DOCX import/export package: ([#4814](https://github.com/udecode/plate/pull/4814))\n\n **Import:**\n\n - `importDocx`: Convert DOCX files to Plate nodes with comment extraction\n\n **Export:**\n\n - `exportToDocx`: Convert Plate content to DOCX blob\n - `downloadDocx`: Download DOCX files\n - `exportEditorToDocx`: Export and download in one call\n - `DocxExportPlugin`: Plugin with `editor.api.docxExport` and `editor.tf.docxExport` methods\n - `DOCX_EXPORT_STYLES`: Default CSS styles for Word rendering\n\n **DOCX Static Components** (in existing static files):\n\n - `CalloutElementDocx`\n - `CodeBlockElementDocx`, `CodeLineElementDocx`, `CodeSyntaxLeafDocx`\n - `ColumnElementDocx`, `ColumnGroupElementDocx`\n - `EquationElementDocx`, `InlineEquationElementDocx`\n - `TocElementDocx`\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4820) · [`v52.1.0...v52.2.0`](https://github.com/udecode/plate/compare/%40platejs%2Fai%4052.1.0...%40platejs%2Fdocx-io%4052.2.0) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2026-01-20", + "packageTag": "@platejs/docx-io@52.2.0", + "tag": "v52.2.0", + "title": "v52.2.0", + "type": "minor", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4820", + "url": "https://github.com/udecode/plate/pull/4820" + }, + { + "content": "`@platejs/ai`\n\n### Features\n\n- Upgraded AI SDK from v5 to v6: ([#4800](https://github.com/udecode/plate/pull/4800))\n\n - Updated `ai` peer dependency to `^6.0.0`\n - Updated `@ai-sdk/react` peer dependency to `^3.0.0`\n\n Enhanced AI capabilities with better table cell handling:\n\n - Added `applyTableCellSuggestion` utility for handling single-cell table operations\n - Added `nestedContainerUtils` for managing nested containers in table cells\n - Enhanced `getMarkdown` with improved table structure handling and better cell content serialization\n - Improved `applyAISuggestions` with more robust cell manipulation support\n - Added comprehensive tests for markdown generation from complex table structures\n\n`@platejs/markdown`\n\n### Bug Fixes\n\n- Enhanced table cell serialization to support multiple blocks within cells: ([#4800](https://github.com/udecode/plate/pull/4800))\n\n - Table cells (td/th) now insert `
        ` separators between multiple blocks when serializing to markdown\n - This allows markdown tables to better represent complex cell content that contains multiple paragraphs or other block elements\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4809) · [`v52.0.17...v52.1.0`](https://github.com/udecode/plate/compare/platejs%4052.0.17...%40platejs%2Fai%4052.1.0) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2026-01-09", + "packageTag": "@platejs/ai@52.1.0", + "tag": "v52.1.0", + "title": "v52.1.0", + "type": "minor", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4809", + "url": "https://github.com/udecode/plate/pull/4809" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- skip react-compiler for static rendering ([#4806](https://github.com/udecode/plate/pull/4806))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4807) · [`v52.0.16...v52.0.17`](https://github.com/udecode/plate/compare/%40platejs%2Fselection%4052.0.16...platejs%4052.0.17) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2026-01-09", + "packageTag": "platejs@52.0.17", + "tag": "v52.0.17", + "title": "v52.0.17", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4807", + "url": "https://github.com/udecode/plate/pull/4807" + }, + { + "content": "`@platejs/selection`\n\n### Bug Fixes\n\n- Added `disableSelectAll` option to **BlockSelectionPlugin**. Set to `true` to disable the plugin's custom selectAll (Cmd+A) behavior and use the editor's default behavior instead. ([#4799](https://github.com/udecode/plate/pull/4799))\n\n ```ts\n BlockSelectionPlugin.configure({\n options: { disableSelectAll: true },\n });\n ```\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4801) · [`v52.0.15...v52.0.16`](https://github.com/udecode/plate/compare/platejs%4052.0.15...%40platejs%2Fselection%4052.0.16) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-12-20", + "packageTag": "@platejs/selection@52.0.16", + "tag": "v52.0.16", + "title": "v52.0.16", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4801", + "url": "https://github.com/udecode/plate/pull/4801" + }, + { + "content": "`@platejs/combobox`\n\n### Bug Fixes\n\n- Add `userId` option to editor for collaborative features ([#4792](https://github.com/udecode/plate/pull/4792))\n\n - Add `userId` option to `usePlateEditor`/`createSlateEditor` options\n - Add `editor.meta.userId` for accessing the current user ID\n - **Breaking**: Remove `getUserId` option from `TriggerComboboxPluginOptions`. Use `editor.meta.userId` instead.\n\n Migration:\n\n ```tsx\n // Before\n MentionPlugin.configure({\n options: {\n getUserId: (editor) => \"123\",\n },\n });\n\n // After\n const editor = usePlateEditor({\n plugins: [MentionPlugin],\n userId: \"123\",\n });\n ```\n\n`@platejs/core`\n\n### Bug Fixes\n\n- Add `userId` option to editor for collaborative features ([#4792](https://github.com/udecode/plate/pull/4792))\n\n - Add `userId` option to `usePlateEditor`/`createSlateEditor` options\n - Add `editor.meta.userId` for accessing the current user ID\n - **Breaking**: Remove `getUserId` option from `TriggerComboboxPluginOptions`. Use `editor.meta.userId` instead.\n\n Migration:\n\n ```tsx\n // Before\n MentionPlugin.configure({\n options: {\n getUserId: (editor) => \"123\",\n },\n });\n\n // After\n const editor = usePlateEditor({\n plugins: [MentionPlugin],\n userId: \"123\",\n });\n ```\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4794) · [`v52.0.14...v52.0.15`](https://github.com/udecode/plate/compare/%40platejs%2Fcombobox%4052.0.14...platejs%4052.0.15) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-12-15", + "packageTag": "platejs@52.0.15", + "tag": "v52.0.15", + "title": "v52.0.15", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4794", + "url": "https://github.com/udecode/plate/pull/4794" + }, + { + "content": "`@platejs/combobox`\n\n### Bug Fixes\n\n- Fix type error ([#4790](https://github.com/udecode/plate/pull/4790))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4791) · [`v52.0.13...v52.0.14`](https://github.com/udecode/plate/compare/%40platejs%2Fcombobox%4052.0.13...%40platejs%2Fcombobox%4052.0.14) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-12-14", + "packageTag": "@platejs/combobox@52.0.14", + "tag": "v52.0.14", + "title": "v52.0.14", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4791", + "url": "https://github.com/udecode/plate/pull/4791" + }, + { + "content": "`@platejs/combobox`\n\n### Bug Fixes\n\n- Add `getUserId` option to `TriggerComboboxPluginOptions` to fix combobox popover opening for all users in Yjs collaboration mode ([#4762](https://github.com/udecode/plate/pull/4762))\n\n When a user types a trigger character (e.g. `/` or `@`), the combobox input now stores the creator's `userId`. Only the creator will see the auto-focused combobox popover.\n\n ```tsx\n SlashPlugin.configure({\n options: {\n getUserId: (editor) => editor.getOption(YjsPlugin, \"userId\"),\n },\n });\n\n MentionPlugin.configure({\n options: {\n getUserId: (editor) => editor.getOption(YjsPlugin, \"userId\"),\n },\n });\n ```\n\n`@platejs/yjs`\n\n### Bug Fixes\n\n- Add `userId` option to **YjsPlugin** for combobox collaboration support ([#4762](https://github.com/udecode/plate/pull/4762))\n\n ```tsx\n YjsPlugin.configure({\n options: {\n userId: user?.id,\n },\n });\n ```\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4789) · [`v52.0.12...v52.0.13`](https://github.com/udecode/plate/compare/%40platejs%2Fyjs%4052.0.12...%40platejs%2Fcombobox%4052.0.13) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-12-14", + "packageTag": "@platejs/combobox@52.0.13", + "tag": "v52.0.13", + "title": "v52.0.13", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4789", + "url": "https://github.com/udecode/plate/pull/4789" + }, + { + "content": "`@platejs/yjs`\n\n### Bug Fixes\n\n- revert @platejs/yjs to 52.0.5 ([#4786](https://github.com/udecode/plate/pull/4786))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4787) · [`v52.0.11...v52.0.12`](https://github.com/udecode/plate/compare/platejs%4052.0.11...%40platejs%2Fyjs%4052.0.12) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-12-13", + "packageTag": "@platejs/yjs@52.0.12", + "tag": "v52.0.12", + "title": "v52.0.12", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4787", + "url": "https://github.com/udecode/plate/pull/4787" + }, + { + "content": "`@platejs/ai`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/autoformat`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/basic-nodes`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/basic-styles`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/callout`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/caption`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/code-block`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/combobox`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/comment`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/core`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/csv`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/cursor`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/date`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/diff`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/dnd`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/docx`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/emoji`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/excalidraw`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/find-replace`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/floating`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/indent`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/juice`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/layout`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/link`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/list`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/list-classic`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/markdown`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/math`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/media`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/mention`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`platejs`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/playwright`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/resizable`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/selection`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/slash-command`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/suggestion`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/tabbable`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/table`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/tag`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/toc`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/toggle`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@udecode/cn`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@udecode/react-hotkeys`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@udecode/react-utils`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/utils`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n`@platejs/yjs`\n\n### Bug Fixes\n\n- Fixed \"Cannot find module 'react/compiler-runtime'\" error for React 18 users ([#4784](https://github.com/udecode/plate/pull/4784))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4785) · [`v52.0.10...v52.0.11`](https://github.com/udecode/plate/compare/platejs%4052.0.10...platejs%4052.0.11) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2025-12-12", + "packageTag": "platejs@52.0.11", + "tag": "v52.0.11", + "title": "v52.0.11", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4785", + "url": "https://github.com/udecode/plate/pull/4785" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- slate 0.120.0 ([`a4d093b`](https://github.com/udecode/plate/commit/a4d093bf0b0c78c40b6bb1bfe5fd15e4198b8942))\n\n`@platejs/markdown`\n\n### Bug Fixes\n\n- Add support for callout serialization with icon attribute ([#4776](https://github.com/udecode/plate/pull/4776))\n\n`@platejs/slate`\n\n### Bug Fixes\n\n- slate 0.120.0 ([`a4d093b`](https://github.com/udecode/plate/commit/a4d093bf0b0c78c40b6bb1bfe5fd15e4198b8942))\n\n`@platejs/test-utils`\n\n### Bug Fixes\n\n- slate 0.120.0 ([`a4d093b`](https://github.com/udecode/plate/commit/a4d093bf0b0c78c40b6bb1bfe5fd15e4198b8942))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4777) · [`v52.0.9...v52.0.10`](https://github.com/udecode/plate/compare/%40platejs%2Fyjs%4052.0.9...platejs%4052.0.10) · By [@zbeyens](https://github.com/zbeyens), [@hhhjin](https://github.com/hhhjin)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + }, + { + "url": "https://github.com/hhhjin", + "username": "@hhhjin" + } + ], + "date": "2025-12-10", + "packageTag": "platejs@52.0.10", + "tag": "v52.0.10", + "title": "v52.0.10", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4777", + "url": "https://github.com/udecode/plate/pull/4777" + }, + { + "content": "`@platejs/yjs`\n\n### Bug Fixes\n\n- Fix yjs init. ([`e359d59`](https://github.com/udecode/plate/commit/e359d5981ca644352a9ae71b2b168b5a709a4d35))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4774) · [`v52.0.8...v52.0.9`](https://github.com/udecode/plate/compare/platejs%4052.0.8...%40platejs%2Fyjs%4052.0.9) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-12-09", + "packageTag": "@platejs/yjs@52.0.9", + "tag": "v52.0.9", + "title": "v52.0.9", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4774", + "url": "https://github.com/udecode/plate/pull/4774" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Export `useSlateStatic` from `slate-react` ([#4768](https://github.com/udecode/plate/pull/4768))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4769) · [`v52.0.7...v52.0.8`](https://github.com/udecode/plate/compare/%40platejs%2Fyjs%4052.0.7...platejs%4052.0.8) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-12-03", + "packageTag": "platejs@52.0.8", + "tag": "v52.0.8", + "title": "v52.0.8", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4769", + "url": "https://github.com/udecode/plate/pull/4769" + }, + { + "content": "`@platejs/yjs`\n\n### Bug Fixes\n\n- Revert previous fixes ([#4765](https://github.com/udecode/plate/pull/4765))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4766) · [`v52.0.6...v52.0.7`](https://github.com/udecode/plate/compare/%40platejs%2Fai%4052.0.6...%40platejs%2Fyjs%4052.0.7) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-12-02", + "packageTag": "@platejs/yjs@52.0.7", + "tag": "v52.0.7", + "title": "v52.0.7", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4766", + "url": "https://github.com/udecode/plate/pull/4766" + }, + { + "content": "`@platejs/ai`\n\n### Bug Fixes\n\n- Fix AI edit mode inserting nodes at wrong position when cursor moves during streaming.() ([#4763](https://github.com/udecode/plate/pull/4763))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4764) · [`v52.0.5...v52.0.6`](https://github.com/udecode/plate/compare/%40platejs%2Fyjs%4052.0.5...%40platejs%2Fai%4052.0.6) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-12-02", + "packageTag": "@platejs/ai@52.0.6", + "tag": "v52.0.6", + "title": "v52.0.6", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4764", + "url": "https://github.com/udecode/plate/pull/4764" + }, + { + "content": "`@platejs/yjs`\n\n### Bug Fixes\n\n- Revert and fixes content duplicated. ([#4759](https://github.com/udecode/plate/pull/4759))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4760) · [`v52.0.4...v52.0.5`](https://github.com/udecode/plate/compare/%40platejs%2Fmarkdown%4052.0.4...%40platejs%2Fyjs%4052.0.5) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-11-29", + "packageTag": "@platejs/yjs@52.0.5", + "tag": "v52.0.5", + "title": "v52.0.5", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4760", + "url": "https://github.com/udecode/plate/pull/4760" + }, + { + "content": "`@platejs/markdown`\n\n### Bug Fixes\n\n- Fix markdown serializer to split mixed-style lists correctly, preserve numbering/todo states ([#4757](https://github.com/udecode/plate/pull/4757))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4758) · [`v52.0.3...v52.0.4`](https://github.com/udecode/plate/compare/%40platejs%2Fyjs%4052.0.3...%40platejs%2Fmarkdown%4052.0.4) · By [@hhhjin](https://github.com/hhhjin)", + "contributors": [ + { + "url": "https://github.com/hhhjin", + "username": "@hhhjin" + } + ], + "date": "2025-11-28", + "packageTag": "@platejs/markdown@52.0.4", + "tag": "v52.0.4", + "title": "v52.0.4", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4758", + "url": "https://github.com/udecode/plate/pull/4758" + }, + { + "content": "`@platejs/yjs`\n\n### Bug Fixes\n\n- Upgrade @hocuspocus/provider to v3 add delayed sync. ([`0b9de29`](https://github.com/udecode/plate/commit/0b9de29874b77f130169765cb2f07ecdab826734))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4756) · [`v52.0.2...v52.0.3`](https://github.com/udecode/plate/compare/%40platejs%2Femoji%4052.0.2...%40platejs%2Fyjs%4052.0.3) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-11-27", + "packageTag": "@platejs/yjs@52.0.3", + "tag": "v52.0.3", + "title": "v52.0.3", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4756", + "url": "https://github.com/udecode/plate/pull/4756" + }, + { + "content": "`@platejs/emoji`\n\n### Bug Fixes\n\n- Fix React Compiler hook ordering issues by converting EmojiPickerState to proper custom hook ([#4752](https://github.com/udecode/plate/pull/4752))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4753) · [`v52.0.1...v52.0.2`](https://github.com/udecode/plate/compare/platejs%4052.0.1...%40platejs%2Femoji%4052.0.2) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2025-11-24", + "packageTag": "@platejs/emoji@52.0.2", + "tag": "v52.0.2", + "title": "v52.0.2", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4753", + "url": "https://github.com/udecode/plate/pull/4753" + }, + { + "content": "`@platejs/ai`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/autoformat`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/basic-nodes`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/basic-styles`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/callout`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/caption`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/code-block`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/combobox`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/comment`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/core`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/csv`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/cursor`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/date`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/diff`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/dnd`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/docx`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/emoji`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/excalidraw`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/find-replace`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/floating`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/indent`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/juice`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/layout`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/link`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/list`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/list-classic`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/markdown`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/math`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/media`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/mention`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`platejs`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/playwright`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/resizable`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/selection`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/slash-command`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/slate`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/suggestion`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/tabbable`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/table`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/tag`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/test-utils`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/toc`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/toggle`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@udecode/cn`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@udecode/react-hotkeys`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@udecode/react-utils`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@udecode/utils`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/utils`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n`@platejs/yjs`\n\n### Bug Fixes\n\n- Add React Compiler support. ([#4750](https://github.com/udecode/plate/pull/4750))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4751) · [`v52.0.0...v52.0.1`](https://github.com/udecode/plate/compare/platejs%4052.0.0...platejs%4052.0.1) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2025-11-24", + "packageTag": "platejs@52.0.1", + "tag": "v52.0.1", + "title": "v52.0.1", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4751", + "url": "https://github.com/udecode/plate/pull/4751" + }, + { + "content": "`@platejs/ai`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/autoformat`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/basic-nodes`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/basic-styles`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/callout`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/caption`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/code-block`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/combobox`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/comment`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/core`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/csv`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/cursor`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/date`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/diff`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/dnd`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/docx`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/emoji`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/excalidraw`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/find-replace`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/floating`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/indent`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/juice`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/layout`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/link`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/list`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/list-classic`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/markdown`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/math`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/media`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/mention`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`platejs`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/playwright`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/resizable`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/selection`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/slash-command`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/slate`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/suggestion`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/tabbable`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/table`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/tag`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/test-utils`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/toc`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/toggle`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@udecode/cn`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@udecode/react-hotkeys`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@udecode/react-utils`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@udecode/utils`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/utils`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n`@platejs/yjs`\n\n### Breaking Changes\n\n- ESM-only ([#4742](https://github.com/udecode/plate/pull/4742))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4743) · [`v51.1.3...v52.0.0`](https://github.com/udecode/plate/compare/platejs%4051.1.3...platejs%4052.0.0) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2025-11-23", + "packageTag": "platejs@52.0.0", + "tag": "v52.0.0", + "title": "v52.0.0", + "type": "major", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4743", + "url": "https://github.com/udecode/plate/pull/4743" + }, + { + "content": "`@platejs/cursor`\n\n### Bug Fixes\n\n- Avoid accessing ref during render ([#4735](https://github.com/udecode/plate/pull/4735))\n\n`@platejs/dnd`\n\n### Bug Fixes\n\n- Avoid accessing ref during render ([#4735](https://github.com/udecode/plate/pull/4735))\n\n`@platejs/toc`\n\n### Bug Fixes\n\n- Avoid accessing ref during render ([#4735](https://github.com/udecode/plate/pull/4735))\n\n`@udecode/react-hotkeys`\n\n### Bug Fixes\n\n- Avoid accessing ref during render ([#4735](https://github.com/udecode/plate/pull/4735))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4736) · [`v51.1.2...v51.1.3`](https://github.com/udecode/plate/compare/platejs%4051.1.2...platejs%4051.1.3) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2025-11-20", + "packageTag": "platejs@51.1.3", + "tag": "v51.1.3", + "title": "v51.1.3", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4736", + "url": "https://github.com/udecode/plate/pull/4736" + }, + { + "content": "`@platejs/ai`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/autoformat`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/basic-nodes`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/basic-styles`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/callout`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/caption`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/code-block`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/combobox`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/comment`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/core`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/csv`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/cursor`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/date`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/diff`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/dnd`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/docx`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/emoji`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/excalidraw`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/find-replace`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/floating`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/indent`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/juice`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/layout`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/link`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/list`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/list-classic`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/markdown`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/math`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/media`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/mention`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`platejs`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/playwright`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/resizable`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/selection`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/slash-command`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/slate`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/suggestion`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/tabbable`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/table`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/tag`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/test-utils`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/toc`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/toggle`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@udecode/cn`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@udecode/react-hotkeys`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@udecode/react-utils`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@udecode/utils`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/utils`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n`@platejs/yjs`\n\n### Bug Fixes\n\n- Format code with Biome ([#4732](https://github.com/udecode/plate/pull/4732))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4734) · [`v51.1.1...v51.1.2`](https://github.com/udecode/plate/compare/%40platejs%2Fmarkdown%4051.1.1...platejs%4051.1.2) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2025-11-20", + "packageTag": "platejs@51.1.2", + "tag": "v51.1.2", + "title": "v51.1.2", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4734", + "url": "https://github.com/udecode/plate/pull/4734" + }, + { + "content": "`@platejs/markdown`\n\n### Bug Fixes\n\n- should deserialize table with math formula in cell ([#4729](https://github.com/udecode/plate/pull/4729))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4730) · [`v51.1.0...v51.1.1`](https://github.com/udecode/plate/compare/%40platejs%2Fyjs%4051.1.0...%40platejs%2Fmarkdown%4051.1.1) · By [@tomdyqin](https://github.com/tomdyqin)", + "contributors": [ + { + "url": "https://github.com/tomdyqin", + "username": "@tomdyqin" + } + ], + "date": "2025-11-19", + "packageTag": "@platejs/markdown@51.1.1", + "tag": "v51.1.1", + "title": "v51.1.1", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4730", + "url": "https://github.com/udecode/plate/pull/4730" + }, + { + "content": "`@platejs/yjs`\n\n### Features\n\n- Add sharedType option ([#4714](https://github.com/udecode/plate/pull/4714))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4715) · [`v51.0.1...v51.1.0`](https://github.com/udecode/plate/compare/%40platejs%2Fai%4051.0.1...%40platejs%2Fyjs%4051.1.0) · By [@baptisteArno](https://github.com/baptisteArno)", + "contributors": [ + { + "url": "https://github.com/baptisteArno", + "username": "@baptisteArno" + } + ], + "date": "2025-11-05", + "packageTag": "@platejs/yjs@51.1.0", + "tag": "v51.1.0", + "title": "v51.1.0", + "type": "minor", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4715", + "url": "https://github.com/udecode/plate/pull/4715" + }, + { + "content": "`@platejs/ai`\n\n### Bug Fixes\n\n- Added `rejectAISuggestions` and `acceptAISuggestions` utility. ([#4697](https://github.com/udecode/plate/pull/4697))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4699) · [`v51.0.0...v51.0.1`](https://github.com/udecode/plate/compare/platejs%4051.0.0...%40platejs%2Fai%4051.0.1) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-10-21", + "packageTag": "@platejs/ai@51.0.1", + "tag": "v51.0.1", + "title": "v51.0.1", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4699", + "url": "https://github.com/udecode/plate/pull/4699" + }, + { + "content": "`@platejs/core`\n\n### Breaking Changes\n\n- Moved static rendering functionality to `@platejs/core/static` / `platejs/static` to make `@platejs/core` / `platejs` React-free. ([#4695](https://github.com/udecode/plate/pull/4695))\n\n **Migration**\n To migrate, update your imports from `platejs` to `platejs/static` for all static rendering features listed below:\n\n - `createStaticEditor`, `CreateStaticEditorOptions` - Create static editor instance\n - `serializeHtml`, `SerializeHtmlOptions` - Serialize editor content to HTML string\n - `PlateStatic`, `PlateStaticProps` - Main static editor component\n - `SlateElement`, `SlateElementProps` - Static element component\n - `SlateText`, `SlateTextProps` - Static text component\n - `SlateLeaf`, `SlateLeafProps` - Static leaf component\n - `getEditorDOMFromHtmlString`\n\n`@platejs/list`\n\n### Bug Fixes\n\n- Updated import path for `SlateRenderElementProps` to use the new static export from `platejs/static`. ([#4695](https://github.com/udecode/plate/pull/4695))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4696) · [`v50.3.9...v51.0.0`](https://github.com/udecode/plate/compare/platejs%4050.3.9...platejs%4051.0.0) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-10-17", + "packageTag": "platejs@51.0.0", + "tag": "v51.0.0", + "title": "v51.0.0", + "type": "major", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4696", + "url": "https://github.com/udecode/plate/pull/4696" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Moved `getNodeDataAttributeKeys` and `keyToDataAttribute` functions from static utilities to regular utilities to decouple React dependencies. ([#4693](https://github.com/udecode/plate/pull/4693))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4694) · [`v50.3.8...v50.3.9`](https://github.com/udecode/plate/compare/platejs%4050.3.8...platejs%4050.3.9) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-10-17", + "packageTag": "platejs@50.3.9", + "tag": "v50.3.9", + "title": "v50.3.9", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4694", + "url": "https://github.com/udecode/plate/pull/4694" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Fixed `editor.setOption` to properly handle function values ([#4691](https://github.com/udecode/plate/pull/4691))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4692) · [`v50.3.7...v50.3.8`](https://github.com/udecode/plate/compare/platejs%4050.3.7...platejs%4050.3.8) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2025-10-16", + "packageTag": "platejs@50.3.8", + "tag": "v50.3.8", + "title": "v50.3.8", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4692", + "url": "https://github.com/udecode/plate/pull/4692" + }, + { + "content": "`@platejs/ai`\n\n### Bug Fixes\n\n- Import `createZustandStore` from `platejs/react` ([#4689](https://github.com/udecode/plate/pull/4689))\n\n`@platejs/core`\n\n### Bug Fixes\n\n- Decouple `createSlateEditor` from React: ([#4689](https://github.com/udecode/plate/pull/4689))\n\n - `createZustandStore` from `@platejs/core` (or `platejs`) is now a vanilla store without React-specific functionality (hooks).\n - The previous behavior of `createZustandStore` is now available in `@platejs/core/react` (or `platejs/react`). This is not part of our public API so it won't be a breaking change, but if you're using it, you'll need to import it from `@platejs/core/react` (or `platejs/react`) instead.\n\n`@platejs/media`\n\n### Bug Fixes\n\n- Import `createZustandStore` from `platejs/react` ([#4689](https://github.com/udecode/plate/pull/4689))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4690) · [`v50.3.6...v50.3.7`](https://github.com/udecode/plate/compare/%40platejs%2Fyjs%4050.3.6...platejs%4050.3.7) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-10-16", + "packageTag": "platejs@50.3.7", + "tag": "v50.3.7", + "title": "v50.3.7", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4690", + "url": "https://github.com/udecode/plate/pull/4690" + }, + { + "content": "`@platejs/yjs`\n\n### Bug Fixes\n\n- Fixed issue where onReady would not be called ([#4685](https://github.com/udecode/plate/pull/4685))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4686) · [`v50.3.5...v50.3.6`](https://github.com/udecode/plate/compare/%40platejs%2Fai%4050.3.5...%40platejs%2Fyjs%4050.3.6) · By [@Pagebakers](https://github.com/Pagebakers)", + "contributors": [ + { + "url": "https://github.com/Pagebakers", + "username": "@Pagebakers" + } + ], + "date": "2025-10-15", + "packageTag": "@platejs/yjs@50.3.6", + "tag": "v50.3.6", + "title": "v50.3.6", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4686", + "url": "https://github.com/udecode/plate/pull/4686" + }, + { + "content": "`@platejs/ai`\n\n### Bug Fixes\n\n- Fix poor AI generation quality when blockSelecting. ([#4676](https://github.com/udecode/plate/pull/4676))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4683) · [`v50.3.4...v50.3.5`](https://github.com/udecode/plate/compare/%40platejs%2Fselection%4050.3.4...%40platejs%2Fai%4050.3.5) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-10-13", + "packageTag": "@platejs/ai@50.3.5", + "tag": "v50.3.5", + "title": "v50.3.5", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4683", + "url": "https://github.com/udecode/plate/pull/4683" + }, + { + "content": "`@platejs/selection`\n\n### Bug Fixes\n\n- Fixed AI menu not opening when blocks are selected by updating `onKeyDownSelecting` callback to include editor parameter ([#4673](https://github.com/udecode/plate/pull/4673))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4674) · [`v50.3.3...v50.3.4`](https://github.com/udecode/plate/compare/%40platejs%2Fai%4050.3.3...%40platejs%2Fselection%4050.3.4) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-10-05", + "packageTag": "@platejs/selection@50.3.4", + "tag": "v50.3.4", + "title": "v50.3.4", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4674", + "url": "https://github.com/udecode/plate/pull/4674" + }, + { + "content": "`@platejs/ai`\n\n### Bug Fixes\n\n- Fix list diff issue ([#4670](https://github.com/udecode/plate/pull/4670))\n\n`@platejs/suggestion`\n\n### Bug Fixes\n\n- Fix: Properly unset transient suggestion key when accepting or rejecting suggestions ([#4658](https://github.com/udecode/plate/pull/4658))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4672) · [`v50.3.2...v50.3.3`](https://github.com/udecode/plate/compare/%40platejs%2Ffloating%4050.3.2...%40platejs%2Fai%4050.3.3) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-10-05", + "packageTag": "@platejs/ai@50.3.3", + "tag": "v50.3.3", + "title": "v50.3.3", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4672", + "url": "https://github.com/udecode/plate/pull/4672" + }, + { + "content": "`@platejs/floating`\n\n### Bug Fixes\n\n- Fix: Resolve infinite loop in useFloatingToolbar hook (v2) Problem: The floating toolbar was causing infinite re-renders under certain conditions, leading to performance issues and potential browser hangs. This occurred when users interacted with text selections while the toolbar was visible. ([#4646](https://github.com/udecode/plate/pull/4646))\n\n WHY the change was made:\n\n - Infinite re-rendering was caused by the open dependency in the useEffect hook.\n - The open dependency was removed and the setOpen function was called with a functional update to access the previous state.\n\n HOW a consumer should update their code:\n\n - No action required. The change is internal and does not affect consumer code.\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4668) · [`v50.3.1...v50.3.2`](https://github.com/udecode/plate/compare/%40platejs%2Fdnd%4050.3.1...%40platejs%2Ffloating%4050.3.2) · By [@narraje](https://github.com/narraje)", + "contributors": [ + { + "url": "https://github.com/narraje", + "username": "@narraje" + } + ], + "date": "2025-10-03", + "packageTag": "@platejs/floating@50.3.2", + "tag": "v50.3.2", + "title": "v50.3.2", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4668", + "url": "https://github.com/udecode/plate/pull/4668" + }, + { + "content": "`@platejs/dnd`\n\n### Bug Fixes\n\n- Fixed support for dnd a node into another editor, it removes now the node from origin editor. ([#4657](https://github.com/udecode/plate/pull/4657))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4660) · [`v50.3.0...v50.3.1`](https://github.com/udecode/plate/compare/%40platejs%2Fexcalidraw%4050.3.0...%40platejs%2Fdnd%4050.3.1) · By [@sneridagh](https://github.com/sneridagh)", + "contributors": [ + { + "url": "https://github.com/sneridagh", + "username": "@sneridagh" + } + ], + "date": "2025-09-29", + "packageTag": "@platejs/dnd@50.3.1", + "tag": "v50.3.1", + "title": "v50.3.1", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4660", + "url": "https://github.com/udecode/plate/pull/4660" + }, + { + "content": "`@platejs/excalidraw`\n\n### Features\n\n- upgrade @excalidraw/excalidraw to fix broken & fix onchange fn ([#4654](https://github.com/udecode/plate/pull/4654))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4659) · [`v50.2.7...v50.3.0`](https://github.com/udecode/plate/compare/%40platejs%2Flink%4050.2.7...%40platejs%2Fexcalidraw%4050.3.0) · By [@electroluxcode](https://github.com/electroluxcode)", + "contributors": [ + { + "url": "https://github.com/electroluxcode", + "username": "@electroluxcode" + } + ], + "date": "2025-09-26", + "packageTag": "@platejs/excalidraw@50.3.0", + "tag": "v50.3.0", + "title": "v50.3.0", + "type": "minor", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4659", + "url": "https://github.com/udecode/plate/pull/4659" + }, + { + "content": "`@platejs/link`\n\n### Bug Fixes\n\n- Fix link conflict with floating toolbar #4651 ([`780eb65`](https://github.com/udecode/plate/commit/780eb656cb717c1e8b2e5fa3ca8a6bddc64675cd))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4655) · [`v50.2.6...v50.2.7`](https://github.com/udecode/plate/compare/%40platejs%2Fyjs%4050.2.6...%40platejs%2Flink%4050.2.7) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-09-26", + "packageTag": "@platejs/link@50.2.7", + "tag": "v50.2.7", + "title": "v50.2.7", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4655", + "url": "https://github.com/udecode/plate/pull/4655" + }, + { + "content": "`@platejs/yjs`\n\n### Bug Fixes\n\n- Make provider accessible on hocuspocus so other libs can use it. ([#4643](https://github.com/udecode/plate/pull/4643))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4652) · [`v50.2.5...v50.2.6`](https://github.com/udecode/plate/compare/%40platejs%2Ffloating%4050.2.5...%40platejs%2Fyjs%4050.2.6) · By [@dpnova](https://github.com/dpnova)", + "contributors": [ + { + "url": "https://github.com/dpnova", + "username": "@dpnova" + } + ], + "date": "2025-09-25", + "packageTag": "@platejs/yjs@50.2.6", + "tag": "v50.2.6", + "title": "v50.2.6", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4652", + "url": "https://github.com/udecode/plate/pull/4652" + }, + { + "content": "`@platejs/floating`\n\n### Bug Fixes\n\n- Revert #4629 ([#4644](https://github.com/udecode/plate/pull/4644))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4645) · [`v50.2.4...v50.2.5`](https://github.com/udecode/plate/compare/%40platejs%2Fyjs%4050.2.4...%40platejs%2Ffloating%4050.2.5) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-09-23", + "packageTag": "@platejs/floating@50.2.5", + "tag": "v50.2.5", + "title": "v50.2.5", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4645", + "url": "https://github.com/udecode/plate/pull/4645" + }, + { + "content": "`@platejs/yjs`\n\n### Bug Fixes\n\n- Add `wsOptions` in YjsPlugin ([#4627](https://github.com/udecode/plate/pull/4627))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4641) · [`v50.2.3...v50.2.4`](https://github.com/udecode/plate/compare/%40platejs%2Ffloating%4050.2.3...%40platejs%2Fyjs%4050.2.4) · By [@MohakBajaj](https://github.com/MohakBajaj)", + "contributors": [ + { + "url": "https://github.com/MohakBajaj", + "username": "@MohakBajaj" + } + ], + "date": "2025-09-22", + "packageTag": "@platejs/yjs@50.2.4", + "tag": "v50.2.4", + "title": "v50.2.4", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4641", + "url": "https://github.com/udecode/plate/pull/4641" + }, + { + "content": "`@platejs/floating`\n\n### Bug Fixes\n\n- Fix: infinite loop in useFloatingToolbar hook ([#4629](https://github.com/udecode/plate/pull/4629))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4640) · [`v50.2.2...v50.2.3`](https://github.com/udecode/plate/compare/%40platejs%2Fai%4050.2.2...%40platejs%2Ffloating%4050.2.3) · By [@narraje](https://github.com/narraje)", + "contributors": [ + { + "url": "https://github.com/narraje", + "username": "@narraje" + } + ], + "date": "2025-09-22", + "packageTag": "@platejs/floating@50.2.3", + "tag": "v50.2.3", + "title": "v50.2.3", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4640", + "url": "https://github.com/udecode/plate/pull/4640" + }, + { + "content": "`@platejs/ai`\n\n### Bug Fixes\n\n- Add undo option in `api.aiChat.hide` ([#4638](https://github.com/udecode/plate/pull/4638))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4639) · [`v50.2.1...v50.2.2`](https://github.com/udecode/plate/compare/%40platejs%2Fai%4050.2.1...%40platejs%2Fai%4050.2.2) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-09-22", + "packageTag": "@platejs/ai@50.2.2", + "tag": "v50.2.2", + "title": "v50.2.2", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4639", + "url": "https://github.com/udecode/plate/pull/4639" + }, + { + "content": "`@platejs/ai`\n\n### Bug Fixes\n\n- Fix `api.aiChat.reload` ([#4635](https://github.com/udecode/plate/pull/4635))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4636) · [`v50.2.0...v50.2.1`](https://github.com/udecode/plate/compare/%40platejs%2Fai%4050.2.0...%40platejs%2Fai%4050.2.1) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-09-22", + "packageTag": "@platejs/ai@50.2.1", + "tag": "v50.2.1", + "title": "v50.2.1", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4636", + "url": "https://github.com/udecode/plate/pull/4636" + }, + { + "content": "`@platejs/ai`\n\n### Features\n\n- Add `applyAISuggestions` utility for applying AI-generated content as suggestions with diff tracking ([#4626](https://github.com/udecode/plate/pull/4626))\n- Add `replacePlaceholders` template system supporting `{prompt}`, `{block}`, `{blockSelection}`, `{editor}` placeholders\n- Improve `acceptAIChat` transform to handle transient suggestions properly\n- Add block selection mode support with `_replaceIds` tracking\n- Fix suggestion acceptance and cleanup in chat mode\n\n`@platejs/diff`\n\n### Features\n\n- Improve diffing of inline elements with same type ([#4626](https://github.com/udecode/plate/pull/4626))\n\n`@platejs/markdown`\n\n### Features\n\n- Add `plainMarks` option to exclude specific marks from markdown serialization ([#4626](https://github.com/udecode/plate/pull/4626))\n- Improve mark handling in text serialization\n\n`@platejs/suggestion`\n\n### Features\n\n- Add `SkipSuggestionDeletes` utility to extract text while excluding removed suggestions ([#4626](https://github.com/udecode/plate/pull/4626))\n- Add transient suggestions support with `getTransientSuggestionKey` and filtering options\n- Unify adjacent insert/remove suggestion IDs for better UI handling\n- Improve `acceptSuggestion` to support inline elements like links\n- Add `transient` parameter to `getSuggestionProps` and `suggestion.nodes()` APIs\n\n`@platejs/comment`\n\n### Bug Fixes\n\n- Add `getTransientCommentKey` utility for temporary comments ([#4626](https://github.com/udecode/plate/pull/4626))\n\n`@platejs/list`\n\n### Bug Fixes\n\n- Fix list operations with improved selection handling ([#4626](https://github.com/udecode/plate/pull/4626))\n\n`@platejs/selection`\n\n### Bug Fixes\n\n- Improve `insertBlocksAndSelect` to handle fragment insertion better ([#4626](https://github.com/udecode/plate/pull/4626))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4631) · [`v50.1.2...v50.2.0`](https://github.com/udecode/plate/compare/%40platejs%2Fai%4050.1.2...%40platejs%2Fai%4050.2.0) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-09-20", + "packageTag": "@platejs/ai@50.2.0", + "tag": "v50.2.0", + "title": "v50.2.0", + "type": "minor", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4631", + "url": "https://github.com/udecode/plate/pull/4631" + }, + { + "content": "`@platejs/ai`\n\n### Bug Fixes\n\n- fix preserveEmptyParagraphs option not merged ([#4622](https://github.com/udecode/plate/pull/4622))\n\n`@platejs/markdown`\n\n### Bug Fixes\n\n- fix preserveEmptyParagraphs option not merged ([#4622](https://github.com/udecode/plate/pull/4622))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4623) · [`v50.1.1...v50.1.2`](https://github.com/udecode/plate/compare/%40platejs%2Fmarkdown%4050.1.1...%40platejs%2Fai%4050.1.2) · By [@baptisteArno](https://github.com/baptisteArno)", + "contributors": [ + { + "url": "https://github.com/baptisteArno", + "username": "@baptisteArno" + } + ], + "date": "2025-09-11", + "packageTag": "@platejs/ai@50.1.2", + "tag": "v50.1.2", + "title": "v50.1.2", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4623", + "url": "https://github.com/udecode/plate/pull/4623" + }, + { + "content": "`@platejs/markdown`\n\n### Bug Fixes\n\n- Fix remarkStringifyOptions not taken into account ([#4620](https://github.com/udecode/plate/pull/4620))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4621) · [`v50.1.0...v50.1.1`](https://github.com/udecode/plate/compare/%40platejs%2Fmarkdown%4050.1.0...%40platejs%2Fmarkdown%4050.1.1) · By [@baptisteArno](https://github.com/baptisteArno)", + "contributors": [ + { + "url": "https://github.com/baptisteArno", + "username": "@baptisteArno" + } + ], + "date": "2025-09-10", + "packageTag": "@platejs/markdown@50.1.1", + "tag": "v50.1.1", + "title": "v50.1.1", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4621", + "url": "https://github.com/udecode/plate/pull/4621" + }, + { + "content": "`@platejs/markdown`\n\n### Features\n\n- Add remarkStringifyOptions ([#4615](https://github.com/udecode/plate/pull/4615))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4618) · [`v50.0.0...v50.1.0`](https://github.com/udecode/plate/compare/%40platejs%2Fai%4050.0.0...%40platejs%2Fmarkdown%4050.1.0) · By [@baptisteArno](https://github.com/baptisteArno)", + "contributors": [ + { + "url": "https://github.com/baptisteArno", + "username": "@baptisteArno" + } + ], + "date": "2025-09-10", + "packageTag": "@platejs/markdown@50.1.0", + "tag": "v50.1.0", + "title": "v50.1.0", + "type": "minor", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4618", + "url": "https://github.com/udecode/plate/pull/4618" + }, + { + "content": "`@platejs/ai`\n\n### Breaking Changes\n\n- Added **AI Comment** functionality to provide AI-powered text feedback and suggestions.And upgrade to AI SDK 5. ([#4587](https://github.com/udecode/plate/pull/4587))\n\n ### New Features:\n\n - **AI Comment Integration**: New utilities for AI-generated comments on selected text\n\n - `aiCommentToRange()` - Convert AI comments to text ranges with proper block mapping\n - `findTextRangeInBlock()` - Find text ranges within blocks for accurate comment positioning\n\n - **Enhanced AI Chat**: Improved chat functionality with comment support\n\n - New `toolName` property in chat helpers for tracking AI tools\n - Support for AI comment prompts in chat submissions\n - Added `mode`, `toolName` params to `submitAIChat`\n - New `toolName` plugin option.\n\n - **Text Matching**: Advanced text matching algorithms\n - Longest Common Subsequence (LCS) algorithm for fuzzy text matching\n - Support for multi-block text selection and comment ranges\n - Accurate text position tracking across block boundaries\n\n ### Example:\n\n ```typescript\n // Convert AI comment to text range\n const range = aiCommentToRange(editor, {\n blockId: 'block-1',\n content: 'Selected text',\n comment: 'Consider adding more detail here',\n });\n ```\n\n ### Breaking Changes:\n\n - `streamInsertChunk` has been moved from `@platejs/ai` to `@platejs/ai/react`.\n - `getEditorPrompt` has been moved from `@platejs/ai/react` to `@platejs/ai`.\n - `getMarkdown` has been moved from `@platejs/ai/react` to `@platejs/ai`.\n - `promptTemplate` and `systemTemplate` have been removed. They are now used directly in `api/ai/command/route.ts`.\n - The placeholder `{selection}` has been renamed to `{blockSelection}`.\n\n`@platejs/comment`\n\n### Bug Fixes\n\n- Enhanced comment plugin to support AI-generated comments. ([#4587](https://github.com/udecode/plate/pull/4587))\n\n ### Changes:\n\n - Added a `transient` option to `tf.unsetMark` to allow removing all AI comments at once.\n\n`@platejs/markdown`\n\n### Bug Fixes\n\n- Added support for preserving block IDs in markdown serialization to enable AI comment tracking. ([#4587](https://github.com/udecode/plate/pull/4587))\n\n ### Changes:\n\n - **Enhanced Serialization**: Updated `serializeMd` to support `withBlockId` option for maintaining block references\n\n ### Example:\n\n ```typescript\n // Serialize with block IDs preserved\n const markdown = serializeMd(editor, {\n withBlockId: true,\n });\n // Output: Content here\n ```\n\n`@platejs/selection`\n\n### Bug Fixes\n\n- Added a `selectionFallback` option to `api.getNodes`. ([#4587](https://github.com/udecode/plate/pull/4587))\n - If `selectionFallback` is set to `true`, and no nodes are selected by `blockSelection`, the method will use the editor's original selection to retrieve blocks.\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4617) · [`v49.2.22...v50.0.0`](https://github.com/udecode/plate/compare/%40platejs%2Fdnd%4049.2.22...%40platejs%2Fai%4050.0.0) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-09-10", + "packageTag": "@platejs/ai@50.0.0", + "tag": "v50.0.0", + "title": "v50.0.0", + "type": "major", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4617", + "url": "https://github.com/udecode/plate/pull/4617" + }, + { + "content": "`@platejs/dnd`\n\n### Bug Fixes\n\n- Fix: Drop line is not visible when hovering white space inside the editor ([#4603](https://github.com/udecode/plate/pull/4603))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4604) · [`v49.2.21...v49.2.22`](https://github.com/udecode/plate/compare/platejs%4049.2.21...%40platejs%2Fdnd%4049.2.22) · By [@delijah](https://github.com/delijah)", + "contributors": [ + { + "url": "https://github.com/delijah", + "username": "@delijah" + } + ], + "date": "2025-09-02", + "packageTag": "@platejs/dnd@49.2.22", + "tag": "v49.2.22", + "title": "v49.2.22", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4604", + "url": "https://github.com/udecode/plate/pull/4604" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- slate 0.118 ([#4600](https://github.com/udecode/plate/pull/4600))\n\n`@platejs/slate`\n\n### Bug Fixes\n\n- slate 0.118 ([#4600](https://github.com/udecode/plate/pull/4600))\n\n`@platejs/test-utils`\n\n### Bug Fixes\n\n- slate 0.118 ([#4600](https://github.com/udecode/plate/pull/4600))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4602) · [`v49.2.20...v49.2.21`](https://github.com/udecode/plate/compare/%40platejs%2Fdnd%4049.2.20...platejs%4049.2.21) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2025-09-02", + "packageTag": "platejs@49.2.21", + "tag": "v49.2.21", + "title": "v49.2.21", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4602", + "url": "https://github.com/udecode/plate/pull/4602" + }, + { + "content": "`@platejs/dnd`\n\n### Bug Fixes\n\n- Hide drop line, whenever drag happens outside the editor ([#4597](https://github.com/udecode/plate/pull/4597))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4598) · [`v49.2.19...v49.2.20`](https://github.com/udecode/plate/compare/%40platejs%2Fdnd%4049.2.19...%40platejs%2Fdnd%4049.2.20) · By [@delijah](https://github.com/delijah)", + "contributors": [ + { + "url": "https://github.com/delijah", + "username": "@delijah" + } + ], + "date": "2025-08-31", + "packageTag": "@platejs/dnd@49.2.20", + "tag": "v49.2.20", + "title": "v49.2.20", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4598", + "url": "https://github.com/udecode/plate/pull/4598" + }, + { + "content": "`@platejs/dnd`\n\n### Bug Fixes\n\n- Enable dnd between editors ([#4595](https://github.com/udecode/plate/pull/4595))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4596) · [`v49.2.18...v49.2.19`](https://github.com/udecode/plate/compare/%40platejs%2Fdnd%4049.2.18...%40platejs%2Fdnd%4049.2.19) · By [@delijah](https://github.com/delijah)", + "contributors": [ + { + "url": "https://github.com/delijah", + "username": "@delijah" + } + ], + "date": "2025-08-29", + "packageTag": "@platejs/dnd@49.2.19", + "tag": "v49.2.19", + "title": "v49.2.19", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4596", + "url": "https://github.com/udecode/plate/pull/4596" + }, + { + "content": "`@platejs/dnd`\n\n### Bug Fixes\n\n- Hide drop line when user leaves document or drops (also besides editor) ([#4592](https://github.com/udecode/plate/pull/4592))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4594) · [`v49.2.17...v49.2.18`](https://github.com/udecode/plate/compare/%40platejs%2Flist%4049.2.17...%40platejs%2Fdnd%4049.2.18) · By [@delijah](https://github.com/delijah)", + "contributors": [ + { + "url": "https://github.com/delijah", + "username": "@delijah" + } + ], + "date": "2025-08-28", + "packageTag": "@platejs/dnd@49.2.18", + "tag": "v49.2.18", + "title": "v49.2.18", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4594", + "url": "https://github.com/udecode/plate/pull/4594" + }, + { + "content": "`@platejs/list`\n\n### Bug Fixes\n\n- Fix copying list from notion ([#4582](https://github.com/udecode/plate/pull/4582))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4584) · [`v49.2.16...v49.2.17`](https://github.com/udecode/plate/compare/%40platejs%2Ftest-utils%4049.2.16...%40platejs%2Flist%4049.2.17) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-08-23", + "packageTag": "@platejs/list@49.2.17", + "tag": "v49.2.17", + "title": "v49.2.17", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4584", + "url": "https://github.com/udecode/plate/pull/4584" + }, + { + "content": "`@platejs/test-utils`\n\n### Bug Fixes\n\n- Remove overriding of index signature on `JSX.IntrinsicElements`, in order to avoid error messages on react jsx elements ([#4578](https://github.com/udecode/plate/pull/4578))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4580) · [`v49.2.15...v49.2.16`](https://github.com/udecode/plate/compare/%40platejs%2Fai%4049.2.15...%40platejs%2Ftest-utils%4049.2.16) · By [@delijah](https://github.com/delijah)", + "contributors": [ + { + "url": "https://github.com/delijah", + "username": "@delijah" + } + ], + "date": "2025-08-21", + "packageTag": "@platejs/test-utils@49.2.16", + "tag": "v49.2.16", + "title": "v49.2.16", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4580", + "url": "https://github.com/udecode/plate/pull/4580" + }, + { + "content": "`@platejs/ai`\n\n### Bug Fixes\n\n- Fix `replaceSelectionAIChat` when selecting whole code block. ([#4577](https://github.com/udecode/plate/pull/4577))\n\n`@platejs/markdown`\n\n### Bug Fixes\n\n- Fix regex not matching MDX opening tags with attributes. ([#4577](https://github.com/udecode/plate/pull/4577))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4579) · [`v49.2.14...v49.2.15`](https://github.com/udecode/plate/compare/%40platejs%2Fai%4049.2.14...%40platejs%2Fai%4049.2.15) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-08-20", + "packageTag": "@platejs/ai@49.2.15", + "tag": "v49.2.15", + "title": "v49.2.15", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4579", + "url": "https://github.com/udecode/plate/pull/4579" + }, + { + "content": "`@platejs/ai`\n\n### Bug Fixes\n\n- Fix deserialize incomplete mdx tag with line breaks. ([#4572](https://github.com/udecode/plate/pull/4572))\n\n`@platejs/markdown`\n\n### Bug Fixes\n\n- Fix deserialize incomplete mdx tag with line breaks. ([#4572](https://github.com/udecode/plate/pull/4572))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4575) · [`v49.2.13...v49.2.14`](https://github.com/udecode/plate/compare/%40platejs%2Ftest-utils%4049.2.13...%40platejs%2Fai%4049.2.14) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-08-20", + "packageTag": "@platejs/ai@49.2.14", + "tag": "v49.2.14", + "title": "v49.2.14", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4575", + "url": "https://github.com/udecode/plate/pull/4575" + }, + { + "content": "`@platejs/test-utils`\n\n### Bug Fixes\n\n- Export voidChildren, elements and createEditor from jsx, in order to create more customised jsx solutions ([#4573](https://github.com/udecode/plate/pull/4573))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4574) · [`v49.2.12...v49.2.13`](https://github.com/udecode/plate/compare/platejs%4049.2.12...%40platejs%2Ftest-utils%4049.2.13) · By [@delijah](https://github.com/delijah)", + "contributors": [ + { + "url": "https://github.com/delijah", + "username": "@delijah" + } + ], + "date": "2025-08-19", + "packageTag": "@platejs/test-utils@49.2.13", + "tag": "v49.2.13", + "title": "v49.2.13", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4574", + "url": "https://github.com/udecode/plate/pull/4574" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Fix parser formats ([#4563](https://github.com/udecode/plate/pull/4563))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4565) · [`v49.2.11...v49.2.12`](https://github.com/udecode/plate/compare/platejs%4049.2.11...platejs%4049.2.12) · By [@delijah](https://github.com/delijah)", + "contributors": [ + { + "url": "https://github.com/delijah", + "username": "@delijah" + } + ], + "date": "2025-08-13", + "packageTag": "platejs@49.2.12", + "tag": "v49.2.12", + "title": "v49.2.12", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4565", + "url": "https://github.com/udecode/plate/pull/4565" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Accept components with props of type PlateElementProps ([#4560](https://github.com/udecode/plate/pull/4560))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4561) · [`v49.2.10...v49.2.11`](https://github.com/udecode/plate/compare/%40platejs%2Fdnd%4049.2.10...platejs%4049.2.11) · By [@delijah](https://github.com/delijah)", + "contributors": [ + { + "url": "https://github.com/delijah", + "username": "@delijah" + } + ], + "date": "2025-08-12", + "packageTag": "platejs@49.2.11", + "tag": "v49.2.11", + "title": "v49.2.11", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4561", + "url": "https://github.com/udecode/plate/pull/4561" + }, + { + "content": "`@platejs/dnd`\n\n### Bug Fixes\n\n- Respect `canDrop` option of `react-dnd` ([#4555](https://github.com/udecode/plate/pull/4555))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4556) · [`v49.2.9...v49.2.10`](https://github.com/udecode/plate/compare/platejs%4049.2.9...%40platejs%2Fdnd%4049.2.10) · By [@delijah](https://github.com/delijah)", + "contributors": [ + { + "url": "https://github.com/delijah", + "username": "@delijah" + } + ], + "date": "2025-08-09", + "packageTag": "@platejs/dnd@49.2.10", + "tag": "v49.2.10", + "title": "v49.2.10", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4556", + "url": "https://github.com/udecode/plate/pull/4556" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Allow to use either `format` OR `mimeTypes` option on `ParserPlugin` and also pass `File` mime type to parsers ([#4553](https://github.com/udecode/plate/pull/4553))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4554) · [`v49.2.8...v49.2.9`](https://github.com/udecode/plate/compare/platejs%4049.2.8...platejs%4049.2.9) · By [@delijah](https://github.com/delijah)", + "contributors": [ + { + "url": "https://github.com/delijah", + "username": "@delijah" + } + ], + "date": "2025-08-08", + "packageTag": "platejs@49.2.9", + "tag": "v49.2.9", + "title": "v49.2.9", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4554", + "url": "https://github.com/udecode/plate/pull/4554" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Added `onNodeChange` and `onTextChange` callbacks to track editor operations: ([#4549](https://github.com/udecode/plate/pull/4549))\n\n - `onNodeChange`: Called for node operations (insert, remove, set, merge, split, move)\n - `onTextChange`: Called for text operations (insert, remove)\n\n ```tsx\n // Usage via Plate component\n {\n console.log('Node changed:', { node, operation, prevNode });\n }}\n onTextChange={({ editor, node, operation, prevText, text }) => {\n console.log('Text changed:', { text, prevText, operation });\n }}\n />;\n\n // Usage via plugin\n MyPlugin.configure({\n handlers: {\n onNodeChange: ({ node, operation, prevNode }) => {\n // Handle node changes\n },\n onTextChange: ({ node, operation, prevText, text }) => {\n // Handle text changes\n },\n },\n });\n ```\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4550) · [`v49.2.7...v49.2.8`](https://github.com/udecode/plate/compare/%40platejs%2Fdnd%4049.2.7...platejs%4049.2.8) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2025-08-07", + "packageTag": "platejs@49.2.8", + "tag": "v49.2.8", + "title": "v49.2.8", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4550", + "url": "https://github.com/udecode/plate/pull/4550" + }, + { + "content": "`@platejs/dnd`\n\n### Bug Fixes\n\n- Fix missing target path onDropFiles ([#4544](https://github.com/udecode/plate/pull/4544))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4545) · [`v49.2.6...v49.2.7`](https://github.com/udecode/plate/compare/platejs%4049.2.6...%40platejs%2Fdnd%4049.2.7) · By [@delijah](https://github.com/delijah)", + "contributors": [ + { + "url": "https://github.com/delijah", + "username": "@delijah" + } + ], + "date": "2025-08-05", + "packageTag": "@platejs/dnd@49.2.7", + "tag": "v49.2.7", + "title": "v49.2.7", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4545", + "url": "https://github.com/udecode/plate/pull/4545" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Remove `inject.nodeProps` from default element, preventing `Error: Rendered more hooks than during the previous render.`. ([#4542](https://github.com/udecode/plate/pull/4542))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4543) · [`v49.2.5...v49.2.6`](https://github.com/udecode/plate/compare/platejs%4049.2.5...platejs%4049.2.6) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2025-08-05", + "packageTag": "platejs@49.2.6", + "tag": "v49.2.6", + "title": "v49.2.6", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4543", + "url": "https://github.com/udecode/plate/pull/4543" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Fix: `Error: Rendered more hooks than during the previous render.` when updating the block type. ([#4540](https://github.com/udecode/plate/pull/4540))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4541) · [`v49.2.4...v49.2.5`](https://github.com/udecode/plate/compare/platejs%4049.2.4...platejs%4049.2.5) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2025-08-05", + "packageTag": "platejs@49.2.5", + "tag": "v49.2.5", + "title": "v49.2.5", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4541", + "url": "https://github.com/udecode/plate/pull/4541" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Added `useFocusedLast` hook to track the last focused editor ([#4537](https://github.com/udecode/plate/pull/4537))\n- Updated `EventEditorPlugin` to track the last focused editor in `EventEditorStore`\n\n`@platejs/selection`\n\n### Bug Fixes\n\n- Fixed `duplicateBlockSelectionNodes` by removing the `nextBlock: true` option ([#4537](https://github.com/udecode/plate/pull/4537))\n- Added `mod+d` hotkey to duplicate selected blocks\n- Added character input handling in block selection - typing a character now replaces selected blocks with a new block containing that character\n- Added support for node and mark operations (bold, italic, etc.)\n\n`@platejs/slate`\n\n### Bug Fixes\n\n- Fixed `duplicateNodes` to allow undefined `at` ([#4537](https://github.com/udecode/plate/pull/4537))\n- Improved error handling by only checking for `nodes` parameter instead of both `nodes` and `at`\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4538) · [`v49.2.3...v49.2.4`](https://github.com/udecode/plate/compare/platejs%4049.2.3...platejs%4049.2.4) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2025-08-01", + "packageTag": "platejs@49.2.4", + "tag": "v49.2.4", + "title": "v49.2.4", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4538", + "url": "https://github.com/udecode/plate/pull/4538" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Allow to pass react components to `as`-prop of PlateElement ([#4531](https://github.com/udecode/plate/pull/4531))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4532) · [`v49.2.2...v49.2.3`](https://github.com/udecode/plate/compare/%40platejs%2Fdnd%4049.2.2...platejs%4049.2.3) · By [@delijah](https://github.com/delijah)", + "contributors": [ + { + "url": "https://github.com/delijah", + "username": "@delijah" + } + ], + "date": "2025-07-30", + "packageTag": "platejs@49.2.3", + "tag": "v49.2.3", + "title": "v49.2.3", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4532", + "url": "https://github.com/udecode/plate/pull/4532" + }, + { + "content": "`@platejs/dnd`\n\n### Bug Fixes\n\n- Fix dnd preview behavior by adding `isAboutToDrag` state for better preview handling ([#4528](https://github.com/udecode/plate/pull/4528))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4529) · [`v49.2.1...v49.2.2`](https://github.com/udecode/plate/compare/%40platejs%2Fai%4049.2.1...%40platejs%2Fdnd%4049.2.2) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-07-29", + "packageTag": "@platejs/dnd@49.2.2", + "tag": "v49.2.2", + "title": "v49.2.2", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4529", + "url": "https://github.com/udecode/plate/pull/4529" + }, + { + "content": "`@platejs/ai`\n\n### Bug Fixes\n\n- ### AI Streaming Improvements ([#4518](https://github.com/udecode/plate/pull/4518))\n\n **@platejs/ai:**\n\n - Fixed empty paragraph removal logic in `streamInsertChunk` to only remove true empty paragraphs (no text content)\n - Enhanced streaming support for tables and columns with proper chunk insertion\n - Fixed interface name typo: `SteamInsertChunkOptions` → `StreamInsertChunkOptions`\n - Improved markdown streaming with better handling of incomplete patterns\n\n **@platejs/layout:**\n\n - Added streaming support for columns in `withColumn`\n - Fixed column width calculations to handle edge cases\n\n **@platejs/markdown:**\n\n - Enhanced column deserialization with proper attribute parsing\n - Added support for column groups in markdown rules\n - Improved attribute parsing in `customMdxDeserialize`\n\n`@platejs/layout`\n\n### Bug Fixes\n\n- ### AI Streaming Improvements ([#4518](https://github.com/udecode/plate/pull/4518))\n\n **@platejs/ai:**\n\n - Fixed empty paragraph removal logic in `streamInsertChunk` to only remove true empty paragraphs (no text content)\n - Enhanced streaming support for tables and columns with proper chunk insertion\n - Fixed interface name typo: `SteamInsertChunkOptions` → `StreamInsertChunkOptions`\n - Improved markdown streaming with better handling of incomplete patterns\n\n **@platejs/layout:**\n\n - Added streaming support for columns in `withColumn`\n - Fixed column width calculations to handle edge cases\n\n **@platejs/markdown:**\n\n - Enhanced column deserialization with proper attribute parsing\n - Added support for column groups in markdown rules\n - Improved attribute parsing in `customMdxDeserialize`\n\n`@platejs/markdown`\n\n### Bug Fixes\n\n- ### AI Streaming Improvements ([#4518](https://github.com/udecode/plate/pull/4518))\n\n **@platejs/ai:**\n\n - Fixed empty paragraph removal logic in `streamInsertChunk` to only remove true empty paragraphs (no text content)\n - Enhanced streaming support for tables and columns with proper chunk insertion\n - Fixed interface name typo: `SteamInsertChunkOptions` → `StreamInsertChunkOptions`\n - Improved markdown streaming with better handling of incomplete patterns\n\n **@platejs/layout:**\n\n - Added streaming support for columns in `withColumn`\n - Fixed column width calculations to handle edge cases\n\n **@platejs/markdown:**\n\n - Enhanced column deserialization with proper attribute parsing\n - Added support for column groups in markdown rules\n - Improved attribute parsing in `customMdxDeserialize`\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4526) · [`v49.2.0...v49.2.1`](https://github.com/udecode/plate/compare/%40platejs%2Flist%4049.2.0...%40platejs%2Fai%4049.2.1) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-07-29", + "packageTag": "@platejs/ai@49.2.1", + "tag": "v49.2.1", + "title": "v49.2.1", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4526", + "url": "https://github.com/udecode/plate/pull/4526" + }, + { + "content": "`@platejs/list`\n\n### Features\n\n- Added `expandListItemsWithChildren` to automatically include list item children when selecting blocks for operations like drag-and-drop. ([#4514](https://github.com/udecode/plate/pull/4514))\n- Added `getListChildren` to get all child list items (with bigger indent) of a given list item.\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4515) · [`v49.1.13...v49.2.0`](https://github.com/udecode/plate/compare/platejs%4049.1.13...%40platejs%2Flist%4049.2.0) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2025-07-25", + "packageTag": "@platejs/list@49.2.0", + "tag": "v49.2.0", + "title": "v49.2.0", + "type": "minor", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4515", + "url": "https://github.com/udecode/plate/pull/4515" + }, + { + "content": "`@platejs/list`\n\n### Bug Fixes\n\n- Fixed `toggleList` to respect list plugin's inject match when applying list transforms. ([#4512](https://github.com/udecode/plate/pull/4512))\n\n`@platejs/slate`\n\n### Bug Fixes\n\n- Added `combineTransformMatchOptions` utility for combining match predicates in transforms. This utility provides default matching behavior that matches the native Slate transform behavior when no match is provided. ([#4512](https://github.com/udecode/plate/pull/4512))\n- Added `editor.meta.isNormalizing` flag to track when the editor is normalizing nodes. This flag is automatically set to `true` during `editor.tf.normalizeNode` and `plugin.normalizeInitialValue` and restored to its previous value when normalization completes.\n\n`@platejs/table`\n\n### Bug Fixes\n\n- Fixed table cell selection \"remove marks\" and \"set nodes\" transforms (e.g. align, list) ([#4512](https://github.com/udecode/plate/pull/4512))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4513) · [`v49.1.12...v49.1.13`](https://github.com/udecode/plate/compare/%40platejs%2Fselection%4049.1.12...platejs%4049.1.13) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2025-07-25", + "packageTag": "platejs@49.1.13", + "tag": "v49.1.13", + "title": "v49.1.13", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4513", + "url": "https://github.com/udecode/plate/pull/4513" + }, + { + "content": "`@platejs/selection`\n\n### Bug Fixes\n\n- Add `addOnContextMenu` API method to BlockSelectionPlugin for cleaner context menu handling ([#4508](https://github.com/udecode/plate/pull/4508))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4511) · [`v49.1.11...v49.1.12`](https://github.com/udecode/plate/compare/%40platejs%2Fmarkdown%4049.1.11...%40platejs%2Fselection%4049.1.12) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-07-24", + "packageTag": "@platejs/selection@49.1.12", + "tag": "v49.1.12", + "title": "v49.1.12", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4511", + "url": "https://github.com/udecode/plate/pull/4511" + }, + { + "content": "`@platejs/markdown`\n\n### Bug Fixes\n\n- Fix tests when custom headings ([`c0857bf`](https://github.com/udecode/plate/commit/c0857bf9739f948c43a6f7fc603f81e02cccde95))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4507) · [`v49.1.10...v49.1.11`](https://github.com/udecode/plate/compare/%40platejs%2Fmarkdown%4049.1.10...%40platejs%2Fmarkdown%4049.1.11) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-07-22", + "packageTag": "@platejs/markdown@49.1.11", + "tag": "v49.1.11", + "title": "v49.1.11", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4507", + "url": "https://github.com/udecode/plate/pull/4507" + }, + { + "content": "`@platejs/markdown`\n\n### Bug Fixes\n\n- Fix custom headings node type. ([#4505](https://github.com/udecode/plate/pull/4505))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4506) · [`v49.1.9...v49.1.10`](https://github.com/udecode/plate/compare/%40platejs%2Fmarkdown%4049.1.9...%40platejs%2Fmarkdown%4049.1.10) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-07-22", + "packageTag": "@platejs/markdown@49.1.10", + "tag": "v49.1.10", + "title": "v49.1.10", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4506", + "url": "https://github.com/udecode/plate/pull/4506" + }, + { + "content": "`@platejs/markdown`\n\n### Bug Fixes\n\n- Fixed markdown serialization of indented lists when using custom paragraph node types. The serializer now correctly identifies custom paragraph nodes instead of only looking for the default 'p' type. ([#4493](https://github.com/udecode/plate/pull/4493))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4494) · [`v49.1.8...v49.1.9`](https://github.com/udecode/plate/compare/%40platejs%2Fmarkdown%4049.1.8...%40platejs%2Fmarkdown%4049.1.9) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-07-16", + "packageTag": "@platejs/markdown@49.1.9", + "tag": "v49.1.9", + "title": "v49.1.9", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4494", + "url": "https://github.com/udecode/plate/pull/4494" + }, + { + "content": "`@platejs/markdown`\n\n### Bug Fixes\n\n- Fix custom plugin key handling in markdown serialization/deserialization. Ensures plugin keys are properly resolved throughout the conversion process for custom plugin configurations. ([#4486](https://github.com/udecode/plate/pull/4486))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4491) · [`v49.1.7...v49.1.8`](https://github.com/udecode/plate/compare/%40platejs%2Fdnd%4049.1.7...%40platejs%2Fmarkdown%4049.1.8) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-07-16", + "packageTag": "@platejs/markdown@49.1.8", + "tag": "v49.1.8", + "title": "v49.1.8", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4491", + "url": "https://github.com/udecode/plate/pull/4491" + }, + { + "content": "`@platejs/dnd`\n\n### Bug Fixes\n\n- Added support for dragging multiple blocks using editor's native selection ([#4481](https://github.com/udecode/plate/pull/4481))\n\n - Multiple blocks can now be dragged using the editor's native selection, not just with block-selection\n - Simplified `useDndNode` hook implementation by removing complex preview logic\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4484) · [`v49.1.6...v49.1.7`](https://github.com/udecode/plate/compare/%40platejs%2Fmarkdown%4049.1.6...%40platejs%2Fdnd%4049.1.7) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-07-14", + "packageTag": "@platejs/dnd@49.1.7", + "tag": "v49.1.7", + "title": "v49.1.7", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4484", + "url": "https://github.com/udecode/plate/pull/4484" + }, + { + "content": "`@platejs/markdown`\n\n### Bug Fixes\n\n- Add support for [display text](mention:id) markdown format for mentions ([#4468](https://github.com/udecode/plate/pull/4468))\n\n - Updated `remarkMention` plugin to only support the `[display text](mention:id)` format\n - Dropped support for legacy `@username` format\n - Mentions now require an explicit display text and ID structure\n - Enables full names, spaces, and special characters in mention display text\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4474) · [`v49.1.5...v49.1.6`](https://github.com/udecode/plate/compare/platejs%4049.1.5...%40platejs%2Fmarkdown%4049.1.6) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-07-10", + "packageTag": "@platejs/markdown@49.1.6", + "tag": "v49.1.6", + "title": "v49.1.6", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4474", + "url": "https://github.com/udecode/plate/pull/4474" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Fix `getSelectedDomFragment` when selecting void. ([#4465](https://github.com/udecode/plate/pull/4465))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4466) · [`v49.1.4...v49.1.5`](https://github.com/udecode/plate/compare/platejs%4049.1.4...platejs%4049.1.5) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-07-05", + "packageTag": "platejs@49.1.5", + "tag": "v49.1.5", + "title": "v49.1.5", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4466", + "url": "https://github.com/udecode/plate/pull/4466" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Fixed `getSelectedDomFragment` to correctly handle partial text selections at the beginning or end of selected blocks by deserializing only the selected portion instead of the entire block. ([#4463](https://github.com/udecode/plate/pull/4463))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4464) · [`v49.1.3...v49.1.4`](https://github.com/udecode/plate/compare/platejs%4049.1.3...platejs%4049.1.4) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-07-04", + "packageTag": "platejs@49.1.4", + "tag": "v49.1.4", + "title": "v49.1.4", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4464", + "url": "https://github.com/udecode/plate/pull/4464" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Added `editor.tf.nodeId.normalize()` API to manually normalize node IDs in the document. ([#4454](https://github.com/udecode/plate/pull/4454))\n\n ```ts\n // Normalize all nodes in the document to ensure they have IDs\n editor.tf.nodeId.normalize();\n ```\n\n - Added `normalizeNodeId` pure function to normalize node IDs in a value without using editor operations.\n\n ```ts\n import { normalizeNodeId } from '@platejs/core';\n\n // Normalize a value without editor operations\n const normalizedValue = normalizeNodeId(value, {\n idKey: 'id',\n idCreator: () => nanoid(10),\n filterInline: true,\n filterText: true,\n });\n ```\n\n This is useful when the value is passed from server to client-side editor.\n\n - Added `getFragment()` API method to **ViewPlugin** for accessing the selected DOM fragment.\n\n **usePlateViewEditor**:\n\n - Added `onReady` handler support for async rendering with automatic re-render when `isAsync` is true\n\n ```typescript\n // New API usage\n const fragment = editor.getApi(ViewPlugin).getFragment();\n\n // Async rendering support\n const editor = usePlateViewEditor({\n onReady: (ctx) => {\n // Called when editor is ready, supports async rendering\n },\n });\n ```\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4462) · [`v49.1.2...v49.1.3`](https://github.com/udecode/plate/compare/platejs%4049.1.2...platejs%4049.1.3) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-07-04", + "packageTag": "platejs@49.1.3", + "tag": "v49.1.3", + "title": "v49.1.3", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4462", + "url": "https://github.com/udecode/plate/pull/4462" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Fix jumpy non-breaking-space ([#4459](https://github.com/udecode/plate/pull/4459))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4460) · [`v49.1.1...v49.1.2`](https://github.com/udecode/plate/compare/%40platejs%2Flink%4049.1.1...platejs%4049.1.2) · By [@delijah](https://github.com/delijah)", + "contributors": [ + { + "url": "https://github.com/delijah", + "username": "@delijah" + } + ], + "date": "2025-07-04", + "packageTag": "platejs@49.1.2", + "tag": "v49.1.2", + "title": "v49.1.2", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4460", + "url": "https://github.com/udecode/plate/pull/4460" + }, + { + "content": "`@platejs/link`\n\n### Bug Fixes\n\n- Fix markdown headings being incorrectly converted to links ([#4452](https://github.com/udecode/plate/pull/4452))\n\n The LinkPlugin's `validateUrl` function now properly distinguishes between markdown headings and anchor links. Previously, any string starting with `#` was treated as a valid link, causing markdown headings like `# heading1` to be converted to links when pasted. Now, the function checks for the markdown heading pattern (hash symbols followed by a space) and correctly rejects these as invalid URLs while still allowing valid anchor links like `#section-name`.\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4456) · [`v49.1.0...v49.1.1`](https://github.com/udecode/plate/compare/%40platejs%2Flist-classic%4049.1.0...%40platejs%2Flink%4049.1.1) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2025-07-03", + "packageTag": "@platejs/link@49.1.1", + "tag": "v49.1.1", + "title": "v49.1.1", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4456", + "url": "https://github.com/udecode/plate/pull/4456" + }, + { + "content": "`@platejs/list-classic`\n\n### Features\n\n- Added task list functionality to **@platejs/list-classic**. ([`36211fa`](https://github.com/udecode/plate/commit/36211fa20dbcb7f7f9b075adff5c826de5c2da49))\n\n - Added **BaseTaskListPlugin** with support for task lists (checklists)\n - Added `checked` property to `TTodoListItemElement` type for tracking task completion state\n - Added `useTodoListElement` and `useTodoListElementState` hooks for task list item management\n - Added `getPropsIfTaskList` utility to check if an element is a task list\n - Added normalization logic to ensure consistent `checked` property state\n - Added `toggleTaskList` transform to convert between regular lists and task lists\n\n ```tsx\n // Before - only regular lists\n createListPlugin();\n\n // After - with task list support\n createListPlugin();\n\n // Toggle task list\n editor.tf.toggle.list({ listType: 'taskList' });\n ```\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4455) · [`v49.0.19...v49.1.0`](https://github.com/udecode/plate/compare/platejs%4049.0.19...%40platejs%2Flist-classic%4049.1.0) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2025-07-03", + "packageTag": "@platejs/list-classic@49.1.0", + "tag": "v49.1.0", + "title": "v49.1.0", + "type": "minor", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4455", + "url": "https://github.com/udecode/plate/pull/4455" + }, + { + "content": "`@platejs/table`\n\n### Bug Fixes\n\n- Fixes ##3660 ([#4446](https://github.com/udecode/plate/pull/4446))\n\n`@platejs/utils`\n\n### Bug Fixes\n\n- Added `taskList` to **KEYS** constant. ([#4437](https://github.com/udecode/plate/pull/4437))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4453) · [`v49.0.18...v49.0.19`](https://github.com/udecode/plate/compare/platejs%4049.0.18...platejs%4049.0.19) · By [@AissaSemaoui](https://github.com/AissaSemaoui), [@calebpitan](https://github.com/calebpitan)", + "contributors": [ + { + "url": "https://github.com/AissaSemaoui", + "username": "@AissaSemaoui" + }, + { + "url": "https://github.com/calebpitan", + "username": "@calebpitan" + } + ], + "date": "2025-07-03", + "packageTag": "platejs@49.0.19", + "tag": "v49.0.19", + "title": "v49.0.19", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4453", + "url": "https://github.com/udecode/plate/pull/4453" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Added `getSelectedDomFragment` utility function that returns Slate nodes from DOM selection. ([#4447](https://github.com/udecode/plate/pull/4447))\n- Deprecated `getSelectedDomBlocks`. Use `getSelectedDomFragment` instead.\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4448) · [`v49.0.17...v49.0.18`](https://github.com/udecode/plate/compare/%40platejs%2Fmarkdown%4049.0.17...platejs%4049.0.18) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-07-03", + "packageTag": "platejs@49.0.18", + "tag": "v49.0.18", + "title": "v49.0.18", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4448", + "url": "https://github.com/udecode/plate/pull/4448" + }, + { + "content": "`@platejs/markdown`\n\n### Bug Fixes\n\n- Added `spread` option to control list spacing in markdown serialization. ([#4440](https://github.com/udecode/plate/pull/4440))\n\n Added a new optional `spread` property to `SerializeMdOptions`:\n\n - When `spread` is `false` (default), lists are rendered compactly\n - When `spread` is `true`, lists have double line breaks between items\n\n Before (default):\n\n ```markdown\n 1. Item 1\n 2. Item 2\n ```\n\n After (with `spread: true`):\n\n ```markdown\n 1. Item 1\n\n 2. Item 2\n ```\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4445) · [`v49.0.16...v49.0.17`](https://github.com/udecode/plate/compare/platejs%4049.0.16...%40platejs%2Fmarkdown%4049.0.17) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2025-07-03", + "packageTag": "@platejs/markdown@49.0.17", + "tag": "v49.0.17", + "title": "v49.0.17", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4445", + "url": "https://github.com/udecode/plate/pull/4445" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Expose mimeType to plugin parser functions ([#4441](https://github.com/udecode/plate/pull/4441))\n\n\n- Added comprehensive copy functionality and view editor support for static rendering. ([#4431](https://github.com/udecode/plate/pull/4431))\n\n **New Components:**\n\n - Added `PlateView` component for static editor rendering with copy support\n - Added `usePlateViewEditor` hook for creating memoized static editors\n\n **Static Editor Enhancements:**\n\n - Added `withStatic` HOC to enhance editors with static rendering capabilities\n - Added `ViewPlugin` that enables copy operations in static editors\n - Added `getStaticPlugins` to configure plugins for static rendering\n - Added `onCopy` handler that properly serializes content with `x-slate-fragment` format\n\n **New Utilities:**\n\n - Added `getSelectedDomBlocks` to extract selected DOM elements with Slate metadata\n - Added `getSelectedDomNode` to get DOM nodes from browser selection\n - Added `isSelectOutside` to check if selection is outside editor bounds\n - Added `getPlainText` to recursively extract plain text from DOM nodes\n\n This enables seamless copy operations from static Plate editors, allowing content to be pasted into other Slate editors while preserving rich formatting and structure.\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4443) · [`v49.0.15...v49.0.16`](https://github.com/udecode/plate/compare/platejs%4049.0.15...platejs%4049.0.16) · By [@delijah](https://github.com/delijah), [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/delijah", + "username": "@delijah" + }, + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-07-02", + "packageTag": "platejs@49.0.16", + "tag": "v49.0.16", + "title": "v49.0.16", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4443", + "url": "https://github.com/udecode/plate/pull/4443" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Updated `SlateElementProps`, `SlateTextProps`, `SlateLeafProps`, `PlateElementProps`, `PlateTextProps`, and `PlateLeafProps` to properly type the `attributes` property to unknown object. ([#4428](https://github.com/udecode/plate/pull/4428))\n\n`@platejs/link`\n\n### Bug Fixes\n\n- Improved return type of `getLinkAttributes` to be more specific and type-safe. ([#4428](https://github.com/udecode/plate/pull/4428))\n\n ```ts\n // The function now returns a properly typed object\n const attributes = getLinkAttributes(editor, linkElement);\n // attributes is now properly typed as Pick, 'href' | 'target'> & UnknownObject\n ```\n\n`@platejs/selection`\n\n### Bug Fixes\n\n- fix(selection): skip empty blocks in copySelectedBlocks to prevent duplication ([#4415](https://github.com/udecode/plate/pull/4415))\n\n`@udecode/react-utils`\n\n### Bug Fixes\n\n- Critical fix for 49.0.13 ([#4434](https://github.com/udecode/plate/pull/4434))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4433) · [`v49.0.14...v49.0.15`](https://github.com/udecode/plate/compare/platejs%4049.0.14...platejs%4049.0.15) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2025-06-30", + "packageTag": "platejs@49.0.15", + "tag": "v49.0.15", + "title": "v49.0.15", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4433", + "url": "https://github.com/udecode/plate/pull/4433" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Add **`getPluginKeys`** (internal) ([#4420](https://github.com/udecode/plate/pull/4420))\n\n`@platejs/markdown`\n\n### Bug Fixes\n\n- Added support for custom node types in markdown serialization and deserialization ([#4420](https://github.com/udecode/plate/pull/4420))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4427) · [`v49.0.13...v49.0.14`](https://github.com/udecode/plate/compare/platejs%4049.0.13...platejs%4049.0.14) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-06-29", + "packageTag": "platejs@49.0.14", + "tag": "v49.0.14", + "title": "v49.0.14", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4427", + "url": "https://github.com/udecode/plate/pull/4427" + }, + { + "content": "`@udecode/react-utils`\n\n### Bug Fixes\n\n- Add react 19 unref callback return in composeRef ([#4423](https://github.com/udecode/plate/pull/4423))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4424) · [`v49.0.12...v49.0.13`](https://github.com/udecode/plate/compare/%40platejs%2Fai%4049.0.12...platejs%4049.0.13) · By [@yf-yang](https://github.com/yf-yang)", + "contributors": [ + { + "url": "https://github.com/yf-yang", + "username": "@yf-yang" + } + ], + "date": "2025-06-29", + "packageTag": "platejs@49.0.13", + "tag": "v49.0.13", + "title": "v49.0.13", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4424", + "url": "https://github.com/udecode/plate/pull/4424" + }, + { + "content": "`@platejs/ai`\n\n### Bug Fixes\n\n- Fixed AI streaming compatibility with markdown serialization changes. Streaming functions now explicitly set `preserveEmptyParagraphs: false` to prevent zero-width space interference during real-time streaming operations. ([#4416](https://github.com/udecode/plate/pull/4416))\n\n`@platejs/markdown`\n\n### Bug Fixes\n\n- Fixed an issue where empty paragraphs were lost during markdown serialization and deserialization. Empty paragraphs are now preserved using zero-width spaces (`\\u200B`) internally. ([#4416](https://github.com/udecode/plate/pull/4416))\n\n ```ts\n // Before: Empty paragraphs would disappear\n const markdown = serializeMd(editor); // \"Text\\n\\nMore text\" → \"Text\\nMore text\"\n\n // After: Empty paragraphs are preserved\n const markdown = serializeMd(editor); // \"Text\\n\\nMore text\" → \"Text\\n\\nMore text\"\n ```\n\n - Added `preserveEmptyParagraphs` option to control this behavior (defaults to `true`)\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4422) · [`v49.0.11...v49.0.12`](https://github.com/udecode/plate/compare/platejs%4049.0.11...%40platejs%2Fai%4049.0.12) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2025-06-28", + "packageTag": "@platejs/ai@49.0.12", + "tag": "v49.0.12", + "title": "v49.0.12", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4422", + "url": "https://github.com/udecode/plate/pull/4422" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Fixed BR tags between block elements from Google Docs creating two empty paragraphs instead of one. The deserialization now correctly converts BR tags between blocks to single empty paragraphs. ([#4411](https://github.com/udecode/plate/pull/4411))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4414) · [`v49.0.10...v49.0.11`](https://github.com/udecode/plate/compare/platejs%4049.0.10...platejs%4049.0.11) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2025-06-27", + "packageTag": "platejs@49.0.11", + "tag": "v49.0.11", + "title": "v49.0.11", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4414", + "url": "https://github.com/udecode/plate/pull/4414" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- PERF: Do not call `createPlateStore` on every hook call for the purposes of the fallback store ([#4410](https://github.com/udecode/plate/pull/4410))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4412) · [`v49.0.9...v49.0.10`](https://github.com/udecode/plate/compare/platejs%4049.0.9...platejs%4049.0.10) · By [@12joan](https://github.com/12joan)", + "contributors": [ + { + "url": "https://github.com/12joan", + "username": "@12joan" + } + ], + "date": "2025-06-26", + "packageTag": "platejs@49.0.10", + "tag": "v49.0.10", + "title": "v49.0.10", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4412", + "url": "https://github.com/udecode/plate/pull/4412" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Use custom type for operations on TPlateEditor ([#4396](https://github.com/udecode/plate/pull/4396))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4397) · [`v49.0.8...v49.0.9`](https://github.com/udecode/plate/compare/%40platejs%2Fdnd%4049.0.8...platejs%4049.0.9) · By [@delijah](https://github.com/delijah)", + "contributors": [ + { + "url": "https://github.com/delijah", + "username": "@delijah" + } + ], + "date": "2025-06-24", + "packageTag": "platejs@49.0.9", + "tag": "v49.0.9", + "title": "v49.0.9", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4397", + "url": "https://github.com/udecode/plate/pull/4397" + }, + { + "content": "`@platejs/dnd`\n\n### Bug Fixes\n\n- Fix the drag preview display error. ([#4393](https://github.com/udecode/plate/pull/4393))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4394) · [`v49.0.7...v49.0.8`](https://github.com/udecode/plate/compare/%40platejs%2Fdnd%4049.0.7...%40platejs%2Fdnd%4049.0.8) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-06-22", + "packageTag": "@platejs/dnd@49.0.8", + "tag": "v49.0.8", + "title": "v49.0.8", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4394", + "url": "https://github.com/udecode/plate/pull/4394" + }, + { + "content": "`@platejs/dnd`\n\n### Bug Fixes\n\n- Fixed an issue where drag and drop functionality would not work properly with multiple selected blocks. ([#4385](https://github.com/udecode/plate/pull/4385))\n- Added logic to ensure only one drop position exists between any two nodes to prevent visual blinking during drag operations.\n\n`@platejs/selection`\n\n### Bug Fixes\n\n- Added `sort` and `collapseTableRows` options to `editor.blockSelection.getNodes()` method. ([#4385](https://github.com/udecode/plate/pull/4385))\n- Added `editor.blockSelection.first` to get the first selected node.\n- Added `normalize` function to handle table selection logic in `useSelectionArea` hook for improved table row and table element selection behavior.\n - It is now possible to select the entire table (table), but the rows (tr) will only be selected if your selection box is within the table.\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4391) · [`v49.0.6...v49.0.7`](https://github.com/udecode/plate/compare/platejs%4049.0.6...%40platejs%2Fdnd%4049.0.7) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-06-22", + "packageTag": "@platejs/dnd@49.0.7", + "tag": "v49.0.7", + "title": "v49.0.7", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4391", + "url": "https://github.com/udecode/plate/pull/4391" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Omit duplicate keys on TPlateEditor to avoid wrong union types ([#4382](https://github.com/udecode/plate/pull/4382))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4383) · [`v49.0.5...v49.0.6`](https://github.com/udecode/plate/compare/platejs%4049.0.5...platejs%4049.0.6) · By [@delijah](https://github.com/delijah)", + "contributors": [ + { + "url": "https://github.com/delijah", + "username": "@delijah" + } + ], + "date": "2025-06-19", + "packageTag": "platejs@49.0.6", + "tag": "v49.0.6", + "title": "v49.0.6", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4383", + "url": "https://github.com/udecode/plate/pull/4383" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Enable [chunking](https://docs.slatejs.org/walkthroughs/09-performance) by default. To disable it, use `chunking: false` when creating the editor. ([#4371](https://github.com/udecode/plate/pull/4371))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4376) · [`v49.0.4...v49.0.5`](https://github.com/udecode/plate/compare/platejs%4049.0.4...platejs%4049.0.5) · By [@12joan](https://github.com/12joan)", + "contributors": [ + { + "url": "https://github.com/12joan", + "username": "@12joan" + } + ], + "date": "2025-06-17", + "packageTag": "platejs@49.0.5", + "tag": "v49.0.5", + "title": "v49.0.5", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4376", + "url": "https://github.com/udecode/plate/pull/4376" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Fixes #4374 ([#4373](https://github.com/udecode/plate/pull/4373))\n- Prevent rendering the editor until the value is loaded (when value is async or `skipInitialization` is true).\n- Added support for both synchronous and asynchronous functions in the `value` option for `createPlateEditor` and `usePlateEditor`. If async, `usePlateEditor` will trigger a re-render when the value is loaded.\n- Added `onReady` callback option to `createPlateEditor` and `usePlateEditor` called after (async) editor initialization.\n\n ```ts\n const editor = usePlateEditor({\n value: async () => {\n const response = await fetch('/api/document');\n const data = await response.json();\n return data.content;\n },\n onReady: ({ editor, value }) => {\n console.info('Editor ready with value:', value);\n },\n });\n ```\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4375) · [`v49.0.3...v49.0.4`](https://github.com/udecode/plate/compare/platejs%4049.0.3...platejs%4049.0.4) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2025-06-17", + "packageTag": "platejs@49.0.4", + "tag": "v49.0.4", + "title": "v49.0.4", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4375", + "url": "https://github.com/udecode/plate/pull/4375" + }, + { + "content": "`@platejs/core`\n\n### Bug Fixes\n\n- Fixes #4367 ([#4369](https://github.com/udecode/plate/pull/4369))\n\n- stopPropagation when the shortcut key is triggered. ([#4365](https://github.com/udecode/plate/pull/4365))\n\n- Fixes #4362 ([`b40df0a`](https://github.com/udecode/plate/commit/b40df0a59440e612f495534cb1e0a5d2477e8682))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4366) · [`v49.0.2...v49.0.3`](https://github.com/udecode/plate/compare/platejs%4049.0.2...platejs%4049.0.3) · By [@zbeyens](https://github.com/zbeyens), [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + }, + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-06-16", + "packageTag": "platejs@49.0.3", + "tag": "v49.0.3", + "title": "v49.0.3", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4366", + "url": "https://github.com/udecode/plate/pull/4366" + }, + { + "content": "`@platejs/date`\n\n### Bug Fixes\n\n- Delete the code that is no longer in use. ([#4351](https://github.com/udecode/plate/pull/4351))\n\n`@platejs/slate`\n\n### Bug Fixes\n\n- upgrade slate ([#4351](https://github.com/udecode/plate/pull/4351))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4361) · [`v49.0.1...v49.0.2`](https://github.com/udecode/plate/compare/%40platejs%2Fselection%4049.0.1...platejs%4049.0.2) · By [@felixfeng33](https://github.com/felixfeng33)", + "contributors": [ + { + "url": "https://github.com/felixfeng33", + "username": "@felixfeng33" + } + ], + "date": "2025-06-13", + "packageTag": "platejs@49.0.2", + "tag": "v49.0.2", + "title": "v49.0.2", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4361", + "url": "https://github.com/udecode/plate/pull/4361" + }, + { + "content": "`@platejs/selection`\n\n### Bug Fixes\n\n- Remove console.log ([#4343](https://github.com/udecode/plate/pull/4343))\n\n`@platejs/table`\n\n### Bug Fixes\n\n- Fix table selection ([#4343](https://github.com/udecode/plate/pull/4343))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4344) · [`v49.0.0...v49.0.1`](https://github.com/udecode/plate/compare/%40udecode%2Fplate-ai%4049.0.0...%40platejs%2Fselection%4049.0.1) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2025-06-11", + "packageTag": "@platejs/selection@49.0.1", + "tag": "v49.0.1", + "title": "v49.0.1", + "type": "patch", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4344", + "url": "https://github.com/udecode/plate/pull/4344" + }, + { + "content": "`@udecode/plate-ai`\n\n### Breaking Changes\n\n- Copilot API method changes: ([#4327](https://github.com/udecode/plate/pull/4327))\n - `editor.api.copilot.accept` is now `editor.tf.copilot.accept`.\n - `editor.api.copilot.acceptNextWord` is now `editor.tf.copilot.acceptNextWord`.\n - `editor.api.copilot.reset` is now `editor.api.copilot.reject`.\n- Removed Default Shortcuts for Copilot:\n - Only `accept` (Tab) and `reject` (Escape) shortcuts are included by default for `CopilotPlugin`.\n - `acceptNextWord` and `triggerSuggestion` shortcuts must now be configured manually using the `shortcuts` field when configuring the plugin.\n - Example:\n ```tsx\n CopilotPlugin.configure({\n // ... other options\n shortcuts: {\n acceptNextWord: {\n keys: 'mod+right',\n },\n triggerSuggestion: {\n keys: 'ctrl+space',\n },\n },\n });\n ```\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-alignment`\n\n### Breaking Changes\n\n- Package `@udecode/plate-alignment` has been deprecated. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n - `TextAlignPlugin` (formerly `AlignPlugin`) has been moved to the `@platejs/basic-styles` package.\n\n - Migration:\n\n - Remove `@udecode/plate-alignment` from your dependencies.\n - Add `@platejs/basic-styles` to your dependencies if not already present.\n - Import `TextAlignPlugin` from `@platejs/basic-styles/react`.\n\n - Renamed `AlignPlugin` to `TextAlignPlugin` and changed plugin key from `'align'` to `'textAlign'`.\n\n ```ts\n // Before\n import { AlignPlugin } from '@udecode/plate-alignment/react';\n\n // After\n import { TextAlignPlugin } from '@platejs/basic-styles/react';\n ```\n\n - `setAlign` signature change:\n\n ```ts\n // Before\n setAlign(editor, { value: 'center', setNodesOptions });\n\n // After\n setAlign(editor, 'center', setNodesOptions);\n ```\n\n - Removed `useAlignDropdownMenu` and `useAlignDropdownMenuState`. Use it in your own codebase, for example:\n\n ```tsx\n export function AlignToolbarButton() {\n const editor = useEditorRef();\n const value = useSelectionFragmentProp({\n defaultValue: 'start',\n structuralTypes,\n getProp: (node) => node.align,\n });\n\n const onValueChange = (newValue: string) => {\n editor.tf.textAlign.setNodes(newValue as Alignment);\n editor.tf.focus();\n };\n\n // ...\n }\n ```\n\n### Features\n\n- New transform method to `AlignPlugin`: ([#4327](https://github.com/udecode/plate/pull/4327))\n - `editor.tf.textAlign.setNodes` - Transform method for setting alignment values. Alias to `setAlign`\n\n`@udecode/plate-autoformat`\n\n### Breaking Changes\n\n- Replaced `BaseAutoformatPlugin` with `AutoformatPlugin`, which is no longer a React plugin. Migration: Replace `@udecode/plate-autoformat/react` import with `@udecode/plate-autoformat`. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n- The following plugins now default to `editOnly: true`. This means their core functionalities (handlers, rendering injections, etc.) will be disabled when the editor is in read-only mode. To override this behavior for a specific plugin, configure its `editOnly` field. For example, `SomePlugin.configure({ editOnly: false })`. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-basic-elements`\n\n### Breaking Changes\n\n- Package `@udecode/plate-basic-elements` has been deprecated. ([#4327](https://github.com/udecode/plate/pull/4327))\n- `BasicElementsPlugin` has been renamed to `BasicBlocksPlugin`.\n- Its plugins have been moved to the new `@platejs/basic-nodes` package.\n- Migration:\n - Replace `@udecode/plate-basic-elements` with `@platejs/basic-nodes` in your dependencies.\n - Update import paths from `@udecode/plate-basic-elements/react` to `@platejs/basic-nodes/react`.\n - For detailed changes to individual plugins, default HTML tags, and shortcut configurations, refer to the changeset for `@platejs/basic-nodes`.\n\n`@udecode/plate-basic-marks`\n\n### Breaking Changes\n\n- Package `@udecode/plate-basic-marks` has been deprecated. ([#4327](https://github.com/udecode/plate/pull/4327))\n- Its plugins have been moved to the new `@platejs/basic-nodes` package.\n- Migration:\n - Replace `@udecode/plate-basic-marks` with `@platejs/basic-nodes` in your dependencies.\n - Update import paths from `@udecode/plate-basic-marks/react` to `@platejs/basic-nodes/react`.\n - For detailed changes to individual plugins, default HTML tags, and shortcut configurations, refer to the changeset for `@platejs/basic-nodes`.\n\n`@udecode/plate-basic-nodes`\n\n### Breaking Changes\n\n- The packages `@udecode/plate-basic-elements` and `@udecode/plate-basic-marks` have been deprecated. All their plugins are now consolidated into the new `@platejs/basic-nodes` package. ([#4327](https://github.com/udecode/plate/pull/4327))\n- **Migration**:\n - Replace `@udecode/plate-basic-elements` and `@udecode/plate-basic-marks` in your dependencies with `@platejs/basic-nodes`.\n - Update all import paths from `@udecode/plate-basic-elements/react` or `@udecode/plate-basic-marks/react` to `@platejs/basic-nodes/react`.\n - `CodeBlockPlugin` is **not** part of `@platejs/basic-nodes`. Ensure it is imported from `@platejs/code-block/react`.\n- `SkipMarkPlugin` (standalone) is removed. Its functionality is now built into the core editor. To enable boundary clearing for a specific mark, configure the mark plugin directly: `plugin.configure({ rules: { selection: { affinity: 'outward' } } })`.\n- Default HTML Tag Changes:\n - **Blocks**: Element plugins in `@udecode/plate-basic-nodes` (e.g., `BlockquotePlugin`, `HeadingPlugin`, `HorizontalRulePlugin`) now default to rendering with specific HTML tags (`
        `, `

        -

        `, `
        ` respectively). `ParagraphPlugin` still defaults to `
        `. If you relied on previous defaults or need different tags, provide a custom component or use the `render.as` option.\n - **Marks**: Mark plugins in `@udecode/plate-basic-nodes` (e.g., `BoldPlugin`, `CodePlugin`, `ItalicPlugin`) now default to specific HTML tags (``, ``, `` respectively). If you relied on previous defaults or need different tags, provide a custom component or use the `render.as` option.\n- Removed Default Shortcuts:\n - Default keyboard shortcuts are no longer bundled with most plugins (exceptions: bold, italic, underline).\n - Configure shortcuts manually via the `shortcuts` field in plugin configuration.\n - Example (Block Plugins):\n ```ts\n H1Plugin.configure({ shortcuts: { toggle: { keys: 'mod+alt+1' } } });\n BlockquotePlugin.configure({\n shortcuts: { toggle: { keys: 'mod+shift+period' } },\n });\n ```\n - Example (Mark Plugins):\n ```ts\n CodePlugin.configure({ shortcuts: { toggle: { keys: 'mod+e' } } });\n StrikethroughPlugin.configure({\n shortcuts: { toggle: { keys: 'mod+shift+x' } },\n });\n SubscriptPlugin.configure({\n shortcuts: { toggle: { keys: 'mod+comma' } },\n });\n SuperscriptPlugin.configure({\n shortcuts: { toggle: { keys: 'mod+period' } },\n });\n HighlightPlugin.configure({\n shortcuts: { toggle: { keys: 'mod+shift+h' } },\n });\n ```\n\n### Features\n\n- New `toggle` Transforms Added: ([#4327](https://github.com/udecode/plate/pull/4327))\n - Block plugins with new `toggle` transforms: `BlockquotePlugin`, `H1Plugin`, `H2Plugin`, `H3Plugin`, `H4Plugin`, `H5Plugin`, `H6Plugin`.\n - All mark plugins in this package now also feature a `toggle` transform, including: `BoldPlugin`, `ItalicPlugin`, `UnderlinePlugin`, `CodePlugin`, `StrikethroughPlugin`, `SubscriptPlugin`, `SuperscriptPlugin`, `KbdPlugin`, `HighlightPlugin`.\n- Individual Heading Plugins Available:\n - `H1Plugin`, `H2Plugin`, `H3Plugin`, `H4Plugin`, `H5Plugin`, and `H6Plugin` offer a flexible alternative to the general `HeadingPlugin`, allowing granular control over heading level inclusion and configuration (e.g., custom components, shortcuts per level).\n- Plugin Consolidations into `@udecode/plate-basic-nodes`:\n - `KbdPlugin` (formerly from `@udecode/plate-kbd`).\n - `HighlightPlugin` (formerly from `@udecode/plate-highlight`).\n\n`@udecode/plate-block-quote`\n\n### Breaking Changes\n\n- Package `@udecode/plate-block-quote` has been deprecated. ([#4327](https://github.com/udecode/plate/pull/4327))\n- `BlockquotePlugin` has been moved to the `@platejs/basic-nodes` package.\n- Migration:\n - Remove `@udecode/plate-block-quote` from your dependencies.\n - Add `@platejs/basic-nodes` to your dependencies if not already present.\n - Import `BlockquotePlugin` from `@platejs/basic-nodes/react`.\n\n`@udecode/plate-break`\n\n### Breaking Changes\n\n- Package `@udecode/plate-break` has been deprecated. ([#4327](https://github.com/udecode/plate/pull/4327))\n- `SoftBreakPlugin` has been removed. Migration:\n - For `shift+enter` rules: no migration is needed - this behavior is built into Slate by default.\n - For `enter` rules: use `plugin.configure({ rules: { break: { default: 'lineBreak' } } })` to insert a line break instead of a hard break on `Enter` keydown when the selection is within the configured node type.\n - For more complex break rules: use `overrideEditor` to override the `insertBreak` transform with custom logic.\n- `ExitBreakPlugin` has been moved to `@platejs/utils` (which is re-exported via `platejs`) with a simplified API and improved behavior.\n\n - **Behavior Change**: Instead of always exiting to the root level of the document, exiting will now insert a block to the nearest exitable ancestor that has `isStrictSiblings: false`. This means deeply nested structures (like tables in columns) are exitable at many levels.\n - Migration:\n\n - Remove `@udecode/plate-break` from your dependencies.\n - Replace `@udecode/plate-break` import with `platejs`.\n - **Important**: If not using Plate plugins, you must set `isStrictSiblings: true` on your custom node plugins that can't have paragraph siblings for exit break to work correctly.\n - Replace complex rule-based configuration with simple shortcuts:\n\n ```tsx\n // Before (old API)\n ExitBreakPlugin.configure({\n options: {\n rules: [\n { hotkey: 'mod+enter' },\n { hotkey: 'mod+shift+enter', before: true },\n ],\n },\n });\n\n // After (new API)\n ExitBreakPlugin.configure({\n shortcuts: {\n insert: { keys: 'mod+enter' },\n insertBefore: { keys: 'mod+shift+enter' },\n },\n });\n ```\n\n`@udecode/plate-callout`\n\n### Breaking Changes\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-caption`\n\n### Breaking Changes\n\n- `CaptionPlugin` option `options.plugins` (accepting an array of `PlatePlugin`) has been renamed to `options.query.allow` (accepting an array of plugin keys). ([#4327](https://github.com/udecode/plate/pull/4327))\n- Migration:\n\n ```tsx\n // Before\n CaptionPlugin.configure({\n options: {\n plugins: [ImagePlugin], // ImagePlugin is an example\n },\n });\n\n // After\n CaptionPlugin.configure({\n options: {\n query: {\n allow: [ImagePlugin.key], // Use the plugin's key\n },\n },\n });\n ```\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-code-block`\n\n### Breaking Changes\n\n- `CodeBlockPlugin` now defaults to rendering the code block container with a `
        ` HTML tag if no custom component is provided for `CodeBlockElement` (or the plugin key `code_block`). ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-combobox`\n\n### Breaking Changes\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-comments`\n\n### Breaking Changes\n\n- `CommentsPlugin` has been renamed to `CommentPlugin`. ([#4327](https://github.com/udecode/plate/pull/4327))\n- Update imports and plugin configurations accordingly.\n    - Example: `CommentsPlugin.key` becomes `CommentPlugin.key`.\n- Package name has been changed to `@platejs/comment`.\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-core`\n\n### Breaking Changes\n\n- `editor.getType()` now takes a `pluginKey: string` instead of a `plugin: PlatePlugin` instance. ([#4327](https://github.com/udecode/plate/pull/4327))\n    - Example: Use `editor.getType(ParagraphPlugin.key)` instead of `editor.getType(ParagraphPlugin)`.\n- Plugins without a `key` property will not be registered into the editor.\n- Passing `disabled: true` prop to `PlateContent` will now also set the editor to `readOnly: true` state internally.\n- Editor DOM state properties have been moved under `editor.dom` namespace:\n    - `editor.currentKeyboardEvent` is now `editor.dom.currentKeyboardEvent`.\n    - `editor.prevSelection` is now `editor.dom.prevSelection`.\n- Editor metadata properties have been moved under `editor.meta` namespace:\n    - `editor.isFallback` is now `editor.meta.isFallback`\n    - `editor.key` is now `editor.meta.key`\n    - `editor.pluginList` is now `editor.meta.pluginList`\n    - `editor.shortcuts` is now `editor.meta.shortcuts`\n    - `editor.uid` is now `editor.meta.uid`\n- `NodeIdPlugin` is now enabled by default as part of the core plugins. This automatically assigns unique IDs to block nodes.\n    - Migration: If you were not previously using `NodeIdPlugin` and wish to maintain the old behavior (no automatic IDs), explicitly disable it in your editor configuration:\n        ```ts\n        const editor = usePlateEditor({\n          // ...other options\n          nodeId: false, // Disables automatic node ID generation\n        });\n        ```\n- The `components` prop has been removed from `serializeHtml` and `PlateStatic`.\n    - Migration: Pass the `components` to `createSlateEditor({ components })` or the individual plugins instead.\n- Plugin Shortcuts System Changes:\n    - Shortcut keys defined in `editor.shortcuts` are now namespaced by the plugin key (e.g., `code.toggle` for `CodePlugin`).\n    - The `priority` property for shortcuts is used to resolve conflicts when multiple shortcuts share the exact same key combination, not for overriding shortcuts by name.\n    - `preventDefault` for plugin shortcuts now defaults to `true`, unless the handler returns `false` (i.e. not handled). This means browser default actions for these key combinations will be prevented unless explicitly allowed.\n        - Migration: If you need to allow browser default behavior for a specific shortcut, set `preventDefault: false` in its configuration:\n            ```ts\n            MyPlugin.configure({\n              shortcuts: {\n                myAction: {\n                  keys: 'mod+s',\n                  preventDefault: false, // Example: Allow browser's default save dialog\n                },\n              },\n            });\n            ```\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n### Features\n\n- New editor DOM state fields available under `editor.dom`: ([#4327](https://github.com/udecode/plate/pull/4327))\n    - `editor.dom.composing`: Boolean, true if the editor is currently composing text (e.g., during IME input).\n    - `editor.dom.focused`: Boolean, true if the editor currently has focus.\n    - `editor.dom.readOnly`: Boolean, true if the editor is in read-only mode. Passing the `readOnly` prop to `PlateContent` will sync its value to this state and to the `useEditorReadOnly` hook.\n- New editor metadata fields:\n    - `editor.meta.components` - stores the plugin components by key\n- New hook `useEditorComposing`: Allows subscription to the editor's composing state (`editor.dom.composing`) outside of `PlateContent`.\n- `createPlateEditor` and `usePlateEditor` now accept a `readOnly` option to initialize the editor in a read-only state. For dynamic read-only changes after initialization, continue to use the `readOnly` prop on the `` or `` component.\n- New plugin field: `editOnly` (boolean or object).\n    - When `true` or when specific properties are true in the object, Plate will disable certain plugin behaviors (handlers, rendering, injections) in read-only mode and re-enable them if the editor becomes editable.\n    - By default, `render`, `handlers`, and `inject` are considered edit-only (`true`). `normalizeInitialValue` defaults to always active (`false`).\n    - Example: `editOnly: { render: false, normalizeInitialValue: true }` would make rendering active always, but normalization only in edit mode.\n- New plugin field: `render.as` (`keyof HTMLElementTagNameMap`).\n    - Specifies the default HTML tag name to be used by `PlateElement` (default: `'div'`) or `PlateLeaf` (default: `'span'`) when rendering the node, but only if no custom `node.component` is provided for the plugin.\n    - Example: `render: { as: 'h1' }` would make the plugin render its node as an `

        ` tag by default without the need to provide a custom component.\n- New plugin field: `node.isContainer` (boolean).\n - When `true`, indicates that the plugin's elements are primarily containers for other content.\n- New plugin field: `node.isStrictSiblings` (boolean).\n - When `true`, indicates that the element enforces strict sibling type constraints and only allows specific siblings (e.g., `td` can only have `td` siblings, `column` can only have `column` siblings).\n - Used by `editor.tf.insertExitBreak` functionality to determine appropriate exit points in nested structures.\n- New plugin field: `rules` (object).\n - Configures common editing behaviors declaratively instead of overriding editor methods. See documentation for more details.\n - `rules.break`: Controls Enter key behavior (`empty`, `default`, `emptyLineEnd`, `splitReset`)\n - `rules.delete`: Controls Backspace key behavior (`start`, `empty`)\n - `rules.merge`: Controls block merging behavior (`removeEmpty`)\n - `rules.normalize`: Controls normalization behavior (`removeEmpty`)\n - `rules.selection`: Controls cursor positioning behavior (`affinity`)\n - `rules.match`: Conditional rule application based on node properties\n- Plugin shortcuts can now automatically leverage existing plugin transforms by specifying the transform name, in addition to custom handlers.\n- New editor transform methods for keyboard handling:\n - `editor.tf.escape`: Handle Escape key events. Returns `true` if the event is handled.\n - `editor.tf.moveLine`: Handle ArrowDown and ArrowUp key events with `reverse` option for direction. Returns `true` if the event is handled.\n - `editor.tf.selectAll`: Handle Ctrl/Cmd+A key events for selecting all content. Returns `true` if the event is handled.\n - `editor.tf.tab`: Handle Tab and Shift+Tab key events with `reverse` option for Shift+Tab. Returns `true` if the event is handled.\n\n### Bug Fixes\n\n- Fixed an issue where `editor.api` and `editor.tf` (transforms) were not consistently available in the props passed to default element components when no custom component was provided for a plugin. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-csv`\n\n### Breaking Changes\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-cursor`\n\n### Breaking Changes\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-date`\n\n### Breaking Changes\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-diff`\n\n### Breaking Changes\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-dnd`\n\n### Breaking Changes\n\n- The following plugins now default to `editOnly: true`. This means their core functionalities (handlers, rendering injections, etc.) will be disabled when the editor is in read-only mode. To override this behavior for a specific plugin, configure its `editOnly` field. For example, `SomePlugin.configure({ editOnly: false })`. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-docx`\n\n### Breaking Changes\n\n- The following plugins now default to `editOnly: true`. This means their core functionalities (handlers, rendering injections, etc.) will be disabled when the editor is in read-only mode. To override this behavior for a specific plugin, configure its `editOnly` field. For example, `SomePlugin.configure({ editOnly: false })`. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-emoji`\n\n### Breaking Changes\n\n- The following plugins now default to `editOnly: true`. This means their core functionalities (handlers, rendering injections, etc.) will be disabled when the editor is in read-only mode. To override this behavior for a specific plugin, configure its `editOnly` field. For example, `SomePlugin.configure({ editOnly: false })`. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-excalidraw`\n\n### Breaking Changes\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-find-replace`\n\n### Breaking Changes\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-floating`\n\n### Breaking Changes\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-font`\n\n### Breaking Changes\n\n- Removed `setBlockBackgroundColor` ([#4327](https://github.com/udecode/plate/pull/4327))\n- Removed `setFontSize` – use `tf.fontSize.addMark` instead:\n\n ```ts\n // Before\n editor.api.fontSize.addMark('16px');\n\n // After\n editor.tf.fontSize.addMark('16px');\n ```\n\n - Removed `useColorInput`. Use it in your own codebase, for example:\n\n ```tsx\n function ColorInput() {\n const inputRef = React.useRef(null);\n\n const onClick = () => {\n inputRef.current?.click();\n };\n\n // ...\n }\n ```\n\n - Removed `useColorsCustom` and `useColorsCustomState`. Use it in your own codebase, for example:\n\n ```tsx\n function ColorCustom({ color, colors, customColors, updateCustomColor }) {\n const [customColor, setCustomColor] = React.useState();\n const [value, setValue] = React.useState(color || '#000000');\n\n React.useEffect(() => {\n if (\n !color ||\n customColors.some((c) => c.value === color) ||\n colors.some((c) => c.value === color)\n ) {\n return;\n }\n\n setCustomColor(color);\n }, [color, colors, customColors]);\n\n const computedColors = React.useMemo(\n () =>\n customColor\n ? [\n ...customColors,\n {\n isBrightColor: false,\n name: '',\n value: customColor,\n },\n ]\n : customColors,\n [customColor, customColors]\n );\n\n const updateCustomColorDebounced = React.useCallback(\n debounce(updateCustomColor, 100),\n [updateCustomColor]\n );\n\n const inputProps = {\n value,\n onChange: (e: React.ChangeEvent) => {\n setValue(e.target.value);\n updateCustomColorDebounced(e.target.value);\n },\n };\n\n // ...\n }\n ```\n\n - Removed `useColorDropdownMenu` and `useColorDropdownMenuState`. Use it in your own codebase, for example:\n\n ```tsx\n export function FontColorToolbarButton({ nodeType }) {\n const editor = useEditorRef();\n\n const selectionDefined = useEditorSelector(\n (editor) => !!editor.selection,\n []\n );\n\n const color = useEditorSelector(\n (editor) => editor.api.mark(nodeType) as string,\n [nodeType]\n );\n\n const [selectedColor, setSelectedColor] = React.useState();\n const [open, setOpen] = React.useState(false);\n\n const onToggle = React.useCallback(\n (value = !open) => {\n setOpen(value);\n },\n [open, setOpen]\n );\n\n const updateColor = React.useCallback(\n (value: string) => {\n if (editor.selection) {\n setSelectedColor(value);\n editor.tf.select(editor.selection);\n editor.tf.focus();\n editor.tf.addMarks({ [nodeType]: value });\n }\n },\n [editor, nodeType]\n );\n\n const clearColor = React.useCallback(() => {\n if (editor.selection) {\n editor.tf.select(editor.selection);\n editor.tf.focus();\n if (selectedColor) {\n editor.tf.removeMarks(nodeType);\n }\n onToggle();\n }\n }, [editor, selectedColor, onToggle, nodeType]);\n\n React.useEffect(() => {\n if (selectionDefined) {\n setSelectedColor(color);\n }\n }, [color, selectionDefined]);\n\n // ...\n }\n ```\n\n`@udecode/plate-heading`\n\n### Breaking Changes\n\n- Package `@udecode/plate-heading` has been deprecated. ([#4327](https://github.com/udecode/plate/pull/4327))\n - `HeadingPlugin` and individual heading plugins (e.g., `H1Plugin`) have been moved to `@platejs/basic-nodes`.\n - Migration: Import from `@platejs/basic-nodes/react` (e.g., `import { HeadingPlugin } from '@platejs/basic-nodes/react';`).\n - `TocPlugin` has been moved to `@platejs/toc`.\n - Migration: Add `@platejs/toc` to your dependencies and import `TocPlugin` from `@platejs/toc/react`.\n- Remove `@udecode/plate-heading` from your dependencies.\n\n`@udecode/plate-highlight`\n\n### Breaking Changes\n\n- Package `@udecode/plate-highlight` has been deprecated. ([#4327](https://github.com/udecode/plate/pull/4327))\n- `HighlightPlugin` has been moved to the `@platejs/basic-nodes` package.\n- Migration:\n - Remove `@udecode/plate-highlight` from your dependencies.\n - Add `@platejs/basic-nodes` to your dependencies if not already present.\n - Import `HighlightPlugin` from `@platejs/basic-nodes/react`.\n\n`@udecode/plate-horizontal-rule`\n\n### Breaking Changes\n\n- Package `@udecode/plate-horizontal-rule` has been deprecated. ([#4327](https://github.com/udecode/plate/pull/4327))\n- `HorizontalRulePlugin` has been moved to the `@platejs/basic-nodes` package.\n- Migration:\n - Remove `@udecode/plate-horizontal-rule` from your dependencies.\n - Add `@platejs/basic-nodes` to your dependencies if not already present.\n - Import `HorizontalRulePlugin` from `@platejs/basic-nodes/react`.\n\n`@udecode/plate-indent`\n\n### Breaking Changes\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-indent-list`\n\n### Breaking Changes\n\n- Package `@udecode/plate-indent-list` has been renamed to `@platejs/list`. ([#4327](https://github.com/udecode/plate/pull/4327))\n- Migration:\n - Rename all import paths from `@udecode/plate-indent-list` to `@platejs/list`.\n - Update your `package.json`: remove `@udecode/plate-indent-list` and add `@platejs/list`.\n\n`@udecode/plate-juice`\n\n### Breaking Changes\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-kbd`\n\n### Breaking Changes\n\n- Package `@udecode/plate-kbd` has been deprecated. ([#4327](https://github.com/udecode/plate/pull/4327))\n- `KbdPlugin` has been moved to the `@platejs/basic-nodes` package.\n- Migration:\n - Remove `@udecode/plate-kbd` from your dependencies.\n - Add `@platejs/basic-nodes` to your dependencies if not already present.\n - Import `KbdPlugin` from `@platejs/basic-nodes/react`.\n\n`@udecode/plate-layout`\n\n### Breaking Changes\n\n- Delete backward from a column start will merge into the previous column ([#4327](https://github.com/udecode/plate/pull/4327))\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-line-height`\n\n### Breaking Changes\n\n- Package `@udecode/plate-line-height` has been deprecated. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n - `LineHeightPlugin` has been moved to the `@platejs/basic-styles` package.\n\n - Migration:\n\n - Remove `@udecode/plate-line-height` from your dependencies.\n - Add `@platejs/basic-styles` to your dependencies if not already present.\n - Import `LineHeightPlugin` from `@platejs/basic-styles/react`.\n\n - `setLineHeight` signature change:\n\n ```ts\n // Before\n setLineHeight(editor, { value: 1.5, setNodesOptions });\n\n // After\n setLineHeight(editor, 1.5, setNodesOptions);\n ```\n\n - Removed `useLineHeightDropdownMenu` and `useLineHeightDropdownMenuState`. Use it in your own codebase, for example:\n\n ```tsx\n export function LineHeightToolbarButton() {\n const editor = useEditorRef();\n const { defaultNodeValue, validNodeValues: values = [] } =\n editor.getInjectProps(LineHeightPlugin);\n\n const value = useSelectionFragmentProp({\n defaultValue: defaultNodeValue,\n getProp: (node) => node.lineHeight,\n });\n\n const onValueChange = (newValue: string) => {\n editor.tf.lineHeight.setNodes(Number(newValue));\n editor.tf.focus();\n };\n\n // ...\n }\n ```\n\n### Features\n\n- New transform method to `LineHeightPlugin`: ([#4327](https://github.com/udecode/plate/pull/4327))\n - `editor.tf.lineHeight.setNodes` - Transform method for setting line height values. Alias to `setLineHeight`\n\n`@udecode/plate-link`\n\n### Breaking Changes\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-list`\n\n### Breaking Changes\n\n- The previous `@udecode/plate-list` (classic list implementation) has been moved to `@platejs/list-classic`. ([#4327](https://github.com/udecode/plate/pull/4327))\n - **Migration for users of the classic list**:\n - Rename all import paths from `@udecode/plate-list` to `@platejs/list-classic`.\n - Update your `package.json`: remove the old `@udecode/plate-list` (if it was a direct dependency) and add `@platejs/list-classic`.\n- This package, `@platejs/list`, now contains the functionality previously in `@udecode/plate-indent-list` (indent-based list system).\n - Plugin names have been generalized: `IndentListPlugin` is now `ListPlugin`, `BaseIndentListPlugin` is `BaseListPlugin`, etc. (`*IndentList*` -> `*List*`).\n - The primary plugin key is now `list` (e.g., `ListPlugin.key`) instead of `listStyleType`.\n - Constants for list keys previously in `INDENT_LIST_KEYS` are now available under `KEYS` from `platejs`.\n - Migration for constants:\n - `INDENT_LIST_KEYS.listStyleType` -> `KEYS.listType`\n - `INDENT_LIST_KEYS.todo` -> `KEYS.listTodo`\n - `INDENT_LIST_KEYS.checked` -> `KEYS.listChecked`\n - Other `INDENT_LIST_KEYS.*` map to `KEYS.*` accordingly.\n - Removed `listStyleTypes` option from `ListPlugin`. You should control list rendering via `render.belowNodes` instead.\n - Removed `renderListBelowNodes`.\n- For changelogs of the indent-based list system (now in this package) version `<=48` (when it was `@udecode/plate-indent-list`), refer to its [archived changelog](https://github.com/udecode/plate/blob/7afd88089f4a76c896f3edf928b03c7e9f2ab903/packages/indent-list/CHANGELOG.md).\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-list-classic`\n\n### Breaking Changes\n\n- **Removed Default Shortcuts** for `BulletedListPlugin` and `NumberedListPlugin`. ([#4327](https://github.com/udecode/plate/pull/4327))\n - These shortcuts must now be configured manually using the `shortcuts` field.\n - Example:\n ```tsx\n BulletedListPlugin.configure({\n shortcuts: { toggle: { keys: 'mod+alt+5' } },\n });\n NumberedListPlugin.configure({\n shortcuts: { toggle: { keys: 'mod+alt+6' } },\n });\n ```\n- Package `@udecode/plate-list` has been moved to `@platejs/list-classic`.\n - **Migration**:\n - Rename all import paths from `@udecode/plate-list` to `@platejs/list-classic`.\n - Update your `package.json`: remove `@udecode/plate-list` and add `@platejs/list-classic`.\n- For changelogs of `@udecode/plate-list` version `<=48`, refer to its [archived changelog](https://github.com/udecode/plate/blob/7afd88089f4a76c896f3edf928b03c7e9f2ab903/packages/list/CHANGELOG.md).\n\n`@udecode/plate-markdown`\n\n### Breaking Changes\n\n- Function `indentListToMdastTree` has been renamed to `listToMdastTree` to align with the list plugin renames (`IndentListPlugin` -> `ListPlugin`). ([#4327](https://github.com/udecode/plate/pull/4327))\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-math`\n\n### Breaking Changes\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-media`\n\n### Breaking Changes\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-mention`\n\n### Breaking Changes\n\n- The type `TMentionInputElement` has been removed. ([#4327](https://github.com/udecode/plate/pull/4327))\n- Use `TComboboxInputElement` from `@udecode/plate` instead for input elements, as mention functionality is built upon the combobox.\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-node-id`\n\n### Breaking Changes\n\n- Package `@udecode/plate-node-id` has been deprecated. ([#4327](https://github.com/udecode/plate/pull/4327))\n- `NodeIdPlugin` functionality is now part of `@platejs/core` and is **enabled by default**.\n- Migration:\n\n - Remove `NodeIdPlugin` from your explicit plugin list if it was added manually.\n\n - Remove `@udecode/plate-node-id` from your dependencies.\n\n - If you had `NodeIdPlugin` configured with options, move these options to the `nodeId` field in your main editor configuration (`createPlateEditor` or `usePlateEditor` options).\n Example:\n\n ```ts\n // Before\n // const editor = usePlateEditor({\n // plugins: [\n // NodeIdPlugin.configure({ /* ...your options... */ }),\n // ],\n // });\n\n // After\n const editor = usePlateEditor({\n nodeId: {\n /* ...your options... */\n },\n // ...other editor options\n });\n ```\n\n - If you want to disable automatic node ID generation (to replicate behavior if you weren't using `NodeIdPlugin` before), set `nodeId: false` in your editor configuration.\n\n`@udecode/plate-normalizers`\n\n### Breaking Changes\n\n- Package `@udecode/plate-normalizers` has been deprecated. ([#4327](https://github.com/udecode/plate/pull/4327))\n- Its plugins have been moved to `platejs` (which is re-exported via `platejs`).\n- Migration:\n - Remove `@udecode/plate-normalizers` from your dependencies.\n - Update import paths to use `platejs`\n\n`@udecode/plate`\n\n### Breaking Changes\n\n- Renamed package to `platejs`: ([#4327](https://github.com/udecode/plate/pull/4327))\n - Replace all `@udecode/plate/react` with `platejs/react`\n - Replace all `'@udecode/plate'` with `'platejs'`\n\n`@udecode/plate-playwright`\n\n### Breaking Changes\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-reset-node`\n\n### Breaking Changes\n\n- Package `@udecode/plate-reset-node` has been deprecated. Its functionality (e.g., `ResetNodePlugin`) is now exclusively configured using the `rules.break` and `rules.delete` options on plugin definitions. Migration: ([#4327](https://github.com/udecode/plate/pull/4327))\n\n - Remove `@udecode/plate-reset-node` from your dependencies.\n - Remove any usage of `ResetNodePlugin` from your project.\n - Configure reset behaviors directly on the relevant plugins by defining `rules.break` and/or `rules.delete`.\n\n **Example: Resetting a Blockquote to a Paragraph**\n\n ```typescript\n ResetNodePlugin.configure({\n options: {\n rules: [\n {\n types: [BlockquotePlugin.key],\n defaultType: ParagraphPlugin.key,\n hotkey: 'Enter',\n predicate: (editor) =>\n editor.api.isEmpty(editor.selection, { block: true }),\n },\n ],\n },\n });\n\n // After\n BlockquotePlugin.configure({\n rules: {\n break: { empty: 'reset' },\n delete: { start: 'reset' },\n },\n });\n ```\n\n **For custom reset logic (previously `onReset`):**\n\n ```typescript\n // Before\n ResetNodePlugin.configure({\n options: {\n rules: [\n {\n types: [CodeBlockPlugin.key],\n defaultType: ParagraphPlugin.key,\n hotkey: 'Enter',\n predicate: isCodeBlockEmpty,\n onReset: unwrapCodeBlock,\n },\n ],\n },\n });\n\n // After\n CodeBlockPlugin.configure({\n rules: {\n delete: { empty: 'reset' },\n },\n }).overrideEditor(({ editor, tf: { resetBlock } }) => ({\n transforms: {\n resetBlock(options) {\n if (\n editor.api.block({\n at: options?.at,\n match: { type: editor.getType(CodeBlockPlugin.key) },\n })\n ) {\n unwrapCodeBlock(editor);\n return;\n }\n\n return resetBlock(options);\n },\n },\n }));\n ```\n\n`@udecode/plate-resizable`\n\n### Breaking Changes\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-select`\n\n### Breaking Changes\n\n- The following plugins now default to `editOnly: true`. This means their core functionalities (handlers, rendering injections, etc.) will be disabled when the editor is in read-only mode. To override this behavior for a specific plugin, configure its `editOnly` field. For example, `SomePlugin.configure({ editOnly: false })`. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n- Package `@udecode/plate-select` has been deprecated. ([#4327](https://github.com/udecode/plate/pull/4327))\n- `SelectOnBackspacePlugin` has been removed. This behavior is now built into Plate by default: delete (backward/forward) at the start of a block will select the previous/next void block instead of removing it.\n- `DeletePlugin` has been removed. This behavior is now built into Plate by default: delete (backward/forward) from an empty block will remove it instead of merging.\n- `RemoveEmptyNodesPlugin` has been removed. This behavior is now available through the `rules: { normalize: { removeEmpty: true } }` configuration on individual plugins.\n- Migration:\n - Remove `@udecode/plate-select` from your dependencies.\n - Remove any usage of `SelectOnBackspacePlugin`, `DeletePlugin` from your project.\n - Replace `RemoveEmptyNodesPlugin.configure({ options: { types: ['custom'] } })` with `CustomPlugin.configure({ rules: { normalize: { removeEmpty: true } } })`. This is used by our `LinkPlugin`.\n\n`@udecode/plate-selection`\n\n### Breaking Changes\n\n- The following plugins now default to `editOnly: true`. This means their core functionalities (handlers, rendering injections, etc.) will be disabled when the editor is in read-only mode. To override this behavior for a specific plugin, configure its `editOnly` field. For example, `SomePlugin.configure({ editOnly: false })`. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-slash-command`\n\n### Breaking Changes\n\n- The following plugins now default to `editOnly: true`. This means their core functionalities (handlers, rendering injections, etc.) will be disabled when the editor is in read-only mode. To override this behavior for a specific plugin, configure its `editOnly` field. For example, `SomePlugin.configure({ editOnly: false })`. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n- The type `TSlashInputElement` has been removed. ([#4327](https://github.com/udecode/plate/pull/4327))\n- Use `TComboboxInputElement` from `platejs` instead for Slash Command input elements, as slash command functionality is built upon the combobox.\n\n`@udecode/slate`\n\n### Breaking Changes\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n- Replaced `editor.api.shouldMergeNodesRemovePrevNode` with `editor.api.shouldMergeNodes`. `shouldMergeNodes` is now controlling the remove + merge behavior ([#4327](https://github.com/udecode/plate/pull/4327))\n\n - Returns `true` if the default merging behavior should be applied.\n - Returns `false` if the default merging behavior should not be applied. This is used by Plate to prevent void blocks deletion, and to prioritize empty block deletion over merging.\n\n ```ts\n // Before\n editor.api.shouldMergeNodesRemovePrevNode(prev, current);\n\n // After\n editor.api.shouldMergeNodes(prev, current);\n ```\n\n - Replace `editor.api.fragment` option `structuralTypes` with `unwrap`.\n\n ```ts\n // Before\n editor.api.fragment(editor.selection, { structuralTypes: ['table'] });\n\n // After\n editor.api.fragment(editor.selection, { unwrap: ['table'] });\n ```\n\n### Features\n\n- `editor.tf.insertSoftBreak` now inserts a soft break instead of a hard break. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-suggestion`\n\n### Breaking Changes\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-tabbable`\n\n### Breaking Changes\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-table`\n\n### Breaking Changes\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-tag`\n\n### Breaking Changes\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-test-utils`\n\n### Breaking Changes\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-toc`\n\n### Breaking Changes\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-toggle`\n\n### Breaking Changes\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n`@udecode/plate-trailing-block`\n\n### Breaking Changes\n\n- The following plugins now default to `editOnly: true`. This means their core functionalities (handlers, rendering injections, etc.) will be disabled when the editor is in read-only mode. To override this behavior for a specific plugin, configure its `editOnly` field. For example, `SomePlugin.configure({ editOnly: false })`. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n- Package `@udecode/plate-trailing-block` has been deprecated. ([#4327](https://github.com/udecode/plate/pull/4327))\n- Its functionality (e.g., `TrailingBlockPlugin`) has been moved to `@platejs/utils` (which is re-exported via `platejs`).\n- Migration:\n - Remove `@udecode/plate-trailing-block` from your dependencies.\n - Update import paths to use `platejs` (e.g., `import { TrailingBlockPlugin } from 'platejs';`).\n\n`@udecode/plate-utils`\n\n### Breaking Changes\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n- Node type definitions (e.g., `TImageElement`, `TParagraphElement`) previously co-located with their respective plugin packages (like `@udecode/plate-media`) have been centralized into `@platejs/utils`. These are typically re-exported via the main `platejs` package. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n - Migration: Update imports for these types to pull from `platejs`.\n\n ```tsx\n // Before\n // import { TImageElement } from '@udecode/plate-media';\n\n // After\n import { TImageElement } from 'platejs';\n ```\n\n - Removed `structuralTypes` option from `useSelectionFragment` and `useSelectionFragmentProp`. These hooks now automatically use `plugin.node.isContainer` from enabled plugins.\n\n - Removed:\n - `createNodesHOC`\n - `createNodesWithHOC`\n - `createNodeHOC`\n\n - Removed `usePlaceholderState` hook.\n - Migration: Use the `BlockPlaceholderPlugin` (typically from `platejs`) instead of the `withPlaceholders` HOC and `usePlaceholderState`. Configure placeholders directly within the `BlockPlaceholderPlugin` options.\n ```ts\n // Example BlockPlaceholderPlugin configuration\n BlockPlaceholderPlugin.configure({\n options: {\n className:\n 'before:absolute before:cursor-text before:opacity-30 before:content-[attr(placeholder)]',\n placeholders: {\n [ParagraphPlugin.key]: 'Type something...',\n // ...other placeholders\n },\n query: ({ editor, path }) => {\n // Example query: only show for top-level empty blocks\n return (\n path.length === 1 && editor.api.isEmpty(editor.children[path[0]])\n );\n },\n },\n });\n ```\n\n### Features\n\n- New plugin `SingleBlockPlugin` to restrict editor content to a single block while preserving line breaks, while `SingleLinePlugin` prevents all line breaks. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n - `@platejs/utils` (and by extension, `platejs`) now exports a comprehensive `KEYS` object containing all official plugin keys.\n\n - This is intended to improve decoupling and provide a centralized way to reference plugin keys.\n - Example Usage:\n\n ```ts\n import { KEYS } from 'platejs';\n\n // Instead of: ParagraphPlugin.key\n // Use: KEYS.p\n ```\n\n - Many node type definitions (e.g., `TParagraphElement`, `TLinkElement`) are also now exported from `platejs`, in addition to being available from their specific plugin packages if those still exist or from `@platejs/basic-nodes`.\n\n`@udecode/plate-yjs`\n\n### Breaking Changes\n\n- Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. ([#4327](https://github.com/udecode/plate/pull/4327))\n\n[`CHANGELOG`](https://github.com/udecode/plate/pull/4339) · [`v48.0.6...v49.0.0`](https://github.com/udecode/plate/compare/%40udecode%2Fplate-node-id%4048.0.6...%40udecode%2Fplate-ai%4049.0.0) · By [@zbeyens](https://github.com/zbeyens)", + "contributors": [ + { + "url": "https://github.com/zbeyens", + "username": "@zbeyens" + } + ], + "date": "2025-06-11", + "packageTag": "@udecode/plate-ai@49.0.0", + "tag": "v49.0.0", + "title": "v49.0.0", + "type": "major", + "versionPackagePrUrl": "https://github.com/udecode/plate/pull/4339", + "url": "https://github.com/udecode/plate/pull/4339" + } +] diff --git a/apps/www/src/registry/blocks/fumadocs/fumadocs-mdx-components.tsx b/apps/www/src/registry/blocks/fumadocs/fumadocs-mdx-components.tsx index 775c0cabc8..3f1a72fae2 100644 --- a/apps/www/src/registry/blocks/fumadocs/fumadocs-mdx-components.tsx +++ b/apps/www/src/registry/blocks/fumadocs/fumadocs-mdx-components.tsx @@ -60,6 +60,7 @@ const plateMdxComponents = { KeyTableItem, MinusIcon, PackageInfo: EmptyComponent, + ReleaseIndex: EmptyComponent, SquareAsteriskIcon, Steps, }; diff --git a/content/migration/index.mdx b/content/migration/index.mdx deleted file mode 100644 index 1e2f4659e4..0000000000 --- a/content/migration/index.mdx +++ /dev/null @@ -1,1062 +0,0 @@ ---- -title: Major Releases ---- - - - This page only covers **major breaking changes**. For patch and minor changes, refer to the individual package `CHANGELOG.md` files or visit the [GitHub Releases](https://github.com/udecode/plate/releases) page. - - -This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. - -# 49.0.0 - -## platejs@49.0.0 - -- [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) – - - Renamed package to `platejs`: - - Replace all `@udecode/plate/react` with `platejs/react` - - Replace all `'@udecode/plate'` with `'platejs'` - -## @platejs/slate@49.0.0 - -- [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) – - - - Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. - -- [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) – - - - Replaced `editor.api.shouldMergeNodesRemovePrevNode` with `editor.api.shouldMergeNodes`. `shouldMergeNodes` is now controlling the remove + merge behavior - - - Returns `true` if the default merging behavior should be applied. - - Returns `false` if the default merging behavior should not be applied. This is used by Plate to prevent void blocks deletion, and to prioritize empty block deletion over merging. - - ```ts - // Before - editor.api.shouldMergeNodesRemovePrevNode(prev, current); - - // After - editor.api.shouldMergeNodes(prev, current); - ``` - - - Replace `editor.api.fragment` option `structuralTypes` with `unwrap`. - - ```ts - // Before - editor.api.fragment(editor.selection, { structuralTypes: ['table'] }); - - // After - editor.api.fragment(editor.selection, { unwrap: ['table'] }); - ``` - -### Minor Changes - -- [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) – - - `editor.tf.insertSoftBreak` now inserts a soft break instead of a hard break. - -## @platejs/core@49.0.0 - -- [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) – - - - `editor.getType()` now takes a `pluginKey: string` instead of a `plugin: PlatePlugin` instance. - - Example: Use `editor.getType(ParagraphPlugin.key)` instead of `editor.getType(ParagraphPlugin)`. - - Plugins without a `key` property will not be registered into the editor. - - Passing `disabled: true` prop to `PlateContent` will now also set the editor to `readOnly: true` state internally. - - Editor DOM state properties have been moved under `editor.dom` namespace: - - `editor.currentKeyboardEvent` is now `editor.dom.currentKeyboardEvent`. - - `editor.prevSelection` is now `editor.dom.prevSelection`. - - Editor metadata properties have been moved under `editor.meta` namespace: - - `editor.isFallback` is now `editor.meta.isFallback` - - `editor.key` is now `editor.meta.key` - - `editor.pluginList` is now `editor.meta.pluginList` - - `editor.shortcuts` is now `editor.meta.shortcuts` - - `editor.uid` is now `editor.meta.uid` - - `NodeIdPlugin` is now enabled by default as part of the core plugins. This automatically assigns unique IDs to block nodes. - - Migration: If you were not previously using `NodeIdPlugin` and wish to maintain the old behavior (no automatic IDs), explicitly disable it in your editor configuration: - ```ts - const editor = usePlateEditor({ - // ...other options - nodeId: false, // Disables automatic node ID generation - }); - ``` - - The `components` prop has been removed from `serializeHtml` and `PlateStatic`. - - Migration: Pass the `components` to `createSlateEditor({ components })` or the individual plugins instead. - - Plugin Shortcuts System Changes: - - Shortcut keys defined in `editor.shortcuts` are now namespaced by the plugin key (e.g., `code.toggle` for `CodePlugin`). - - The `priority` property for shortcuts is used to resolve conflicts when multiple shortcuts share the exact same key combination, not for overriding shortcuts by name. - - `preventDefault` for plugin shortcuts now defaults to `true`, unless the handler returns `false` (i.e. not handled). This means browser default actions for these key combinations will be prevented unless explicitly allowed. - - Migration: If you need to allow browser default behavior for a specific shortcut, set `preventDefault: false` in its configuration: - ```ts - MyPlugin.configure({ - shortcuts: { - myAction: { - keys: 'mod+s', - preventDefault: false, // Example: Allow browser's default save dialog - }, - }, - }); - ``` - -- [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) – - - Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. - -### Minor Changes - -- [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) – - - New editor DOM state fields available under `editor.dom`: - - `editor.dom.composing`: Boolean, true if the editor is currently composing text (e.g., during IME input). - - `editor.dom.focused`: Boolean, true if the editor currently has focus. - - `editor.dom.readOnly`: Boolean, true if the editor is in read-only mode. Passing the `readOnly` prop to `PlateContent` will sync its value to this state and to the `useEditorReadOnly` hook. - - New editor metadata fields: - - `editor.meta.components` - stores the plugin components by key - - New hook `useEditorComposing`: Allows subscription to the editor's composing state (`editor.dom.composing`) outside of `PlateContent`. - - `createPlateEditor` and `usePlateEditor` now accept a `readOnly` option to initialize the editor in a read-only state. For dynamic read-only changes after initialization, continue to use the `readOnly` prop on the `` or `` component. - - New plugin field: `editOnly` (boolean or object). - - When `true` or when specific properties are true in the object, Plate will disable certain plugin behaviors (handlers, rendering, injections) in read-only mode and re-enable them if the editor becomes editable. - - By default, `render`, `handlers`, and `inject` are considered edit-only (`true`). `normalizeInitialValue` defaults to always active (`false`). - - Example: `editOnly: { render: false, normalizeInitialValue: true }` would make rendering active always, but normalization only in edit mode. - - New plugin field: `render.as` (`keyof HTMLElementTagNameMap`). - - Specifies the default HTML tag name to be used by `PlateElement` (default: `'div'`) or `PlateLeaf` (default: `'span'`) when rendering the node, but only if no custom `node.component` is provided for the plugin. - - Example: `render: { as: 'h1' }` would make the plugin render its node as an `

        ` tag by default without the need to provide a custom component. - - New plugin field: `node.isContainer` (boolean). - - When `true`, indicates that the plugin's elements are primarily containers for other content. - - New plugin field: `node.isStrictSiblings` (boolean). - - When `true`, indicates that the element enforces strict sibling type constraints and only allows specific siblings (e.g., `td` can only have `td` siblings, `column` can only have `column` siblings). - - Used by `editor.tf.insertExitBreak` functionality to determine appropriate exit points in nested structures. - - New plugin field: `rules` (object). - - Configures common editing behaviors declaratively instead of overriding editor methods. See documentation for more details. - - `rules.break`: Controls Enter key behavior (`empty`, `default`, `emptyLineEnd`, `splitReset`) - - `rules.delete`: Controls Backspace key behavior (`start`, `empty`) - - `rules.merge`: Controls block merging behavior (`removeEmpty`) - - `rules.normalize`: Controls normalization behavior (`removeEmpty`) - - `rules.selection`: Controls cursor positioning behavior (`affinity`) - - `rules.match`: Conditional rule application based on node properties - - Plugin shortcuts can now automatically leverage existing plugin transforms by specifying the transform name, in addition to custom handlers. - - New editor transform methods for keyboard handling: - - `editor.tf.escape`: Handle Escape key events. Returns `true` if the event is handled. - - `editor.tf.moveLine`: Handle ArrowDown and ArrowUp key events with `reverse` option for direction. Returns `true` if the event is handled. - - `editor.tf.selectAll`: Handle Ctrl/Cmd+A key events for selecting all content. Returns `true` if the event is handled. - - `editor.tf.tab`: Handle Tab and Shift+Tab key events with `reverse` option for Shift+Tab. Returns `true` if the event is handled. - -## @platejs/utils@49.0.0 - -- [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) – - - - Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. - -- [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) – - - - Node type definitions (e.g., `TImageElement`, `TParagraphElement`) previously co-located with their respective plugin packages (like `@udecode/plate-media`) have been centralized into `@platejs/utils`. These are typically re-exported via the main `platejs` package. - - - Migration: Update imports for these types to pull from `platejs`. - - ```tsx - // Before - // import { TImageElement } from '@udecode/plate-media'; - - // After - import { TImageElement } from 'platejs'; - ``` - - - Removed `structuralTypes` option from `useSelectionFragment` and `useSelectionFragmentProp`. These hooks now automatically use `plugin.node.isContainer` from enabled plugins. - - - Removed: - - `createNodesHOC` - - `createNodesWithHOC` - - `createNodeHOC` - - - Removed `usePlaceholderState` hook. - - Migration: Use the `BlockPlaceholderPlugin` (typically from `platejs`) instead of the `withPlaceholders` HOC and `usePlaceholderState`. Configure placeholders directly within the `BlockPlaceholderPlugin` options. - ```ts - // Example BlockPlaceholderPlugin configuration - BlockPlaceholderPlugin.configure({ - options: { - className: - 'before:absolute before:cursor-text before:opacity-30 before:content-[attr(placeholder)]', - placeholders: { - [ParagraphPlugin.key]: 'Type something...', - // ...other placeholders - }, - query: ({ editor, path }) => { - // Example query: only show for top-level empty blocks - return ( - path.length === 1 && editor.api.isEmpty(editor.children[path[0]]) - ); - }, - }, - }); - ``` - -## @platejs/ai@49.0.0 - -- [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) – - - - Copilot API method changes: - - `editor.api.copilot.accept` is now `editor.tf.copilot.accept`. - - `editor.api.copilot.acceptNextWord` is now `editor.tf.copilot.acceptNextWord`. - - `editor.api.copilot.reset` is now `editor.api.copilot.reject`. - - Removed Default Shortcuts for Copilot: - - Only `accept` (Tab) and `reject` (Escape) shortcuts are included by default for `CopilotPlugin`. - - `acceptNextWord` and `triggerSuggestion` shortcuts must now be configured manually using the `shortcuts` field when configuring the plugin. - - Example: - ```tsx - CopilotPlugin.configure({ - // ... other options - shortcuts: { - acceptNextWord: { - keys: 'mod+right', - }, - triggerSuggestion: { - keys: 'ctrl+space', - }, - }, - }); - ``` - -- [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) – - - Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. - -## @platejs/alignment@49.0.0 - -- [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) – - - - Package `@udecode/plate-alignment` has been deprecated. - - - `TextAlignPlugin` (formerly `AlignPlugin`) has been moved to the `@platejs/basic-styles` package. - - - Migration: - - - Remove `@udecode/plate-alignment` from your dependencies. - - Add `@platejs/basic-styles` to your dependencies if not already present. - - Import `TextAlignPlugin` from `@platejs/basic-styles/react`. - - - Renamed `AlignPlugin` to `TextAlignPlugin` and changed plugin key from `'align'` to `'textAlign'`. - - ```ts - // Before - import { AlignPlugin } from '@udecode/plate-alignment/react'; - - // After - import { TextAlignPlugin } from '@platejs/basic-styles/react'; - ``` - - - `setAlign` signature change: - - ```ts - // Before - setAlign(editor, { value: 'center', setNodesOptions }); - - // After - setAlign(editor, 'center', setNodesOptions); - ``` - - - Removed `useAlignDropdownMenu` and `useAlignDropdownMenuState`. Use it in your own codebase, for example: - - ```tsx - export function AlignToolbarButton() { - const editor = useEditorRef(); - const value = useSelectionFragmentProp({ - defaultValue: 'start', - structuralTypes, - getProp: (node) => node.align, - }); - - const onValueChange = (newValue: string) => { - editor.tf.textAlign.setNodes(newValue as Alignment); - editor.tf.focus(); - }; - - // ... - } - ``` - -## @platejs/autoformat@49.0.0 - -- [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) – - - - Replaced `BaseAutoformatPlugin` with `AutoformatPlugin`, which is no longer a React plugin. Migration: Replace `@udecode/plate-autoformat/react` import with `@udecode/plate-autoformat`. - -- [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) – - - - The following plugins now default to `editOnly: true`. This means their core functionalities (handlers, rendering injections, etc.) will be disabled when the editor is in read-only mode. To override this behavior for a specific plugin, configure its `editOnly` field. For example, `SomePlugin.configure({ editOnly: false })`. - -- [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) – - - Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. - -## @platejs/basic-elements@49.0.0 - -- [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) – - - Package `@udecode/plate-basic-elements` has been deprecated. - - `BasicElementsPlugin` has been renamed to `BasicBlocksPlugin`. - - Its plugins have been moved to the new `@platejs/basic-nodes` package. - - Migration: - - Replace `@udecode/plate-basic-elements` with `@platejs/basic-nodes` in your dependencies. - - Update import paths from `@udecode/plate-basic-elements/react` to `@platejs/basic-nodes/react`. - - For detailed changes to individual plugins, default HTML tags, and shortcut configurations, refer to the changeset for `@platejs/basic-nodes`. - -## @platejs/basic-marks@49.0.0 - -- [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) – - - Package `@udecode/plate-basic-marks` has been deprecated. - - Its plugins have been moved to the new `@platejs/basic-nodes` package. - - Migration: - - Replace `@udecode/plate-basic-marks` with `@platejs/basic-nodes` in your dependencies. - - Update import paths from `@udecode/plate-basic-marks/react` to `@platejs/basic-nodes/react`. - - For detailed changes to individual plugins, default HTML tags, and shortcut configurations, refer to the changeset for `@platejs/basic-nodes`. - -## @platejs/basic-nodes@49.0.0 - -- [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) – - - The packages `@udecode/plate-basic-elements` and `@udecode/plate-basic-marks` have been deprecated. All their plugins are now consolidated into the new `@platejs/basic-nodes` package. - - **Migration**: - - Replace `@udecode/plate-basic-elements` and `@udecode/plate-basic-marks` in your dependencies with `@platejs/basic-nodes`. - - Update all import paths from `@udecode/plate-basic-elements/react` or `@udecode/plate-basic-marks/react` to `@platejs/basic-nodes/react`. - - `CodeBlockPlugin` is **not** part of `@platejs/basic-nodes`. Ensure it is imported from `@platejs/code-block/react`. - - `SkipMarkPlugin` (standalone) is removed. Its functionality is now built into the core editor. To enable boundary clearing for a specific mark, configure the mark plugin directly: `plugin.configure({ rules: { selection: { affinity: 'outward' } } })`. - - Default HTML Tag Changes: - - **Blocks**: Element plugins in `@udecode/plate-basic-nodes` (e.g., `BlockquotePlugin`, `HeadingPlugin`, `HorizontalRulePlugin`) now default to rendering with specific HTML tags (`
        `, `

        -

        `, `
        ` respectively). `ParagraphPlugin` still defaults to `
        `. If you relied on previous defaults or need different tags, provide a custom component or use the `render.as` option. - - **Marks**: Mark plugins in `@udecode/plate-basic-nodes` (e.g., `BoldPlugin`, `CodePlugin`, `ItalicPlugin`) now default to specific HTML tags (``, ``, `` respectively). If you relied on previous defaults or need different tags, provide a custom component or use the `render.as` option. - - Removed Default Shortcuts: - - Default keyboard shortcuts are no longer bundled with most plugins (exceptions: bold, italic, underline). - - Configure shortcuts manually via the `shortcuts` field in plugin configuration. - - Example (Block Plugins): - ```ts - H1Plugin.configure({ shortcuts: { toggle: { keys: 'mod+alt+1' } } }); - BlockquotePlugin.configure({ - shortcuts: { toggle: { keys: 'mod+shift+period' } }, - }); - ``` - - Example (Mark Plugins): - ```ts - CodePlugin.configure({ shortcuts: { toggle: { keys: 'mod+e' } } }); - StrikethroughPlugin.configure({ - shortcuts: { toggle: { keys: 'mod+shift+x' } }, - }); - SubscriptPlugin.configure({ - shortcuts: { toggle: { keys: 'mod+comma' } }, - }); - SuperscriptPlugin.configure({ - shortcuts: { toggle: { keys: 'mod+period' } }, - }); - HighlightPlugin.configure({ - shortcuts: { toggle: { keys: 'mod+shift+h' } }, - }); - ``` - -## @platejs/block-quote@49.0.0 - -- [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) – - - Package `@udecode/plate-block-quote` has been deprecated. - - `BlockquotePlugin` has been moved to the `@platejs/basic-nodes` package. - - Migration: - - Remove `@udecode/plate-block-quote` from your dependencies. - - Add `@platejs/basic-nodes` to your dependencies if not already present. - - Import `BlockquotePlugin` from `@platejs/basic-nodes/react`. - -## @platejs/break@49.0.0 - -- [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) – - - - Package `@udecode/plate-break` has been deprecated. - - `SoftBreakPlugin` has been removed. Migration: - - For `shift+enter` rules: no migration is needed - this behavior is built into Plate by default. - - For `enter` rules: use `plugin.configure({ rules: { break: { default: 'lineBreak' } } })` to insert a line break instead of a hard break on `Enter` keydown when the selection is within the configured node type. - - For more complex break rules: use `overrideEditor` to override the `insertBreak` transform with custom logic. - - `ExitBreakPlugin` has been moved to `@platejs/utils` (which is re-exported via `platejs`) with a simplified API and improved behavior. - - - **Behavior Change**: Instead of always exiting to the root level of the document, exiting will now insert a block to the nearest exitable ancestor that has `isStrictSiblings: false`. This means deeply nested structures (like tables in columns) are exitable at many levels. - - Migration: - - - Remove `@udecode/plate-break` from your dependencies. - - Replace `@udecode/plate-break` import with `platejs`. - - **Important**: If not using Plate plugins, you must set `isStrictSiblings: true` on your custom node plugins that can't have paragraph siblings for exit break to work correctly. - - Replace complex rule-based configuration with simple shortcuts: - - ```tsx - // Before (old API) - ExitBreakPlugin.configure({ - options: { - rules: [ - { hotkey: 'mod+enter' }, - { hotkey: 'mod+shift+enter', before: true }, - ], - }, - }); - - // After (new API) - ExitBreakPlugin.configure({ - shortcuts: { - insert: { keys: 'mod+enter' }, - insertBefore: { keys: 'mod+shift+enter' }, - }, - }); - ``` - -## @platejs/callout@49.0.0 - -- [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) – - - Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. - -## @platejs/caption@49.0.0 - -- [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) – - - - `CaptionPlugin` option `options.plugins` (accepting an array of `PlatePlugin`) has been renamed to `options.query.allow` (accepting an array of plugin keys). - - Migration: - - ```tsx - // Before - CaptionPlugin.configure({ - options: { - plugins: [ImagePlugin], // ImagePlugin is an example - }, - }); - - // After - CaptionPlugin.configure({ - options: { - query: { - allow: [ImagePlugin.key], // Use the plugin's key - }, - }, - }); - ``` - -- [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) – - - Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code. - -## @platejs/code-block@49.0.0 - -- [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) – - - `CodeBlockPlugin` now defaults to rendering the code block container with a `
        ` HTML tag if no custom component is provided for `CodeBlockElement` (or the plugin key `code_block`).
        -
        -## @platejs/combobox@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/comments@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -
        -    -   `CommentsPlugin` has been renamed to `CommentPlugin`.
        -    -   Update imports and plugin configurations accordingly.
        -        -   Example: `CommentsPlugin.key` becomes `CommentPlugin.key`.
        -    -   Package name has been changed to `@platejs/comment`.
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/csv@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/cursor@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/date@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/diff@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/dnd@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -
        -    -   The following plugins now default to `editOnly: true`. This means their core functionalities (handlers, rendering injections, etc.) will be disabled when the editor is in read-only mode. To override this behavior for a specific plugin, configure its `editOnly` field. For example, `SomePlugin.configure({ editOnly: false })`.
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/docx@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -
        -    -   The following plugins now default to `editOnly: true`. This means their core functionalities (handlers, rendering injections, etc.) will be disabled when the editor is in read-only mode. To override this behavior for a specific plugin, configure its `editOnly` field. For example, `SomePlugin.configure({ editOnly: false })`.
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/emoji@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -
        -    -   The following plugins now default to `editOnly: true`. This means their core functionalities (handlers, rendering injections, etc.) will be disabled when the editor is in read-only mode. To override this behavior for a specific plugin, configure its `editOnly` field. For example, `SomePlugin.configure({ editOnly: false })`.
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/excalidraw@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/find-replace@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/floating@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/font@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -
        -    -   Removed `setBlockBackgroundColor`
        -    -   Removed `setFontSize` – use `tf.fontSize.addMark` instead:
        -
        -    ```ts
        -    // Before
        -    editor.api.fontSize.addMark('16px');
        -
        -    // After
        -    editor.tf.fontSize.addMark('16px');
        -    ```
        -
        -    -   Removed `useColorInput`. Use it in your own codebase, for example:
        -
        -    ```tsx
        -    function ColorInput() {
        -      const inputRef = React.useRef(null);
        -
        -      const onClick = () => {
        -        inputRef.current?.click();
        -      };
        -
        -      // ...
        -    }
        -    ```
        -
        -    -   Removed `useColorsCustom` and `useColorsCustomState`. Use it in your own codebase, for example:
        -
        -    ```tsx
        -    function ColorCustom({ color, colors, customColors, updateCustomColor }) {
        -      const [customColor, setCustomColor] = React.useState();
        -      const [value, setValue] = React.useState(color || '#000000');
        -
        -      React.useEffect(() => {
        -        if (
        -          !color ||
        -          customColors.some((c) => c.value === color) ||
        -          colors.some((c) => c.value === color)
        -        ) {
        -          return;
        -        }
        -
        -        setCustomColor(color);
        -      }, [color, colors, customColors]);
        -
        -      const computedColors = React.useMemo(
        -        () =>
        -          customColor
        -            ? [
        -                ...customColors,
        -                {
        -                  isBrightColor: false,
        -                  name: '',
        -                  value: customColor,
        -                },
        -              ]
        -            : customColors,
        -        [customColor, customColors]
        -      );
        -
        -      const updateCustomColorDebounced = React.useCallback(
        -        debounce(updateCustomColor, 100),
        -        [updateCustomColor]
        -      );
        -
        -      const inputProps = {
        -        value,
        -        onChange: (e: React.ChangeEvent) => {
        -          setValue(e.target.value);
        -          updateCustomColorDebounced(e.target.value);
        -        },
        -      };
        -
        -      // ...
        -    }
        -    ```
        -
        -    -   Removed `useColorDropdownMenu` and `useColorDropdownMenuState`. Use it in your own codebase, for example:
        -
        -    ```tsx
        -    export function FontColorToolbarButton({ nodeType }) {
        -      const editor = useEditorRef();
        -
        -      const selectionDefined = useEditorSelector(
        -        (editor) => !!editor.selection,
        -        []
        -      );
        -
        -      const color = useEditorSelector(
        -        (editor) => editor.api.mark(nodeType) as string,
        -        [nodeType]
        -      );
        -
        -      const [selectedColor, setSelectedColor] = React.useState();
        -      const [open, setOpen] = React.useState(false);
        -
        -      const onToggle = React.useCallback(
        -        (value = !open) => {
        -          setOpen(value);
        -        },
        -        [open, setOpen]
        -      );
        -
        -      const updateColor = React.useCallback(
        -        (value: string) => {
        -          if (editor.selection) {
        -            setSelectedColor(value);
        -            editor.tf.select(editor.selection);
        -            editor.tf.focus();
        -            editor.tf.addMarks({ [nodeType]: value });
        -          }
        -        },
        -        [editor, nodeType]
        -      );
        -
        -      const clearColor = React.useCallback(() => {
        -        if (editor.selection) {
        -          editor.tf.select(editor.selection);
        -          editor.tf.focus();
        -          if (selectedColor) {
        -            editor.tf.removeMarks(nodeType);
        -          }
        -          onToggle();
        -        }
        -      }, [editor, selectedColor, onToggle, nodeType]);
        -
        -      React.useEffect(() => {
        -        if (selectionDefined) {
        -          setSelectedColor(color);
        -        }
        -      }, [color, selectionDefined]);
        -
        -      // ...
        -    }
        -    ```
        -
        -## @platejs/heading@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Package `@udecode/plate-heading` has been deprecated.
        -        -   `HeadingPlugin` and individual heading plugins (e.g., `H1Plugin`) have been moved to `@platejs/basic-nodes`.
        -            -   Migration: Import from `@platejs/basic-nodes/react` (e.g., `import { HeadingPlugin } from '@platejs/basic-nodes/react';`).
        -        -   `TocPlugin` has been moved to `@platejs/toc`.
        -            -   Migration: Add `@platejs/toc` to your dependencies and import `TocPlugin` from `@platejs/toc/react`.
        -    -   Remove `@udecode/plate-heading` from your dependencies.
        -    -   `HEADING_LEVELS` has been removed. Use `KEYS.heading` instead.
        -
        -## @platejs/highlight@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Package `@udecode/plate-highlight` has been deprecated.
        -    -   `HighlightPlugin` has been moved to the `@platejs/basic-nodes` package.
        -    -   Migration:
        -        -   Remove `@udecode/plate-highlight` from your dependencies.
        -        -   Add `@platejs/basic-nodes` to your dependencies if not already present.
        -        -   Import `HighlightPlugin` from `@platejs/basic-nodes/react`.
        -
        -## @platejs/horizontal-rule@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Package `@udecode/plate-horizontal-rule` has been deprecated.
        -    -   `HorizontalRulePlugin` has been moved to the `@platejs/basic-nodes` package.
        -    -   Migration:
        -        -   Remove `@udecode/plate-horizontal-rule` from your dependencies.
        -        -   Add `@platejs/basic-nodes` to your dependencies if not already present.
        -        -   Import `HorizontalRulePlugin` from `@platejs/basic-nodes/react`.
        -
        -## @platejs/indent@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/indent-list@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Package `@udecode/plate-indent-list` has been renamed to `@platejs/list`.
        -    -   Migration:
        -        -   Rename all import paths from `@udecode/plate-indent-list` to `@platejs/list`.
        -        -   Update your `package.json`: remove `@udecode/plate-indent-list` and add `@platejs/list`.
        -
        -## @platejs/juice@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/kbd@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Package `@udecode/plate-kbd` has been deprecated.
        -    -   `KbdPlugin` has been moved to the `@platejs/basic-nodes` package.
        -    -   Migration:
        -        -   Remove `@udecode/plate-kbd` from your dependencies.
        -        -   Add `@platejs/basic-nodes` to your dependencies if not already present.
        -        -   Import `KbdPlugin` from `@platejs/basic-nodes/react`.
        -
        -## @platejs/layout@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -
        -    -   Delete backward from a column start will merge into the previous column
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/line-height@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -
        -    -   Package `@udecode/plate-line-height` has been deprecated.
        -
        -    -   `LineHeightPlugin` has been moved to the `@platejs/basic-styles` package.
        -
        -    -   Migration:
        -
        -        -   Remove `@udecode/plate-line-height` from your dependencies.
        -        -   Add `@platejs/basic-styles` to your dependencies if not already present.
        -        -   Import `LineHeightPlugin` from `@platejs/basic-styles/react`.
        -
        -    -   `setLineHeight` signature change:
        -
        -    ```ts
        -    // Before
        -    setLineHeight(editor, { value: 1.5, setNodesOptions });
        -
        -    // After
        -    setLineHeight(editor, 1.5, setNodesOptions);
        -    ```
        -
        -    -   Removed `useLineHeightDropdownMenu` and `useLineHeightDropdownMenuState`. Use it in your own codebase, for example:
        -
        -    ```tsx
        -    export function LineHeightToolbarButton() {
        -      const editor = useEditorRef();
        -      const { defaultNodeValue, validNodeValues: values = [] } =
        -        editor.getInjectProps(LineHeightPlugin);
        -
        -      const value = useSelectionFragmentProp({
        -        defaultValue: defaultNodeValue,
        -        getProp: (node) => node.lineHeight,
        -      });
        -
        -      const onValueChange = (newValue: string) => {
        -        editor.tf.lineHeight.setNodes(Number(newValue));
        -        editor.tf.focus();
        -      };
        -
        -      // ...
        -    }
        -    ```
        -
        -## @platejs/link@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/list@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -
        -    -   The previous `@udecode/plate-list` (classic list implementation) has been moved to `@platejs/list-classic`.
        -        -   **Migration for users of the classic list**:
        -            -   Rename all import paths from `@udecode/plate-list` to `@platejs/list-classic`.
        -            -   Update your `package.json`: remove the old `@udecode/plate-list` (if it was a direct dependency) and add `@platejs/list-classic`.
        -    -   This package, `@platejs/list`, now contains the functionality previously in `@udecode/plate-indent-list` (indent-based list system).
        -        -   Plugin names have been generalized: `IndentListPlugin` is now `ListPlugin`, `BaseIndentListPlugin` is `BaseListPlugin`, etc. (`*IndentList*` -> `*List*`).
        -        -   The primary plugin key is now `list` (e.g., `ListPlugin.key`) instead of `listStyleType`.
        -        -   Constants for list keys previously in `INDENT_LIST_KEYS` are now available under `KEYS` from `platejs`.
        -            -   Migration for constants:
        -                -   `INDENT_LIST_KEYS.listStyleType` -> `KEYS.listType`
        -                -   `INDENT_LIST_KEYS.todo` -> `KEYS.listTodo`
        -                -   `INDENT_LIST_KEYS.checked` -> `KEYS.listChecked`
        -                -   Other `INDENT_LIST_KEYS.*` map to `KEYS.*` accordingly.
        -        -   Removed `listStyleTypes` option from `ListPlugin`. You should control list rendering via `render.belowNodes` instead.
        -        -   Removed `renderListBelowNodes`.
        -    -   For changelogs of the indent-based list system (now in this package) version `<=48` (when it was `@udecode/plate-indent-list`), refer to its [archived changelog](https://github.com/udecode/plate/blob/7afd88089f4a76c896f3edf928b03c7e9f2ab903/packages/indent-list/CHANGELOG.md).
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/list-classic@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   **Removed Default Shortcuts** for `BulletedListPlugin` and `NumberedListPlugin`.
        -        -   These shortcuts must now be configured manually using the `shortcuts` field.
        -        -   Example:
        -            ```tsx
        -            BulletedListPlugin.configure({
        -              shortcuts: { toggle: { keys: 'mod+alt+5' } },
        -            });
        -            NumberedListPlugin.configure({
        -              shortcuts: { toggle: { keys: 'mod+alt+6' } },
        -            });
        -            ```
        -    -   Package `@udecode/plate-list` has been moved to `@platejs/list-classic`.
        -        -   **Migration**:
        -            -   Rename all import paths from `@udecode/plate-list` to `@platejs/list-classic`.
        -            -   Update your `package.json`: remove `@udecode/plate-list` and add `@platejs/list-classic`.
        -    -   For changelogs of `@udecode/plate-list` version `<=48`, refer to its [archived changelog](https://github.com/udecode/plate/blob/7afd88089f4a76c896f3edf928b03c7e9f2ab903/packages/list/CHANGELOG.md).
        -
        -## @platejs/markdown@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -
        -    -   Function `indentListToMdastTree` has been renamed to `listToMdastTree` to align with the list plugin renames (`IndentListPlugin` -> `ListPlugin`).
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/math@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/media@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/mention@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -
        -    -   The type `TMentionInputElement` has been removed.
        -    -   Use `TComboboxInputElement` from `@udecode/plate` instead for input elements, as mention functionality is built upon the combobox.
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/node-id@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -
        -    -   Package `@udecode/plate-node-id` has been deprecated.
        -    -   `NodeIdPlugin` functionality is now part of `@platejs/core` and is **enabled by default**.
        -    -   Migration:
        -
        -        -   Remove `NodeIdPlugin` from your explicit plugin list if it was added manually.
        -
        -        -   Remove `@udecode/plate-node-id` from your dependencies.
        -
        -        -   If you had `NodeIdPlugin` configured with options, move these options to the `nodeId` field in your main editor configuration (`createPlateEditor` or `usePlateEditor` options).
        -            Example:
        -
        -            ```ts
        -            // Before
        -            // const editor = usePlateEditor({
        -            //   plugins: [
        -            //     NodeIdPlugin.configure({ /* ...your options... */ }),
        -            //   ],
        -            // });
        -
        -            // After
        -            const editor = usePlateEditor({
        -              nodeId: {
        -                /* ...your options... */
        -              },
        -              // ...other editor options
        -            });
        -            ```
        -
        -        -   If you want to disable automatic node ID generation (to replicate behavior if you weren't using `NodeIdPlugin` before), set `nodeId: false` in your editor configuration.
        -
        -## @platejs/normalizers@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Package `@udecode/plate-normalizers` has been deprecated.
        -    -   Its plugins have been moved to `platejs` (which is re-exported via `platejs`).
        -    -   Migration:
        -        -   Remove `@udecode/plate-normalizers` from your dependencies.
        -        -   Update import paths to use `platejs`
        -
        -## @platejs/playwright@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/reset-node@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) – Package `@udecode/plate-reset-node` has been deprecated. Its functionality (e.g., `ResetNodePlugin`) is now exclusively configured using the `rules.break` and `rules.delete` options on plugin definitions. Migration:
        -
        -    -   Remove `@udecode/plate-reset-node` from your dependencies.
        -    -   Remove any usage of `ResetNodePlugin` from your project.
        -    -   Configure reset behaviors directly on the relevant plugins by defining `rules.break` and/or `rules.delete`.
        -
        -    **Example: Resetting a Blockquote to a Paragraph**
        -
        -    ```typescript
        -    ResetNodePlugin.configure({
        -      options: {
        -        rules: [
        -          {
        -            types: [BlockquotePlugin.key],
        -            defaultType: ParagraphPlugin.key,
        -            hotkey: 'Enter',
        -            predicate: (editor) =>
        -              editor.api.isEmpty(editor.selection, { block: true }),
        -          },
        -        ],
        -      },
        -    });
        -
        -    // After
        -    BlockquotePlugin.configure({
        -      rules: {
        -        break: { empty: 'reset' },
        -        delete: { start: 'reset' },
        -      },
        -    });
        -    ```
        -
        -    **For custom reset logic (previously `onReset`):**
        -
        -    ```typescript
        -    // Before
        -    ResetNodePlugin.configure({
        -      options: {
        -        rules: [
        -          {
        -            types: [CodeBlockPlugin.key],
        -            defaultType: ParagraphPlugin.key,
        -            hotkey: 'Enter',
        -            predicate: isCodeBlockEmpty,
        -            onReset: unwrapCodeBlock,
        -          },
        -        ],
        -      },
        -    });
        -
        -    // After
        -    CodeBlockPlugin.configure({
        -      rules: {
        -        delete: { empty: 'reset' },
        -      },
        -    }).overrideEditor(({ editor, tf: { resetBlock } }) => ({
        -      transforms: {
        -        resetBlock(options) {
        -          if (
        -            editor.api.block({
        -              at: options?.at,
        -              match: { type: editor.getType(CodeBlockPlugin.key) },
        -            })
        -          ) {
        -            unwrapCodeBlock(editor);
        -            return;
        -          }
        -
        -          return resetBlock(options);
        -        },
        -      },
        -    }));
        -    ```
        -
        -## @platejs/resizable@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/select@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -
        -    -   The following plugins now default to `editOnly: true`. This means their core functionalities (handlers, rendering injections, etc.) will be disabled when the editor is in read-only mode. To override this behavior for a specific plugin, configure its `editOnly` field. For example, `SomePlugin.configure({ editOnly: false })`.
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Package `@udecode/plate-select` has been deprecated.
        -    -   `SelectOnBackspacePlugin` has been removed. This behavior is now built into Plate by default: delete (backward/forward) at the start of a block will select the previous/next void block instead of removing it.
        -    -   `DeletePlugin` has been removed. This behavior is now built into Plate by default: delete (backward/forward) from an empty block will remove it instead of merging.
        -    -   `RemoveEmptyNodesPlugin` has been removed. This behavior is now available through the `rules: { normalize: { removeEmpty: true } }` configuration on individual plugins.
        -    -   Migration:
        -        -   Remove `@udecode/plate-select` from your dependencies.
        -        -   Remove any usage of `SelectOnBackspacePlugin`, `DeletePlugin` from your project.
        -        -   Replace `RemoveEmptyNodesPlugin.configure({ options: { types: ['custom'] } })` with `CustomPlugin.configure({ rules: { normalize: { removeEmpty: true } } })`. This is used by our `LinkPlugin`.
        -
        -## @platejs/selection@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -
        -    -   The following plugins now default to `editOnly: true`. This means their core functionalities (handlers, rendering injections, etc.) will be disabled when the editor is in read-only mode. To override this behavior for a specific plugin, configure its `editOnly` field. For example, `SomePlugin.configure({ editOnly: false })`.
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/slash-command@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -
        -    -   The following plugins now default to `editOnly: true`. This means their core functionalities (handlers, rendering injections, etc.) will be disabled when the editor is in read-only mode. To override this behavior for a specific plugin, configure its `editOnly` field. For example, `SomePlugin.configure({ editOnly: false })`.
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   The type `TSlashInputElement` has been removed.
        -    -   Use `TComboboxInputElement` from `platejs` instead for Slash Command input elements, as slash command functionality is built upon the combobox.
        -
        -## @platejs/suggestion@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/tabbable@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/table@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/tag@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/test-utils@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/toc@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/toggle@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        -## @platejs/trailing-block@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -
        -    -   The following plugins now default to `editOnly: true`. This means their core functionalities (handlers, rendering injections, etc.) will be disabled when the editor is in read-only mode. To override this behavior for a specific plugin, configure its `editOnly` field. For example, `SomePlugin.configure({ editOnly: false })`.
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Package `@udecode/plate-trailing-block` has been deprecated.
        -    -   Its functionality (e.g., `TrailingBlockPlugin`) has been moved to `@platejs/utils` (which is re-exported via `platejs`).
        -    -   Migration:
        -        -   Remove `@udecode/plate-trailing-block` from your dependencies.
        -        -   Update import paths to use `platejs` (e.g., `import { TrailingBlockPlugin } from 'platejs';`).
        -
        -## @platejs/yjs@49.0.0
        -
        --   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        -    -   Renamed all `@udecode/plate-*` packages to `@platejs/*`. Replace `@udecode/plate-` with `@platejs/` in your code.
        -
        diff --git a/content/migration/v48.cn.mdx b/content/migration/v48.cn.mdx
        index 9ff83dadbe..143df103d7 100644
        --- a/content/migration/v48.cn.mdx
        +++ b/content/migration/v48.cn.mdx
        @@ -3,7 +3,7 @@ title: 主要版本更新
         ---
         
         
        -  本文档涵盖 Plate 直至 v48 版本的**重大破坏性变更**。最新迁移指南请参阅[最新迁移文档](/docs/migration)。补丁和次要版本变更请参考各包的 `CHANGELOG.md` 文件或访问 [GitHub Releases](https://github.com/udecode/plate/releases) 页面。
        +  本文档涵盖 Plate 直至 v48 版本的**重大破坏性变更**。后续主要版本请参阅[版本发布](/docs/releases)。补丁和次要版本变更请参考各包的 `CHANGELOG.md` 文件或访问 [GitHub Releases](https://github.com/udecode/plate/releases) 页面。
         
         
         # 48.0.0
        @@ -438,4 +438,4 @@ title: 主要版本更新
           - `ImagePreviewStore`
           - `FloatingMediaStore`
         
        -- [#4048](https://github.com/udecode/plate/pull
        \ No newline at end of file
        +- [#4048](https://github.com/udecode/plate/pull
        diff --git a/content/migration/v48.mdx b/content/migration/v48.mdx
        index 7d45347f11..db0066c8ba 100644
        --- a/content/migration/v48.mdx
        +++ b/content/migration/v48.mdx
        @@ -3,7 +3,7 @@ title: Major Releases
         ---
         
         
        -  This page covers **major breaking changes** for Plate up to v48. For the latest migration guide, see [Latest Migration](/docs/migration). For patch and minor changes, refer to the individual package `CHANGELOG.md` files or visit the [GitHub Releases](https://github.com/udecode/plate/releases) page.
        +  This page covers **major breaking changes** for Plate up to v48. For later major releases, see [Releases](/docs/releases). For patch and minor changes, refer to the individual package `CHANGELOG.md` files or visit the [GitHub Releases](https://github.com/udecode/plate/releases) page.
         
         
         # 48.0.0
        diff --git a/content/releases/index.mdx b/content/releases/index.mdx
        new file mode 100644
        index 0000000000..ad02b347ac
        --- /dev/null
        +++ b/content/releases/index.mdx
        @@ -0,0 +1,5 @@
        +---
        +title: Releases
        +---
        +
        +
        diff --git a/docs/plans/2026-03-30-table-multi-cell-selection.md b/docs/plans/2026-03-30-table-multi-cell-selection.md
        index 9753d97717..b3ef584c9c 100644
        --- a/docs/plans/2026-03-30-table-multi-cell-selection.md
        +++ b/docs/plans/2026-03-30-table-multi-cell-selection.md
        @@ -30,7 +30,7 @@ Fix the `/docs/table` multi-cell selection runtime error:
         - Reloaded task/debug/browser/test/planning skills for this bug.
         - Searched local learnings and code for `table`, `multi-cell`, and the exact Slate error.
         - Started a dedicated plan file for this bug.
        -- Reproduced the browser crash on `/docs/table` with `dev-browser` by dragging a multi-cell selection in the first table demo.
        +- Reproduced the browser crash on `/docs/table` with `browser-use` by dragging a multi-cell selection in the first table demo.
         - Added `createDemoValueSnapshot` and switched both mounted table demos to pass cloned initial values into `usePlateEditor`.
         - Cleaned non-versioned local env after the `.bun` mirror reintroduced the `is-hotkey` parse failure during verification, then reinstalled with `pnpm install`.
         - Verified the regression test passes, `apps/www` typechecks cleanly, `pnpm lint:fix` passes, and the fresh `/docs/table` browser repro no longer throws page errors.
        diff --git a/docs/plans/2026-04-04-footnote-inline-void-fix.md b/docs/plans/2026-04-04-footnote-inline-void-fix.md
        index 7c9dc65023..a5339f6d08 100644
        --- a/docs/plans/2026-04-04-footnote-inline-void-fix.md
        +++ b/docs/plans/2026-04-04-footnote-inline-void-fix.md
        @@ -10,7 +10,7 @@ Make footnote references behave like real inline atoms instead of editable text.
         - [x] Add red tests for inline-void ref behavior and backlink navigation
         - [x] Implement the footnote reference inline-void model and renderer/nav fixes
         - [x] Verify with targeted tests, package checks, registry build, lint, and
        -  `dev-browser`
        +  `browser-use`
         
         ## Notes
         
        diff --git a/docs/plans/2026-04-06-footnote-stale-duplicate-warning-after-delete.md b/docs/plans/2026-04-06-footnote-stale-duplicate-warning-after-delete.md
        index 3f90461ea5..ec90f1b43e 100644
        --- a/docs/plans/2026-04-06-footnote-stale-duplicate-warning-after-delete.md
        +++ b/docs/plans/2026-04-06-footnote-stale-duplicate-warning-after-delete.md
        @@ -54,5 +54,5 @@ resolved by deletion, not just by the explicit renumber action.
           `useNodePath`
         - 2026-04-06: made duplicate-warning chrome `contentEditable={false}` so the
           warning text and repair button are not editable text surface
        -- 2026-04-06: verified with targeted `bun test`, `dev-browser`, and
        +- 2026-04-06: verified with targeted `bun test`, `browser-use`, and
           `pnpm lint:fix`; skipped `www` build/typecheck by direct user request
        diff --git a/docs/plans/2026-04-06-toc-interaction-polish.md b/docs/plans/2026-04-06-toc-interaction-polish.md
        index 13c0d53c3d..238a1f4626 100644
        --- a/docs/plans/2026-04-06-toc-interaction-polish.md
        +++ b/docs/plans/2026-04-06-toc-interaction-polish.md
        @@ -38,7 +38,7 @@ not just a rendered heading list.
         - [x] add failing tests for that behavior first
         - [x] implement the minimal durable fix in package and app code
         - [x] verify with targeted tests, package build/typecheck where needed, lint,
        -      and dev-browser
        +      and browser-use
         
         ## Progress Log
         
        @@ -53,7 +53,7 @@ not just a rendered heading list.
         - 2026-04-06: updated the registry TOC node so only the active heading row gets
           `aria-current="location"` and active styling
         - 2026-04-06: verified red/green with targeted `toc` + app specs, package
        -  build/typecheck, `www build:registry`, `lint:fix`, and `dev-browser`
        +  build/typecheck, `www build:registry`, `lint:fix`, and `browser-use`
         - 2026-04-06: browser verification only worked correctly on `localhost:3001`;
           `127.0.0.1:3001` left the docs preview stuck on `Loading...` because Next dev
           blocked cross-origin HMR resources by default
        diff --git a/docs/plans/2026-04-09-date-media-expansion-consensus-plan.md b/docs/plans/2026-04-09-date-media-expansion-consensus-plan.md
        index 9e92a97f73..6b83a30a11 100644
        --- a/docs/plans/2026-04-09-date-media-expansion-consensus-plan.md
        +++ b/docs/plans/2026-04-09-date-media-expansion-consensus-plan.md
        @@ -499,7 +499,7 @@ pnpm lint:fix
         Browser verification:
         
         ```bash
        -dev-browser --connect http://127.0.0.1:9222
        +browser-use --connect http://127.0.0.1:9222
         ```
         
         Verify the date demo path still shows:
        diff --git a/docs/plans/2026-04-10-dnd-missing-context-runtime-guard.md b/docs/plans/2026-04-10-dnd-missing-context-runtime-guard.md
        index 5bcd4d08f3..ef04d6a89f 100644
        --- a/docs/plans/2026-04-10-dnd-missing-context-runtime-guard.md
        +++ b/docs/plans/2026-04-10-dnd-missing-context-runtime-guard.md
        @@ -29,4 +29,4 @@ Prevent `@platejs/dnd` from throwing `Expected drag drop context` when a Plate s
           - `pnpm turbo build --filter=./packages/dnd --filter=./apps/www`
           - `pnpm turbo typecheck --filter=./packages/dnd --filter=./apps/www`
           - `pnpm lint:fix`
        -  - `dev-browser --connect http://127.0.0.1:9222` verification on `/` and `/cn` with no console errors, no page errors, and no `Expected drag drop context` crash text
        +  - `browser-use --connect http://127.0.0.1:9222` verification on `/` and `/cn` with no console errors, no page errors, and no `Expected drag drop context` crash text
        diff --git a/docs/plans/2026-04-10-math-delimiter-trigger-implementation-plan.md b/docs/plans/2026-04-10-math-delimiter-trigger-implementation-plan.md
        index f46aeb6720..06766bb0ec 100644
        --- a/docs/plans/2026-04-10-math-delimiter-trigger-implementation-plan.md
        +++ b/docs/plans/2026-04-10-math-delimiter-trigger-implementation-plan.md
        @@ -314,7 +314,7 @@ Scenarios:
         
         ### Browser verification
         
        -Verify on the real docs/demo surface with `dev-browser`:
        +Verify on the real docs/demo surface with `browser-use`:
         
         - open the equation demo/docs page
         - confirm selection-wrap works
        diff --git a/docs/plans/2026-04-17-code-block-demo-debug.md b/docs/plans/2026-04-17-code-block-demo-debug.md
        index 4d9fccdea0..92c7559aaf 100644
        --- a/docs/plans/2026-04-17-code-block-demo-debug.md
        +++ b/docs/plans/2026-04-17-code-block-demo-debug.md
        @@ -29,13 +29,13 @@ Find the real cause of the `/blocks/code-block-demo` regression where typing tri
         ## Progress
         
         - Recovered prior context from the last session and active edits.
        -- Loaded `learnings-researcher`, `debug`, `testing`, `tdd`, `dev-browser`, and `planning-with-files`.
        +- Loaded `learnings-researcher`, `debug`, `testing`, `tdd`, `browser-use`, and `planning-with-files`.
         - Searched `docs/solutions/` for hydration, registry, and code-block failures before touching more code.
         - Rebuilt registry output, wiped `apps/www/.next`, and restarted `apps/www` to get one clean repro surface.
         - Reverted speculative demo-only changes after proving they were not the fix.
         - Moved the Python grammar workaround into `packages/code-block` and reverted
           the app-level lowlight helper so kits remain stock.
        -- Verified the route in `dev-browser`: Python highlight succeeds, no hydration
        +- Verified the route in `browser-use`: Python highlight succeeds, no hydration
           error, and triple backticks still promote to a code block.
         - Added a reusable learning in `docs/solutions/logic-errors/2026-04-17-code-block-browser-highlight-must-match-server-output.md`.
         
        diff --git a/docs/plans/2026-04-17-codeblock-regression.md b/docs/plans/2026-04-17-codeblock-regression.md
        index 573cd30378..b9d054c1a8 100644
        --- a/docs/plans/2026-04-17-codeblock-regression.md
        +++ b/docs/plans/2026-04-17-codeblock-regression.md
        @@ -8,7 +8,7 @@ the root cause, and fix it with fresh verification.
         ## Context
         
         - User reports a `codeblock` regression and asks whether it was verified in
        -  `dev-browser`.
        +  `browser-use`.
         - Current browser surface is `http://localhost:3001/`.
         - I did not specifically verify code block behavior in browser before the
           report.
        @@ -24,7 +24,7 @@ the root cause, and fix it with fresh verification.
         ## Working Plan
         
         - [x] Load relevant skills and scan repo learnings
        -- [x] Reproduce the regression in `dev-browser`
        +- [x] Reproduce the regression in `browser-use`
         - [x] Identify the exact failing flow and likely owner layer
         - [x] Fix the root cause
         - [x] Run fresh verification for the touched surface
        diff --git a/docs/plans/2026-04-17-selection-coverage-and-list-regression.md b/docs/plans/2026-04-17-selection-coverage-and-list-regression.md
        index 054fb6eeec..b253730fba 100644
        --- a/docs/plans/2026-04-17-selection-coverage-and-list-regression.md
        +++ b/docs/plans/2026-04-17-selection-coverage-and-list-regression.md
        @@ -28,7 +28,7 @@ Expand rule regression coverage around selection placement after autoformat-styl
         
         ## Progress
         
        -- Loaded `testing`, `tdd`, `learnings-researcher`, `debug`, `planning-with-files`, and `dev-browser`.
        +- Loaded `testing`, `tdd`, `learnings-researcher`, `debug`, `planning-with-files`, and `browser-use`.
         - Started a batch pass instead of treating link selection and list autoformat as isolated one-offs.
         - Added selection assertions across the current rule coverage that can be
           modeled honestly: list, markdown link, blockquote, code block, and the broad
        diff --git a/docs/plans/2026-04-27-major-release-migration-sync.md b/docs/plans/2026-04-27-major-release-migration-sync.md
        new file mode 100644
        index 0000000000..8bb2354fc5
        --- /dev/null
        +++ b/docs/plans/2026-04-27-major-release-migration-sync.md
        @@ -0,0 +1,226 @@
        +# Release Workflow and Docs Sync
        +
        +## Goal
        +
        +Hard cut Plate's generated release-doc body sync and move to a Better Auth-style release architecture: Changesets publishes packages, the workflow creates one global GitHub Release with deterministic package-changelog notes plus Claude polish, and `/docs/releases` renders GitHub Releases instead of storing giant generated changelog blobs in MDX.
        +
        +## Scope
        +
        +- Release automation only.
        +- Source of truth: Changesets version output and package `CHANGELOG.md` sections for the published package versions.
        +- Target: GitHub Releases as the release-note CMS, with `/docs/releases` fetching and rendering recent repo-level releases.
        +- Keep: Plate's `prepare-release-changesets` step, auto-release checkbox flow, and post-publish registry/template sync jobs.
        +- Cut: generated release body sync into `content/releases/index.mdx`, repo compare links, the temporary `global-release` helper, Better Auth beta/LTS/snapshot branch support, and Better Auth's product-domain `pr-analyzer`.
        +- Verification: focused script tests, workflow assertions, docs typecheck, lint, and Browser Use verification for `/docs/releases`.
        +
        +## Plan
        +
        +1. Research current release workflow and Changesets hooks. - complete
        +2. Implement a deterministic migration sync script. - complete
        +3. Wire release workflow to run the script during version PR generation. - complete
        +4. Add focused tests for release extraction and no-op cases. - complete
        +5. Run targeted verification. - complete
        +6. Check Base UI release docs pattern and expose a manual sync command. - complete
        +7. Copy Base UI release timeline design into the releases overview. - superseded
        +8. Move the release overview to `/docs/releases` and sidebar Get Started. - complete
        +9. Replace the timeline with a compact release index and split release details into `v*.mdx` pages. - superseded by 12
        +10. Expand release docs from major-only to major/minor/patch package entries. - complete
        +11. Auto-prune release docs to the last year and link older releases to GitHub. - complete
        +12. Replace generated detail pages with a Better Auth-style expanded feed. - complete
        +13. Create a repo-level GitHub Release from the Version Packages PR body. - complete
        +14. Hard cut the generated release-doc body sync and temporary global-release helper. - complete
        +15. Copy Better Auth's release workflow/script architecture as the starting point. - complete
        +16. Prune Better Auth branch modes: `next`, `release/**`, maintenance dist-tags, main-to-next sync, and snapshot releases. - complete
        +17. Keep the Claude AI release-note rewrite path and adapt its prompt to Plate. - complete
        +18. Replace Better Auth's `pr-analyzer` with a Plate package-changelog parser. - complete
        +19. Generate deterministic raw notes from `PUBLISHED_PACKAGES` and each package's matching `CHANGELOG.md` section. - complete
        +20. Validate AI output preserves package headings, change-type headings, PR links, entry count, and migration notes. - complete
        +21. Create or update a global `vX.Y.Z` GitHub Release from the validated AI notes, falling back to raw notes. - complete
        +22. Convert `/docs/releases` to fetch GitHub Releases with revalidation and a GitHub-link fallback. - superseded by 25
        +23. Keep Plate's registry/template post-release job and auto-release checkbox behavior. - complete
        +24. Add focused tests for release-note parsing, workflow pruning, AI-output validation, and docs fetch mapping. - complete
        +25. Stop runtime fetching releases and generate `/docs/releases` from the Version Packages PR body. - complete
        +26. Change `Full changelog` links to GitHub compare links using the preferred package tag per version. - complete
        +27. Verify generated docs, focused tests, typecheck, lint, and Browser Use. - complete
        +
        +## Findings
        +
        +- `.github/workflows/release.yml` uses `changesets/action@v1` to create `[Release] Version packages`.
        +- The old release overview lived at `content/migration/index.mdx` and contained the raw Changesets release-PR intro, which suggests release PR body content was copied into the page instead of curated by automation.
        +- `tooling/scripts/prepare-release-changesets.mjs` runs before `changesets/action`; it prepares extra changesets but cannot see the generated changelogs/versioned package files.
        +- The right insertion point is a custom Changesets `version` command: run `pnpm changeset version`, then sync release changelog sections into `content/releases/index.mdx`.
        +- `changesets/action@v1` supports a `version` input for the command that updates package versions and changelogs.
        +- `tooling/scripts/sync-release-docs.mjs` parses package changelogs after versioning, extracts `### Major Changes`, `### Minor Changes`, and `### Patch Changes`, and writes typed package entries.
        +- Base UI keeps release docs at `docs/src/app/(docs)/react/overview/releases`: an overview timeline page, one `page.mdx` per release, and `docs/src/data/releases.ts` metadata.
        +- Base UI does not have a full release-doc sync script. Its `scripts/README.md` says to run `pnpm release:changelog:docs`, create the release page manually, and update `releases.ts`.
        +- Base UI's changelog generation comes from `@mui/internal-code-infra` through `pnpm release:changelog` and `pnpm release:changelog:docs`, with `scripts/changelog.config.mjs` formatting docs output.
        +- For Plate's current Changesets setup, the matching first step is a manual `pnpm release:releases` wrapper around the sync script, then redesign the docs shape later.
        +- The first Base UI-inspired timeline pass rendered, but the shape was wrong for Plate's monorepo release payload.
        +- Plate needs generated release data because this repo is a monorepo. A curated single-product timeline does not fit; the useful shape is a typed expanded feed with fade/expand for long releases.
        +- Contentlayer rejects HTML comment markers in MDX. Generated markers must use `{/* ... */}` comments.
        +- The alternating Base UI timeline does not fit Plate's release payload. It works for short curated release summaries, not for broad Changesets output across dozens of packages.
        +- The all-release sync must not cross-multiply package versions. Each package parses its own current version, plus explicit historical major pages the docs already own.
        +- Release docs use package tag dates for retention. Version-PR output has no tag yet, so new generated entries use the current sync date.
        +- Better Auth's reusable changelog pieces are the exact-release GitHub link, dashed feed rhythm, markdown rendering, and fade/expand body. The sticky marketing panel does not belong inside Plate's docs shell.
        +- `content/releases/index.mdx` is now the canonical retained-release store. The sync parses generated `` data back on the next run, then deletes any remaining generated `v*.mdx` pages.
        +- Repo-level tag comparisons such as `v53.0.1...v53.0.2` are empty for Changesets package-only releases, so they are the wrong docs target.
        +- The release workflow can create or update a repo-level GitHub Release named `vX.Y.Z` after publish, using the merged Version Packages PR body as the one-click full changelog.
        +- Better Auth does not write release notes into docs files. Its workflow writes GitHub Releases, and `docs/app/changelog/page.tsx` fetches `https://api.github.com/repos/better-auth/better-auth/releases` with `next: { revalidate: 3600 }`.
        +- Better Auth's Claude action uses `claude_code_oauth_token`. Keep this path in the plan, but keep deterministic raw notes as the fallback and validation source of truth.
        +- Better Auth's snapshot job publishes temporary package versions with `changeset version --snapshot` and `changeset publish --no-git-tag --tag "$SNAPSHOT_TAG"`. Plate should prune this for now.
        +- Better Auth's `pr-analyzer.ts` maps conventional commit scopes, PR labels, and changed files to Better Auth product domains and packages. Plate should not copy it because Plate's reliable grouping already exists in Changesets package changelogs.
        +- Plate uses a linked Changesets group, not Better Auth's fixed group. Released linked packages align versions, but unchanged packages do not necessarily publish on every release.
        +
        +## Hard-Cut Implementation Plan
        +
        +### 1. Workflow Baseline
        +
        +Start from Better Auth's `.github/workflows/release.yml`, then adapt it for Plate:
        +
        +- Keep `push` on `main`.
        +- Keep `workflow_dispatch` only for release-note preview once the deterministic script exists.
        +- Keep `concurrency`, pinned actions, job-scoped permissions, GitHub App token support, and `persist-credentials: false`.
        +- Set `changesets/action` `createGithubReleases: false`.
        +- Keep Plate's `node tooling/scripts/prepare-release-changesets.mjs` before `changesets/action`.
        +- Keep Plate's auto-release checkbox detection and Version Packages PR merge path, using an app token or `API_TOKEN_GITHUB`, not default `GITHUB_TOKEN`.
        +- Keep Plate's `sync-release-artifacts` job after publish.
        +
        +Prune from Better Auth:
        +
        +- `next` branch trigger and guard.
        +- `release/**` branch trigger and maintenance dist-tags.
        +- `Sync main to next via PR`.
        +- Snapshot input and whole snapshot job.
        +- Better Auth blog-post injection.
        +- Better Auth package/domain classifier.
        +
        +### 2. Release Commands
        +
        +Add or adapt root scripts:
        +
        +- `ci:version`: `pnpm changeset version && pnpm install --no-frozen-lockfile`
        +- `ci:release`: current Plate publish path, still building before `changeset publish`
        +
        +Remove `pnpm release:releases` from the Changesets `version` command once `/docs/releases` no longer stores generated release bodies.
        +
        +### 3. Deterministic Notes
        +
        +Create a Plate-specific release-note script, likely under `.github/scripts/release-notes.ts` or `tooling/scripts/release-notes.mjs`:
        +
        +- Input: `PUBLISHED_PACKAGES` from `changesets/action`.
        +- Determine global version from the highest published package version.
        +- Build a workspace package map from package manifests.
        +- For each published package, find its package directory and matching `CHANGELOG.md`.
        +- Extract the section for the published version.
        +- Preserve `### Major Changes`, `### Minor Changes`, and `### Patch Changes`.
        +- Emit markdown grouped by package.
        +- Add per-package `CHANGELOG.md` links pinned to `GITHUB_SHA`.
        +- Do not emit repo compare links.
        +- Optionally collect contributors from PR links in the extracted changelog body.
        +
        +Do not copy Better Auth's `pr-analyzer.ts`. If a helper is needed, make it a small package-map/changelog parser, not a product-domain classifier.
        +
        +### 4. Claude Polish
        +
        +Keep Better Auth's Claude rewrite shape:
        +
        +- Generate raw release notes.
        +- Build a prompt from `.github/prompts/release-notes-rewrite.md`.
        +- Run Claude with `claude_code_oauth_token`.
        +- Validate output.
        +- Use AI notes only if validation passes; otherwise use raw notes.
        +
        +Adapt the prompt for Plate:
        +
        +- Plate is a rich-text editor framework.
        +- Preserve package headings.
        +- Preserve Major/Minor/Patch headings.
        +- Preserve PR links and package `CHANGELOG` links.
        +- Preserve migration notes, especially breaking changes.
        +- Do not add or remove entries.
        +- Do not add repo compare links.
        +
        +Validation must check:
        +
        +- Same package headings as raw notes.
        +- Same change-type headings where entries exist.
        +- PR-link count is not lower than raw notes.
        +- Entry count is not lower than raw notes.
        +- Migration-note blocks under breaking changes are still present.
        +
        +### 5. Global GitHub Release
        +
        +After publish:
        +
        +- Determine `VERSION` from release-note output or `PUBLISHED_PACKAGES`.
        +- Create `refs/tags/v${VERSION}` at `GITHUB_SHA` if missing.
        +- Create or update GitHub Release `v${VERSION}`.
        +- Use AI notes if valid, raw notes otherwise.
        +- Treat release creation failure as a real failure, because docs now depend on GitHub Releases.
        +
        +### 6. Docs Page
        +
        +Convert `/docs/releases` to GitHub Releases as the data source:
        +
        +- Fetch `https://api.github.com/repos/udecode/plate/releases`.
        +- Use `next: { revalidate: 3600 }`.
        +- Use `GITHUB_TOKEN` when present.
        +- Filter prereleases for now.
        +- Render latest 20-ish releases.
        +- Keep Better Auth-style fade/expand markdown rendering.
        +- Add a fallback state linking to GitHub Releases if the fetch fails.
        +- Stop embedding all release bodies in `content/releases/index.mdx`.
        +
        +### 7. Tests and Verification
        +
        +Focused tests:
        +
        +- Release-note script resolves the global version from `PUBLISHED_PACKAGES`.
        +- Package changelog extraction finds the exact published version section.
        +- Major/minor/patch sections are preserved.
        +- No repo compare URL is emitted.
        +- AI validation rejects removed package headings, removed PR links, removed entries, or dropped migration notes.
        +- Workflow is main-only and contains `createGithubReleases: false`.
        +- Workflow does not contain `next`, `release/**`, snapshot job, or Better Auth `pr-analyzer`.
        +- Docs fetch mapper filters prereleases and handles fetch failure.
        +
        +Verification commands:
        +
        +- `node --test` for focused release workflow/docs tests.
        +- `pnpm lint:fix`.
        +- `pnpm --filter www typecheck`.
        +- Browser Use check for `/docs/releases`.
        +
        +## Progress
        +
        +- 2026-04-27: Started from user request, inspected release workflow, migration doc, changeset config, and existing release helper tests.
        +- 2026-04-27: Added the release-doc sync script, focused node tests, and wired release workflow `version` command.
        +- 2026-04-27: Ran focused node tests successfully. Ran sync script; it backfilled five `53.0.0` package sections into the generated major releases page.
        +- 2026-04-27: Fixed idempotence, added a workflow wiring assertion, reran tests and lint successfully. Final script run reported the page was already up to date.
        +- 2026-04-27: Inspected Base UI release docs and release scripts. Added a manual release-doc sync command and changed the workflow to call it after `pnpm changeset version`.
        +- 2026-04-27: Verified the manual sync command, focused node tests, and `pnpm lint:fix`.
        +- 2026-04-27: Added `ReleaseTimeline`, registered it for MDX, and updated the sync script to generate the timeline block above the release sections.
        +- 2026-04-27: Verified focused node tests, release sync, `pnpm lint:fix`, `pnpm --filter www typecheck`, and `browser-use` desktop/mobile screenshots for the release page.
        +- 2026-04-27: Captured the MDX marker learning in `docs/solutions/developer-experience/2026-04-27-mdx-generated-markers-must-use-jsx-comments.md`.
        +- 2026-04-27: Moved the overview file to `content/releases/index.mdx`, exposed `/docs/releases`, and moved the sidebar item to Get Started after Installation.
        +- 2026-04-27: Verified `/docs/releases` rendered the release timeline, `Releases` appeared immediately after `Installation` in the sidebar, and `/docs/migration` no longer rendered the old release overview.
        +- 2026-04-27: Replaced `ReleaseTimeline` with `ReleaseIndex`, refactored `pnpm release:releases` to generate compact index metadata plus `content/releases/v53.mdx` and `content/releases/v49.mdx`, and kept the script idempotent.
        +- 2026-04-27: Verified focused script tests, release sync idempotence, `pnpm lint:fix`, `pnpm --filter www typecheck`, and `browser-use` desktop/mobile checks for `/docs/releases` and `/docs/releases/v53`.
        +- 2026-04-27: Captured the monorepo release-doc shape in `docs/solutions/developer-experience/2026-04-27-monorepo-release-docs-need-index-and-detail-pages.md`.
        +- 2026-04-27: Expanded the generator to all change types, added typed package badges, added full-version slugs for minor/patch pages, and fixed a cross-version parsing bug caught by tests.
        +- 2026-04-28: Removed visible change-type text from package chips and fixed callout icons by using stroked lucide icons instead of filled SVGs.
        +- 2026-04-30: Added one-year release-doc retention, package-tag date lookup, old-page pruning, and an older releases GitHub link.
        +- 2026-04-30: Replaced generated `v*.mdx` pages with an expanded Better Auth-style feed, added exact GitHub release links, preserved retained inline bodies through generator parsing, and deleted the remaining generated detail pages.
        +- 2026-04-30: Removed generated repo compare links because Changesets publishes package tags. Added a post-publish global GitHub Release step that copies the merged Version Packages PR body into `vX.Y.Z`.
        +- 2026-04-30: Updated the next plan to hard cut generated release-doc bodies, keep Claude AI rewrite, skip Better Auth's product-specific `pr-analyzer`, and use package changelog sections as the deterministic release-note source.
        +- 2026-04-30: Hard cut `sync-release-docs` and `global-release`, added `tooling/scripts/release-notes.mjs`, added the Claude rewrite prompt, and rewired `release.yml` to create one repo-level `vX.Y.Z` GitHub Release after publish.
        +- 2026-04-30: Converted `/docs/releases` to load recent GitHub Releases through a cached same-origin API route. Current package-only GitHub Releases are grouped by version until the next repo-level releases exist.
        +- 2026-04-30: Verified focused release tests, `pnpm lint:fix`, `pnpm --filter www typecheck`, and Browser Use on `http://localhost:3001/docs/releases`.
        +- 2026-04-30: Superseded runtime GitHub Release fetching with generated `apps/www/src/generated/release-index.json` from Version Packages PR bodies. `Full changelog` now links to package-tag compare URLs, using `platejs@version` when present and the first package tag otherwise.
        +- 2026-04-30: Verified the generated release page in Browser Use after restarting the dev server to clear a stale deleted route/chunk. `v53.0.3` links to compare `%40platejs/list@53.0.2...platejs@53.0.3`, and `CHANGELOG` links to Version Packages PR #4969.
        +
        +## Errors
        +
        +- `pnpm lint:fix` failed on first run because Biome requires regex literals in the release-doc sync script to be module-level constants. Moved regexes to top-level constants.
        +- `pnpm --filter www typecheck` failed once because Contentlayer does not accept HTML comments in MDX. Switched generated markers to JSX comments and kept legacy marker replacement.
        +- `pnpm --filter www build` was started accidentally and ran the CI-owned registry build before the Next build completed. Restored the generated `apps/www/public/r/*` outputs and used the targeted typecheck path instead.
        diff --git a/docs/plans/4527-ai-menu-streaming-anchor-undefined.md b/docs/plans/4527-ai-menu-streaming-anchor-undefined.md
        index 6985967305..c9445f6fef 100644
        --- a/docs/plans/4527-ai-menu-streaming-anchor-undefined.md
        +++ b/docs/plans/4527-ai-menu-streaming-anchor-undefined.md
        @@ -45,4 +45,4 @@
         ## Errors
         
         - `planning-with-files` session catchup script path from the generated skill is missing locally, so catchup could not run in this repo.
        -- `dev-browser` screenshot writes only accept repo-relative paths, not absolute paths.
        +- `browser-use` screenshot writes only accept repo-relative paths, not absolute paths.
        diff --git a/docs/plans/4637-verify-space-overflow-repro.md b/docs/plans/4637-verify-space-overflow-repro.md
        index ee7c7168f8..ecab102b60 100644
        --- a/docs/plans/4637-verify-space-overflow-repro.md
        +++ b/docs/plans/4637-verify-space-overflow-repro.md
        @@ -47,7 +47,7 @@ Determine whether GitHub issue `#4637` is reproducible on the current Plate site
           - no horizontal overflow was observed
           - `editable.scrollWidth === editable.clientWidth`
           - caret remained inside the editor width
        -- Browser screenshot saved at `/Users/zbeyens/.dev-browser/tmp/issue-4637-current-site.png`.
        +- Browser screenshot saved at `/Users/zbeyens/.browser-use/tmp/issue-4637-current-site.png`.
         - This does not prove the original 2025 report was wrong; it does show the issue is not reproducible on the current site surface.
         
         ## Progress Log
        @@ -55,7 +55,7 @@ Determine whether GitHub issue `#4637` is reproducible on the current Plate site
         - Loaded `task`, `learnings-researcher`, `planning-with-files`, and `reproduce-bug`.
         - Deleted the prior `#4535` investigation plan artifact at the user's request.
         - Fetched the full GitHub issue with comments.
        -- Loaded `dev-browser` once the issue was confirmed to be browser-facing.
        +- Loaded `browser-use` once the issue was confirmed to be browser-facing.
         - Inspected `apps/www/src/registry/ui/editor.tsx` and confirmed current wrapping classes are present.
         - Ran a live browser repro on `platejs.org` with 80 inserted spaces in the middle of a word and captured screenshot + DOM measurements.
         
        diff --git a/docs/plans/4798-fix-table-left-border-multirow-selection.md b/docs/plans/4798-fix-table-left-border-multirow-selection.md
        index 9ed76f05c9..4207665d57 100644
        --- a/docs/plans/4798-fix-table-left-border-multirow-selection.md
        +++ b/docs/plans/4798-fix-table-left-border-multirow-selection.md
        @@ -65,5 +65,5 @@ Fix GitHub issue `#4798` so toggling the left border for a multi-row table selec
         - Browser:
           - Connected to the persistent debug Chrome and verified the real local docs page at `http://localhost:3000/docs/table`.
           - Reached the live editor instance through the React tree, set a real multi-cell selection on the table demo, and confirmed the DOM reflected the expected two selected cells.
        -  - Saved fresh browser proof at `~/.dev-browser/tmp/issue-4798-longterm-selection.png`.
        +  - Saved fresh browser proof at `~/.browser-use/tmp/issue-4798-longterm-selection.png`.
           - The exact border-toggle dropdown path was still not reliably automatable in-browser, so final behavior proof remains strongest in the new selection-override regression plus the border integration regression.
        diff --git a/docs/solutions/developer-experience/2026-03-28-static-demo-values-need-deterministic-ids-and-timestamps-for-hydration.md b/docs/solutions/developer-experience/2026-03-28-static-demo-values-need-deterministic-ids-and-timestamps-for-hydration.md
        index d197bd97e7..d4eabca284 100644
        --- a/docs/solutions/developer-experience/2026-03-28-static-demo-values-need-deterministic-ids-and-timestamps-for-hydration.md
        +++ b/docs/solutions/developer-experience/2026-03-28-static-demo-values-need-deterministic-ids-and-timestamps-for-hydration.md
        @@ -72,4 +72,4 @@ It is used by:
         - `pnpm -C apps/www build`
         - `pnpm -C apps/www typecheck`
         - `pnpm lint:fix`
        -- `dev-browser` reload of `http://localhost:3000/` with no hydration console error
        +- `browser-use` reload of `http://localhost:3000/` with no hydration console error
        diff --git a/docs/solutions/developer-experience/2026-04-27-mdx-generated-markers-must-use-jsx-comments.md b/docs/solutions/developer-experience/2026-04-27-mdx-generated-markers-must-use-jsx-comments.md
        new file mode 100644
        index 0000000000..b862f8805e
        --- /dev/null
        +++ b/docs/solutions/developer-experience/2026-04-27-mdx-generated-markers-must-use-jsx-comments.md
        @@ -0,0 +1,70 @@
        +---
        +title: Generated MDX markers must use JSX comments
        +date: 2026-04-27
        +category: docs/solutions/developer-experience
        +module: Docs release automation
        +problem_type: developer_experience
        +component: documentation
        +symptoms:
        +  - Contentlayer failed while processing a generated MDX document.
        +  - The error pointed at an HTML comment marker in a generated MDX release page.
        +root_cause: wrong_api
        +resolution_type: tooling_addition
        +severity: low
        +tags: [contentlayer, mdx, release-docs, tooling]
        +---
        +
        +# Generated MDX markers must use JSX comments
        +
        +## Problem
        +
        +The release sync script needed stable markers so it could replace the generated release timeline in the releases MDX page. HTML comments looked harmless, but this Contentlayer + MDX pipeline rejects them.
        +
        +## Symptoms
        +
        +- `pnpm --filter www typecheck` failed during `contentlayer2 build`.
        +- The MDX error said: `Unexpected character ! (U+0021)` and pointed at ``.
        +
        +## What Didn't Work
        +
        +- Plain HTML comments:
        +
        +  ```mdx
        +  
        +  
        +  
        +  ```
        +
        +  This does not compile in this MDX setup.
        +
        +## Solution
        +
        +Generate JSX comments instead:
        +
        +```mdx
        +{/* release-timeline:start */}
        +
        +{/* release-timeline:end */}
        +```
        +
        +When changing an existing generator, keep a legacy replacement path for any already-written HTML markers:
        +
        +```js
        +const releaseTimelineStartMarker = '{/* release-timeline:start */}';
        +const releaseTimelineEndMarker = '{/* release-timeline:end */}';
        +const legacyReleaseTimelineStartMarker = '';
        +const legacyReleaseTimelineEndMarker = '';
        +```
        +
        +## Why This Works
        +
        +MDX treats `{/* ... */}` as a JSX comment expression, so the marker is valid inside MDX and invisible in the rendered page. Keeping legacy marker detection makes the sync script self-heal old generated content on the next run.
        +
        +## Prevention
        +
        +- For generated MDX markers, use JSX comments, not HTML comments.
        +- Run `pnpm --filter www typecheck` after changing generated MDX structure. Contentlayer catches parser errors before the docs app reaches runtime.
        +
        +## Related Issues
        +
        +- Related local files: `tooling/scripts/sync-release-docs.mjs`, `content/releases/index.mdx`.
        diff --git a/docs/solutions/developer-experience/2026-04-27-monorepo-release-docs-need-index-and-detail-pages.md b/docs/solutions/developer-experience/2026-04-27-monorepo-release-docs-need-index-and-detail-pages.md
        new file mode 100644
        index 0000000000..f87c0bc245
        --- /dev/null
        +++ b/docs/solutions/developer-experience/2026-04-27-monorepo-release-docs-need-index-and-detail-pages.md
        @@ -0,0 +1,98 @@
        +---
        +title: Monorepo release docs need a typed expanded feed
        +date: 2026-04-27
        +category: docs/solutions/developer-experience
        +module: Docs release automation
        +problem_type: developer_experience
        +component: tooling
        +symptoms:
        +  - The release overview became a long package changelog instead of a navigable index.
        +  - The Base UI alternating timeline pattern was too narrow for Plate's monorepo release payload.
        +  - Major-only extraction hid minor and patch package changes from the generated release docs.
        +  - Keeping every generated release page forever would make docs carry stale patch/minor history that GitHub already archives.
        +  - Repo-level tag comparisons were empty because Changesets publishes package tags, not a single monorepo release diff.
        +  - Runtime release fetching made `/docs/releases` feel slow and could leave stale route/client chunks in local dev.
        +root_cause: scope_issue
        +resolution_type: tooling_addition
        +severity: medium
        +tags: [release-docs, changesets, mdx, monorepo, tooling, github-releases]
        +last_updated: 2026-05-01
        +---
        +
        +# Monorepo release docs need a typed expanded feed
        +
        +## Problem
        +
        +Plate's Changesets output is package-granular and typed by package. Copying a single-product release timeline made the overview look nice, but it forced too much package detail into one page and hid minor and patch changes when the docs only extracted `### Major Changes`.
        +
        +The durable version is to use the Version Packages PR body as the docs source and keep `/docs/releases` as a generated recent feed. The workflow copies the PR description into checked-in release data, so the docs page renders immediately without fetching GitHub at runtime.
        +
        +## Symptoms
        +
        +- `/docs/releases` needed one-click access to the full release notes without per-package links.
        +- Timeline cards worked for short curated summaries, but Plate needed room for package-level migration notes.
        +- Package badges could not show whether a package entry was major, minor, or patch.
        +- Wrapped Changesets bullets put the PR link in the middle of generated prose when continuation lines were formatted separately.
        +
        +## What Didn't Work
        +
        +- A Base UI-style left/right timeline looked good for two cards, but it compressed real Plate releases into tiny alternating blocks.
        +- Deriving card summaries from the first bullet of each package changelog was fake structure. Changesets gives package sections, not curated release abstracts.
        +- Keeping every package changelog on `/docs/releases` made the page scale badly as releases accumulate.
        +- Extracting only major sections dropped legitimate package release notes from mixed releases.
        +- Storing generated release dates as arbitrary frontmatter made Contentlayer warn because `date` is not part of the `Doc` schema.
        +- Linking to repo compare URLs such as `v53.0.1...v53.0.2` produced empty changelogs for package-only Changesets releases.
        +- Fetching release data at runtime made a static docs page depend on GitHub response time and local dev route cache invalidation.
        +
        +## Solution
        +
        +Generate release docs from the Version Packages PR body:
        +
        +- `.github/workflows/release.yml` sets `changesets/action` `createGithubReleases: false`.
        +- After `changesets/action` creates or updates `[Release] Version packages`, the workflow checks out that PR and runs `tooling/scripts/sync-version-package-releases.mjs --pr `.
        +- `tooling/scripts/sync-version-package-releases.mjs` parses the PR description's `# Releases` section, groups package headings by version, and writes `apps/www/src/generated/release-index.json`.
        +- For historical backfills, run `node tooling/scripts/sync-version-package-releases.mjs --latest  --from v49` so the docs include every generated release entry from v49 onward without pulling older history.
        +- The generated entry keeps the Better Auth-style body, appends `For detailed changes, see CHANGELOG` pointing to the Version Packages PR, and stores a preferred package tag for compare links.
        +- Preferred tags use `platejs@version` when that package published, otherwise the first package tag in the release PR body.
        +- `Full changelog` links compare the previous generated release's preferred package tag to the current release's preferred package tag.
        +- `tooling/scripts/release-notes.mjs` reads `PUBLISHED_PACKAGES`, resolves the matching package directories, extracts each package's exact `CHANGELOG.md` section, and emits deterministic raw notes grouped by package.
        +- The parser folds indented continuation lines into the same bullet before moving `[#1234] by @user` metadata to the end of the sentence.
        +- `.github/prompts/release-notes-rewrite.md` lets Claude polish the raw notes, but validation keeps package headings, change-type headings, PR links, changelog links, entry count, and migration notes intact.
        +- The release workflow creates or updates one repo-level `vX.Y.Z` GitHub Release using the validated AI notes or the raw notes.
        +- `content/releases/index.mdx` renders ``; the component imports the generated JSON directly.
        +
        +The release-note generator shape is:
        +
        +```md
        +## `@platejs/table`
        +
        +### Major Changes
        +
        +- [#4941](https://github.com/udecode/plate/pull/4941) by [@zbeyens](https://github.com/zbeyens) – ...
        +
        +For detailed changes, see [`CHANGELOG`](https://github.com/udecode/plate/blob//packages/table/CHANGELOG.md)
        +```
        +
        +## Why This Works
        +
        +The feed answers "what changed recently?" without inventing summaries. Each release row carries all package changes inline, with Better Auth-style fade/expand behavior for long releases and one `CHANGELOG` link back to the Version Packages PR.
        +
        +Package-tag compares solve the one-click diff problem without relying on nonexistent repo tags. When `platejs` is published, the compare uses `platejs@version`; otherwise it uses the first published package tag for that version.
        +
        +Checked-in JSON avoids GitHub API latency and Vercel runtime fetch cost entirely. The client component stays inside the existing MDX component registry but renders static data.
        +
        +## Prevention
        +
        +- Do not generate prose summaries from Changesets package bullets unless a real curated summary source exists.
        +- Backfill with a version floor, not a fixed page-size assumption. Verify the generated JSON count plus first and last tags before checking the browser.
        +- Do not fetch GitHub Releases at docs runtime for the main release feed. Generate the feed from the Version Packages PR body.
        +- Do not copy Better Auth's product-domain `pr-analyzer` into Plate. Plate's reliable grouping source is each package's Changesets changelog section.
        +- Do not link release docs to repo `vX.Y.Z` comparisons for Changesets package releases. Compare package tags instead.
        +- Keep AI release-note rewrites behind deterministic raw notes and validation. AI can polish, but package headings, PR links, changelog links, entry counts, and migration notes are structural truth.
        +- Test wrapped Changesets bullets directly. A multi-line bullet should render the full sentence first and append the PR link after the folded summary.
        +- If the docs component is rendered through the client MDX component registry, prefer checked-in generated data over a runtime fetch.
        +- While historical GitHub Releases are package-tagged, keep one row per version and never expose one primary click per package.
        +
        +## Related Issues
        +
        +- [Generated MDX markers must use JSX comments](./2026-04-27-mdx-generated-markers-must-use-jsx-comments.md)
        diff --git a/docs/solutions/logic-errors/2026-04-06-toc-elements-must-reuse-content-controller-for-active-heading-state.md b/docs/solutions/logic-errors/2026-04-06-toc-elements-must-reuse-content-controller-for-active-heading-state.md
        index 0bccc96389..ddf536375a 100644
        --- a/docs/solutions/logic-errors/2026-04-06-toc-elements-must-reuse-content-controller-for-active-heading-state.md
        +++ b/docs/solutions/logic-errors/2026-04-06-toc-elements-must-reuse-content-controller-for-active-heading-state.md
        @@ -98,7 +98,7 @@ pnpm --filter www build:registry
         pnpm lint:fix
         ```
         
        -Browser verification used `dev-browser` against a clean `www` dev server on
        +Browser verification used `browser-use` against a clean `www` dev server on
         `127.0.0.1:3001`. The standalone TOC block route loaded, but the docs preview
         surface stayed stuck on `Loading...`, so browser proof for active-row promotion
         was limited to route-level sanity rather than a full interactive TOC assertion.
        diff --git a/docs/solutions/logic-errors/2026-04-09-date-payload-migrations-must-canonicalize-safe-values-and-preserve-unparseable-legacy-text.md b/docs/solutions/logic-errors/2026-04-09-date-payload-migrations-must-canonicalize-safe-values-and-preserve-unparseable-legacy-text.md
        index a22c5fe6b6..556f2f5fde 100644
        --- a/docs/solutions/logic-errors/2026-04-09-date-payload-migrations-must-canonicalize-safe-values-and-preserve-unparseable-legacy-text.md
        +++ b/docs/solutions/logic-errors/2026-04-09-date-payload-migrations-must-canonicalize-safe-values-and-preserve-unparseable-legacy-text.md
        @@ -113,7 +113,7 @@ pnpm lint:fix
         ```
         
         Browser verification also passed against `http://127.0.0.1:3000/docs/date`
        -through `dev-browser`, confirming the page loaded and still surfaced the date
        +through `browser-use`, confirming the page loaded and still surfaced the date
         UI content.
         
         ## Related Issues
        diff --git a/docs/solutions/logic-errors/2026-04-17-code-block-browser-highlight-must-match-server-output.md b/docs/solutions/logic-errors/2026-04-17-code-block-browser-highlight-must-match-server-output.md
        index 8b5e2354b7..d71b0fae85 100644
        --- a/docs/solutions/logic-errors/2026-04-17-code-block-browser-highlight-must-match-server-output.md
        +++ b/docs/solutions/logic-errors/2026-04-17-code-block-browser-highlight-must-match-server-output.md
        @@ -124,7 +124,7 @@ pnpm lint:fix
         
         Browser proof:
         
        -- Fresh `dev-browser` load of `http://localhost:3001/blocks/code-block-demo`
        +- Fresh `browser-use` load of `http://localhost:3001/blocks/code-block-demo`
           shows no hydration error and no `[CODE_HIGHLIGHT]` warnings for Python
         - `editor.plugins.code_block.options.lowlight.highlight('python', ...)`
           succeeds in the live page and returns highlighted nodes
        diff --git a/docs/solutions/logic-errors/2026-04-17-custom-block-start-list-rules-must-delete-matched-marker-text.md b/docs/solutions/logic-errors/2026-04-17-custom-block-start-list-rules-must-delete-matched-marker-text.md
        index f0294e4870..2ad245ff52 100644
        --- a/docs/solutions/logic-errors/2026-04-17-custom-block-start-list-rules-must-delete-matched-marker-text.md
        +++ b/docs/solutions/logic-errors/2026-04-17-custom-block-start-list-rules-must-delete-matched-marker-text.md
        @@ -103,7 +103,7 @@ pnpm lint:fix
         
         Browser proof:
         
        -- Fresh `dev-browser` load of `http://localhost:3001/blocks/list-demo`
        +- Fresh `browser-use` load of `http://localhost:3001/blocks/list-demo`
         - Reset to an empty paragraph
         - Type `- `
         - Result is an empty list item with selection at `[0, 0]`, offset `0`
        diff --git a/package.json b/package.json
        index 88b8cc24f0..bbd471219e 100644
        --- a/package.json
        +++ b/package.json
        @@ -10,6 +10,8 @@
             "build:watch": "ROARR_LOG=true turbowatch ./tooling/config/turbowatch.config.ts | roarr",
             "check": "pnpm lint && pnpm typecheck && pnpm test:all && pnpm test:slowest",
             "check:push": "pnpm lint && pnpm typecheck && pnpm test:all",
        +    "ci:release": "pnpm release",
        +    "ci:version": "pnpm changeset version && pnpm install --no-frozen-lockfile",
             "deps:check": "npx npm-check-updates@latest --configFileName config/ncurc.yml --workspaces --root --mergeConfig",
             "deps:update": "npx npm-check-updates@latest --configFileName config/ncurc.yml -u --workspaces --root --mergeConfig",
             "dev": "turbo --filter=www dev",
        diff --git a/packages/table/CHANGELOG.md b/packages/table/CHANGELOG.md
        index fdfce38ded..5b945b435d 100644
        --- a/packages/table/CHANGELOG.md
        +++ b/packages/table/CHANGELOG.md
        @@ -6,6 +6,8 @@
         
         - [#4941](https://github.com/udecode/plate/pull/4941) by [@zbeyens](https://github.com/zbeyens) – Escalate the second `selectAll` from the current table to the whole document.
         
        +- `useTableElement` no longer returns `isSelectingCell`. Use `usePluginOption(TablePlugin, 'isSelectingCell')` for table-level state, or `useTableCellElement` inside table cell components.
        +
         ## 52.3.20
         
         ### Patch Changes
        diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
        index 06a7c62234..91e0a05401 100644
        --- a/pnpm-lock.yaml
        +++ b/pnpm-lock.yaml
        @@ -684,6 +684,9 @@ importers:
               '@types/unist':
                 specifier: ^3.0.3
                 version: 3.0.3
        +      agentation:
        +        specifier: ^3.0.2
        +        version: 3.0.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
               autoprefixer:
                 specifier: 10.4.21
                 version: 10.4.21(postcss@8.5.4)
        @@ -5359,6 +5362,17 @@ packages:
             resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
             engines: {node: '>= 14'}
         
        +  agentation@3.0.2:
        +    resolution: {integrity: sha512-iGzBxFVTuZEIKzLY6AExSLAQH6i6SwxV4pAu7v7m3X6bInZ7qlZXAwrEqyc4+EfP4gM7z2RXBF6SF4DeH0f2lA==}
        +    peerDependencies:
        +      react: '>=18.0.0'
        +      react-dom: '>=18.0.0'
        +    peerDependenciesMeta:
        +      react:
        +        optional: true
        +      react-dom:
        +        optional: true
        +
           ai@6.0.116:
             resolution: {integrity: sha512-7yM+cTmyRLeNIXwt4Vj+mrrJgVQ9RMIW5WO0ydoLoYkewIvsMcvUmqS4j2RJTUXaF1HphwmSKUMQ/HypNRGOmA==}
             engines: {node: '>=18'}
        @@ -14236,6 +14250,11 @@ snapshots:
         
           agent-base@7.1.4: {}
         
        +  agentation@3.0.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
        +    optionalDependencies:
        +      react: 19.2.4
        +      react-dom: 19.2.4(react@19.2.4)
        +
           ai@6.0.116(zod@3.25.61):
             dependencies:
               '@ai-sdk/gateway': 3.0.66(zod@3.25.61)
        diff --git a/skills-lock.json b/skills-lock.json
        index b4e9fa934a..3348b0a6a8 100644
        --- a/skills-lock.json
        +++ b/skills-lock.json
        @@ -36,6 +36,11 @@
               "sourceType": "github",
               "computedHash": "b1d795a6f3a4004ba9ac21ad10b3473df2917bdbe4f851894dbee66fd513afea"
             },
        +    "codex-review": {
        +      "source": "steipete/agent-scripts",
        +      "sourceType": "github",
        +      "computedHash": "2daa33984003fbeb27b821725eb15d6e469251d63e58a6883a42580a0efa0d25"
        +    },
             "coding-tutor": {
               "source": "EveryInc/compound-engineering-plugin",
               "sourceType": "github",
        @@ -46,11 +51,6 @@
               "sourceType": "github",
               "computedHash": "09c9096e132640e201c4ffd592ffa144d949243ca7a71c5ef020e111927c495a"
             },
        -    "dev-browser": {
        -      "source": "sawyerhood/dev-browser",
        -      "sourceType": "github",
        -      "computedHash": "d50d11bbefdc1599d78900828dcb69d48e5091769d3f5ce10e06a60efe2405bf"
        -    },
             "frontend-design": {
               "source": "EveryInc/compound-engineering-plugin",
               "sourceType": "github",
        diff --git a/tooling/config/test-suites.mjs b/tooling/config/test-suites.mjs
        index 988d806be8..5ad8819795 100644
        --- a/tooling/config/test-suites.mjs
        +++ b/tooling/config/test-suites.mjs
        @@ -1,6 +1,7 @@
         export const TEST_FILE_PATTERNS = [
           'apps/**/*.spec.{ts,tsx}',
           'packages/**/*.spec.{ts,tsx}',
        +  'tooling/scripts/**/*.test.mjs',
         ];
         
         export const TEST_SLOW_FILE_PATTERNS = [
        diff --git a/tooling/preset/.agents/AGENTS.md b/tooling/preset/.agents/AGENTS.md
        index ad65f85902..40649fe2ca 100644
        --- a/tooling/preset/.agents/AGENTS.md
        +++ b/tooling/preset/.agents/AGENTS.md
        @@ -14,22 +14,16 @@ When using the following skills, override the default behavior.
         
         - Do not create `task_plan.md`, `findings.md`, or `progress.md` at repo root. Merge that content into one file under `.claude/docs/plans/`. Example: `.claude/docs/plans/2026-03-11-task.md`
         
        -`dev-browser`:
        +Browser usage:
         
        -- Use `dev-browser --connect http://127.0.0.1:9222` by default for browser work.
        -- If `http://127.0.0.1:9222` is unavailable, use `browser-debug-setup` first.
        -- Reuse the persistent debug browser/profile. Do not spin up disposable browser instances unless the user asks.
        -- Do not close or stop the user's connected debug browser. Close named pages only when needed.
        -- Keep scripts small and direct. Prefer `browser.getPage("persistent-main")` for the main app.
        -- Use `dev-browser` instead of `agent-browser` or next-devtools `browser_eval`.
        -- If `dev-browser` gets blocked by a human prompt or loops on the same step, stop and ask the user to unblock. After the unblock works:
        -  - [Add browser learning]
        +- Always try `[@browser-use](plugin://browser-use@openai-bundled)` first for browser usage.
        +- Do not substitute Puppeteer, standalone Playwright, or raw Chrome DevTools for browser usage.
         
         `ce-*`:
         
         - **Git:** Never git add, commit, push, or create PR unless the user explicitly asks.
         - **PR:** Before creating or updating a PR, run the local verification that actually matters here. At minimum: `bun run typecheck`, `bun run lint:fix`, and `bun run build` if the task touched app behavior or build config.
        -- **plan:** Include `dev-browser` in acceptance criteria for browser features.
        +- **plan:** Include Browser Use in acceptance criteria for browser features.
         - **deepen-plan:** Context7 only when not covered by skills.
         - **work:** UI tasks require browser verification before marking complete. Never guess.
         
        @@ -81,7 +75,7 @@ When using the following skills, override the default behavior.
         • Condition YES -> in_progress -> verify -> completed
         • NEVER git commit unless explicitly asked
         • Avoid unnecessary `bun dev`
        -• Use Skill(dev-browser) for browser testing instead of next-devtools browser evaluation
        +• Use Browser Use for browser testing instead of next-devtools browser evaluation
         
         **Verification Checklist:**
         
        diff --git a/tooling/preset/.claude/prompt.yml b/tooling/preset/.claude/prompt.yml
        index c9ef99f72b..3f4d49ced3 100644
        --- a/tooling/preset/.claude/prompt.yml
        +++ b/tooling/preset/.claude/prompt.yml
        @@ -30,7 +30,7 @@ beforeComplete:
               - NEVER git commit unless explicitly asked
               - 'NEVER `bun dev` or `bun run build` unless explicitly asked'
             todos:
        -      - 'Test Browser (IF new features, styling, visual bugs, state changes. SKIP trivial markup, non-UI): Skill(dev-browser)'
        +      - 'Test Browser (IF new features, styling, visual bugs, state changes. SKIP trivial markup, non-UI): Browser Use'
               - 'Typecheck (IF updated .ts files): Bash `bun typecheck`'
               - 'Lint: Bash `bun lint:fix`'
               - |
        diff --git a/tooling/preset/preset.toml b/tooling/preset/preset.toml
        index dfff7f9f28..690555ed74 100644
        --- a/tooling/preset/preset.toml
        +++ b/tooling/preset/preset.toml
        @@ -2,8 +2,8 @@ version = 1
         
         include = [
           "../../.agents/rules/agent-browser-issue.mdc",
        -  "../../.agents/rules/browser-debug-setup.mdc",
           "../../.agents/rules/components.mdc",
        +  "../../.agents/rules/dev-browser.mdc",
           "../../.agents/rules/grill-me.mdc",
           "../../.agents/rules/hard-cut.mdc",
           "../../.agents/rules/react.mdc",
        diff --git a/tooling/scripts/published-package-tags.mjs b/tooling/scripts/published-package-tags.mjs
        new file mode 100644
        index 0000000000..84068b4132
        --- /dev/null
        +++ b/tooling/scripts/published-package-tags.mjs
        @@ -0,0 +1,44 @@
        +#!/usr/bin/env node
        +
        +import path from 'node:path';
        +import { fileURLToPath } from 'node:url';
        +
        +if (isMainModule()) {
        +  const tags = getPublishedPackageTags(process.env.PUBLISHED_PACKAGES ?? '');
        +
        +  if (tags.length === 0) {
        +    console.error('No published package tags found in PUBLISHED_PACKAGES.');
        +    process.exit(1);
        +  }
        +
        +  console.log(tags.join('\n'));
        +}
        +
        +function isMainModule() {
        +  return (
        +    !!process.argv[1] &&
        +    path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)
        +  );
        +}
        +
        +export function getPublishedPackageTags(value) {
        +  let packages;
        +
        +  try {
        +    packages = JSON.parse(value);
        +  } catch {
        +    return [];
        +  }
        +
        +  if (!Array.isArray(packages)) return [];
        +
        +  return packages
        +    .filter(
        +      (packageInfo) =>
        +        typeof packageInfo?.name === 'string' &&
        +        typeof packageInfo?.version === 'string' &&
        +        packageInfo.name.length > 0 &&
        +        packageInfo.version.length > 0
        +    )
        +    .map((packageInfo) => `${packageInfo.name}@${packageInfo.version}`);
        +}
        diff --git a/tooling/scripts/published-package-tags.test.mjs b/tooling/scripts/published-package-tags.test.mjs
        new file mode 100644
        index 0000000000..8f6bfe0b5a
        --- /dev/null
        +++ b/tooling/scripts/published-package-tags.test.mjs
        @@ -0,0 +1,31 @@
        +import assert from 'node:assert/strict';
        +import test from 'node:test';
        +
        +import { getPublishedPackageTags } from './published-package-tags.mjs';
        +
        +test('builds package tag names from Changesets published packages', () => {
        +  assert.deepEqual(
        +    getPublishedPackageTags(
        +      JSON.stringify([
        +        { name: '@platejs/ai', version: '53.0.3' },
        +        { name: 'platejs', version: '53.0.3' },
        +      ])
        +    ),
        +    ['@platejs/ai@53.0.3', 'platejs@53.0.3']
        +  );
        +});
        +
        +test('ignores malformed published package entries', () => {
        +  assert.deepEqual(
        +    getPublishedPackageTags(
        +      JSON.stringify([
        +        { name: '@platejs/ai' },
        +        { version: '53.0.3' },
        +        { name: '', version: '53.0.3' },
        +        { name: 'platejs', version: '53.0.3' },
        +      ])
        +    ),
        +    ['platejs@53.0.3']
        +  );
        +  assert.deepEqual(getPublishedPackageTags('not json'), []);
        +});
        diff --git a/tooling/scripts/release-notes.mjs b/tooling/scripts/release-notes.mjs
        new file mode 100644
        index 0000000000..fff5e6db99
        --- /dev/null
        +++ b/tooling/scripts/release-notes.mjs
        @@ -0,0 +1,500 @@
        +#!/usr/bin/env node
        +
        +import { appendFile, readFile, readdir, rm, writeFile } from 'node:fs/promises';
        +import path from 'node:path';
        +import { fileURLToPath } from 'node:url';
        +
        +const scriptDir = path.dirname(fileURLToPath(import.meta.url));
        +const repoRoot = path.resolve(scriptDir, '..', '..');
        +const repo = 'udecode/plate';
        +const packageRoots = [
        +  path.join(repoRoot, 'packages'),
        +  path.join(repoRoot, 'packages', 'udecode'),
        +];
        +const semverPattern = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
        +const releaseTypeHeadingPattern =
        +  /^###\s+(Major|Minor|Patch) Changes[^\S\r\n]*$/gm;
        +const nextVersionHeadingPattern = /^##\s+/m;
        +const packageHeadingPattern = /^## `[^`]+`[^\S\r\n]*$/gm;
        +const changeHeadingPattern = /^###\s+(Major|Minor|Patch) Changes[^\S\r\n]*$/gm;
        +const changelogLinkPattern =
        +  /For detailed changes, see \[`CHANGELOG`\]\([^)]+\)/g;
        +const pullRequestLinkPattern = /\[#\d+\]\(https:\/\/github\.com\/[^)]+\)/g;
        +const commitLinkPattern =
        +  /\[`[0-9a-f]{7,40}`\]\(https:\/\/github\.com\/udecode\/plate\/commit\/[0-9a-f]{7,40}\)/g;
        +const bulletEntryPattern = /^-\s+/gm;
        +const migrationPattern = /\bMigration\b/g;
        +const contributorPattern =
        +  /by \[@([A-Za-z0-9-]+)\]\(https:\/\/github\.com\/[^)]+\)/g;
        +const contributorHandlePattern = /(?:^|[\s,])@([A-Za-z0-9-]+)(?=$|[\s,.;)])/gm;
        +const contributorsHeadingPattern = /^## Contributors[^\n]*\n/m;
        +const headingBoundaryPattern = /\n##\s+/;
        +const releaseTypes = ['major', 'minor', 'patch'];
        +const releaseTypeLabels = {
        +  major: 'Major',
        +  minor: 'Minor',
        +  patch: 'Patch',
        +};
        +
        +if (isMainModule()) {
        +  try {
        +    await main(process.argv.slice(2));
        +  } catch (error) {
        +    console.error(error?.message ?? error);
        +    process.exit(1);
        +  }
        +}
        +
        +function isMainModule() {
        +  const entrypoint = process.argv[1];
        +
        +  return (
        +    !!entrypoint && path.resolve(entrypoint) === fileURLToPath(import.meta.url)
        +  );
        +}
        +
        +async function main(args) {
        +  if (args[0] === 'validate') {
        +    const [, rawPath, finalPath] = args;
        +
        +    if (!rawPath || !finalPath) {
        +      throw new Error('Usage: release-notes.mjs validate  ');
        +    }
        +
        +    const result = await validateAiReleaseNotesFiles(rawPath, finalPath);
        +
        +    if (!result.valid) {
        +      for (const error of result.errors) {
        +        console.warn(`::warning::${error}`);
        +      }
        +
        +      await rm(finalPath, { force: true });
        +      return;
        +    }
        +
        +    console.log('AI release notes passed validation.');
        +    return;
        +  }
        +
        +  const publishedPackages = parsePublishedPackages(
        +    process.env.PUBLISHED_PACKAGES ?? process.env.PUBLISHED_PACKAGES_JSON ?? ''
        +  );
        +  const version = getGlobalReleaseVersion(publishedPackages);
        +
        +  if (!version) {
        +    throw new Error('No published package version found.');
        +  }
        +
        +  const workspacePackages = await getWorkspacePackages();
        +  const body = await generateRawReleaseNotes({
        +    commitRef: process.env.GITHUB_SHA ?? 'main',
        +    publishedPackages,
        +    workspacePackages,
        +  });
        +  const rawFile = path.join(repoRoot, `.release-notes-raw-${version}.md`);
        +
        +  await writeFile(rawFile, body);
        +  await setOutput('version', version);
        +  await setOutput('raw_changelog_path', rawFile);
        +
        +  console.log(`Wrote raw release notes to ${rawFile}`);
        +}
        +
        +export function parsePublishedPackages(publishedPackagesJson) {
        +  try {
        +    const publishedPackages = JSON.parse(publishedPackagesJson || '[]');
        +
        +    return Array.isArray(publishedPackages) ? publishedPackages : [];
        +  } catch {
        +    return [];
        +  }
        +}
        +
        +export function getGlobalReleaseVersion(publishedPackages) {
        +  return publishedPackages
        +    .map((publishedPackage) => publishedPackage?.version)
        +    .filter((version) => typeof version === 'string')
        +    .filter((version) => semverPattern.test(version))
        +    .sort(compareVersionsDesc)[0];
        +}
        +
        +export async function getWorkspacePackages(roots = packageRoots) {
        +  const workspacePackages = new Map();
        +
        +  for (const root of roots) {
        +    let entries;
        +
        +    try {
        +      entries = await readdir(root, { withFileTypes: true });
        +    } catch (error) {
        +      if (error?.code === 'ENOENT') continue;
        +      throw error;
        +    }
        +
        +    for (const entry of entries) {
        +      if (!entry.isDirectory()) continue;
        +
        +      const directory = path.join(root, entry.name);
        +      const packageJson = await readPackageJson(directory);
        +
        +      if (packageJson?.name) {
        +        workspacePackages.set(packageJson.name, {
        +          directory,
        +          packageJson,
        +        });
        +      }
        +    }
        +  }
        +
        +  return workspacePackages;
        +}
        +
        +export async function generateRawReleaseNotes({
        +  commitRef,
        +  publishedPackages,
        +  workspacePackages,
        +}) {
        +  const lines = [];
        +  const contributors = new Set();
        +  const packages = publishedPackages
        +    .filter(
        +      (publishedPackage) =>
        +        typeof publishedPackage?.name === 'string' &&
        +        typeof publishedPackage?.version === 'string'
        +    )
        +    .sort(
        +      (a, b) =>
        +        compareVersionsDesc(a.version, b.version) ||
        +        a.name.localeCompare(b.name)
        +    );
        +
        +  for (const publishedPackage of packages) {
        +    const workspacePackage = workspacePackages.get(publishedPackage.name);
        +    const changelog = workspacePackage
        +      ? await readOptionalFile(
        +          path.join(workspacePackage.directory, 'CHANGELOG.md')
        +        )
        +      : null;
        +    const releaseChanges = changelog
        +      ? extractReleaseChanges(changelog, publishedPackage.version)
        +      : null;
        +
        +    lines.push(`## \`${publishedPackage.name}\``);
        +    lines.push('');
        +
        +    if (releaseChanges) {
        +      lines.push(releaseChanges.body);
        +      collectContributors(contributors, releaseChanges.body);
        +    } else {
        +      lines.push(
        +        `Published \`${publishedPackage.name}@${publishedPackage.version}\`.`
        +      );
        +    }
        +
        +    lines.push('');
        +
        +    if (workspacePackage) {
        +      const changelogUrl = packageToChangelogUrl(
        +        workspacePackage.directory,
        +        commitRef
        +      );
        +
        +      lines.push(`For detailed changes, see [\`CHANGELOG\`](${changelogUrl})`);
        +      lines.push('');
        +    }
        +  }
        +
        +  if (contributors.size > 0) {
        +    lines.push('## Contributors');
        +    lines.push('');
        +    lines.push('Thanks to everyone who contributed to this release:');
        +    lines.push('');
        +    lines.push(
        +      [...contributors]
        +        .sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
        +        .map((contributor) => `@${contributor}`)
        +        .join(', ')
        +    );
        +    lines.push('');
        +  }
        +
        +  return `${lines.join('\n').trimEnd()}\n`;
        +}
        +
        +export function extractReleaseChanges(changelog, version) {
        +  const versionSection = extractVersionSection(changelog, version);
        +
        +  if (!versionSection) return null;
        +
        +  const sections = extractReleaseTypeSections(versionSection);
        +
        +  if (sections.length === 0) return null;
        +
        +  return {
        +    body: sections
        +      .map(
        +        (section) =>
        +          `### ${releaseTypeLabels[section.type]} Changes\n\n${section.body}`
        +      )
        +      .join('\n\n'),
        +    type: sections[0].type,
        +  };
        +}
        +
        +export function validateAiReleaseNotes(raw, final) {
        +  const errors = [];
        +  const rawPackageHeadings = matchAll(raw, packageHeadingPattern);
        +  const finalPackageHeadings = matchAll(final, packageHeadingPattern);
        +  const rawChangeHeadings = matchAll(raw, changeHeadingPattern);
        +  const finalChangeHeadings = matchAll(final, changeHeadingPattern);
        +  const rawChangelogLinks = matchAll(raw, changelogLinkPattern);
        +  const finalChangelogLinks = matchAll(final, changelogLinkPattern);
        +  const rawPullRequestLinks = matchAll(raw, pullRequestLinkPattern);
        +  const finalPullRequestLinks = matchAll(final, pullRequestLinkPattern);
        +  const rawCommitLinks = matchAll(raw, commitLinkPattern);
        +  const finalCommitLinks = matchAll(final, commitLinkPattern);
        +  const rawContributorsSection = getContributorsSection(raw);
        +  const finalContributorsSection = getContributorsSection(final);
        +  const didDropContributorsSection =
        +    rawContributorsSection.trim().length > 0 &&
        +    finalContributorsSection.trim().length === 0;
        +  const missingSectionContributors = didDropContributorsSection
        +    ? []
        +    : extractContributorSectionHandles(rawContributorsSection).filter(
        +        (handle) => !hasContributorHandle(finalContributorsSection, handle)
        +      );
        +  const missingContributors = extractContributorHandles(raw).filter(
        +    (handle) => !hasContributorHandle(final, handle)
        +  );
        +
        +  if (final.trim().length === 0) {
        +    errors.push('AI output is empty.');
        +  }
        +
        +  if (!sameList(rawPackageHeadings, finalPackageHeadings)) {
        +    errors.push('AI output changed package headings.');
        +  }
        +
        +  if (!sameList(rawChangeHeadings, finalChangeHeadings)) {
        +    errors.push('AI output changed change-type headings.');
        +  }
        +
        +  if (!sameList(rawChangelogLinks, finalChangelogLinks)) {
        +    errors.push('AI output changed package changelog links.');
        +  }
        +
        +  if (!sameList(rawPullRequestLinks, finalPullRequestLinks)) {
        +    errors.push('AI output changed PR links.');
        +  }
        +
        +  if (!sameList(rawCommitLinks, finalCommitLinks)) {
        +    errors.push('AI output changed commit links.');
        +  }
        +
        +  if (
        +    countMatches(final, bulletEntryPattern) !==
        +    countMatches(raw, bulletEntryPattern)
        +  ) {
        +    errors.push('AI output changed release entry count.');
        +  }
        +
        +  if (
        +    countMatches(final, migrationPattern) < countMatches(raw, migrationPattern)
        +  ) {
        +    errors.push('AI output dropped migration notes.');
        +  }
        +
        +  if (didDropContributorsSection) {
        +    errors.push('AI output dropped Contributors section.');
        +  }
        +
        +  if (missingContributors.length > 0 || missingSectionContributors.length > 0) {
        +    errors.push('AI output dropped contributors.');
        +  }
        +
        +  return {
        +    errors,
        +    valid: errors.length === 0,
        +  };
        +}
        +
        +async function validateAiReleaseNotesFiles(rawPath, finalPath) {
        +  const [raw, final] = await Promise.all([
        +    readFile(rawPath, 'utf8'),
        +    readFile(finalPath, 'utf8'),
        +  ]);
        +
        +  return validateAiReleaseNotes(raw, final);
        +}
        +
        +function extractVersionSection(changelog, version) {
        +  const versionHeadingPattern = new RegExp(
        +    `^##\\s+${escapeRegExp(version)}(?:\\s|$).*`,
        +    'm'
        +  );
        +  const match = versionHeadingPattern.exec(changelog);
        +
        +  if (!match) return null;
        +
        +  const bodyStart = match.index + match[0].length;
        +  const rest = changelog.slice(bodyStart);
        +  const nextMatch = rest.match(nextVersionHeadingPattern);
        +  const bodyEnd =
        +    nextMatch?.index === undefined
        +      ? changelog.length
        +      : bodyStart + nextMatch.index;
        +
        +  return changelog.slice(bodyStart, bodyEnd);
        +}
        +
        +function extractReleaseTypeSections(content) {
        +  const matches = [...content.matchAll(releaseTypeHeadingPattern)];
        +
        +  return matches
        +    .map((match, index) => {
        +      const bodyStart = match.index + match[0].length;
        +      const nextMatch = matches[index + 1];
        +      const bodyEnd = nextMatch?.index ?? content.length;
        +      const body = content.slice(bodyStart, bodyEnd).trim();
        +
        +      if (!body) return null;
        +
        +      return {
        +        body,
        +        type: match[1].toLowerCase(),
        +      };
        +    })
        +    .filter(Boolean)
        +    .sort((a, b) => compareReleaseTypes(a.type, b.type));
        +}
        +
        +function packageToChangelogUrl(directory, commitRef) {
        +  const relativePath = path.relative(
        +    repoRoot,
        +    path.join(directory, 'CHANGELOG.md')
        +  );
        +
        +  return `https://github.com/${repo}/blob/${commitRef}/${relativePath}`;
        +}
        +
        +async function readPackageJson(directory) {
        +  const content = await readOptionalFile(path.join(directory, 'package.json'));
        +
        +  return content ? JSON.parse(content) : null;
        +}
        +
        +async function readOptionalFile(filePath) {
        +  try {
        +    return await readFile(filePath, 'utf8');
        +  } catch (error) {
        +    if (error?.code === 'ENOENT') return null;
        +    throw error;
        +  }
        +}
        +
        +function collectContributors(contributors, content) {
        +  for (const match of content.matchAll(contributorPattern)) {
        +    contributors.add(match[1]);
        +  }
        +}
        +
        +function collectContributorSectionHandles(contributors, content) {
        +  for (const match of content.matchAll(contributorHandlePattern)) {
        +    contributors.add(match[1]);
        +  }
        +}
        +
        +function extractContributorHandles(content) {
        +  const contributors = new Set();
        +
        +  collectContributors(contributors, content);
        +  collectContributorSectionHandles(
        +    contributors,
        +    getContributorsSection(content)
        +  );
        +
        +  return [...contributors];
        +}
        +
        +function extractContributorSectionHandles(content) {
        +  const contributors = new Set();
        +
        +  collectContributorSectionHandles(contributors, content);
        +
        +  return [...contributors];
        +}
        +
        +function getContributorsSection(content) {
        +  const match = contributorsHeadingPattern.exec(content);
        +
        +  if (!match) return '';
        +
        +  const sectionStart = match.index + match[0].length;
        +  const rest = content.slice(sectionStart);
        +  const nextHeading = headingBoundaryPattern.exec(rest);
        +
        +  return rest.slice(0, nextHeading?.index ?? rest.length);
        +}
        +
        +function hasContributorHandle(content, handle) {
        +  const escapedHandle = escapeRegExp(handle);
        +
        +  return new RegExp(
        +    `(?:^|[^A-Za-z0-9_/-])@${escapedHandle}\\b|github\\.com/${escapedHandle}\\b`,
        +    'm'
        +  ).test(content);
        +}
        +
        +function compareVersionsDesc(a, b) {
        +  const parsedA = parseVersion(a);
        +  const parsedB = parseVersion(b);
        +
        +  for (let index = 0; index < 3; index++) {
        +    const delta = parsedB.parts[index] - parsedA.parts[index];
        +
        +    if (delta !== 0) return delta;
        +  }
        +
        +  if (parsedA.prerelease && !parsedB.prerelease) return 1;
        +  if (!parsedA.prerelease && parsedB.prerelease) return -1;
        +
        +  return parsedB.prerelease.localeCompare(parsedA.prerelease);
        +}
        +
        +function parseVersion(version) {
        +  const [core, prerelease = ''] = version.split('-');
        +
        +  return {
        +    parts: core.split('.').map(Number),
        +    prerelease,
        +  };
        +}
        +
        +function compareReleaseTypes(a, b) {
        +  return releaseTypes.indexOf(a) - releaseTypes.indexOf(b);
        +}
        +
        +function matchAll(content, pattern) {
        +  return [...content.matchAll(pattern)].map((match) => match[0]);
        +}
        +
        +function countMatches(content, pattern) {
        +  return matchAll(content, pattern).length;
        +}
        +
        +function sameList(left, right) {
        +  return (
        +    left.length === right.length &&
        +    left.every((item, index) => item === right[index])
        +  );
        +}
        +
        +function escapeRegExp(value) {
        +  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
        +}
        +
        +async function setOutput(name, value) {
        +  if (!process.env.GITHUB_OUTPUT) return;
        +
        +  await appendFile(process.env.GITHUB_OUTPUT, `${name}=${value}\n`);
        +}
        diff --git a/tooling/scripts/release-notes.test.mjs b/tooling/scripts/release-notes.test.mjs
        new file mode 100644
        index 0000000000..e3e0f12805
        --- /dev/null
        +++ b/tooling/scripts/release-notes.test.mjs
        @@ -0,0 +1,251 @@
        +import assert from 'node:assert/strict';
        +import { mkdir, mkdtemp, writeFile } from 'node:fs/promises';
        +import { tmpdir } from 'node:os';
        +import path from 'node:path';
        +import test from 'node:test';
        +
        +import {
        +  extractReleaseChanges,
        +  generateRawReleaseNotes,
        +  getGlobalReleaseVersion,
        +  getWorkspacePackages,
        +  parsePublishedPackages,
        +  validateAiReleaseNotes,
        +} from './release-notes.mjs';
        +
        +test('selects the highest published package version for the global release', () => {
        +  assert.equal(
        +    getGlobalReleaseVersion([
        +      { name: '@platejs/list', version: '53.0.2' },
        +      { name: 'depset', version: '0.1.2' },
        +      { name: '@platejs/table', version: '53.0.1' },
        +    ]),
        +    '53.0.2'
        +  );
        +});
        +
        +test('parses changesets published package output safely', () => {
        +  assert.deepEqual(
        +    parsePublishedPackages('[{"name":"@platejs/list","version":"53.0.2"}]'),
        +    [{ name: '@platejs/list', version: '53.0.2' }]
        +  );
        +  assert.deepEqual(parsePublishedPackages('not json'), []);
        +});
        +
        +test('extracts exact package changelog sections and preserves change types', () => {
        +  const changelog = `# @platejs/table
        +
        +## 54.0.0
        +
        +### Major Changes
        +
        +- Remove \`oldApi\`.
        +
        +### Minor Changes
        +
        +- Add \`newApi\`.
        +
        +### Patch Changes
        +
        +- Fix docs.
        +
        +## 53.0.0
        +
        +### Major Changes
        +
        +- Older major.
        +`;
        +
        +  assert.deepEqual(extractReleaseChanges(changelog, '54.0.0'), {
        +    body: '### Major Changes\n\n- Remove `oldApi`.\n\n### Minor Changes\n\n- Add `newApi`.\n\n### Patch Changes\n\n- Fix docs.',
        +    type: 'major',
        +  });
        +});
        +
        +test('generates raw release notes from published package changelogs', async () => {
        +  const root = await mkdtemp(path.join(tmpdir(), 'plate-release-notes-'));
        +  const packageRoot = path.join(root, 'packages');
        +  const packageDirectory = path.join(packageRoot, 'list');
        +
        +  await mkdir(packageDirectory, { recursive: true });
        +  await writeFile(
        +    path.join(packageDirectory, 'package.json'),
        +    JSON.stringify({ name: '@platejs/list', version: '53.0.2' })
        +  );
        +  await writeFile(
        +    path.join(packageDirectory, 'CHANGELOG.md'),
        +    `# @platejs/list
        +
        +## 53.0.2
        +
        +### Patch Changes
        +
        +- [#4954](https://github.com/udecode/plate/pull/4954) by [@dylans](https://github.com/dylans) – Fix ordered list numbering.
        +`
        +  );
        +
        +  const body = await generateRawReleaseNotes({
        +    commitRef: 'abc123',
        +    publishedPackages: [{ name: '@platejs/list', version: '53.0.2' }],
        +    workspacePackages: await getWorkspacePackages([packageRoot]),
        +  });
        +
        +  assert.match(body, /## `@platejs\/list`/);
        +  assert.match(body, /### Patch Changes/);
        +  assert.match(body, /Fix ordered list numbering/);
        +  assert.match(
        +    body,
        +    /For detailed changes, see \[`CHANGELOG`\]\(https:\/\/github\.com\/udecode\/plate\/blob\/abc123\//
        +  );
        +  assert.match(body, /## Contributors/);
        +  assert.match(body, /@dylans/);
        +  assert.doesNotMatch(body, /compare\/v/);
        +  assert.doesNotMatch(body, /Full changelog/);
        +});
        +
        +test('validates AI release notes preserve deterministic structure', () => {
        +  const raw = `## \`@platejs/table\`
        +
        +### Major Changes
        +
        +- Removed \`oldApi\` ([#5000](https://github.com/udecode/plate/pull/5000))
        +> **Migration:** Use \`newApi\`.
        +
        +For detailed changes, see [\`CHANGELOG\`](https://github.com/udecode/plate/blob/abc/packages/table/CHANGELOG.md)
        +
        +## Contributors
        +
        +Thanks to everyone who contributed to this release:
        +
        +@alice
        +`;
        +  const good = raw.replace('Removed `oldApi`', 'Removed `oldApi` from tables');
        +  const bad = raw
        +    .replace('## `@platejs/table`', '## `@platejs/core`')
        +    .replace('[#5000](https://github.com/udecode/plate/pull/5000)', '')
        +    .replace('> **Migration:** Use `newApi`.\n', '')
        +    .replace(
        +      '\n## Contributors\n\nThanks to everyone who contributed to this release:\n\n@alice\n',
        +      ''
        +    );
        +
        +  assert.deepEqual(validateAiReleaseNotes(raw, good), {
        +    errors: [],
        +    valid: true,
        +  });
        +  assert.equal(validateAiReleaseNotes(raw, bad).valid, false);
        +  assert.match(
        +    validateAiReleaseNotes(raw, bad).errors.join('\n'),
        +    /AI output dropped Contributors section\./
        +  );
        +});
        +
        +test('validates AI release notes preserve the Contributors section itself', () => {
        +  const raw = `## \`@platejs/table\`
        +
        +### Patch Changes
        +
        +- [#5000](https://github.com/udecode/plate/pull/5000) by [@alice](https://github.com/alice) – Fix table.
        +
        +For detailed changes, see [\`CHANGELOG\`](https://github.com/udecode/plate/blob/abc/packages/table/CHANGELOG.md)
        +
        +## Contributors
        +
        +Thanks to everyone who contributed to this release:
        +
        +@alice
        +`;
        +  const withoutContributors = raw.replace(
        +    '\n## Contributors\n\nThanks to everyone who contributed to this release:\n\n@alice\n',
        +    '\n'
        +  );
        +
        +  assert.deepEqual(validateAiReleaseNotes(raw, withoutContributors), {
        +    errors: ['AI output dropped Contributors section.'],
        +    valid: false,
        +  });
        +});
        +
        +test('validates AI release notes preserve comma-separated contributor handles', () => {
        +  const raw = `## \`@platejs/table\`
        +
        +### Patch Changes
        +
        +- [#5000](https://github.com/udecode/plate/pull/5000) by [@alice](https://github.com/alice) – Fix table.
        +- [#5001](https://github.com/udecode/plate/pull/5001) by [@bob](https://github.com/bob) – Fix more.
        +
        +For detailed changes, see [\`CHANGELOG\`](https://github.com/udecode/plate/blob/abc/packages/table/CHANGELOG.md)
        +
        +## Contributors
        +
        +Thanks to everyone who contributed to this release:
        +
        +@alice, @bob
        +`;
        +  const missingBob = raw.replace('@alice, @bob', '@alice');
        +
        +  assert.deepEqual(validateAiReleaseNotes(raw, missingBob), {
        +    errors: ['AI output dropped contributors.'],
        +    valid: false,
        +  });
        +});
        +
        +test('validates AI release notes preserve exact PR links', () => {
        +  const raw = `## \`@platejs/table\`
        +
        +### Patch Changes
        +
        +- Fix table ([#5000](https://github.com/udecode/plate/pull/5000))
        +
        +For detailed changes, see [\`CHANGELOG\`](https://github.com/udecode/plate/blob/abc/packages/table/CHANGELOG.md)
        +`;
        +  const wrongPullRequest = raw.replace(
        +    '[#5000](https://github.com/udecode/plate/pull/5000)',
        +    '[#5999](https://github.com/udecode/plate/pull/5999)'
        +  );
        +
        +  assert.deepEqual(validateAiReleaseNotes(raw, wrongPullRequest), {
        +    errors: ['AI output changed PR links.'],
        +    valid: false,
        +  });
        +});
        +
        +test('validates AI release notes preserve exact commit links', () => {
        +  const raw = `## \`@platejs/slate\`
        +
        +### Patch Changes
        +
        +- Updated \`slate-react\`. ([\`ce9ec87\`](https://github.com/udecode/plate/commit/ce9ec871c9547a8a3c78ded13a93049ef9fe049c))
        +
        +For detailed changes, see [\`CHANGELOG\`](https://github.com/udecode/plate/blob/abc/packages/slate/CHANGELOG.md)
        +`;
        +  const withoutCommit = raw.replace(
        +    '([`ce9ec87`](https://github.com/udecode/plate/commit/ce9ec871c9547a8a3c78ded13a93049ef9fe049c))',
        +    ''
        +  );
        +
        +  assert.deepEqual(validateAiReleaseNotes(raw, withoutCommit), {
        +    errors: ['AI output changed commit links.'],
        +    valid: false,
        +  });
        +});
        +
        +test('validates AI release notes reject added release entries', () => {
        +  const raw = `## \`@platejs/table\`
        +
        +### Patch Changes
        +
        +- Fix table ([#5000](https://github.com/udecode/plate/pull/5000))
        +
        +For detailed changes, see [\`CHANGELOG\`](https://github.com/udecode/plate/blob/abc/packages/table/CHANGELOG.md)
        +`;
        +  const withAddedEntry = raw.replace(
        +    'For detailed changes',
        +    '- Invent unrelated change\n\nFor detailed changes'
        +  );
        +
        +  assert.deepEqual(validateAiReleaseNotes(raw, withAddedEntry), {
        +    errors: ['AI output changed release entry count.'],
        +    valid: false,
        +  });
        +});
        diff --git a/tooling/scripts/release-workflow.test.mjs b/tooling/scripts/release-workflow.test.mjs
        new file mode 100644
        index 0000000000..80c768dfb9
        --- /dev/null
        +++ b/tooling/scripts/release-workflow.test.mjs
        @@ -0,0 +1,72 @@
        +import assert from 'node:assert/strict';
        +import { readFile } from 'node:fs/promises';
        +import test from 'node:test';
        +
        +const releaseWorkflowPath = new URL(
        +  '../../.github/workflows/release.yml',
        +  import.meta.url
        +);
        +const packageJsonPath = new URL('../../package.json', import.meta.url);
        +const nextConfigPath = new URL(
        +  '../../apps/www/next.config.ts',
        +  import.meta.url
        +);
        +
        +test('release workflow uses the pruned GitHub Release path', async () => {
        +  const workflow = await readFile(releaseWorkflowPath, 'utf8');
        +
        +  assert.match(workflow, /branches:\s*\[main\]/);
        +  assert.match(workflow, /createGithubReleases:\s*false/);
        +  assert.match(workflow, /version:\s*pnpm ci:version/);
        +  assert.match(workflow, /publish:\s*pnpm ci:release/);
        +  assert.match(workflow, /node tooling\/scripts\/published-package-tags\.mjs/);
        +  assert.match(workflow, /refs\/tags\/\$\{tag\}:refs\/tags\/\$\{tag\}/);
        +  assert.match(
        +    workflow,
        +    /node tooling\/scripts\/sync-version-package-releases\.mjs --pr "\$RELEASE_PR" --from v49/
        +  );
        +  assert.match(
        +    workflow,
        +    /git status --porcelain --untracked-files=all -- apps\/www\/src\/generated\/release-index\.json/
        +  );
        +  assert.match(workflow, /node tooling\/scripts\/release-notes\.mjs/);
        +  assert.match(workflow, /anthropics\/claude-code-action\/base-action/);
        +  assert.match(workflow, /touch "\$\{RAW_PATH\}\.final\.validated"/);
        +  assert.match(
        +    workflow,
        +    /-f "\$\{RAW_PATH\}\.final" && -f "\$\{RAW_PATH\}\.final\.validated"/
        +  );
        +  assert.match(workflow, /Ignoring unvalidated AI-rewritten release notes/);
        +  assert.match(workflow, /gh release (create|edit)/);
        +  assert.match(workflow, /sync-release-artifacts:/);
        +  assert.doesNotMatch(workflow, /sync-release-docs/);
        +  assert.doesNotMatch(workflow, /global-release/);
        +  assert.doesNotMatch(workflow, /pr-analyzer/);
        +  assert.doesNotMatch(workflow, /snapshot:/);
        +  assert.doesNotMatch(workflow, /release\/\*\*/);
        +  assert.doesNotMatch(workflow, /branches:\s*[\s\S]*-\s*next/);
        +});
        +
        +test('package scripts expose CI version and release commands only', async () => {
        +  const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8'));
        +
        +  assert.equal(
        +    packageJson.scripts['ci:version'],
        +    'pnpm changeset version && pnpm install --no-frozen-lockfile'
        +  );
        +  assert.equal(packageJson.scripts['ci:release'], 'pnpm release');
        +  assert.equal(packageJson.scripts['release:releases'], undefined);
        +});
        +
        +test('release docs keep old migration route redirects', async () => {
        +  const nextConfig = await readFile(nextConfigPath, 'utf8');
        +
        +  assert.match(
        +    nextConfig,
        +    /source:\s*'\/docs\/migration'[\s\S]*destination:\s*'\/docs\/releases'|destination:\s*'\/docs\/releases'[\s\S]*source:\s*'\/docs\/migration'/
        +  );
        +  assert.match(
        +    nextConfig,
        +    /source:\s*'\/cn\/docs\/migration'[\s\S]*destination:\s*'\/cn\/docs\/releases'|destination:\s*'\/cn\/docs\/releases'[\s\S]*source:\s*'\/cn\/docs\/migration'/
        +  );
        +});
        diff --git a/tooling/scripts/sync-version-package-releases.mjs b/tooling/scripts/sync-version-package-releases.mjs
        new file mode 100644
        index 0000000000..8db2009115
        --- /dev/null
        +++ b/tooling/scripts/sync-version-package-releases.mjs
        @@ -0,0 +1,544 @@
        +#!/usr/bin/env node
        +
        +import { execFile as execFileCallback } from 'node:child_process';
        +import { mkdir, readFile, writeFile } from 'node:fs/promises';
        +import path from 'node:path';
        +import { fileURLToPath } from 'node:url';
        +import { promisify } from 'node:util';
        +
        +const execFile = promisify(execFileCallback);
        +
        +const defaultOutputPath = 'apps/www/src/generated/release-index.json';
        +const githubRepo = 'udecode/plate';
        +const githubBaseUrl = `https://github.com/${githubRepo}`;
        +const packageReleaseHeadingPattern =
        +  /^##\s+(?.+)@(?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)[^\S\r\n]*$/gm;
        +const releaseTypeHeadingPattern =
        +  /^###\s+(Major|Minor|Patch) Changes[^\S\r\n]*$/gm;
        +const releaseSectionHeadingPattern =
        +  /^###\s+(?Major|Minor|Patch) Changes[^\S\r\n]*$/gm;
        +const releaseIntroPattern = /^#\s+Releases[^\S\r\n]*$/m;
        +const listIndentPattern = /^-\s{3}/gm;
        +const continuationLinePattern = /^\s{2,}\S/;
        +const generatedFooterPattern =
        +  /\n*(?:For detailed changes, see \[`CHANGELOG`\]\([^)]+\)(?:\n\nThanks to [\s\S]*?)?(?:\n\nFull changelog: \[`[^`]+`\]\([^)]+\))?|\[`CHANGELOG`\]\([^)]+\)(?:\s+·\s+\[`[^`]+`\]\([^)]+\))?(?:\s+·\s+(?:Thanks to|By) [^\n]+)?)\s*$/;
        +const changesetChangeLinePattern =
        +  /^-\s+\[(?#\d+|`[0-9a-f]{7,40}`)\]\((?[^)]+)\)\s+by\s+\[(?@[A-Za-z0-9-]+(?:\\?\[bot\\?\])?)\]\((?[^)]+)\)\s+[–-]\s*(?.*)$/;
        +const leadingIndentPattern = /^\s{4}/;
        +const nestedListIndentPattern = /^(\s*)-\s{3}/;
        +const semverTagPattern = /^v/;
        +const releaseTypes = ['major', 'minor', 'patch'];
        +const releaseHeadingLabels = {
        +  Major: 'Breaking Changes',
        +  Minor: 'Features',
        +  Patch: 'Bug Fixes',
        +};
        +
        +if (isMainModule()) {
        +  try {
        +    await main(process.argv.slice(2));
        +  } catch (error) {
        +    console.error(error?.message ?? error);
        +    process.exit(1);
        +  }
        +}
        +
        +function isMainModule() {
        +  return (
        +    !!process.argv[1] &&
        +    path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)
        +  );
        +}
        +
        +async function main(args) {
        +  const options = parseArgs(args);
        +  const existingReleases = await readExistingReleases(options.output);
        +  const pullRequests =
        +    options.pullRequestNumber !== undefined
        +      ? [await readPullRequest(options.pullRequestNumber)]
        +      : await readLatestPullRequests(options.limit);
        +  const parsedReleases = pullRequests.flatMap((pullRequest) =>
        +    parseVersionPackagesPullRequest(pullRequest)
        +  );
        +  const releases = filterReleasesFromVersion(
        +    mergeReleases(existingReleases, parsedReleases),
        +    options.fromVersion
        +  );
        +
        +  await writeReleaseIndex(options.output, releases);
        +
        +  console.log(`Synced ${releases.length} release entries to ${options.output}`);
        +}
        +
        +function parseArgs(args) {
        +  const options = {
        +    fromVersion: undefined,
        +    limit: 20,
        +    output: defaultOutputPath,
        +    pullRequestNumber: undefined,
        +  };
        +
        +  let index = 0;
        +
        +  while (index < args.length) {
        +    const arg = args[index];
        +
        +    if (arg === '--latest') {
        +      options.limit = Number(args[index + 1] ?? options.limit);
        +      index += 2;
        +    } else if (arg === '--from') {
        +      options.fromVersion = args[index + 1];
        +      index += 2;
        +    } else if (arg === '--output') {
        +      options.output = args[index + 1] ?? options.output;
        +      index += 2;
        +    } else if (arg === '--pr') {
        +      options.pullRequestNumber = Number(args[index + 1]);
        +      index += 2;
        +    } else {
        +      index++;
        +    }
        +  }
        +
        +  return options;
        +}
        +
        +export function filterReleasesFromVersion(releases, fromVersion) {
        +  if (!fromVersion) return releases;
        +
        +  return releases.filter(
        +    (release) => compareVersionsDesc(release.tag, fromVersion) <= 0
        +  );
        +}
        +
        +async function readPullRequest(number) {
        +  const { stdout } = await execFile('gh', [
        +    'pr',
        +    'view',
        +    String(number),
        +    '--repo',
        +    githubRepo,
        +    '--json',
        +    'number,title,body,createdAt,mergedAt,updatedAt,url',
        +  ]);
        +
        +  return JSON.parse(stdout);
        +}
        +
        +async function readLatestPullRequests(limit) {
        +  const { stdout } = await execFile('gh', [
        +    'pr',
        +    'list',
        +    '--repo',
        +    githubRepo,
        +    '--state',
        +    'merged',
        +    '--search',
        +    '"[Release] Version packages"',
        +    '--json',
        +    'number,title,body,createdAt,mergedAt,updatedAt,url',
        +    '--limit',
        +    String(limit),
        +  ]);
        +
        +  return filterMergedVersionPackagePullRequests(JSON.parse(stdout));
        +}
        +
        +export function filterMergedVersionPackagePullRequests(pullRequests) {
        +  return pullRequests.filter(
        +    (pullRequest) =>
        +      isVersionPackagesPullRequest(pullRequest) && pullRequest.mergedAt
        +  );
        +}
        +
        +export function parseVersionPackagesPullRequest(pullRequest) {
        +  if (!isVersionPackagesPullRequest(pullRequest)) return [];
        +
        +  const releasesBody = extractReleasesBody(pullRequest.body ?? '');
        +  const packageChanges = parsePackageChanges(releasesBody);
        +
        +  if (packageChanges.length === 0) return [];
        +
        +  return [...groupPackageChangesByVersion(packageChanges).entries()].map(
        +    ([version, sameVersionPackageChanges]) =>
        +      compileVersionPackageRelease({
        +        packageChanges: sameVersionPackageChanges,
        +        pullRequest,
        +        version,
        +      })
        +  );
        +}
        +
        +function compileVersionPackageRelease({
        +  packageChanges,
        +  pullRequest,
        +  version,
        +}) {
        +  const selectedPackage =
        +    packageChanges.find(
        +      (packageChange) => packageChange.packageName === 'platejs'
        +    ) ?? packageChanges[0];
        +  const selectedTag = getPackageTag(selectedPackage);
        +  const { content, contributors } = compileReleaseContent(packageChanges);
        +
        +  return {
        +    content,
        +    contributors,
        +    date: formatDate(
        +      pullRequest.mergedAt ?? pullRequest.updatedAt ?? pullRequest.createdAt
        +    ),
        +    packageTag: selectedTag,
        +    tag: `v${version}`,
        +    title: `v${version}`,
        +    type: getReleaseType(packageChanges),
        +    versionPackagePrUrl: pullRequest.url,
        +  };
        +}
        +
        +function groupPackageChangesByVersion(packageChanges) {
        +  const packageChangesByVersion = new Map();
        +
        +  for (const packageChange of packageChanges) {
        +    const versionPackageChanges =
        +      packageChangesByVersion.get(packageChange.version) ?? [];
        +
        +    versionPackageChanges.push(packageChange);
        +    packageChangesByVersion.set(packageChange.version, versionPackageChanges);
        +  }
        +
        +  return packageChangesByVersion;
        +}
        +
        +export function mergeReleases(existingReleases, nextReleases) {
        +  const releasesByTag = new Map();
        +
        +  for (const release of [...existingReleases, ...nextReleases]) {
        +    releasesByTag.set(release.tag, release);
        +  }
        +
        +  const releases = [...releasesByTag.values()].sort((a, b) =>
        +    compareVersionsDesc(a.tag, b.tag)
        +  );
        +
        +  return releases.map((release, index) => {
        +    const previousRelease = releases[index + 1];
        +    const url =
        +      release.versionPackagePrUrl ?? getPackageReleaseUrl(release.packageTag);
        +    const fullChangelogUrl =
        +      previousRelease?.packageTag && release.packageTag
        +        ? getCompareUrl(previousRelease.packageTag, release.packageTag)
        +        : url;
        +    const changelogLabel = previousRelease
        +      ? `${previousRelease.tag}...${release.tag}`
        +      : release.tag;
        +
        +    return {
        +      ...release,
        +      content: getReleaseContentWithFooter({
        +        changelogLabel,
        +        content: release.content,
        +        contributors: release.contributors,
        +        detailedChangesUrl: release.versionPackagePrUrl ?? url,
        +        fullChangelogUrl,
        +      }),
        +      url,
        +    };
        +  });
        +}
        +
        +function getReleaseContentWithFooter({
        +  changelogLabel,
        +  content,
        +  contributors = [],
        +  detailedChangesUrl,
        +  fullChangelogUrl,
        +}) {
        +  const footer = [
        +    `[\`CHANGELOG\`](${detailedChangesUrl})`,
        +    `[\`${changelogLabel}\`](${fullChangelogUrl})`,
        +    contributors.length > 0
        +      ? `By ${formatContributorList(contributors)}`
        +      : undefined,
        +  ]
        +    .filter(Boolean)
        +    .join(' · ');
        +
        +  return [content.replace(generatedFooterPattern, '').trim(), footer].join(
        +    '\n\n'
        +  );
        +}
        +
        +function isVersionPackagesPullRequest(pullRequest) {
        +  return pullRequest?.title === '[Release] Version packages';
        +}
        +
        +function extractReleasesBody(body) {
        +  const match = releaseIntroPattern.exec(body);
        +
        +  return match ? body.slice(match.index + match[0].length) : body;
        +}
        +
        +function parsePackageChanges(body) {
        +  const matches = [...body.matchAll(packageReleaseHeadingPattern)];
        +
        +  return matches.map((match, index) => {
        +    const bodyStart = match.index + match[0].length;
        +    const nextMatch = matches[index + 1];
        +    const bodyEnd = nextMatch?.index ?? body.length;
        +
        +    return {
        +      body: body.slice(bodyStart, bodyEnd).trim(),
        +      packageName: match.groups.packageName,
        +      version: match.groups.version,
        +    };
        +  });
        +}
        +
        +function compileReleaseContent(packageChanges) {
        +  const contributorsByUsername = new Map();
        +  const content = packageChanges
        +    .map((packageChange) =>
        +      compilePackageChange(packageChange, contributorsByUsername)
        +    )
        +    .filter(Boolean)
        +    .join('\n\n');
        +
        +  return {
        +    content,
        +    contributors: [...contributorsByUsername.values()],
        +  };
        +}
        +
        +function compilePackageChange(packageChange, contributorsByUsername) {
        +  const sections = parseReleaseSections(packageChange.body);
        +  const content = sections
        +    .map((section) => compileReleaseSection(section, contributorsByUsername))
        +    .filter(Boolean)
        +    .join('\n\n');
        +
        +  if (!content) return '';
        +
        +  return [`\`${packageChange.packageName}\``, '', content].join('\n').trim();
        +}
        +
        +function parseReleaseSections(body) {
        +  const matches = [...body.matchAll(releaseSectionHeadingPattern)];
        +
        +  if (matches.length === 0) {
        +    return [{ body, type: undefined }];
        +  }
        +
        +  return matches.map((match, index) => {
        +    const bodyStart = match.index + match[0].length;
        +    const nextMatch = matches[index + 1];
        +    const bodyEnd = nextMatch?.index ?? body.length;
        +
        +    return {
        +      body: body.slice(bodyStart, bodyEnd).trim(),
        +      type: match.groups.type,
        +    };
        +  });
        +}
        +
        +function compileReleaseSection(section, contributorsByUsername) {
        +  const body = formatChangeLines(section.body, contributorsByUsername);
        +
        +  if (!body) return '';
        +
        +  return [`### ${formatReleaseSectionHeading(section.type)}`, '', body].join(
        +    '\n'
        +  );
        +}
        +
        +function formatReleaseSectionHeading(type) {
        +  if (!type) return 'Changes';
        +
        +  return releaseHeadingLabels[type] ?? `${type} Changes`;
        +}
        +
        +function formatChangeLine(line, contributorsByUsername) {
        +  const match = changesetChangeLinePattern.exec(line);
        +
        +  if (!match) return line;
        +
        +  const { changeLabel, changeUrl, summary } = match.groups;
        +
        +  collectChangeContributor(match, contributorsByUsername);
        +
        +  return `- ${summary.trim()} ([${changeLabel}](${changeUrl}))`;
        +}
        +
        +function formatChangeLines(body, contributorsByUsername) {
        +  const lines = normalizePackageBody(body).split('\n');
        +  const formattedLines = [];
        +
        +  for (let index = 0; index < lines.length; index++) {
        +    const line = lines[index];
        +    const match = changesetChangeLinePattern.exec(line);
        +
        +    if (!match) {
        +      formattedLines.push(line);
        +      continue;
        +    }
        +
        +    collectChangeContributor(match, contributorsByUsername);
        +
        +    if (!match.groups.summary.trim()) {
        +      const continuationLines = [];
        +
        +      while ((lines[index + 1] ?? '').trim() === '') {
        +        index++;
        +      }
        +
        +      while (continuationLinePattern.test(lines[index + 1] ?? '')) {
        +        index++;
        +        continuationLines.push(formatWrapperContinuationLine(lines[index]));
        +      }
        +
        +      if (continuationLines.length > 0) {
        +        formattedLines.push(
        +          ...formatWrapperOnlyChangeLines(match, continuationLines)
        +        );
        +      }
        +
        +      continue;
        +    }
        +
        +    const summaryParts = [match.groups.summary.trim()];
        +
        +    while (continuationLinePattern.test(lines[index + 1] ?? '')) {
        +      index++;
        +      summaryParts.push(lines[index].trim());
        +    }
        +
        +    formattedLines.push(
        +      formatChangeLine(
        +        line.replace(match.groups.summary, summaryParts.join(' ')),
        +        contributorsByUsername
        +      )
        +    );
        +  }
        +
        +  return formattedLines.join('\n').trim();
        +}
        +
        +function collectChangeContributor(match, contributorsByUsername) {
        +  const { username, userUrl } = match.groups;
        +
        +  if (!contributorsByUsername.has(username)) {
        +    contributorsByUsername.set(username, { url: userUrl, username });
        +  }
        +}
        +
        +function formatWrapperContinuationLine(line) {
        +  return line
        +    .replace(leadingIndentPattern, '')
        +    .replace(nestedListIndentPattern, '$1- ');
        +}
        +
        +function formatWrapperOnlyChangeLines(match, continuationLines) {
        +  const [firstLine, ...restLines] = continuationLines;
        +  const changeLink = `([${match.groups.changeLabel}](${match.groups.changeUrl}))`;
        +
        +  if (!firstLine) return restLines;
        +
        +  const firstLineWithLink = firstLine.startsWith('- ')
        +    ? `${firstLine} ${changeLink}`
        +    : `- ${firstLine} ${changeLink}`;
        +
        +  return [firstLineWithLink, ...restLines];
        +}
        +
        +function formatContributorList(contributors) {
        +  return contributors
        +    .map((contributor) => `[${contributor.username}](${contributor.url})`)
        +    .join(', ');
        +}
        +
        +function normalizePackageBody(body) {
        +  return body
        +    .replaceAll('\r\n', '\n')
        +    .replaceAll('\r', '\n')
        +    .replace(listIndentPattern, '- ')
        +    .trim();
        +}
        +
        +function getReleaseType(packageChanges) {
        +  return packageChanges
        +    .flatMap((packageChange) =>
        +      [...packageChange.body.matchAll(releaseTypeHeadingPattern)].map((match) =>
        +        match[1].toLowerCase()
        +      )
        +    )
        +    .sort((a, b) => releaseTypes.indexOf(a) - releaseTypes.indexOf(b))[0];
        +}
        +
        +function getPackageTag(packageChange) {
        +  return `${packageChange.packageName}@${packageChange.version}`;
        +}
        +
        +function getCompareUrl(fromTag, toTag) {
        +  return `${githubBaseUrl}/compare/${encodeTag(fromTag)}...${encodeTag(toTag)}`;
        +}
        +
        +function getPackageReleaseUrl(tag) {
        +  if (!tag) return;
        +
        +  return `${githubBaseUrl}/releases/tag/${encodeTag(tag)}`;
        +}
        +
        +function encodeTag(tag) {
        +  return encodeURIComponent(tag);
        +}
        +
        +async function readExistingReleases(outputPath) {
        +  try {
        +    return JSON.parse(await readFile(outputPath, 'utf8'));
        +  } catch (error) {
        +    if (error?.code === 'ENOENT') return [];
        +    throw error;
        +  }
        +}
        +
        +async function writeReleaseIndex(outputPath, releases) {
        +  await mkdir(path.dirname(outputPath), { recursive: true });
        +  await writeFile(outputPath, `${JSON.stringify(releases, null, 2)}\n`);
        +}
        +
        +function compareVersionsDesc(a, b) {
        +  const parsedA = parseVersion(a);
        +  const parsedB = parseVersion(b);
        +
        +  for (let index = 0; index < 3; index++) {
        +    const delta = parsedB.parts[index] - parsedA.parts[index];
        +
        +    if (delta !== 0) return delta;
        +  }
        +
        +  if (parsedA.prerelease && !parsedB.prerelease) return 1;
        +  if (!parsedA.prerelease && parsedB.prerelease) return -1;
        +
        +  return parsedB.prerelease.localeCompare(parsedA.prerelease);
        +}
        +
        +function parseVersion(version) {
        +  const [core, prerelease = ''] = version
        +    .replace(semverTagPattern, '')
        +    .split('-');
        +  const parts = core.split('.').map(Number);
        +
        +  while (parts.length < 3) {
        +    parts.push(0);
        +  }
        +
        +  return {
        +    parts,
        +    prerelease,
        +  };
        +}
        +
        +function formatDate(value) {
        +  if (!value) return new Date().toISOString().slice(0, 10);
        +
        +  return new Date(value).toISOString().slice(0, 10);
        +}
        diff --git a/tooling/scripts/sync-version-package-releases.test.mjs b/tooling/scripts/sync-version-package-releases.test.mjs
        new file mode 100644
        index 0000000000..26845dc222
        --- /dev/null
        +++ b/tooling/scripts/sync-version-package-releases.test.mjs
        @@ -0,0 +1,226 @@
        +import assert from 'node:assert/strict';
        +import test from 'node:test';
        +
        +import {
        +  filterMergedVersionPackagePullRequests,
        +  filterReleasesFromVersion,
        +  mergeReleases,
        +  parseVersionPackagesPullRequest,
        +} from './sync-version-package-releases.mjs';
        +
        +test('parses Version Packages PR body into release entries', () => {
        +  const releases = parseVersionPackagesPullRequest({
        +    body: `This PR was opened by Changesets.
        +
        +# Releases
        +## @platejs/ai@53.0.3
        +
        +### Patch Changes
        +
        +-   [#4945](https://github.com/udecode/plate/pull/4945) by [@felixfeng33](https://github.com/felixfeng33) – Clear block streaming state
        +
        +## platejs@53.0.3
        +
        +### Patch Changes
        +
        +-   Updated \`@platejs/utils\`.
        +
        +## @platejs/footnote@53.0.3
        +
        +### Minor Changes
        +
        +-   [#4941](https://github.com/udecode/plate/pull/4941) by [@zbeyens](https://github.com/zbeyens) – Add \`FootnoteReferencePlugin\`, \`FootnoteDefinitionPlugin\`, and
        +    \`FootnoteInputPlugin\` for real footnote nodes and inline \`[^\` combobox
        +    insertion in Plate editors.
        +
        +## depset@0.1.2
        +
        +### Patch Changes
        +
        +-   [#4891](https://github.com/udecode/plate/pull/4891) by [@zbeyens](https://github.com/zbeyens) – Fix TypeScript 6 compatibility.
        +`,
        +    mergedAt: '2026-04-29T07:11:20Z',
        +    title: '[Release] Version packages',
        +    url: 'https://github.com/udecode/plate/pull/4969',
        +  });
        +  const release = releases.find((item) => item.tag === 'v53.0.3');
        +  const depsetRelease = releases.find((item) => item.tag === 'v0.1.2');
        +
        +  assert.equal(releases.length, 2);
        +  assert.equal(release.tag, 'v53.0.3');
        +  assert.equal(release.date, '2026-04-29');
        +  assert.equal(release.packageTag, 'platejs@53.0.3');
        +  assert.match(release.content, /^`@platejs\/ai`$/m);
        +  assert.match(release.content, /### Bug Fixes/);
        +  assert.match(
        +    release.content,
        +    /- Clear block streaming state \(\[#4945\]\(https:\/\/github\.com\/udecode\/plate\/pull\/4945\)\)/
        +  );
        +  assert.doesNotMatch(release.content, / by \[/);
        +  assert.match(release.content, /^`platejs`$/m);
        +  assert.match(
        +    release.content,
        +    /- Add `FootnoteReferencePlugin`, `FootnoteDefinitionPlugin`, and `FootnoteInputPlugin` for real footnote nodes and inline `\[\^` combobox insertion in Plate editors\. \(\[#4941\]\(https:\/\/github\.com\/udecode\/plate\/pull\/4941\)\)/
        +  );
        +  assert.doesNotMatch(
        +    release.content,
        +    /and \(\[#4941\]\(https:\/\/github\.com\/udecode\/plate\/pull\/4941\)\)\n {4}`FootnoteInputPlugin`/
        +  );
        +  assert.deepEqual(release.contributors, [
        +    {
        +      url: 'https://github.com/felixfeng33',
        +      username: '@felixfeng33',
        +    },
        +    {
        +      url: 'https://github.com/zbeyens',
        +      username: '@zbeyens',
        +    },
        +  ]);
        +  assert.equal(depsetRelease.tag, 'v0.1.2');
        +  assert.equal(depsetRelease.packageTag, 'depset@0.1.2');
        +  assert.match(depsetRelease.content, /^`depset`$/m);
        +});
        +
        +test('filters unmerged Version Packages PRs from release history', () => {
        +  assert.deepEqual(
        +    filterMergedVersionPackagePullRequests([
        +      {
        +        mergedAt: null,
        +        number: 1,
        +        title: '[Release] Version packages',
        +      },
        +      {
        +        mergedAt: '2026-04-29T07:11:20Z',
        +        number: 2,
        +        title: '[Release] Version packages',
        +      },
        +      {
        +        mergedAt: '2026-04-29T07:11:20Z',
        +        number: 3,
        +        title: 'docs: update releases',
        +      },
        +    ]).map((pullRequest) => pullRequest.number),
        +    [2]
        +  );
        +});
        +
        +test('falls back to first package tag and builds compare URLs', () => {
        +  const releases = mergeReleases(
        +    [
        +      {
        +        content: 'older',
        +        date: '2026-04-25',
        +        packageTag: '@platejs/media@53.0.1',
        +        tag: 'v53.0.1',
        +        title: 'v53.0.1',
        +        versionPackagePrUrl: 'https://github.com/udecode/plate/pull/1',
        +      },
        +    ],
        +    [
        +      {
        +        content: 'newer',
        +        contributors: [
        +          { url: 'https://github.com/beta', username: '@beta' },
        +          { url: 'https://github.com/alpha', username: '@alpha' },
        +        ],
        +        date: '2026-04-25',
        +        packageTag: '@platejs/list@53.0.2',
        +        tag: 'v53.0.2',
        +        title: 'v53.0.2',
        +        versionPackagePrUrl: 'https://github.com/udecode/plate/pull/2',
        +      },
        +    ]
        +  );
        +
        +  assert.equal(releases[0].url, 'https://github.com/udecode/plate/pull/2');
        +  assert.match(
        +    releases[0].content,
        +    /\[`CHANGELOG`\]\(https:\/\/github\.com\/udecode\/plate\/pull\/2\) · \[`v53\.0\.1\.\.\.v53\.0\.2`\]\(https:\/\/github\.com\/udecode\/plate\/compare\/%40platejs%2Fmedia%4053\.0\.1\.\.\.%40platejs%2Flist%4053\.0\.2\) · By \[@beta\]\(https:\/\/github\.com\/beta\), \[@alpha\]\(https:\/\/github\.com\/alpha\)/
        +  );
        +  assert.doesNotMatch(releases[0].content, /For detailed changes/);
        +  assert.doesNotMatch(releases[0].content, /Full changelog:/);
        +  assert.equal(releases[1].url, 'https://github.com/udecode/plate/pull/1');
        +  assert.match(
        +    releases[1].content,
        +    /\[`CHANGELOG`\]\(https:\/\/github\.com\/udecode\/plate\/pull\/1\) · \[`v53\.0\.1`\]\(https:\/\/github\.com\/udecode\/plate\/pull\/1\)/
        +  );
        +});
        +
        +test('parses wrapper-only Changesets entries', () => {
        +  const [release] = parseVersionPackagesPullRequest({
        +    body: `# Releases
        +## @udecode/plate-ai@49.0.0
        +
        +### Major Changes
        +
        +-   [#4327](https://github.com/udecode/plate/pull/4327) by [@zbeyens](https://github.com/zbeyens) –
        +
        +    -   Copilot API method changes:
        +        -   \`editor.api.copilot.accept\` is now \`editor.tf.copilot.accept\`.
        +    -   Removed default shortcuts for Copilot.
        +`.replaceAll('\n', '\r\n'),
        +    mergedAt: '2025-06-11T00:00:00Z',
        +    title: '[Release] Version packages',
        +    url: 'https://github.com/udecode/plate/pull/4339',
        +  });
        +
        +  assert.match(
        +    release.content,
        +    /- Copilot API method changes: \(\[#4327\]\(https:\/\/github\.com\/udecode\/plate\/pull\/4327\)\)/
        +  );
        +  assert.match(
        +    release.content,
        +    / {4}- `editor\.api\.copilot\.accept` is now `editor\.tf\.copilot\.accept`\./
        +  );
        +  assert.match(release.content, /- Removed default shortcuts for Copilot\./);
        +  assert.doesNotMatch(release.content, /\r/);
        +  assert.doesNotMatch(release.content, / by \[/);
        +  assert.deepEqual(release.contributors, [
        +    {
        +      url: 'https://github.com/zbeyens',
        +      username: '@zbeyens',
        +    },
        +  ]);
        +});
        +
        +test('parses commit-linked Changesets entries', () => {
        +  const [release] = parseVersionPackagesPullRequest({
        +    body: `# Releases
        +## @platejs/slate@53.0.5
        +
        +### Patch Changes
        +
        +-   [\`ce9ec87\`](https://github.com/udecode/plate/commit/ce9ec871c9547a8a3c78ded13a93049ef9fe049c) by [@github-actions\\[bot\\]](https://github.com/github-actions%5Bbot%5D) – Updated \`slate-react\`.
        +`,
        +    mergedAt: '2026-05-01T00:00:00Z',
        +    title: '[Release] Version packages',
        +    url: 'https://github.com/udecode/plate/pull/4978',
        +  });
        +
        +  assert.match(
        +    release.content,
        +    /- Updated `slate-react`\. \(\[`ce9ec87`\]\(https:\/\/github\.com\/udecode\/plate\/commit\/ce9ec871c9547a8a3c78ded13a93049ef9fe049c\)\)/
        +  );
        +  assert.doesNotMatch(release.content, / by \[/);
        +  assert.deepEqual(release.contributors, [
        +    {
        +      url: 'https://github.com/github-actions%5Bbot%5D',
        +      username: '@github-actions\\[bot\\]',
        +    },
        +  ]);
        +});
        +
        +test('filters generated release history from a version floor', () => {
        +  assert.deepEqual(
        +    filterReleasesFromVersion(
        +      [
        +        { tag: 'v53.0.3' },
        +        { tag: 'v52.3.7' },
        +        { tag: 'v49.0.0' },
        +        { tag: 'v48.6.1' },
        +      ],
        +      'v49'
        +    ).map((release) => release.tag),
        +    ['v53.0.3', 'v52.3.7', 'v49.0.0']
        +  );
        +});
        
        From 25eea556f800ef93b802709aec1600a451b04898 Mon Sep 17 00:00:00 2001
        From: zbeyens 
        Date: Wed, 20 May 2026 19:41:19 +0200
        Subject: [PATCH 2/2] chore: ignore generated Next env
        
        ---
         .gitignore             | 3 ++-
         apps/www/next-env.d.ts | 7 -------
         2 files changed, 2 insertions(+), 8 deletions(-)
         delete mode 100644 apps/www/next-env.d.ts
        
        diff --git a/.gitignore b/.gitignore
        index 743b135db2..c0e2266b2e 100644
        --- a/.gitignore
        +++ b/.gitignore
        @@ -16,6 +16,7 @@ templates/my-app
         
         # Generated files
         .history
        +apps/www/next-env.d.ts
         node_modules/
         report.*
         *.tsv
        @@ -129,4 +130,4 @@ packages/plate/docs/
         
         *.local*
         
        -.omx/
        \ No newline at end of file
        +.omx/
        diff --git a/apps/www/next-env.d.ts b/apps/www/next-env.d.ts
        deleted file mode 100644
        index 0c7fad710c..0000000000
        --- a/apps/www/next-env.d.ts
        +++ /dev/null
        @@ -1,7 +0,0 @@
        -/// 
        -/// 
        -/// 
        -import "./.next/dev/types/routes.d.ts";
        -
        -// NOTE: This file should not be edited
        -// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.