#3160·emdash

Inline Portable Text editor rewrites a `url`-identified custom block to `id`, dropping `url` (and the block modal can only ever insert `id`)

Author: akapugCreated Sep 16, 2026Updated Sep 16, 2026
Labelsbot:needs-attentionbot:task

Environment: emdash 0.38.0, @emdash-cms/admin 0.38.0, @emdash-cms/gutenberg-to-portable-text 0.38.0, Astro 6.4.8, pnpm.

What happens. src/components/InlinePortableTextEditor.tsx (published as source via the ./ui export) converts a custom block to ProseMirror at :625const { _type, _key, id, url, ...rest } = block; — folding url into attrs.id, and converts back at :299-311 as {...data, _type, _key, id}. url is in neither data nor the output, so any inline save rewrites {_type:"embed", url, …} to {_type:"embed", id, …}. The comment at :300 promises a lossless round trip.

This shape is first-party: @emdash-cms/gutenberg-to-portable-text dist/index.mjs:1008-1014 emits {_type:"embed", url, provider, html} for every WordPress embed block. The admin editor is immune because it stashes the original block, but its insert path hardcodes the identity field — @emdash-cms/admin/dist/index.js:22814, … : "id" — so a freshly inserted block can never be stored url-identified, and mints a stray id: "" beside declared fields.

Repro. Import a WordPress export containing a YouTube/Twitter embed. Render the page with <PortableText>, enable visual editing, type one character in any paragraph, save. The embed's url is gone, replaced by id.

Fix. Mirror customBlockIdentityField in the inline converter (keep the key the block arrived with), and derive the insert-path identity from the block schema rather than hardcoding "id".

Workaround we use. Our plugin declares its URL field as action_id: "id", and our renderer accepts id as url.