Skip to content

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

Open
adamsilverstein wants to merge 107 commits into
trunkfrom
add-notes-emoji-reactions-try-addditional-comment-type
Open

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

Conversation

@adamsilverstein
Copy link
Copy Markdown
Member

@adamsilverstein adamsilverstein commented Mar 23, 2026

Summary

Closes #75144

Adds the foundation for emoji reactions on block notes. This PR is scoped to a small curated 5-emoji quick row (heart, celebration, smile, eyes, rocket). The full searchable emoji picker behind a + button lives in a follow-up PR stacked on this one — per @t-hamano's request, so library/bundling decisions can be reviewed separately.

Storage: reactions as a custom comment type

Reactions are stored as individual reaction comment records (one per user-emoji-note triple), as suggested by @swissspidy, in preference to JSON blobs in comment meta:

comment_type    = 'reaction'
comment_parent  = <note ID>
comment_content = <emoji slug>             // e.g. 'heart'
user_id         = <reacting user>

This is more flexible, aligns with WordPress data patterns (similar to the React plugin), and could eventually extend to regular comments. The custom type is hidden from wp-admin/edit-comments.php and excluded from comment counts.

Curated emoji set

The default 5 emojis (heart, celebration, smile, eyes, rocket) are stored as ASCII slugs (e.g. heart). Storing slugs sidesteps utf8 vs utf8mb4 portability issues on the comments table and gives stable grouping in the reaction_summary aggregation.

The list is filterable via the gutenberg_note_reaction_emojis PHP filter and exposed to clients through the REST API schema (reaction_emojis property on OPTIONS /wp/v2/comments), implementing the approach suggested by @Mamaduka.

Efficient reaction loading

Each note response includes a reaction_summary field — { [emojiSlug]: { count, reacted, my_reaction_id } }. The schema lives in class-gutenberg-rest-comment-controller-7-1.php, 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 embedded in reaction_summary — names are fetched on tooltip hover, so the initial notes load stays cheap.

Popover architecture

The curated picker uses @wordpress/components/Dropdown, whose Popover portals to <body>. This is essential because the collab sidebar has an overflow: hidden chain reaching all the way up to .interface-interface-skeleton__sidebar (framework-level); a non-portaled popover would clip. The note thread's onBlur handler exempts .components-popover so that focus moving into the picker doesn't deselect the note (which would unmount the trigger and close the picker mid-click).

Related

Remaining feedback from #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

  • Move PHP code to wordpress-7.1 compat dir (@Mamaduka, @t-hamano) — Done
  • Rename comment type to reaction (@swissspidy) — Done
  • Emoji extensibility / filtering (@Mamaduka, @youknowriad, @aaronjorbin) — gutenberg_note_reaction_emojis filter, surfaced via REST schema
  • Re-add reaction after removing — Fixed both server-side (status='approve' duplicate guard) and client-side (force-delete + parent-note refetch); regression-guarded by E2E test
  • Full picker scope (@t-hamano) — Moved to a follow-up PR stacked on this one

Screenshot

horizontal picker

i switched to a horizontal layout

Screencast

horizontal.picker.mp4

Test plan

  • Create a note on a block
  • Click 😊 — pick from the curated 5-emoji row
  • Pick the same emoji twice — verify reaction count goes up, not a duplicate
  • Remove a reaction by clicking its pill, then add the same emoji back — verify it works (regression for the trash-counts-as-duplicate bug)
  • Add multiple different emojis to the same note
  • Hover a reaction pill — verify the tooltip shows the reactor names
  • Verify the popover doesn't get clipped by the pinned "All notes" sidebar's overflow
  • Check wp-admin comments page doesn't show reaction comments
  • Check comment counts aren't inflated by reactions
  • Verify keyboard accessibility: Enter on smiley opens picker, ArrowDown/ArrowUp navigates, Enter selects, Escape closes
  • Run E2E tests: npm run test:e2e -- test/e2e/specs/editor/various/block-notes.spec.js
  • 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.
The file contained only a docblock and was require'd from lib/load.php.
Block-comments / notes compat lives in wordpress-7.1/block-comments.php
now; this shim was a leftover with no callers.
Two follow-ups from review of the re-add fix.

1) Force-delete the reaction comment instead of trashing it.
   `deleteEntityRecord` defaults to the WP REST trash workflow, so
   each toggle-off was leaving a row behind in `wp_comments`. Reactions
   don't have a trash UX, so trashed reactions are pure debris that
   accumulates unboundedly with usage. Pass `{ force: true }` so the
   row is removed.

