Make WordPress Core

Opened 8 months ago

Last modified 29 hours ago

#64638 assigned enhancement

Add emoji reactions as a comment type for Notes

Reported by: adamsilverstein Owned by: adamsilverstein
Priority: normal Milestone: 7.2
Component: Editor Version:
Severity: normal Keywords: has-patch has-unit-tests gutenberg-merge commit
Cc: Focuses:

Description (last modified by adamsilverstein)

Claude rewrote this description to match the approach that shipped in Gutenberg:

Adds a reaction comment type so users can react to collaborative Notes with one of five emoji: heart, celebration, smile, eyes and rocket. Each reaction is stored as its own comment under the note, rather than as _wp_note_reactions comment meta on the note as originally proposed here.

Data model

  • comment_type is reaction, comment_parent is the note, and comment_content is one of five emoji hex keys: 2764, 1f389, 1f604, 1f440, 1f680. Raw emoji bytes aren't stored, since the comments table isn't guaranteed to be utf8mb4.
  • One reaction per emoji per user per note. Reactions are always approved and authored by the current user.

REST API

  • Reactions are created and deleted through /wp/v2/comments, using the note permission checks (edit_post on the note's post) rather than moderate_comments. They aren't publicly readable and can't be edited.
  • On create, the parent must be a note on the same post that isn't trashed, spam, or in a resolved thread. Invalid keys return 400 and duplicates return 409.
  • Notes get a read-only reaction_summary field, keyed by hex key, with { count, current_user_reaction }. Summaries for a page of notes are prefetched in at most two queries.

Everywhere else

  • Reactions are trashed and deleted along with their note.
  • They're excluded from the admin comments list, comment counts, pending counts, get_lastcommentmodified(), comment feeds, and a note's children link.
  • reaction is added to the avatar comment types.

Restoring a trashed note doesn't restore its reactions yet, see ​https://github.com/WordPress/gutenberg/issues/84202.

References

Change History (18)

This ticket was mentioned in ​PR #10930 on ​WordPress/wordpress-develop by ​@adamsilverstein.


8 months ago
#1

  • Keywords has-unit-tests added

## Summary

Register the _wp_note_reactions comment meta in wp_create_initial_comment_meta(), alongside the existing _wp_note_status meta. This meta stores emoji reaction data for the block editor's collaborative Notes feature.

### Data Model

Each emoji key (e.g., "👍", "❤️") maps to an array of reaction objects:

{
  "👍": [
    { "userId": 1, "date": "2025-01-15T10:30:00" },
    { "userId": 2, "date": "2025-01-15T11:00:00" }
  ],
  "❤️": [
    { "userId": 1, "date": "2025-01-15T10:35:00" }
  ]
}

### REST Schema

  • Type: object
  • Single: true
  • Default: [] (empty array)
  • additionalProperties: Each property is an array of objects with:
    • userId (integer) — The reacting user's ID
    • date (string|null, date-time format) — When the reaction was added

### Auth

Uses the same edit_comment capability check as _wp_note_status.

## References

---

#2 @swissspidy
8 months ago

Why store reactions like this and not as a separate comment type? Makes it more flexible and potentially useful for regular comments as well. See also ​https://wordpress.org/plugins/react/

#3 @wildworks
8 months ago

  • Keywords gutenberg-merge added

​@adamsilverstein commented on ​PR #10930:


8 months ago
#4

I have updated this backport PR to apply the custom comment type approach for storage used in ​https://github.com/WordPress/gutenberg/pull/75549 which replaces the meta based approach in ​https://github.com/WordPress/gutenberg/issues/75144.

#5 @audrasjb
8 months ago

  • Milestone 7.0 → 7.1

WP 7.0 pre-beta1 Triage:
Unfortunately this enhancement didn't make it before beta 1 code freeze. Thus, let's move it to milestone 7.1.

​@adamsilverstein commented on ​PR #10930:


5 months ago
#6

Thanks for the review @t-hamano! I've pushed ​f3b5af251b addressing the feedback:

  • @since bumps: Updated all PR-introduced @since 7.0.0 to @since 7.1.0 (in comment.php, link-template.php, and class-wp-rest-comments-controller.php).
  • WP_Comment_Query: Updated ​the existing exclusion block to iterate over wp_get_internal_comment_types() so both note and reaction are excluded by default unless explicitly requested.
  • get_lastcommentmodified(): Added a comment_type NOT IN (…) clause built from wp_get_internal_comment_types() to all three timezone branches so internal comment types no longer affect the last modified date.
  • Reaction icon list: Moved wp_get_note_reaction_emojis() into WP_REST_Comments_Controller::get_note_reaction_emojis() as a protected static method, per your suggestion to avoid a public global while the icon-library plans are still being worked out.
  • wp_internal_comment_types filter: Removed — agreed it's not needed for an internal helper.
  • wp_list_pluck simplification at line 360: Applied. The 'note' === $comment->comment_type check inside the loop was redundant since $request['type'] is already gated to 'note' above and WP_Comment_Query constrains the result set, so it's gone too.

Locally, the relevant test suites (Tests_Comment_*, Tests_REST_Comments, Tests_Avatar) all pass: OK (165 tests, 476 assertions).

#7 @audrasjb
5 months ago

Removing trunk version as this is not going to be shipped with WP 7.0 but in the next releases.

#8 @desrosj
5 months ago

  • Description modified (diff)
  • Summary Editor: Register emoji reactions comment meta for Notes → Register emoji reactions comment meta for Notes
  • Version trunk

Since this is an enhancement, there's no first version of WordPress this can be reproduced in. Removing trunk version.

This ticket was mentioned in ​Slack in #core by adrianduffell. ​View the logs.


3 months ago

#10 @adrianduffell
3 months ago

With the 7.1 beta approaching, this has a chance to be included if the PR can be reviewed in the coming days.

#11 @wildworks
3 months ago

  • Milestone 7.1 → 7.2

Since this feature has been punt to 7.2, let's punt this ticket as well.

​@adamsilverstein commented on ​PR #10930:


3 months ago
#12

Re-synced this branch with the latest changes on the upstream Gutenberg PR (​https://github.com/WordPress/gutenberg/pull/76767), which had accumulated some PHP hardening since the last update here. Two new commits:

REST API: Harden reaction validation and canonicalize stored slugs (dd9b750d51)

  • A reaction's parent note must now belong to the post the reaction targets.
  • Hex-codepoint slugs are validated against assignable Unicode: values above U+10FFFF or in the UTF-16 surrogate range (U+D800-U+DFFF) are rejected.
  • The stored comment_content is the validated canonical slug, so markup in the request (e.g. <b>heart</b>) can no longer split reaction_summary grouping.
  • Error codes now match the Gutenberg implementation (rest_comment_invalid_parent, rest_comment_invalid_reaction, rest_comment_duplicate_reaction), so client-observable behavior won't change when the feature ships in core.

REST API: Converge concurrent duplicate reactions to a single row (417c5bf9c1)

  • The pre-insert uniqueness check is not atomic, so concurrent requests could both insert. After insert, the earliest matching row (lowest comment ID) is kept, later duplicates are deleted, and the response repoints to the survivor.

New tests cover the same-post parent check, invalid codepoints (5 cases), canonical slug storage, and the concurrency convergence (simulated via rest_pre_insert_comment injection). Full WP_Test_REST_Comments_Controller suite passes: 223 tests, 1089 assertions.

​@adamsilverstein commented on ​PR #10930:


7 weeks ago
#13

Four of the eight findings from the review round on the Gutenberg PR land in this backend rather than the client, so the matching fixes are up here now. Two of them change REST behavior, so worth a look before this goes any further.

_Claude wrote up the four fixes:_

The findings come from an AI-assisted review on ​https://github.com/WordPress/gutenberg/pull/76767. All four are fixed in 4f1c947, with the @since tags retargeted at 7.2.0 in a892699, keeping the plugin and core in step.

Reaction updates are no longer allowed. create_item() validates the author, parent note, target post and canonical slug as a set, and the inherited generic update route re-validates none of it. check_edit_permission() falls through to edit_comment, which maps to edit_post on the note's post - so anyone who can edit that post could PATCH a reaction onto another user, move it to a note on a post they cannot edit, or store a duplicate or invalid slug. update_item_permissions_check() now returns rest_comment_update_not_allowed (403) for reactions. Notes stay editable. Removing a reaction was always a delete, so nothing legitimate loses a path.

Reactions no longer outlive their note. wp_delete_comment() reparents a deleted comment's children one level up instead of deleting them, so permanently deleting a note left its reactions behind as approved top-level rows still carrying the reactor's identity and date. wp_trash_comment() cascades only to note children, so a trashed note kept its reactions approved. Both now cascade, along with wp_untrash_comment(), via a new wp_get_note_reaction_ids(). Replies come along for free since core trashes and deletes each one in turn. A regression test pins that non-note children are still reparented as before.

Hex codepoint keys are no longer accepted. They were valid input but nothing shipping today decodes them back, so a hex slug rendered as its own storage key in place of the emoji and its label. Only slugs from get_note_reaction_emojis() are accepted now. Widening this later, when a picker exists that can decode them, stays backward compatible - narrowing it later would not.

The duplicate cleanup repoints correctly when it loses the race. The count( $duplicates ) > 1 guard skipped the repoint in exactly the case that needed it: a competing request running the same cleanup deletes this request's row first, leaving one survivor that is not $comment_id. get_comment() then returned null and the request failed on state that had already changed. It now repoints whenever any matching row survives.

Verified locally: 535 --group comment and 227 WP_Test_REST_Comments_Controller tests pass. Each new test was checked red first - the update lock, the deletion and trash cascades, and the concurrency repoint all fail without their fix, the last erroring on get_comment() returning null for the deleted row.

#14 @im3dabasia1
44 hours ago

I see there’s been a lot of recent activity on the ​PR, including @t-hamano’s review yesterday, which is great to see.

As 7.2 beta 1 is approaching, @adamsilverstein @wildworks, are we targeting 7.2 for this? If it needs more time, we can punt it to 7.3.

#15 @adamsilverstein
29 hours ago

  • Keywords commit added

I see there’s been a lot of recent activity on the PR, including @t-hamano’s review yesterday, which is great to see.

The upstream PR is approved, giving it some final review and polish.

Then I'm going to get this merged ahead of beta 1 so we can get some wider testing and feedback.

Last edited 29 hours ago by adamsilverstein (previous) (diff)

#16 @adamsilverstein
29 hours ago

  • Owner set to adamsilverstein
  • Status new → assigned

#17 @adamsilverstein
29 hours ago

The upstream PR is merged, I'll get this backport committed before beta 1.

#18 @adamsilverstein
29 hours ago

  • Description modified (diff)
  • Summary Register emoji reactions comment meta for Notes → Add emoji reactions as a comment type for Notes
Note: See TracTickets for help on using tickets.