-
- Guests can now use the polling fallback on public channels — the channel pull and publish endpoints accept anonymous requests and let each channel's own access check decide
- A failing push transport no longer stops messages from reaching the other transports, so the polling fallback keeps working while a hub is down
- Collaborative editing now works on child pages of a hidden home route, instead of failing to find the page, when "Hide home route in URLs" is enabled (getgrav/grav-plugin-api#10)
- Save notifications now reach collaborators editing child pages of a hidden home route, so they no longer keep seeing phantom unsaved changes after a peer saves (getgrav/grav-plugin-admin2#59)
-
- New
sync:page-saved:<roomId>broadcast channel. Fires whenever a page is saved through the api plugin so peer editors in the same collab room can advance their dirty baseline and stop showing phantom unsaved changes. Subscribers needapi.pages.readon the page. Used by admin-next to fix getgrav/grav-plugin-admin2#25.
- New
-
- Collaborative editing now uses regular page permissions instead of a separate
api.collab.*permission. Anyone who can read or write a page via the API can do the same collaboratively — no extra grant needed. Fixes getgrav/grav-plugin-admin2#24.
- Collaborative editing now uses regular page permissions instead of a separate
-
- Fixed a 404 error when an editor subscribed to a page's save-notification channel before the first save had happened.
- Fixed a 403 error that wrongly blocked super admins from subscribing to a page's save-notification channel.
-
- New
Transport Preference Ordersetting in the Sync admin. Reorder which installed transport handles live delivery (e.g. put Ably above Mercure) without uninstalling plugins. The dropdown is populated from transports currently registered in this instance, so deployers only see options they actually have. - New SQLite storage backend for Yjs update logs and snapshots. One database per room under
user/data/sync/storage/, with WAL mode and writer-locked transactions — eliminates the file-lock contention the file backend can hit under heavy multi-user editing. storage.adapterdefaults toauto, which picks SQLite when thepdo_sqlitePHP extension is available and falls back to the file backend otherwise. Existing installs that haveadapter: filesaved keep using file storage.- Public pub/sub facade
$grav['sync']. Plugins can register channels and publish messages without coupling to a specific transport. - Three message types:
crdt(Yjs binary updates),broadcast(arbitrary payload with optional TTL replay),awareness(ephemeral presence). - Transport-provider registry. External plugins (Mercure, Ably) plug in via the new
onSyncRegisterTransportsevent. Built-in polling stays as the universal fallback. - Per-channel auth delegation via callback or the new
onSyncCheckAccessevent. - New
onSyncBeforeCrdtChannelRegisteredevent so plugins can attach proper permission gating to a page's CRDT channel before sync falls back to its permissive auto-register. - New endpoints
GET /sync/channels/pull?id=<channelId>andPOST /sync/channels/publish?id=<channelId>for the broadcast pub/sub model. Available under both the api-plugin route prefix and the legacy/sync/*dispatcher. The channel id rides in the query string (not a path segment) so colons in ids — common, e.g.comments-pro:blog/post-1— are not eaten by Grav's URI param parser. - New
onSyncResolveChannelevent. Fired by the pull/publish handler when an incoming request names a channel the in-process registry hasn't seen yet, giving owner plugins one last chance to lazy-register before sync 404s. - Legacy
/sync/*HTTP path now uses the api plugin's full auth chain (X-API-Token, Authorization Bearer) when api is loaded, in addition to session auth. The mutex with/api/v1/sync/*is unchanged; this enables forward-compat for sites that choose to expose both prefixes.
- New
-
- Capabilities response now lists every registered transport with id, name, priority, and supported message types, plus a
preferredfield. The existingpollingandpresencesub-blocks are unchanged.
- Capabilities response now lists every registered transport with id, name, priority, and supported message types, plus a
-
- Transport plugins (Mercure, Ably) now reliably land in the transport registry — previously they could register too late and only appear in the capabilities response as a stub entry.
- Presence room membership is no longer corrupted when three or more peers heartbeat at once. Three browsers editing the same page used to each see a different subset of peers (one would even see only itself) because concurrent load-modify-save writes on the shared presence cache key clobbered each other; an exclusive flock around the heartbeat critical section keeps every peer's entry intact.
- Polling transport's pull/publish URLs now honor Grav's base path, and route through the api plugin's prefix when api is loaded — fixes 404s on Grav installs mounted at a subpath.
- Legacy
/sync/*dispatcher now matches correctly on subpath installs (it stripped the wrong portion of the path before checking the/sync/prefix).
-
- The CRDT path used by editor-pro is unchanged. All existing endpoints, events (
onSyncUpdate,onSyncAwareness,onSyncCapabilities), and storage layouts remain compatible.
- The CRDT path used by editor-pro is unchanged. All existing endpoints, events (
-
- Sync now runs on Grav 1.7 in addition to 2.0. The api plugin is no longer a hard dependency. When api is installed sync's endpoints are still served at
/api/v1/sync/*exactly as before; otherwise sync provides its own minimal HTTP layer at/sync/*. - Self-contained PSR-7 HTTP base class (
Grav\Plugin\Sync\Http\AbstractSyncController) replaces the prior dependency on the api plugin'sAbstractApiController. Plugin authors extending sync's controllers should target the new base class.
- Sync now runs on Grav 1.7 in addition to 2.0. The api plugin is no longer a hard dependency. When api is installed sync's endpoints are still served at
-
- Composer dependencies updated to declare PSR-7 message and server-request packages directly. Run
composer installinside the plugin after updating.
- Composer dependencies updated to declare PSR-7 message and server-request packages directly. Run
-
- Sync data no longer lands inside
user/pages/. Per-room logs and snapshots used to be written next to the page they belonged to (e.g.user/pages/02.typography/.sync/), and the page route was used as a literal folder name — so language variants ended up in spurious siblings likeuser/pages/typography.en/, which Grav then listed in admin as extra pages. Storage now lives underuser/data/sync/<md5(route)>/with ameta.jsonfor reverse lookup, and language is encoded as a filename suffix (default.en.log) rather than baked into the folder name. Existing rooms underuser/pages/**/.sync/should be deleted; the next edit reseeds cleanly. - Room ids no longer fold the language into the route segment. The format is now
<route>@<template>(default language) or<route>@<template>@<lang>(explicit), matching the cleanly separated route/template/lang model the storage layer already used internally. Routes carrying a.<lang>suffix were a footgun for both the storage path and clients computing topic names.
- Sync data no longer lands inside