Skip to content

fix(core): preserve plugin block payloads in visual-editing inline editor#805

Merged
ascorbic merged 1 commit intomainfrom
fix/visual-editing-pluginblock-roundtrip
Apr 28, 2026
Merged

fix(core): preserve plugin block payloads in visual-editing inline editor#805
ascorbic merged 1 commit intomainfrom
fix/visual-editing-pluginblock-roundtrip

Conversation

@ascorbic
Copy link
Copy Markdown
Collaborator

@ascorbic ascorbic commented Apr 28, 2026

What does this PR do?

Fixes silent data loss in the visual-editing inline editor (InlinePortableTextEditor) for plugin-contributed Portable Text block types.

The bug

For any unknown PT block type (e.g. marketing.hero with headline/subheadline/CTAs etc.), opening a page in visual edit mode silently destroyed every field except id. Combined with the absence of a registered pluginBlock TipTap node, ProseMirror also filtered the node out of the document on load -- and the next save persisted the disappearance.

A marketing.hero block with seven fields became { _type, _key, id: "" } after a single edit-mode visit + save.

The fix

Three small changes, all in packages/core/src/components/InlinePortableTextEditor.tsx:

  1. Register a minimal pluginBlock TipTap node so ProseMirror accepts it on load instead of filtering. Atom + selectable: true + draggable: true. Renders as a read-only placeholder ("Plugin block: marketing.hero (edit in admin)") with dashed border styling.
  2. Capture every non-well-known field into a data attribute on PT → PM (filtering _-prefixed keys to prevent accumulation across edit cycles).
  3. Spread data back out on PM → PT so the block round-trips losslessly.

This mirrors the lossless pattern already in the admin editor at PortableTextEditor.tsx:572-588 (PT → PM) and :298-305 (PM → PT). Closely related to #646's diagnosis, which correctly identified the bug for the inline editor specifically -- this PR ports the round-trip half of that proposal. Editing in visual mode remains intentionally out of scope (admin is the editing surface; visual editing just needs to not destroy data).

DOM attributes (blockType, id, data) are hidden from the rendered DOM via rendered: false so they live only on the PM node. Otherwise serialised <div data="[object Object]" ...> would leak into the page.

Type of change

  • Bug fix
  • Feature
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes (4 pre-existing diagnostics in packages/blocks/src/validation.ts, unchanged from main)
  • pnpm test passes (4498 tests across all packages)
  • pnpm format has been run
  • I have added/updated tests -- 10 new unit tests in tests/unit/components/inline-portable-text-plugin-block.test.ts covering PT → PM, PM → PT, and full round-trip including a marketing.hero-shaped block, nested objects, and repeated round-trips
  • User-visible strings -- one new string ("Plugin block: <type> (edit in admin)") not localised; this surface is dev-facing while the editor surface is being designed (worth a follow-up if visual editing of plugin blocks is added later)
  • I have added a changeset
  • New features link to an approved Discussion -- N/A, bug fix

AI-generated code disclosure

  • This PR includes AI-generated code

Screenshots / test output

Verified end-to-end with the marketing template's home page on the feat/marketing-template-editable-blocks branch (which has the marketing-blocks plugin):

  • Before: visual edit mode on / shows nothing where the four marketing blocks should be -- they've been silently filtered. Next save would persist the disappearance.
  • After: visual edit mode shows four read-only placeholder cards -- "Plugin block: marketing.hero (edit in admin)", and three more for features/testimonials/faq. Edits to the surrounding content don't disturb the blocks. The full block payload (headline, primaryCta, etc.) is preserved on the PM node and round-trips back to the API on save.
Tests: 10 new
✓ inline editor: PT → PM (unknown blocks)
  ✓ captures every non-well-known field into data
  ✓ strips _-prefixed keys from data to prevent accumulation
  ✓ uses url as a fallback for id
✓ inline editor: PM → PT (pluginBlock)
  ✓ spreads data fields back into the PT block
  ✓ data fields cannot overwrite _type or _key
  ✓ falls back blockType to 'embed' when missing
  ✓ handles non-object data gracefully
✓ inline editor: round-trip preserves plugin block payloads
  ✓ a marketing.hero-shaped block survives PT → PM → PT intact
  ✓ nested objects in unknown fields survive round-trip
  ✓ repeated round-trips are stable (no _-key leakage)

…itor

The inline editor used by visual editing (`InlinePortableTextEditor`) had
two bugs that combined into silent data loss:

1. PT → PM coerced unknown block types into a `pluginBlock` ProseMirror
   node carrying only `{ blockType, id }`, dropping every other field.
2. The `pluginBlock` node type was never registered as a TipTap extension,
   so ProseMirror's schema silently filtered the node out on load.
3. PM → PT serialised back as `{ _type, _key, id }`, persisting the loss.

For a `marketing.hero` block with seven fields, opening the page in edit
mode and pressing save would persist `{ _type, _key, id: "" }` -- the
block's content gone.

Fixes:

- Register a minimal `pluginBlock` TipTap node so ProseMirror accepts it.
  Renders as a read-only placeholder ("Plugin block: <type> (edit in
  admin)") with dashed border styling. Editing plugin blocks remains an
  admin-side feature; the visual editor just preserves them.
- Capture every non-well-known field into a `data` attribute on PT → PM
  (filtering `_`-prefixed keys to prevent accumulation), and spread it
  back out on PM → PT. Mirrors the lossless pattern in the admin editor
  (PortableTextEditor.tsx:572-588 and :298-305).
- Hide the `blockType`, `id`, and `data` attrs from DOM rendering so
  they live only on the PM node (no `data="[object Object]"` leakage).

Tests: 10 new unit tests covering both directions plus full PT → PM → PT
round-trips for marketing.hero-shaped blocks, nested objects, and
repeated round-trips (no `_`-key accumulation).
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 28, 2026

🦋 Changeset detected

Latest commit: 139fed6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/admin Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
create-emdash Patch
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

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

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-perf-coordinator 139fed6 Apr 28 2026, 11:56 AM

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
docs 139fed6 Apr 28 2026, 11:56 AM

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 28, 2026

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@805

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@805

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@805

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@805

emdash

npm i https://pkg.pr.new/emdash@805

create-emdash

npm i https://pkg.pr.new/create-emdash@805

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@805

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@805

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@805

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@805

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@805

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@805

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@805

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@805

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@805

commit: 139fed6

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-cache 139fed6 Apr 28 2026, 11:58 AM

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-playground 139fed6 Apr 28 2026, 11:58 AM

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-i18n 139fed6 Apr 28 2026, 11:58 AM

@ascorbic ascorbic merged commit 6a4e9b8 into main Apr 28, 2026
34 checks passed
@ascorbic ascorbic deleted the fix/visual-editing-pluginblock-roundtrip branch April 28, 2026 12:19
@emdashbot emdashbot Bot mentioned this pull request Apr 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant