Skip to content

feat: add support for diff rendering, apply and reject#2312

Merged
Saul-Mirone merged 13 commits intomainfrom
feat/diff
Apr 1, 2026
Merged

feat: add support for diff rendering, apply and reject#2312
Saul-Mirone merged 13 commits intomainfrom
feat/diff

Conversation

@Saul-Mirone
Copy link
Copy Markdown
Member

@Saul-Mirone Saul-Mirone commented Apr 1, 2026

  • I read the contributing guide
  • I agree to follow the code of conduct

Summary

This PR adds diff review support to milkdown. Users can compare two markdown documents side-by-side within the editor, see what changed, and accept or reject each change individually.

diff.mp4

What's new

@milkdown/plugin-diff — the headless diff engine. It computes changes between two ProseMirror documents using prosemirror-changeset (Myers diff), manages diff state (pending/rejected changes), and provides commands for accept/reject/clear operations. The editor is locked during review by default.

@milkdown/components/diff — the decoration layer that renders the diff UI. Deletions get strikethrough (inline) or a faded overlay with red dashed outline (block). Insertions are shown as green widgets with the new content serialized from the target document. Each change has Accept/Reject buttons.

Crepe integrationCrepeFeature.Diff feature flag, theme CSS (diff.css) with CSS variables for all 6 themes, and a DiffReview story in every Storybook theme variant.

Handling custom node views

ProseMirror's inline decorations can't penetrate custom node views (CodeMirror, image-block, table). To handle this, changes inside these nodes are merged into block-level replacements. Which node types get this treatment is configurable via customBlockTypes — no node type names are hardcoded in the core packages.

A custom token encoder makes prosemirror-changeset aware of atom node attribute changes (e.g. different image src), which the default encoder ignores.

Notable details

  • Range-based accept/reject commands (acceptDiffRange/rejectDiffRange) for merged block changes, so accepting a table change replaces the whole table at once instead of individual cells
  • snapToBlockBoundary walks up the node tree to find the right insertion point, so widgets inside blockquotes/lists render at the correct position
  • Reject uses fromB/toB coordinates directly (not indices) to avoid index drift after previous rejections
  • The diff auto-deactivates and unlocks the editor when all changes are resolved
  • Trailing empty paragraphs (editor placeholders) are filtered from the diff
  • Decoration set is cached and only rebuilt on diff state changes, not on every selection update

How did you test this change?

  • 37 vitest unit tests covering diff computation (text, images, code blocks, tables, lists) and plugin state (rejection filtering, range-based rejection)
  • 17 playwright e2e tests covering commands, inline diff, block diff, image diff, and table diff
  • Manual testing in Storybook across all 6 Crepe themes

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 1, 2026

⚠️ No Changeset found

Latest commit: 59e98f4

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 1, 2026

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

Project Deployment Actions Updated (UTC)
milkdown-storybook Ready Ready Preview, Comment Apr 1, 2026 1:32pm

Request Review

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 1, 2026

Open in StackBlitz

@milkdown/components

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/components@2312

@milkdown/core

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/core@2312

@milkdown/crepe

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/crepe@2312

@milkdown/ctx

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/ctx@2312

@milkdown/exception

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/exception@2312

@milkdown/kit

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/kit@2312

@milkdown/prose

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/prose@2312

@milkdown/transformer

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/transformer@2312

@milkdown/utils

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/utils@2312

@milkdown/react

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/react@2312

@milkdown/vue

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/vue@2312

@milkdown/plugin-automd

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/plugin-automd@2312

@milkdown/plugin-block

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/plugin-block@2312

@milkdown/plugin-clipboard

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/plugin-clipboard@2312

@milkdown/plugin-collab

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/plugin-collab@2312

@milkdown/plugin-cursor

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/plugin-cursor@2312

@milkdown/plugin-diff

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/plugin-diff@2312

@milkdown/plugin-emoji

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/plugin-emoji@2312

@milkdown/plugin-highlight

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/plugin-highlight@2312

@milkdown/plugin-history

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/plugin-history@2312

@milkdown/plugin-indent

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/plugin-indent@2312

@milkdown/plugin-listener

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/plugin-listener@2312

@milkdown/plugin-prism

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/plugin-prism@2312

@milkdown/plugin-slash

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/plugin-slash@2312

@milkdown/plugin-tooltip

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/plugin-tooltip@2312

@milkdown/plugin-trailing

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/plugin-trailing@2312

@milkdown/plugin-upload

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/plugin-upload@2312

@milkdown/preset-commonmark

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/preset-commonmark@2312

@milkdown/preset-gfm

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/preset-gfm@2312

@milkdown/theme-nord

npm i https://pkg.pr.new/Milkdown/milkdown/@milkdown/theme-nord@2312

commit: 59e98f4

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new diff-review capability to Milkdown (plugin + UI component) and wires it into Crepe, Storybook, and E2E so users can render diffs and accept/reject changes.

Changes:

  • Introduces @milkdown/plugin-diff with commands/state for starting diff review and accepting/rejecting changes.
  • Adds a diff decoration component (@milkdown/components/diff) to render inserted/removed content and per-change controls.
  • Integrates the feature into Crepe + Storybook demos and adds Playwright coverage.

Reviewed changes

Copilot reviewed 41 out of 44 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tsconfig.json Adds project reference for the new diff plugin package.
storybook/stories/plugins/diff.stories.ts New Storybook story demonstrating diff review + commands.
storybook/stories/plugins/diff.css Styling for diff decorations/controls used in the Storybook demo.
storybook/stories/crepe/setup.ts Extends Crepe Storybook setup with diff args + a diff review toolbar helper.
storybook/stories/crepe/nord.stories.ts Adds Crepe diff review story and hides diff args in non-diff stories.
storybook/stories/crepe/nord-dark.stories.ts Adds Crepe diff review story and hides diff args in non-diff stories.
storybook/stories/crepe/frame.stories.ts Adds Crepe diff review story and hides diff args in non-diff stories.
storybook/stories/crepe/frame-dark.stories.ts Adds Crepe diff review story and hides diff args in non-diff stories.
storybook/stories/crepe/crepe.stories.ts Adds Crepe diff review story and hides diff args in non-diff stories.
storybook/stories/crepe/crepe-dark.stories.ts Adds Crepe diff review story and hides diff args in non-diff stories.
pnpm-lock.yaml Adds workspace links for @milkdown/plugin-diff where needed.
packages/plugins/plugin-diff/vitest.config.ts Vitest config for the new plugin package tests.
packages/plugins/plugin-diff/vite.config.ts Vite build config for the new plugin package.
packages/plugins/plugin-diff/tsconfig.json TS build config and references for the new plugin package.
packages/plugins/plugin-diff/src/types.ts Defines diff state/config/action/range types.
packages/plugins/plugin-diff/src/index.ts Public exports and diff plugin bundle.
packages/plugins/plugin-diff/src/diff-plugin.ts ProseMirror plugin managing diff state + edit locking.
packages/plugins/plugin-diff/src/diff-config.ts Context config slice for diff (e.g. lock-on-review).
packages/plugins/plugin-diff/src/diff-compute.ts ChangeSet-based document diff computation with custom token encoding.
packages/plugins/plugin-diff/src/diff-commands.ts Commands for starting review and accepting/rejecting changes.
packages/plugins/plugin-diff/src/test/diff-plugin.spec.ts Unit tests for diff state helpers (pending/rejected filtering).
packages/plugins/plugin-diff/src/test/diff-compute.spec.ts Unit tests for diff computation across node types.
packages/plugins/plugin-diff/src/internal/with-meta.ts Metadata helper for plugin-diff plugins.
packages/plugins/plugin-diff/package.json New published package definition for @milkdown/plugin-diff.
packages/kit/tsconfig.json Adds TS reference to plugin-diff so kit can re-export it.
packages/kit/src/plugin/diff.ts Re-export entry for @milkdown/kit/plugin/diff.
packages/kit/src/component/diff.ts Re-export entry for @milkdown/kit/component/diff.
packages/kit/package.json Adds subpath exports for kit diff plugin + diff component.
packages/crepe/src/theme/common/style.css Includes diff theme CSS in the common Crepe theme bundle.
packages/crepe/src/theme/common/diff.css Adds Crepe theme styling for diff decorations/controls.
packages/crepe/src/feature/loader.ts Loads the new Crepe diff feature.
packages/crepe/src/feature/index.ts Adds CrepeFeature.Diff and config typing/defaults.
packages/crepe/src/feature/diff/index.ts Implements Crepe diff feature wiring (plugin + component config).
packages/components/tsconfig.json Adds TS reference to plugin-diff for the new diff component.
packages/components/src/diff/index.ts New diff component entry exporting config + decoration plugin.
packages/components/src/diff/diff-decoration-plugin.ts Renders diff decorations/widgets + per-change accept/reject controls.
packages/components/src/diff/config.ts Diff component configuration slice.
packages/components/package.json Adds @milkdown/components/diff export and dependency on plugin-diff.
e2e/tests/crepe/diff.spec.ts New Playwright coverage for diff review interactions.
e2e/src/data.ts Registers the new crepe-diff e2e case.
e2e/src/crepe-diff/main.ts E2E harness page exposing diff commands to tests.
e2e/src/crepe-diff/index.ts Declares the “Crepe Diff” e2e case metadata.
e2e/src/crepe-diff/index.html New e2e page entry for diff tests.
e2e/shim.d.ts Adds global typings for diff-related e2e helpers.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 43 out of 46 changed files in this pull request and generated 3 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 43 out of 46 changed files in this pull request and generated 3 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 43 out of 46 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 43 out of 46 changed files in this pull request and generated 3 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 43 out of 46 changed files in this pull request and generated 3 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Saul-Mirone Saul-Mirone marked this pull request as ready for review April 1, 2026 13:46
@Saul-Mirone Saul-Mirone added this pull request to the merge queue Apr 1, 2026
Merged via the queue into main with commit b456bac Apr 1, 2026
19 checks passed
@Saul-Mirone Saul-Mirone deleted the feat/diff branch April 1, 2026 13:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants