Skip to content

Notes: Add emoji reactions - stored as custom comment type#76767

Draft
adamsilverstein wants to merge 84 commits intotrunkfrom
add-notes-emoji-reactions-try-addditional-comment-type
Draft

Notes: Add emoji reactions - stored as custom comment type#76767
adamsilverstein wants to merge 84 commits intotrunkfrom
add-notes-emoji-reactions-try-addditional-comment-type

Conversation

@adamsilverstein
Copy link
Copy Markdown
Member

@adamsilverstein adamsilverstein commented Mar 23, 2026

Summary

Closes #75144

Switches emoji reaction storage from _wp_note_reactions comment meta to individual reaction comment records, as suggested by @swissspidy.

Each reaction is stored as its own comment with:

  • comment_type = 'reaction'
  • comment_parent = <note ID>
  • comment_content = <emoji slug> (e.g. heart, rocket)
  • user_id = <reacting user>

This approach is more flexible, aligns with WordPress data patterns (similar to the React plugin), and could eventually extend to regular comments.

Related

Remaining feedback to address

From the review on #75549:

  • Unbounded reaction requests (@ellatrix) — useEntityRecords for reactions has no per_page limit. Consider lazy-loading reactions only when a note thread is opened. Comment
  • Move emoji SVG icons to @wordpress/icons (@ellatrix) — Low priority, fine for now. Comment
  • Reaction embedding (@Mamaduka) — Reactions could be an embeddable resource on each note to reduce the number of requests. Comment
  • Performance at scale (@Mamaduka) — Consider how reactions perform with thousands of comments (e.g. make.wordpress.org). Comment

Already addressed

Test plan

  • Create a note on a block
  • Add emoji reactions — verify they appear
  • Remove reactions by clicking — verify they disappear
  • Add multiple different emojis to same note
  • Check wp-admin comments page doesn't show reaction comments
  • Check comment counts aren't inflated by reactions
  • Run E2E tests: npm run test:e2e -- test/e2e/specs/editor/various/block-comments.spec.js
  • Run JS unit tests: npm run test:unit packages/editor/src/components/collab-sidebar/
  • Run PHP tests: npm run test:unit:php:base -- --filter=Gutenberg_REST_Comments

adamsilverstein and others added 30 commits February 2, 2026 10:21
Introduce a new component that displays a horizontal row of emoji
buttons for adding reactions to notes. Features include:

- Curated emoji set: 👍 👎 ❤️ 🎉 😄 😕 👀 🚀
- Keyboard navigation with arrow keys, Home, and End
- Accessible with role="listbox" and role="option"

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Introduce a component that displays current reactions with counts
as pill-shaped buttons. Features include:

- Shows reaction counts for each emoji
- Highlights user's own reactions with distinct styling
- Click to toggle (add/remove) reaction
- "+" button opens emoji picker dropdown

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Introduce a popover component that shows who reacted and when.
Features include:

- Displays reactions grouped by emoji
- Shows user avatars and names
- Uses humanTimeDiff() for relative timestamps (e.g., "3 days ago")
- Fetches user data for all reactors

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extend the useBlockCommentsActions hook with three new functions:

- onAddReaction: Add a reaction to a comment
- onRemoveReaction: Remove user's reaction from a comment
- onToggleReaction: Toggle reaction (add if not present, remove if present)

Reactions are stored in comment meta._wp_reactions with structure:
{ emoji: [{ userId, timestamp }] }

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Connect the reaction components to the Notes sidebar:

- Import and render ReactionDisplay in CommentBoard
- Add ReactionDetailsPopover for viewing reaction details
- Add "See emoji reaction details" menu action
- Pass onToggleReaction through component hierarchy
- Get current user ID for highlighting own reactions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add SCSS styles for:

- Reactions container with flexbox layout
- Pill-shaped reaction buttons with active state
- Add reaction button with dashed border
- Emoji picker dropdown
- Reaction details popover with user avatars

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add comprehensive E2E tests for the emoji reactions feature:

- can add an emoji reaction to a note
- can remove own emoji reaction by clicking it
- can see emoji reaction details
- reaction buttons are keyboard accessible
- can add multiple different reactions to same note

Also adds addReactionToComment helper to BlockCommentUtils.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Resolve merge conflicts with trunk's selectedNote editor state changes
(#75177) while preserving emoji reaction features.

Fix all 5 emoji reaction E2E tests that were timing out because the
Dropdown popover was stealing focus from the thread, triggering the
onBlur handler which collapsed the note and unmounted the emoji picker.

- Add focusOnMount: false to the reaction Dropdown popoverProps
- Add popover focus check to the thread onBlur handler
- Update addReactionToComment E2E helper to wait for the emoji picker

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…yling

Replace the plus icon with a Google Docs-inspired smiley face SVG, change
focusOnMount to 'firstElement' so the emoji picker captures focus and
prevents the note from collapsing, and restyle the button to be perfectly
round with a clean white background that appears on hover.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move the "Add reaction" smiley button from below the note content to the
upper-right header area alongside the resolve and actions buttons. Also
register _wp_reactions as comment meta in PHP so the REST API accepts it,
and fix the 500 error caused by spreading all comment meta (including
potentially invalid _wp_note_status) when saving reactions.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
Add variant="tertiary" to the reaction pill Button components so the
WordPress default dark button styling doesn't override the custom
light gray background.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
The Button component automatically adds the is-pressed class when
aria-pressed is true, which sets a dark background (#1E1E1E). Override
with matching specificity to keep the light blue active state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Increase height to 32px, use equal padding on all sides, and reduce
gap between emoji and count to bring the aspect ratio closer to 1:1.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The reactions meta registration is a new feature targeting
WordPress 7.0, not a 6.9 backport. Move it to its own file
in lib/compat/wordpress-7.0/ per reviewer feedback.
The editor assumes users are logged in by default, so these
guards are redundant. Matches the pattern used by other
collab sidebar actions.
Reactions update comment metadata without adding, removing,
or resizing comments, so reflowing is unnecessary.
Replace individual getUser() calls with a single getUsers()
request using include, context: view, and _fields to reduce
API calls and support low-capability users.
Negative reactions are better expressed as comments in a
collaborative editing context. The thinking emoji provides
a constructive "I need to consider this" signal instead.
The horizontal layout caused emojis to overflow and get
cut off in the sidebar popover.
Start conservatively with ❤️ 🎉 😄 👀 🚀 to avoid
skin-tone concerns and keep the picker compact. More
reactions can be added later.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 23, 2026

Flaky tests detected in ea19200.
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/25089941102
📝 Reported issues:

- Fix children link to query for 'reaction' type when parent is a note
- Add unmount cleanup flag to useReactionEmojis to prevent state updates
  after component unmounts
- Wrap onToggleReaction in useCallback with reactionsMap dependency to
  prevent stale closure
The OPTIONS dispatch on a collection route may not include the schema
in the expected structure. Test the controller's schema directly.
@swissspidy
Copy link
Copy Markdown
Member

Regarding unbounded requests & embedding reactions:

If embedding all the reactions on a given note is too much, we could think about caching/retrieving only the reaction count (grouped by emoji). This way we can quickly display the different reactions on a note and lazy load the individual reactions (and who gave them) after that.

Add a computed reaction_summary field to note comment REST responses
that returns grouped reaction counts, whether the current user reacted,
and the user's reaction ID for each emoji. This eliminates the need for
clients to fetch all individual reaction records separately.

Includes batch pre-fetching in get_items() to avoid N+1 queries when
returning multiple notes.
Replace the separate unbounded useEntityRecords call for reactions with
the reaction_summary field returned on each note. This eliminates a
separate API request and the per_page:-1 fetch of all individual
reaction records, addressing unbounded request and performance concerns.

Also removes the eager user data fetch for tooltips, using count-based
labels instead.
Test schema structure, populated summary with current user's reaction,
summary for a different user, and empty summary when no reactions exist.
Add the smiley face SVG to the icons library and import it in
reaction-display.js instead of defining it inline.
Fetch individual reaction details (who reacted) only when the user
hovers or focuses a reaction pill button, with a module-level cache
to avoid repeated requests. This restores the GitHub-style tooltip
showing user names without eagerly fetching all user data on page load.
@github-actions github-actions Bot added the [Package] Icons /packages/icons label Apr 4, 2026
Split the single GROUP_CONCAT-based query into two simpler queries:
one for aggregated counts and one for the current user's reaction IDs.
This avoids MySQL's group_concat_max_len truncation on notes with many
reactions.
…omment-type

Reconcile emoji reactions with trunk's notes architecture refactor.
Drop PR's old positioning API (reflowComments, boardOffsets,
calculatedOffset, setHeights, setBlockRef) in favor of trunk's
floating prop backed by useFloatingBoard. Preserve the reactions
data flow (reactionsMap, onToggleReaction) on top of the new
architecture. Drop require statements for icons registry files
removed in trunk (#76455).
…omment-type

Resolve conflicts from trunk's collab-sidebar refactor (#77614):
- Adopt new component structure (notes.js, note.js, note-thread.js)
- Rename useBlockCommentsActions → useNoteActions; keep reactionsMap
- Thread onToggleReaction and reactionsMap through Notes → NoteThread → Note
- Render AddReactionButton + ReactionDisplay inside Note when isSelected
- Delete obsolete comments.js
- Replace experimental HStack with Stack from @wordpress/ui
- Add missing useCallback import in hooks.js
@adamsilverstein adamsilverstein changed the title Notes: Store emoji reactions as custom comment type Notes: Add emoji reactions - stored as custom comment type Apr 29, 2026
@adamsilverstein
Copy link
Copy Markdown
Member Author

adamsilverstein commented Apr 29, 2026

Two open threads carried over from #75549 worth noting:

  1. Notes query is still unbounded (hooks.js:39 uses per_page: -1). reaction_summary solved the per-reaction fan-out, but Mamaduka's scale concern about thousands of notes (e.g. make.wordpress.org blogs) is still open. Probably a follow-up rather than a blocker, but flagging.
  2. Storage decision (custom comment type vs. meta) was settled across #75148 and #75549 but isn't documented here. Linking the rationale in the PR description would save a future reviewer from relitigating it.

All other feedback from #75549 has been addressed — see my comparison for details.

adamsilverstein and others added 3 commits April 28, 2026 19:55
Add a docblock to lib/compat/wordpress-7.1/block-comments.php explaining
why reactions use a custom 'reaction' comment type instead of comment meta:
free authorship/timestamps from comment APIs, generic type that can attach
to other resources, and per-row writes to avoid serialized-meta races.
Links to the discussion in PRs #75549 and #75148.
Trunk's collab-sidebar refactor renamed the test fixture and helper:
- blockCommentUtils → blockNoteUtils
- addBlockWithComment → addBlockWithNote

Update the Emoji Reactions describe block to use the current names so
Playwright stops failing with 'Test has unknown parameter blockCommentUtils'.
@adamsilverstein adamsilverstein requested review from Mamaduka and t-hamano and removed request for spacedmonkey April 29, 2026 03:01
@adamsilverstein adamsilverstein marked this pull request as draft April 29, 2026 03:01
@adamsilverstein
Copy link
Copy Markdown
Member Author

adamsilverstein commented Apr 29, 2026

@swissspidy this is what's shipping now - in case you want to double-check it matches what you had in mind:

  • Counts grouped by emoji: each note response includes a reaction_summary field — { [emojiSlug]: { count, reacted, my_reaction_id } }. Schema in class-gutenberg-rest-comment-controller-7-1.php:54, populated in prepare_item_for_response().
  • No N+1: get_items() pre-fetches summaries for the entire result set with one aggregated GROUP BY query plus one query for the current user's own reactions, regardless of how many notes are returned (see prefetch_reaction_summaries()).
  • Lazy-loaded reactor names: who reacted is not in reaction_summary — names are fetched on tooltip hover (commit 776efb9bc00), so the initial notes load stays cheap.

The unbounded per_page: -1 on the notes query itself is still open — that's a separate concern about how many notes we load at once, not how reactions are loaded per note.

adamsilverstein and others added 3 commits April 28, 2026 20:26
The Lint Styles check enforces using the design system cursor token for
interactive non-link controls (rule added in trunk). Replace 'cursor: pointer'
on .editor-collab-sidebar-panel__add-reaction-button to satisfy the rule.
…omment_types().

Mirrors the change being proposed for core in
WordPress/wordpress-develop#10930. Introduces a
single helper that returns the list of internal comment types ('note',
'reaction'), filterable so future additions only need to update one place.

Apply it across all the existing 'note'/'reaction' guards in the 7.1
compat layer:

* gutenberg_update_get_avatar_comment_type_7_1() — avatar-eligible types.
* gutenberg_exclude_block_comments_from_admin_7_1() — admin query exclude.
* gutenberg_filter_comment_count_query_exclude_block_comments_7_1() —
  pending-count SQL guard.
* gutenberg_hide_note_from_comment_list_table_7_1() — list table args.
* gutenberg_exclude_notes_from_comment_count_7_1() — approved-count SQL.
* Gutenberg_REST_Comment_Controller_7_1::is_note_or_reaction() — REST
  controller's per-request type check (used by permissions, prepare,
  validation, and links).

Props westonruter for the suggestion to centralize this list.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Feature] Notes Phase 3 of the Gutenberg roadmap around block commenting [Package] Editor /packages/editor [Package] Icons /packages/icons [Status] In Progress Tracking issues with work in progress [Type] Enhancement A suggestion for improvement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add emoji reactions as a first class comment type

3 participants