Skip to content

RTC: Add session activity notifications#75790

Closed
shekharnwagh wants to merge 7 commits intoWordPress:fix/sync-stale-crdt-doc-on-savefrom
Automattic:add/session-activity-notifications
Closed

RTC: Add session activity notifications#75790
shekharnwagh wants to merge 7 commits intoWordPress:fix/sync-stale-crdt-doc-on-savefrom
Automattic:add/session-activity-notifications

Conversation

@shekharnwagh
Copy link
Copy Markdown
Contributor

@shekharnwagh shekharnwagh commented Feb 20, 2026

What?

Closes #75323

Depends on #75975

Adds snackbar notifications for real-time collaboration session activity: when a collaborator joins, leaves, or saves the post.

Why?

Collaborator avatars in the top bar show who's present, but they're easy to miss. There's no active notification when someone joins, leaves, or saves — you'd only notice by watching the avatars constantly.

How?

  • Adds a useCollaboratorNotifications hook that compares the current and previous collaborator lists to detect joins, leaves, and remote saves.
  • Broadcasts save events to other collaborators via a new lastSaveEvent field on the awareness state.
  • Detects leaves using the isConnected property transition rather than list removal, so the notification fires when the avatar greys out (not after the 5s cleanup delay).
  • Skips notifications during initial load to avoid spurious toasts for users already present.
  • Notification types can be toggled via the editor.collaborationNotifications WordPress filter.

Testing Instructions

  1. Go to Settings > Writing > Enable real-time collaboration.
  2. Open a post for editing in two different browser tabs (or two different browsers for different users).
  3. Verify a "X has joined the post" snackbar appears in the first tab when the second tab opens the post.
  4. Close the second tab. After ~30 seconds, verify a "X has left the post" snackbar appears in the first tab.
  5. With both tabs open, save the post from the second tab. Verify a "Draft saved by X" snackbar appears in the first tab.
  6. Publish the post from the second tab. Verify a "Post updated by X" snackbar appears in the first tab.
  7. Verify no notification appears for your own saves.
  8. Verify no duplicate notifications appear on page load for users already present.

Testing Instructions for Keyboard

No new interactive UI elements are added. Snackbar notifications are announced to screen readers automatically via the existing notice system.

Screenshots or screencast

Screenshot 2026-02-24 at 7 11 17 PM
Notification when a collaborator has left

@github-actions github-actions bot added [Package] Core data /packages/core-data [Package] Editor /packages/editor labels Feb 20, 2026
@shekharnwagh shekharnwagh force-pushed the add/session-activity-notifications branch from 4bf3ffe to d3119e6 Compare February 23, 2026 19:10
@shekharnwagh shekharnwagh self-assigned this Feb 24, 2026
@shekharnwagh shekharnwagh force-pushed the add/session-activity-notifications branch from d3119e6 to d6646ec Compare February 24, 2026 13:36
@shekharnwagh shekharnwagh added [Feature] Real-time Collaboration Phase 3 of the Gutenberg roadmap around real-time collaboration [Type] Feature New feature to highlight in changelogs. labels Feb 24, 2026
@shekharnwagh shekharnwagh force-pushed the add/session-activity-notifications branch 2 times, most recently from 5b9ff52 to 18e7c87 Compare February 24, 2026 13:59
@shekharnwagh shekharnwagh marked this pull request as ready for review February 24, 2026 16:04
@shekharnwagh shekharnwagh requested a review from nerrad as a code owner February 24, 2026 16:04
@github-actions
Copy link
Copy Markdown

github-actions bot commented Feb 24, 2026

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: shekharnwagh <[email protected]>
Co-authored-by: chriszarate <[email protected]>
Co-authored-by: ingeniumed <[email protected]>
Co-authored-by: smithjw1 <[email protected]>
Co-authored-by: maxschmeling <[email protected]>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

