Minor Changes
-
#679
493e317Thanks @drudge! - Adds arepeaterBlock Kit element: array-of-objects with scalar sub-fields, drag-to-reorder, and collapsible item cards. Plugin block forms can now capture repeating data (FAQ rows, carousel slides, card grids) inline in the portable-text editor. -
#779
e402890Thanks @ascorbic! - Addssettings_getandsettings_updateMCP tools so agents can read and update site-wide settings (title, tagline, logo, favicon, URL, posts-per-page, date format, timezone, social, SEO).settings_getresolves media references (logo/favicon/seo.defaultOgImage) to URLs;settings_updateis a partial update that preserves omitted fields. Newsettings:read(EDITOR+) andsettings:manage(ADMIN) API token scopes back the tools, with matching options in the personal API token settings UI. -
#777
3eca9d5Thanks @ascorbic! - Behavior change — MCPtaxonomy_list_termsnow uses an opaque base64 keyset cursor over(label, id)instead of the previous raw term-id cursor. The new cursor is robust to concurrent term deletion: it encodes a position in sort space rather than a reference to a specific row. MCP clients that persisted page cursors across this upgrade should drop them and restart pagination — pre-upgrade cursors will returnINVALID_CURSOR.Adds parent-chain validation to
taxonomy_create_term(previously onlytaxonomy_update_termvalidated): rejects non-existent parents, cross-taxonomy parents, self-parent on update, cycles on update, and parent chains exceeding 100 ancestors. Existing taxonomies with chains over the depth limit continue to function but cannot accept new descendants until the chain is shortened. -
#675
b6cb2e6Thanks @eyupcanakman! - Renders local media through storagepublicUrlwhen configured.EmDashImageand the Portable Text image block now call a newlocals.emdash.getPublicMediaUrl()helper, so R2 and S3 deployments with a custom domain serve images from that domain.S3Storage.getPublicUrlnow returns the/_emdash/api/media/file/{key}path when nopublicUrlis set (previously{endpoint}/{bucket}/{key}). -
#398
31333dcThanks @simnaut! - Adds pluggable auth provider system with AT Protocol as the first plugin-based provider. Refactors GitHub and Google OAuth from hardcoded buttons into the sameAuthProviderDescriptorinterface. All auth methods (passkey, AT Protocol, GitHub, Google) are equal options on the login page and setup wizard.
Patch Changes
-
#777
3eca9d5Thanks @ascorbic! - Fixes MCP ownership checks failing with an internal error on content that has noauthorId(seed-imported rows). Admins and editors can now edit, publish, unpublish, schedule, and restore such items; users with only own-content permissions get a clean permission error. -
#777
3eca9d5Thanks @ascorbic! - Fixes content create / update silently accepting invalid data: required fields are now enforced, select / multiSelect values must match the configured options, and reference fields must resolve to a real, non-trashed target. Errors surface with a structuredVALIDATION_ERRORcode and a message naming every offending field. -
#670
37ada52Thanks @segmentationfaulter! - Change text direction of input fields and tiptap editor depending upon the language entered -
#688
0557b62Thanks @corwinperdomo! - Fixes the Settings > Email admin page so activeemail:beforeSend/email:afterSendmiddleware plugins are listed (previously always empty). AddsHookPipeline.getHookProviders()for enumerating non-exclusive hook providers. -
#673
5a581d9Thanks @mvanhorn! - Fixes WordPress media import to emit relative/_emdash/api/media/file/...URLs instead of absolute ones, matching every other media endpoint. Imported media is now recognized byINTERNAL_MEDIA_PREFIXfor enrichment, and no longer pins URLs to the origin that happened to serve the import request (breaking renders on a different port or behind a reverse proxy). -
#750
0ecd3b4Thanks @edrpls! - Make the admin collection list column headers sortable.Title,Status,Locale, andDateare now clickable buttons that toggle direction; the current sort state is exposed viaaria-sorton the<th>so screen readers announce it correctly.The server's
orderByfield whitelist now acceptsstatus,locale, andnamealongside the existing date fields — unchanged from a security standpoint, the repo still rejects unknown field names to prevent column enumeration.Callers of
<ContentList>that don't passonSortChangerender the previous static-label headers, so legacy integrations (e.g. the content picker) are unaffected. -
3138432Thanks @r2sake! - Fixes hydration of the inline PortableText editor on pnpm projects by aliasinguse-sync-external-store/shimto the mainuse-sync-external-storepackage. The shim is a CJS-only React<18 polyfill imported transitively by@tiptap/react; under pnpm's virtual store Vite cannot pre-bundle it, and the browser receives rawmodule.exportswhich fails to load as ESM (SyntaxError: ... does not provide an export named 'useSyncExternalStore'). The aliases redirect to React's built-inuseSyncExternalStore(peer-dep floor is React 18), so users no longer need to add the workaround themselves inastro.config.mjs. -
#755
70924cdThanks @mvanhorn! - Fixes the WordPress importer so collections created mid-import are visible to the subsequent execute phase.POST /_emdash/api/import/wordpress/preparenow callsemdash.invalidateManifest()when it creates new collections or fields. Without this, the DB-persisted manifest cache (emdash:manifest_cachein theoptionstable) stays stale and theexecuterequest reportsCollection "<slug>" does not existfor every item destined for a freshly created collection — a bug that survived dev-server restarts and required manually deleting the cache row. -
#757
1f0f6f2Thanks @ascorbic! - Removes two redundant in-scope database queries from the FTS verify-and-repair path. The inner block re-fetched searchable fields and search config that were already loaded in the outer scope of the same method. No behavior change. -
#777
3eca9d5Thanks @ascorbic! - Fixes paginated list endpoints silently returning the first page when given a malformed cursor. Bad cursors now produce a structuredINVALID_CURSORerror so client pagination bugs surface immediately.Note for plugin authors: the low-level
decodeCursorexport fromemdash/database/repositoriesnow throwsInvalidCursorErroron invalid input instead of returningnull. Direct callers (rare — most code usesfindMany-style helpers that handle this internally) should wrap the call intry/catchor migrate to the higher-level helpers. -
#777
3eca9d5Thanks @ascorbic! - Fixesschema_create_collectionMCP tool to apply its documented default of['drafts', 'revisions']forsupportswhen omitted. -
#189
f5658f0Thanks @Sayeem3051! - Add url and email plugin setting field types (Issue #175) -
#777
3eca9d5Thanks @ascorbic! - Preserves structured error codes through MCP tool responses. Errors returned by MCP tools now include a stable[CODE]prefix in the message text and a_meta.codefield on the response envelope, so MCP clients can distinguish failure modes (e.g. NOT_FOUND, CONFLICT, VALIDATION_ERROR) instead of seeing only a generic message. -
#777
3eca9d5Thanks @ascorbic! - Fixesrevision_restorefor collections that support revisions: restore now creates a new draft revision from the source revision's data and updatesdraft_revision_id, leaving the live columns untouched. Previously, restore overwrote the live row directly and left any pending draft unchanged, opposite to the documented contract ("Replaces the current draft..."). The response is also hydrated so the returneddatareflects the post-restore state.Behavior is unchanged for collections that do not support revisions.
-
#734
cf1edaeThanks @huckabarry! - Preserve clearer error logging and run sandboxedafter()content hook tasks in parallel when deferred plugin hooks execute after save and publish. -
#794
b352e88Thanks @ascorbic! - Sanitises thesnippetfield returned by thesearch()API so it is safe to render withset:html/innerHTML. Previously SQLite's FTS5snippet()function spliced literal<mark>tags around matched terms but left the surrounding text unescaped, meaning a post title likeHello <script>alert(1)</script>would render as live markup. Templates and components rendering snippets directly were exposed; the in-treeLiveSearchcomponent already worked around this client-side. Snippets now contain only HTML-escaped source text plus literal<mark>...</mark>highlight tags, matching the documented contract. -
#183
da3d065Thanks @masonjames! - Fixes Astro dev to use the built admin package for external app installs while keeping source aliasing for local monorepo development. -
#777
3eca9d5Thanks @ascorbic! - Tightens conflict-error matchers inhandleContentCreateandhandleContentUpdate. Both paths now match specifically on"unique constraint failed"or"duplicate key"(avoiding false positives where the word "unique" appears in unrelated error text), and produce sanitizedSLUG_CONFLICT/CONFLICTmessages so raw database error text — including Postgres-internal index names — no longer leaks to API consumers. Clients that pattern-match the previous unsanitized messages will see normalized text instead. -
#777
3eca9d5Thanks @ascorbic! - Fixestaxonomy_listexposing collection slugs for collections that no longer exist. Orphaned slugs are filtered out so the response stays consistent withschema_list_collections. -
#777
3eca9d5Thanks @ascorbic! - Fixescontent_unpublishso thatpublishedAtis cleared when an item is unpublished. -
#608
47978b5Thanks @drudge! - Fixes/_emdash/api/widget-areas/*endpoints returning raw DB rows (snake_case fields,contentas a JSON string) instead of the transformedWidgetshape. Admin UI expectscontentto already be a parsed PortableText array andcomponentId/componentProps/menuNamein camelCase, so expanding a content widget in/_emdash/admin/widgetsproduced an empty editor. All four route handlers (GET /widget-areas,GET /widget-areas/:name,POST /widget-areas/:name/widgets,PUT /widget-areas/:name/widgets/:id) now run their results throughrowToWidget, which was made module-exported. -
#777
3eca9d5Thanks @ascorbic! - Addstaxonomies:manageandmenus:manageAPI token scopes for fine-grained control over taxonomy and menu mutations via MCP and REST. Existing tokens withcontent:writecontinue to work for those operations:content:writenow implicitly grantsmenus:manageandtaxonomies:manageso PATs issued before the split keep their effective permissions. The reverse implication does not hold — a token with onlymenus:managecannot create or edit content. -
Updated dependencies [
86b26f6,493e317,e998083,37ada52,acab807,0ecd3b4,4c9f04d,e402890,ed4d880,31333dc,3eca9d5]:- @emdash-cms/admin@1.0.0
- @emdash-cms/auth@1.0.0
- @emdash-cms/auth-atproto@1.0.0
- @emdash-cms/gutenberg-to-portable-text@1.0.0