2) Replace full notes-list invalidation with a targeted single-record
   refetch. The previous fix invalidated `getEntityRecords` for the
   entire post's notes, causing a full list re-fetch (response payload
   linear in N notes) on every emoji click. Instead, fetch only the
   parent note via apiFetch and merge it back via
   `receiveEntityRecords` with no `query` arg — that updates the
   per-record cache, which the list selector reads through by ID. The
   LIST view picks up the fresh `reaction_summary` without re-fetching
   any other note.

All 38 block-notes E2E tests still pass.
@swissspidy
Copy link
Copy Markdown
Member

Looks awesome with this new emoji picker! 🎉

Comment thread bin/copy-emojibase-data.mjs Outdated
Copy link
Copy Markdown
Member

@swissspidy swissspidy May 8, 2026

Choose a reason for hiding this comment

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

IIUC emojibase-data could also be served from a CDN, which probably should be considered prior to release given the size of this per locale.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, I think loading from CDN is an option, but I bundled because I wasn't sure we were able to do that here, do we already include other dependencies from a CDN? or do you mean giving developers the ability to load from a CDN?

Copy link
Copy Markdown
Member Author

@adamsilverstein adamsilverstein May 8, 2026

Choose a reason for hiding this comment

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

i guess this is almost like a locale that we do server from cdn?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Haven't looked at the data format here but we already serve Twemoji from the dotorg CDN

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thanks for the pointer — agreed CDN is the right path. The full emoji picker (and its data bundling) moved out of this PR to the stacked follow-up #78176, so this thread is no longer load-bearing here. I’ll carry the CDN consideration over to that PR (where the emojibase bundle/data delivery is actually being designed) so we can weigh dotorg CDN delivery against bundling there.

@t-hamano
Copy link
Copy Markdown
Contributor

t-hamano commented May 9, 2026

Perhaps the most important thing is whether all the text provided by the library is localizable. As far as I have investigated, some of the library's text is localized here.

https://github.com/milesj/emojibase/tree/master/po

Ideally, it should be localized for all locales supported by WordPress and be translatable via GlotPress for all of them.

Perhaps it would be better to rely on external libraries only for emoji datasets and implement internationalization features and UI on our own.

@adamsilverstein
Copy link
Copy Markdown
Member Author

Perhaps it would be better to rely on external libraries only for emoji datasets and implement internationalization features and UI on our own.

I'm open to this idea I will give it a try if you feel the additional flexibility is worth the maintenance burden. I guess we already have some of the components we need to create a similar experience to Frimousse already.

My goal here was to explore how using an existing maintained library would work, and Frimousse seemed like the best candidate after comparing several.

Emoji picker libraries considered