* @param postId The ID of the post being edited.
* @param postType The post type of the post being edited.
*/
export function useCollaboratorNotifications(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already track entity save events in the Y.Doc state map. We could listen and dispatch notices from PostAwarenessState

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you explore this? Run into any issues?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean the lastSaveEvent state field we added here in this PR or did we used to have something for this already ?

I find it simpler to have all notifications dispatched from the same hook that detects changes, instead of spreading them out. This also avoids directly accessing the Y.Doc state map in useCollaboratorNotifications hook. Given this do you still think it might be better to do it in useBroadcastSaveEvent hook.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm saying we could access the Y.Doc state map in PostEditorAwareness and dispatch directly from there, no hook needed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the issues that we encountered last time was that we were mixing UI elements (notices package) into core-data. Since the PostEditorAwareness is located within core-data, the solution should ideally fire the notification from outside core-data. I don't know if we have the convo on a PR on hand, but that's what I recall the last time we explored this.

That said, I do agree that we should re-used the existing keys in the state map that track all this already. We don't need to introduce new ones imo.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've pushed a few commits to use the existing keys synced using YDoc. The useCollaboratorNotifications hook which dispatches notifications is in editor package so that should address not doing UI stuff in core-data.

@shekharnwagh shekharnwagh force-pushed the add/session-activity-notifications branch 3 times, most recently from ffe0f12 to 353dee6 Compare February 27, 2026 18:20
@shekharnwagh shekharnwagh changed the base branch from trunk to fix/sync-stale-crdt-doc-on-save February 27, 2026 18:22
* @var int
*/
const AWARENESS_TIMEOUT = 30;
const AWARENESS_TIMEOUT = 70;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing that this'd need to be backported into 7.0 as a separate PR as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened a backport PR and linked in new PR #76065

* notices if the same event is processed more than once.
*/
const NOTIFICATION_TYPE = {
POST_UPDATED: 'collab-post-updated',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this was how we kept it in the plugin, but we should match the key with the name.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean update the key to COLLAB_POST_UPDATED or update the value to align with key ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point was more to just align them.

The collab prefix was added to make it clear that this notification was to do with RTC mode only. So IMO, I think COLLAB_POST_UPDATED makes more sense than just POST_UPDATED since we don't handle notifications in non-RTC mode.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

continue;
}

notify(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the content be generated from within the notify method? The collaborator id and name could be passed in as arguments, or just the entire collaborator object even.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its tricky for post updated notification as that needs additional data like if its first publish and current status to generate notification content - https://github.com/WordPress/gutenberg/pull/75790/changes#diff-ec598d4eb0aacc0ff99f30afd0f8e524f4ac9e242cf69e9045bf6aeaf4c3b291R239-R243. So this seems easier to me for now, but open to being convinced otherwise.


const notificationsConfig = useMemo(
() =>
applyFilters(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the best way to configure notifications? Is this inline within how Gutenberg does it? I think there'd need to be a UI (similar to their preferences) to turn this on/off and have it follow the same preferences storage system.

Would it be similar to move the notifications config to a separate PR, and have this be focused on introducing it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going off this comment from the issue -

We've also seen a desire to default some of these to "on" until users turn them off. I don't think we need UI, but perhaps a filter to set the default for an application.

@smithjw1 Any thoughts on dropping the filter and then adding the UI in a follow up PR while keeping all the notifications ON for now ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No objections.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed the filter and made it on by default for all notifications.

.isCurrentCollaboratorDisconnected;
}

export interface PostSaveEvent {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this go in the types?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. Not adding commit as that changes on rebases but can check in #76065

* @param postId The ID of the post.
* @param postType The type of the post.
*/
export function useLastPostSave(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function at the moment isn't called setup on the Awareness instance.

The other functions in this hook go through a single common method usePostEditorAwarenessState that ensures the awareness instance is properly setup, before accessing anything on it. useLastPostSave benefits from being called after useActiveCollaborators which ensures that the PostEditorAwareness state is setup. If that changes, there's going to be a problem because the setUp function on the awareness instance isn't being called in this method.

It'd be worth exploring how this could go through the common shared hook (might require some refactoring) to ensure this is avoided.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch on the missing setUp(). I've added that call in useLastPostSave now, so it no longer depends on useActiveCollaborators being invoked first. Since setUp() is idempotent, calling it from both hooks is safe.

I looked into routing this through usePostEditorAwarenessState. useLastPostSave subscribes to the Y.Doc state map where markEntityAsSaved writes savedAt/savedBy. The usePostEditorAwarenessState hook is used to subscribe to the awareness, so I'll rather keep them separate.

// syncs historical state on page load or peer reconnect.
const setupTime = Date.now();

const observer = ( event: Y.YMapEvent< unknown > ) => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's may be more properties down the line that we might be interested in, it'd be worth exploring how this bit could be redone to make that easier.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now savedAt is the only YDoc state map property we observe, so the observer is straightforward. When we need to watch additional properties, we could revisit this.

Did you have anything specific in mind ?

@shekharnwagh shekharnwagh force-pushed the fix/sync-stale-crdt-doc-on-save branch from fdd556a to 0e59564 Compare March 2, 2026 17:47
Collaborators had no way to know when others joined, left, or saved
the post. This adds snackbar notices for all four events, broadcast
via a new `lastSaveEvent` field on the Yjs awareness state.

Notification types can be toggled via the
`editor.collaborationNotifications` WordPress filter.
Replace custom awareness-based save broadcasting with the
existing CRDT state map already written by markEntityAsSaved
in @wordpress/sync.
The notification always showed "Draft saved by X" because post status
was read from the local Redux store, which lags behind the Y.Doc.

Fix by reading status from the Y.Doc record map (updated in the same
transaction as the save marker) and distinguishing three cases:
"Post published", "Post updated", and "Draft saved".
The 30 s awareness timeout matched the background-tab polling interval
exactly, causing awareness entries to expire between polls and triggering
false "X has left" notifications that resolved on the next poll cycle.

Increase to 70 s to comfortably exceed the polling interval. The value
accounts for Chrome's intensive throttling which can delay background
tab timers to ~60 s
@shekharnwagh shekharnwagh force-pushed the add/session-activity-notifications branch from 516abab to ad398fb Compare March 2, 2026 17:47
@ingeniumed ingeniumed added Backported to WP Core Pull request that has been successfully merged into WP Core Backport to WP 7.0 Beta/RC Pull request that needs to be backported to the WordPress major release that's currently in beta and removed Backported to WP Core Pull request that has been successfully merged into WP Core labels Mar 2, 2026
@chriszarate chriszarate deleted the branch WordPress:fix/sync-stale-crdt-doc-on-save March 2, 2026 23:36
@chriszarate chriszarate closed this Mar 2, 2026
@shekharnwagh
Copy link
Copy Markdown
Contributor Author

Re-opened the PR here - #76065

@ellatrix ellatrix removed the Backport to WP 7.0 Beta/RC Pull request that needs to be backported to the WordPress major release that's currently in beta label Mar 3, 2026
@shekharnwagh
Copy link
Copy Markdown
Contributor Author

@ingeniumed I've addressed or replied to your review comments. FYI the PR was moved here - #76065

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Feature] Real-time Collaboration Phase 3 of the Gutenberg roadmap around real-time collaboration [Package] Core data /packages/core-data [Package] Editor /packages/editor [Type] Feature New feature to highlight in changelogs.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RTC: [Interface] Notifications for session activity

5 participants