github emdash-cms/emdash @emdash-cms/cloudflare@1.5.0

Minor Changes

  • #4695 22b87ed Thanks @khoinguyenpham04! - Adds real-time co-editing to the admin editor, off by default for now. With coEditing: true, in collections with revisions, several people can edit an entry at the same time: each person sees the others' avatars, the field each one is in and their cursors in rich text, concurrent rich-text edits merge, and undo reverts only your own edits. A room for each entry saves everyone's edits together, with the same permissions, validation, hooks and revisions as other saves, and changes from the REST API, MCP, the CLI, the visual editor and plugins appear in open editors.

    Without co-editing, opening an entry takes an edit lock, and everyone else can only Open read-only or Take over. With it on, edit locks apply only in single-editor mode, which the editor uses, for example, in collections without revisions, on hosts that can't run rooms, and in browsers that can't open WebSocket connections. Admin tabs opened before co-editing is turned on keep single-editor mode until they reload. Edit with other people describes what editors see.

    • Saved appears once a save holds all of your own edits. After 5 seconds without a connection, the button shows Offline: typing continues and merges on reconnect, and Preview, publishing and the other operations are disabled until then.
    • Publishing, scheduling, unpublishing, discarding changes, restoring a version and moving to Trash first save everyone's edits, then run.
    • Fields other than rich text keep one of two simultaneous changes, except bylines, reference fields and taxonomy terms, which keep what each person adds and removes. Turn into, dragging, deleting, wrapping, joining or indenting a block loses text someone else types inside it at the same moment.
    • Edits that can't merge are saved as a recovery revision.

    Public pages run no new queries, with co-editing on or off. Where rooms run, opening an entry adds a join request and a WebSocket connection. A room saves 2 seconds after the latest change, and at least every 10 seconds while people keep typing, however many people edit. Measured on Node.js with an in-memory SQLite database, saving a 2,000-word entry took 2.27 ms of CPU time at the median (3.83 ms at the 95th percentile), about two thirds of it the site's own draft save. Merging a write from outside the editor took 0.80 to 1.19 ms, and a notification with nothing to merge 0.67 ms (CPU time).

    What should I do?

    Nothing, unless you want co-editing. To turn it on, set the option and rebuild and deploy the site:

    emdash({
    	coEditing: true,
    });

    On Node.js with @astrojs/node, in standalone or middleware mode, co-editing runs in the site's own process and the site starts as before. Check these cases first, which Co-editing on Node.js covers in detail:

    • A reverse proxy must forward WebSocket upgrades on /_emdash/api/content/{collection}/{id}/room. Otherwise each browser session falls back to single-editor mode after three failed connections, and the server logs a [co-editing] warning.
    • Several processes on one database: route each room path to one process, for example by hashing the path, or leave co-editing off.
    • node:cluster workers, including PM2's cluster mode, keep single-editor mode.
    • Another WebSocket handler on the same server must leave room paths alone. Replace a ws server created with { server }, express-ws or @fastify/websocket with a ws server created with noServer: true that handles only your paths, or leave co-editing off.
    • On Node.js 22.16 to 22.20, 23, and 24.0 to 24.8, a server whose only upgrade listener is EmDash's answers upgrade requests on other paths, such as Upgrade: h2c, with 404. Node.js 22.21 and later, and 24.9 and later, serve them as before.
    • While rooms are open, EmDash handles SIGTERM and SIGINT and stops the process up to 5 seconds later. Give process managers a kill timeout over 5 seconds, such as PM2's kill_timeout, which defaults to 1.6 seconds.

    On Cloudflare, sites created from this release's templates already have the binding and the room exports, so they need only coEditing: true. For an existing site, also add the binding and the exports entry to wrangler.jsonc, as Co-editing on Cloudflare shows:

    "durable_objects": { "bindings": [{ "name": "EMDASH_ROOMS", "class_name": "EmDashRoom" }] },
    "exports": { "EmDashRoom": { "type": "durable-object", "storage": "sqlite" } }

    A Worker that uses the migrations array, which can't be combined with exports, adds { "tag": "emdash-rooms", "new_sqlite_classes": ["EmDashRoom"] } to it instead. Then re-export the room classes from the Worker entry point:

    import handler, {
    	createScheduledHandler,
    	EmDashRoom,
    	EmDashRoomSite,
    	PluginBridge,
    } from "@emdash-cms/cloudflare/worker";
    
    export { EmDashRoom, EmDashRoomSite, PluginBridge };
    
    export default {
    	...handler,
    	scheduled: createScheduledHandler(),
    } satisfies ExportedHandler;

    A Worker with its own fetch calls handleRoomUpgrade(request, env) first and returns its response when it isn't null. A site without the binding keeps single-editor mode.

    Changes for API clients and plugins

    These apply only on sites with co-editing on.

    • While people co-edit an entry, it has no edit lock: writes that got 409 ENTRY_LOCKED land and merge into the editor. Writers that send _rev can get 409 CONFLICT more often, because the room saves every few seconds, and MCP publish, unpublish, schedule and discard conflict while the room holds unsaved edits.
    • The lock route's POST deletes user locks while a room holds the entry, instead of granting one.
    • A room saves as one user, the actor, but a save can hold several people's edits. A hook that checks the actor's role before accepting content should check everyone in editedBy. In a room, content:beforeSave hooks get only the fields a save changed, as they do for visual-editor saves.
    • While a room is open, editor-draft requests skip the baseRevision check, and the editor refuses a plugin's patch only when a field it changes was edited after the capture.

    These apply on every site, with co-editing on or off:

    • PUT /_emdash/api/content/{collection}/{id} takes an optional expected object with the bylines and reference selections (references, every page of a field) that a change was based on, as a read returned them. POST /_emdash/api/content/{collection}/{id}/terms/{taxonomy} takes an optional expected list of term ids. When a stored list differs, nothing is written and the request fails with 409 LIST_CONFLICT: read the entry again, apply the change to the list it holds now, and retry. Requests without expected work as before.
    • POST /_emdash/api/content/{collection}/{id}/room joins an entry's room. It needs a signed-in session rather than an API token, and answers { "mode": "single-editor" } unless the site runs co-editing.
    • A migration adds the _emdash_rooms and _emdash_room_updates tables, which hold co-editing rooms. Site transfer never copies them.
    • GET and POST /_emdash/api/content/{collection}/{id}/lock responses include a room field, which is true only while a co-editing room holds the entry.

Patch Changes

  • #4555 b14c091 Thanks @emdash-autofix! - Fixes Cloudflare Access logins without identity-provider groups, such as one-time PIN logins, failing when roleMapping is configured. These logins now receive defaultRole (30 if not configured).

  • #4582 2abe403 Thanks @swissky! - Fixes @emdash-cms/cloudflare installing its own copy of emdash when the site has a different emdash version. The adapter now declares emdash as a peer dependency with its exact matching version, so a site always has a single emdash.

    Keep both packages on the same version and update them together:

    npm install emdash@latest @emdash-cms/cloudflare@latest

    A project with mismatched versions now fails npm install with an ERESOLVE error instead of installing a second copy. pnpm and Yarn print a peer dependency warning, and astro build and astro dev warn as before.

  • #4716 b766773 Thanks @emdash-autofix! - Fixes the preview path for Cloudflare Stream videos stuck at 0:00 in the Media Library detail panel. The panel uses Stream's hosted player instead of the browser's native HLS/DASH support. Stream preview URLs ending in /watch are converted to the embeddable /iframe URL. Media providers can supply an optional playerUrl for admin video previews; providers without one keep native video playback.

    If you use Astro's content security policy, allow the Stream player host in frame-src so the preview can load. EmDash's default admin policy already allows HTTPS frames.

  • #3115 daa6164 Thanks @dchaudhari7177! - Fixes emdash migrate on D1 failing with no hint about how to select a database. When neither --d1 nor --wrangler-config is given, the error now names both options and the binding the build expects, and says that a Wrangler config in the project root is not read unless it is named.

  • #4308 4df65eb Thanks @khoinguyenpham04! - Updates @cloudflare/kumo to 2.15.0. These packages now require exactly @cloudflare/kumo@2.15.0, so sites and plugins that install Kumo themselves should upgrade to that version to match the admin's prebuilt styles. Update emdash and the Forms, Field Kit, AI Moderation and AI Search plugins together: their earlier releases require Kumo 2.6.0, and these plugin releases need an emdash release with this change. Updating only one side leaves Kumo 2.15 next to an admin built for 2.6.

    In the admin, Select menus now open below their field instead of over it, and text sized text-base or text-lg has a taller line (1.5), so labels and headings take slightly more height. Kumo's DropdownMenu now spaces item icons itself, so plugin admin pages should remove any margin classes such as me-2 from the elements they pass as an item's icon.

  • #4668 4d4165c Thanks @khoinguyenpham04! - Fixes ctx.content.delete() in sandboxed plugins, on Cloudflare and with @emdash-cms/sandbox-workerd, so it moves entries to Trash as the admin does. Sites with an objectCache no longer keep serving the trashed entry from the cache, and the entry's updated_at and _rev no longer change.

  • #4712 9e3e34d Thanks @emdash-autofix! - Raises the default sandbox subrequest allowance from 10 to 30 per invocation, allowing plugins to make more settings, storage, and HTTP bridge calls before reaching the budget. On Cloudflare, 30 bridge calls is the maximum permitted by the platform's Worker-invocation cap. The Node.js workerd runner uses the same default value but does not enforce subrequest limits.

    CPU and wall-time limits are unchanged, and existing explicit limits.subrequests overrides still apply. No configuration changes are required.

    To retain the previous ceiling, supply a custom runner module through emdash({ sandboxRunner: ... }). Its exported createSandboxRunner factory must pass limits: { subrequests: 10 } in the SandboxOptions supplied to CloudflareSandboxRunner or createSandboxRunner from @emdash-cms/cloudflare/sandbox. These are runtime-only APIs; neither sandbox() nor emdash() accepts a direct limits option. See the custom runner example.

  • #4277 26528bb Thanks @emdash-autofix! - Fixes ctx.kv values disappearing when a plugin with the same ID moves between plugins: [] and sandboxed: []. This storage migration requires a coordinated upgrade; it cannot be deployed as a rolling upgrade.

    Both sandbox runners share the in-process KV store. A database migration copies existing non-settings: sandbox values and revisions without overwriting in-process values, warns about conflicting plugin IDs and keys, and retains the original sandbox rows for recovery. Uninstalling a marketplace or registry plugin with data deletion removes its shared KV values and settings; failed-install rollback preserves existing data, including partial writes made by an installation hook before it fails. Plugin-test KV fixtures and inspection use the shared store; use the settings inspection API for settings: keys.

    What should I do?

    1. Back up the database.
    2. Stop all application instances and plugin jobs that write to it.
    3. Upgrade emdash and your sandbox runner together, then build the site without starting it.
    4. Run pnpm emdash migrate against the reviewed database target.
    5. Resolve conflicting KV copies before plugins can write again.
    6. Start or deploy the upgraded site.

    Do not rely on default auto migrations for this upgrade: they start plugins immediately after migrating, leaving no conflict-review window. Mixed package versions are not automatically rejected. See the plugin upgrade guide for target selection, recovery queries, and rollback instructions. Downgrading packages alone hides post-upgrade KV writes; restoring the pre-upgrade database backup discards all site changes made since it was taken.

  • Updated dependencies [c108e7b, 1eedf99, 118dcf9, 32eab5c, 40e4400, e79a7ff, 7169073, 2abe403, 22b87ed, e8ba1b8, 39a9ccb, 007f71c, b766773, ddc4e82, 638cbaf, 678a0c2, 7d48e95, 18844a7, 8418def, fbddae0, f5aab79, 5137be5, 679ad6c, 612569b, b331cf3, a17a4b3, 786fd09, 97aa98a, f8462af, 55273d7, 53b4f32, 55964d5, 235a11a, 4ccc279, ab2c40b, 98e0924, e30df1e, caca761, 7159eda, 9699645, dd4ae19, 82eb778, 73c9b70, 3f9c77b, 4d4165c, e25d2ee, 5a9f1bd, 6331f6b, 19c4f28, fc44f40, df0f952, 60fef17, b9fc5f2, dfbb9fc, 5605cec, 9aa2259, b528fc0, 752cbcd, b8886d9, c095458, 8f397e0, b436669, 4cb8305, 006cfc0, 4d4165c, 0c57c94, 56c801c, aef09ae, 1f46f0d, c9696af, 4d4165c, 9db2de8, 1357172, 2c2a1e8, 532e78b, 5845694, 284fa80, c9dffb7, 2f64c3c, 25574da, 34a8e5a, 3a13512, 1cf644e, fac4008, c54ca68, e21e01e, f298866, dafb551, 3d569ab, 6ee8a5f, d541993, 34a6816, 88a61f8, 0008289, a7cf38f, 76c1936, 222658c, c2792d5, 064b566, 96945d3, 26528bb, a107170, c77bfb1, 776c95e, 44daa08, 746db9f, 652d957]:

    • emdash@1.5.0

Don't miss a new emdash release

NewReleases is sending notifications on new releases.