License-compatible (MIT or Apache-2.0, both compatible with WordPress's GPLv2) React emoji pickers, evaluated as alternatives to Frimousse:

Library License Last push Stars Notes
frimousse (chosen) MIT 2026-04-30 1.7k Headless, Ariakit-based (matches WPDS primitives), backed by Emojibase. Unstyled — we own 100% of the styling and theme it with WPDS tokens. Data is loaded same-origin from emojibaseUrl, so no JS-bundle inflation.
emoji-picker-react MIT 2026-04-27 1.4k "Most popular React picker." Ships its own UI + data (~80 KB gzipped). Hard to restyle to match WPDS without override fights.
emoji-picker-element Apache-2.0 2026-05-08 1.7k Web component (~13 KB), lazy-loads data, IndexedDB-cached. Excellent engineering by Nolan Lawson, but not idiomatic in a React/Ariakit codebase — would need a wrapper.
@ferrucc-io/emoji-picker MIT 2026-04-11 352 Newer (pre-1.0, v0.0.48), Tailwind-styled. Smaller user base, less battle-tested.
emoji-mart MIT 2024-08-12 ⚠️ 9.4k Historically the dominant React picker, but no commits since August 2024 — effectively stalled.

Ideally, it should be localized for all locales supported by WordPress and be translatable via GlotPress for all of them.

Good point, I didn't even think about the need to localize the emojis.

adamsilverstein and others added 2 commits May 10, 2026 16:52
…add-success snackbar

The existing 'can re-add the same reaction after removing it' test
asserted only the post-add reaction button state. A regression on
either fix (server status='approve' guard or client parent-note
refetch) would still surface the user-visible 'You have already
reacted with this emoji' snackbar that triggered the bug report.
Make the test fail loudly on that exact symptom by:

- Asserting 'Reaction removed.' appears after the toggle-off step
  (proves the delete actually completed before the re-add).
- Asserting two 'Reaction added.' snackbars exist after the re-add
  (proves the second add succeeded, not just that the button is
  visible from a stale cache).
- Asserting zero snackbars contain 'already reacted' (pins the
  exact server-side error that the bug surfaced).
… picker (#78129)

Drops the Frimousse dependency in favor of a WordPress-native emoji picker built from existing components. Routes UI strings through GlotPress and ships per-locale Emojibase data for 28 locales (vs. English only previously). Same UX, same data source, ~125 net lines in packages/editor.
@t-hamano
Copy link
Copy Markdown
Contributor

@adamsilverstein, Regarding the emoji picker, can we address that in a follow-up? It seems like there's still research needed on which libraries to use and whether to bundle them into the core. Perhaps it would be better to focus on building the foundation for basic emoji reactions in this PR.

@adamsilverstein
Copy link
Copy Markdown
Member Author

@adamsilverstein, Regarding the emoji picker, can we address that in a follow-up? It seems like there's still research needed on which libraries to use and whether to bundle them into the core. Perhaps it would be better to focus on building the foundation for basic emoji reactions in this PR.

I am happy to break off a second pr to add the complete emoji picker behind the + button, and leave this PR to add only the initial smaller set of default available reactions. Note that the latest version doesn't rely on an external picker, instead using native components. Still, the PR has grown large, so I will move the custom picker back to a separate PR stacked on this one and rework this one to only support the default set.

adamsilverstein added a commit to adamsilverstein/wordpress-develop that referenced this pull request May 11, 2026
Align the reaction comment type handling with the latest Gutenberg
notes reactions PR (WordPress/gutenberg#76767):

- Accept emoji slugs as either a curated slug (heart, celebration,
  smile, eyes, rocket) or a lowercase hex-codepoint sequence (e.g.
  1f44d for 👍 or 1f468-200d-1f4bb for 👨‍💻). Raw emoji bytes are
  rejected since the comments table is not guaranteed to be utf8mb4
  across installs; clients normalize before submitting.
- Scope the uniqueness check to active reactions only, so a user can
  re-add the same emoji after removing (trashing) it on the same note.
- Point note's children link at reaction children, not at notes, so
  embedded children resolve to the reactions on the note.
- Add a read-only reaction_emojis schema property exposing the allowed
  emoji list, so clients can discover accepted slugs via OPTIONS.
- Add a reaction_summary field aggregating per-emoji counts on note
  responses, with reacted/my_reaction_id for the current user.
- Pre-fetch reaction summaries in get_items() to avoid N+1 queries
  when listing many notes.

See #63191.
Keep this PR focused on the foundation for basic emoji reactions: the
curated 5-emoji quick row served from REST schema, the reaction custom
comment type, and the reaction_summary aggregation.

The "+" More-emojis trigger, the lazy-loaded native SearchControl+Composite
picker, the per-locale Emojibase data plumbing (build copy step, PHP
inline-script registration, label-overrides filter), and the
emojibase-data devDependency move to a follow-up PR stacked on this one.
Picker library selection and bundling strategy still need wider review,
per t-hamano in #76767.
adamsilverstein added a commit that referenced this pull request May 11, 2026
Adds the "+" More-emojis trigger as a sibling of the curated smiley
trigger. Tapping it opens a lazy-loaded native picker built from
SearchControl + Composite over Emojibase data served same-origin from
the plugin (28 locales, fetched per-session on first open).

Picks fold into the curated slug when they match (e.g. ❤ → `heart`)
and store as a normalized hex-codepoint key otherwise (e.g. `1f44d` for
👍), so visually-equivalent presentations don't fragment the
reaction_summary aggregation.

Stacked on the basic-reactions baseline so picker library and bundling
choices can be reviewed independently, per t-hamano in #76767.
@adamsilverstein
Copy link
Copy Markdown
Member Author

@adamsilverstein, Regarding the emoji picker, can we address that in a follow-up? It seems like there's still research needed on which libraries to use and whether to bundle them into the core. Perhaps it would be better to focus on building the foundation for basic emoji reactions in this PR.

Thanks for the suggestion. I reduced this PR to the simpler base emoji set and created a follow up PR #78176 (stacked on this PR) to add the complete emoji set picker. We can land this one first, then work on landing the second.

Both bits only existed to support the full emoji picker, which is now a
follow-up PR. Without the `+` trigger, no caller ever passes a hex
codepoint key into the reaction storage, so the encode/decode pair and
the HEX_KEY_RE fallback in getEmojiBySlug/getLabelBySlug never fire.
The `.editor-collab-sidebar-panel__more-reaction-button` rule styled
that trigger and has no remaining consumer.
adamsilverstein added a commit that referenced this pull request May 11, 2026
Adds the "+" More-emojis trigger as a sibling of the curated smiley
trigger. Tapping it opens a lazy-loaded native picker built from
SearchControl + Composite over Emojibase data served same-origin from
the plugin (28 locales, fetched per-session on first open).

Picks fold into the curated slug when they match (e.g. ❤ → `heart`)
and store as a normalized hex-codepoint key otherwise (e.g. `1f44d` for
👍), so visually-equivalent presentations don't fragment the
reaction_summary aggregation. The hex encode/decode pair and the
HEX_KEY_RE fallback in getEmojiBySlug/getLabelBySlug come along for the
ride, since they only ever fire when this picker is enabled.

Stacked on the basic-reactions baseline so picker library and bundling
choices can be reviewed independently, per t-hamano in #76767.
Four near-identical tests (no parent, parent is a regular comment,
content not in the emoji list, anonymous user) all built the same POST
request and asserted a specific WP_Error. Replace them with a single
parameterized test_cannot_create_reaction_with_invalid_input plus a
named-key data provider, so the matrix of error cases is readable at a
glance and each new case is one row instead of a new method.

Net -23 lines; the duplicate-reaction and "can stack different reactions"
tests stay separate because their request shape differs.
Five emojis fit in a single ~176px-wide row that drops below the smiley
trigger without clipping. This matches the dominant pattern for emoji
reactions (Facebook/Slack/GitHub/Linear/Notion) so the affordance reads
as reactions instead of a generic dropdown menu.

Composite gets orientation="horizontal" so ArrowLeft/ArrowRight drive
the keyboard nav. The e2e test that exercises arrow keys is updated to
match.
Copy link
Copy Markdown
Contributor

@t-hamano t-hamano left a comment

Choose a reason for hiding this comment

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

Sorry for the late review, @adamsilverstein!

I reviewed only the frontend code. For the backend code, I reviewed it in the core PR. WordPress/wordpress-develop#10930 (review)

Comment thread backport-changelog/7.0/10930.md
* @param {Object} props Component props.
* @param {Function} props.onToggleReaction Callback to toggle a reaction.
*/
export function AddReactionButton( { onToggleReaction } ) {
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.

Standardize button height to 24px and remove unnecessary CSS.

Details
diff --git a/packages/editor/src/components/collab-sidebar/reaction-display.js b/packages/editor/src/components/collab-sidebar/reaction-display.js
index 6852a3728b3..5543d5515e5 100644
--- a/packages/editor/src/components/collab-sidebar/reaction-display.js
+++ b/packages/editor/src/components/collab-sidebar/reaction-display.js
@@ -279,9 +279,10 @@ export function AddReactionButton( { onToggleReaction } ) {
                        contentClassName="editor-collab-sidebar-panel__add-reaction-popover"
                        renderToggle={ ( { isOpen, onToggle } ) => (
                                <Button
-                                       size="compact"
+                                       size="small"
                                        className="editor-collab-sidebar-panel__add-reaction-button"
                                        icon={ smileyIcon }
+                                       iconSize={ 20 }
                                        label={ __( 'Add reaction' ) }
                                        aria-expanded={ isOpen }
                                        onClick={ onToggle }
diff --git a/packages/editor/src/components/collab-sidebar/style.scss b/packages/editor/src/components/collab-sidebar/style.scss
index b18515470f8..945a33a0d9d 100644
--- a/packages/editor/src/components/collab-sidebar/style.scss
+++ b/packages/editor/src/components/collab-sidebar/style.scss
@@ -226,17 +226,8 @@
 }
 
 .editor-collab-sidebar-panel__add-reaction-button {
-       display: inline-flex;
-       margin: 0;
-       align-items: center;
-       justify-content: center;
-       width: 28px;
-       height: 28px !important; // Override Button component's size constraints.
-       min-width: 28px !important;
-       padding: 0;
        border-radius: $radius-round;
        border: $border-width solid $gray-300;
-       background-color: $white;
        cursor: var(--wpds-cursor-control);
 
        &:hover {
@@ -251,10 +242,7 @@
        }
 
        svg {
-               fill: none;
                color: $gray-700;
-               width: 20px;
-               height: 20px;
        }
 }
Before After
Image Image

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Applied the suggested approach in b6e3cb1 — switched to size="small" + iconSize={20} and dropped the bespoke 28px/!important CSS in favor of the standard Button sizing.

popoverProps={ POPOVER_PROPS }
contentClassName="editor-collab-sidebar-panel__add-reaction-popover"
renderToggle={ ( { isOpen, onToggle } ) => (
<Button
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.

It's strange that we can react to resolved note threads. I think this button should be disabled if the note thread is resolved.

Image

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good call — resolved notes are archived conversations, no reason to keep reaction input live. Fixed in 89289cf: the add-reaction toggle is now disabled (and accessibleWhenDisabled) when note.status === "approved".

Comment on lines +46 to +49
fetchPromise = apiFetch( {
path: '/wp/v2/comments',
method: 'OPTIONS',
} )
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 feel this approach is unusual. The OPTIONS method is typically used to retrieve the allowed operations for an endpoint, not to fetch a list of data. My intuition suggests this should be a GET request, and perhaps a new endpoint is needed to retrieve a list of emojis 🤔

@Mamaduka, do you have any ideas?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fair concern — OPTIONS is unusual for data retrieval. The choice here was driven by wanting the curated emoji list to live in the REST schema (as the reaction_emojis schema default on /wp/v2/comments), since the schema is what the server already exposes as the authoritative shape for a comment. That avoided introducing a new endpoint just to fetch a 5-item curated list. The data lands in response.schema.properties.reaction_emojis.default, which is how OPTIONS is already used today to discover allowed values for other fields. Happy to revisit if @Mamaduka prefers a dedicated GET endpoint — that would be cleaner if the curated list is ever expected to grow significantly or vary per request. cc @Mamaduka

Comment thread packages/editor/src/components/collab-sidebar/style.scss Outdated
*/
export function getLabelBySlug( slug, emojis = REACTION_EMOJIS ) {
return emojis.find( ( r ) => r.value === slug )?.label ?? slug;
}
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.

These logics might cause performance degradation when the reaction icon list grows extensively in the future. How about using a map instead?

We can probably do it this way.

const emojiBySlug = new Map( emojis.map( ( e ) => [ e.value, e ] ) );
const entry = emojiBySlug.get( slug );

console.log( entry?.value );
console.log( entry?.label  );
console.log( entry?.emoji  );

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done in b7db753. Replaced the per-render find() pair with a useMemo-built Map keyed by slug in ReactionDisplay, and exposed a buildEmojiBySlugMap helper from reaction-emoji-picker.js. Lookup is now O(1) regardless of how the curated set grows.

Comment thread packages/icons/src/library/smiley.svg Outdated
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.

For now, it might be better to inline this SVG data and not expose it to consumers yet.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agreed — pulled the smiley back inline in 033e765. Removed smiley.svg and the manifest entry from @wordpress/icons, and the SVG now lives directly in reaction-display.js (via SVG/Path from @wordpress/primitives to keep it cross-platform per ESLint react/forbid-elements). Easy to promote back to @wordpress/icons later if there’s a second consumer.

"@wordpress/patterns": "file:../patterns",
"@wordpress/plugins": "file:../plugins",
"@wordpress/preferences": "file:../preferences",
"@wordpress/primitives": "file:../primitives",
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.

It seems the primitives package is not used anywhere in this PR.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Re-evaluated this in context of the smiley-icon discussion above: once the icon is inlined here (per 033e765), reaction-display.js imports SVG and Path from @wordpress/primitives (required by the react/forbid-elements ESLint rule for cross-platform compatibility). That makes the @wordpress/primitives dependency actually used by this PR, so leaving it in packages/editor/package.json is correct. Let me know if you’d rather a different approach for the inline SVG.

adamsilverstein and others added 6 commits May 15, 2026 10:51
Co-authored-by: Aki Hamano <54422211+t-hamano@users.noreply.github.com>
Co-authored-by: Aki Hamano <54422211+t-hamano@users.noreply.github.com>
Apply review feedback: drop the bespoke 28px-with-!important sizing on
the curated add-reaction button in favor of the standard Button "small"
size with iconSize=20. Removes redundant flex/centering CSS the Button
component already handles.
A resolved note thread shouldn't accept new reactions — they're for
in-progress conversations. Disable the curated reaction picker toggle
when the note's status is "approved" and keep it accessible so screen
readers still announce the control's disabled state.
Replace per-render array.find() calls in ReactionDisplay with a Map
keyed by slug, memoized against the emoji list. O(1) lookup keeps
the render cheap if the curated set grows (full picker landing later
via the stacked PR).
Per review feedback, the smiley face shouldn't be promoted to the
public icons API while the reactions feature is still settling. Move
the SVG back inline in reaction-display.js (using SVG/Path from
@wordpress/primitives so it stays cross-platform) and remove the
smiley entry from @wordpress/icons' manifest and library.
@github-actions github-actions Bot removed the [Package] Icons /packages/icons label May 15, 2026
@adamsilverstein adamsilverstein requested a review from t-hamano May 16, 2026 05:12
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 [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

4 participants