Skip to content

Sync: Fix awareness state JSON serialization format#75919

Closed
pkevan wants to merge 5 commits intotrunkfrom
pkevan/fix-awareness-format
Closed

Sync: Fix awareness state JSON serialization format#75919
pkevan wants to merge 5 commits intotrunkfrom
pkevan/fix-awareness-format

Conversation

@pkevan
Copy link
Copy Markdown
Contributor

@pkevan pkevan commented Feb 25, 2026

What?

Ensures awareness state values are cast to stdClass objects before storage and in response maps, preventing JSON serialization issues.

Why?

When clients send empty awareness objects {}, PHP's json_decode($body, true) converts them to empty arrays []. Without casting, these empty arrays serialize back as [] instead of {}, breaking the TypeScript type contract (LocalAwarenessState = object | null). This fix ensures proper round-tripping through PHP for both empty and non-empty awareness states.

How?

Cast awareness state values to (object) at three points: when storing new entries, when building the response map, and when assigning the awareness map to the response. Also added defensive validation to skip entries from storage with missing required keys.

Testing

Collaborative editing with multiple users should properly sync awareness state (cursor positions, presence) without type errors. The fix handles both empty and populated awareness states correctly during JSON serialization/deserialization.

@pkevan pkevan requested a review from spacedmonkey as a code owner February 25, 2026 16:40
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Feb 25, 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: pkevan <paulkevan@git.wordpress.org>
Co-authored-by: chriszarate <czarate@git.wordpress.org>

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

@pkevan pkevan added [Feature] Real-time Collaboration Phase 3 of the Gutenberg roadmap around real-time collaboration [Type] Bug An existing feature does not function as intended Needs PHP backport Needs PHP backport to Core labels Feb 25, 2026
Comment on lines +332 to +334
// Skip entries with unexpected structure.
if ( ! is_array( $entry ) || ! isset( $entry['client_id'], $entry['state'], $entry['updated_at'] ) ) {
continue;
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.

Why not handle this with REST type validation?

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.

didn't think of this - probably makes more sense than here.

@chriszarate
Copy link
Copy Markdown
Contributor

I'm curious, under what conditions does this surface? The request should always include the user's own awareness state and guarantee at least one member.

@pkevan
Copy link
Copy Markdown
Contributor Author

pkevan commented Feb 26, 2026

I'm curious, under what conditions does this surface? The request should always include the user's own awareness state and guarantee at least one member.

I came across this while testing something unrelated, but since it's a REST API request, any input can be sent (with the appropriate permissions) outside of its use within the editor. I guess we could also restrict it only within the editor, but this felt simpler.

@pkevan pkevan force-pushed the pkevan/fix-awareness-format branch from 2bfd9cb to 3ee7671 Compare February 26, 2026 09:50
Cast awareness state to (object) at the REST input handling layer in
handle_request, ensuring empty JSON objects {} are not lost to PHP's
json_decode array conversion. Update process_awareness_update to accept
?object instead of ?array.

Also cast state values to (object) in the response map for backward
compatibility with existing stored entries, and cast the outer awareness
map to (object) to handle the empty-map case.

Add defensive validation of existing entries from storage to skip any
with missing required keys.
Move awareness entry validation (client_id, state, updated_at) into the
REST route schema definition alongside existing properties. Use
rest_validate_value_from_schema to validate stored entries, and add a
sanitize_callback to cast awareness to object for correct JSON
serialization of empty states.
@pkevan pkevan force-pushed the pkevan/fix-awareness-format branch from 4006b99 to 1013abe Compare February 26, 2026 11:54
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Feb 26, 2026

Flaky tests detected in ad9826e.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/22583593097
📝 Reported issues:

'type' => 'integer',
),
),
'additionalProperties' => false,
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 it ok to omit this and give ourselves some additional flexibility?


foreach ( $existing_awareness as $entry ) {
// Skip entries that don't match the expected schema.
if ( is_wp_error( rest_validate_value_from_schema( $entry, $this->awareness_entry_schema ) ) ) {
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 necessary now that we have validation on input? If it is, do we need to add it other places?

$response = array();
foreach ( $updated_awareness as $entry ) {
$response[ $entry['client_id'] ] = $entry['state'];
$response[ $entry['client_id'] ] = (object) $entry['state'];
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.

necessary?

@chriszarate
Copy link
Copy Markdown
Contributor

@pkevan Is this PR still relevant? I haven't heard any bug reports around this.

@pkevan
Copy link
Copy Markdown
Contributor Author

pkevan commented Mar 6, 2026

@pkevan Is this PR still relevant? I haven't heard any bug reports around this.

Depends on your perspective 😁 0 bugs were ever reported on this.

@chriszarate
Copy link
Copy Markdown
Contributor

Ok, can you close it or move it forward? It will need a separate Core backport (can't piggyback on 10894 since that's closed).

@pkevan
Copy link
Copy Markdown
Contributor Author

pkevan commented Mar 6, 2026

Closing for now - can revisit if it come up again.

@pkevan pkevan closed this Mar 6, 2026
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 Needs PHP backport Needs PHP backport to Core [Type] Bug An existing feature does not function as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants