Repository navigation
Add emoji picker with Autocomplete grid - #83538
adamsilverstein wants to merge 453 commits into
Conversation
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.
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.
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.
Require edit_post capability to create a note or reaction, mirroring core's note guard, so the check no longer depends on a status field being present in the request. Also validate that a reaction's parent note belongs to the same post the reaction targets. Addresses review feedback on PR #76767.
The prepare_links override repointed a note's _links.children from its replies (type=note) to reactions (type=reaction), breaking core's reply-embedding contract for clients relying on _embed. Reactions reach the client through the reaction_summary field and an on-hover fetch, so the override served nothing. Remove it and inherit core's behavior. Addresses review feedback on PR #76767.
The reaction picker fetched the curated emoji list via OPTIONS /wp/v2/comments, reading it from a reaction_emojis schema default. OPTIONS is for discovering allowed operations, not data retrieval, and the curated set is a fixed five-emoji row. Use the hardcoded REACTION_EMOJIS list directly, drop the useReactionEmojis hook and the client request, and remove the read-only reaction_emojis schema property. The gutenberg_note_reaction_emojis filter still drives server-side validation. Addresses review feedback on PR #76767.
…plugin class The reaction controller extended Gutenberg_REST_Comment_Controller_6_9, but that plugin class was deleted when the minimum WordPress version was bumped to 6.9 (notes graduated to core). Loading the plugin therefore fataled with 'Class not found', which cascaded into every PHP, Playwright and performance CI job. Extend the core WP_REST_Comments_Controller directly, add a local check_post_type_supports_notes() (core declares it private, so it cannot be reused from a subclass), and drop the now-dead remove_action() for the deleted 6.9 registration function.
Replace the .editor-collab-sidebar-panel__reaction-button CSS-class
locators with getByRole( 'button', { name: /<emoji>/ } ), following the
Playwright guidance to prefer user-facing attributes over CSS selectors.
The reaction pill's accessible label always contains the emoji name, so
the regex match is stable across the count and tooltip label variants.
Addresses review feedback from @manzoorwanijk.
The numbered-step comments were removed from the build script because they get unwieldy when steps are inserted between existing ones. Drop the lone leftover marker. Addresses review feedback from @manzoorwanijk.
Core's check_read_permission() only excludes the 'note' comment type from
the public read path, where an approved comment on a readable post is
world-readable. Reactions are an internal comment type too, so a direct
GET /wp/v2/comments/{reaction_id} fell through to that public path and
exposed reaction rows - including the reacting user's identity - to any
reader of a published post. The collection endpoint already blocks
non-editors from querying type=note|reaction, but the single-item-by-ID
path did not.
Override check_read_permission() to give reactions the same restriction
core gives notes: never public, readable only by the reacting user or a
user who can edit the comment. This also closes the direct-ID read path
flagged for internal types. Add coverage asserting logged-out and
subscriber reads are denied while the author and an editor are allowed.
Addresses review feedback from @scruffian.
The comment-count query filter bailed entirely whenever the query already contained the note-only exclusion (comment_type != 'note'), which core and older revisions of the filter inject. In that case the 'reaction' type was never excluded, so reactions inflated the post comment count. Iterate the internal comment types and append an exclusion clause for each one not already present, instead of skipping when any exclusion exists. This guarantees reactions are excluded too and keeps the filter idempotent across multiple runs. Addresses review feedback from @scruffian.
Store the validated reaction slug as the comment content instead of the raw request value, so input like '<b>heart</b>' is persisted as 'heart' and stays consistent for reaction grouping and counts. Also reject hex-codepoint slugs that are not assignable Unicode code points (above U+10FFFF or in the UTF-16 surrogate range), which the shape-only regex previously accepted. Addresses CodeRabbit review (M2, m6).
The cached tooltip user-name list was only cleared when toggling an existing reaction pill. Adding the same slug through the picker updated the count but left a stale tooltip. Thread noteId into AddReactionButton and clear the cache entry on the picker path too. Addresses CodeRabbit review (m8).
Wrap the reaction-emoji filter test in try/finally so a failed assertion cannot leak the gutenberg_note_reaction_emojis filter into later tests. Replace the flaky cumulative toHaveCount( 2 ) snackbar assertion in the re-add e2e with per-add checks: assert and dismiss the first success snackbar, then assert the re-add surfaces its own, instead of requiring both auto-dismissing snackbars to be present simultaneously. Addresses CodeRabbit review (m9, m10).
The pre-insert uniqueness check for reactions is not atomic: two near-simultaneous requests for the same user, note, and emoji can both pass the check and insert an approved row, since reactions deliberately bypass core's wp_allow_comment() duplicate detection. After a successful reaction insert, re-query the user's approved reactions for the note and, when more than one matches the slug, keep the earliest (lowest comment ID) and delete the rest. Every concurrent request applies the same deterministic rule, so they all converge on the same surviving row; a request whose own row lost the race repoints its response to the survivor. Add a test that injects a competing reaction via rest_pre_insert_comment (after the pre-insert check, before the insert) and asserts a single row survives and the response references it. Addresses CodeRabbit review (M3).
…dditional-comment-type' into add-notes-emoji-reactions-full-picker # Conflicts: # lib/compat/wordpress-7.1/block-comments.php # lib/compat/wordpress-7.1/class-gutenberg-rest-comment-controller-7-1.php # lib/load.php # package-lock.json # packages/editor/src/components/collab-sidebar/hooks.js # packages/editor/src/components/collab-sidebar/note.js # packages/editor/src/components/collab-sidebar/reaction-display.js # packages/editor/src/components/collab-sidebar/reaction-emoji-picker.js # packages/editor/src/components/collab-sidebar/style.scss # phpunit/experimental/class-wp-rest-comments-controller-gutenberg-test.php # test/e2e/specs/editor/various/block-notes.spec.js
Dismissing the 'Reaction added.' snackbar moves focus and selection away from the note thread, which collapses the thread and unmounts the reaction pill. The subsequent click on the pill then timed out because the button was no longer rendered. Re-select the thread after dismissing the snackbar so the pill is visible again, matching how a user would remove a reaction from a collapsed thread.
Cover the client-side reaction components that previously had no unit coverage: pill rendering (counts, pressed state, label pluralization, unknown-slug fallback), toggle callback wiring, focus hand-off to the parent thread when removing the last reaction, GitHub-style tooltip name formatting with lazy loading and error fallback, the curated picker's listbox semantics, slug-based selection, and the disabled state of the add-reaction button.
The comment-count query filter rewrites wp_update_comment_count_now()'s SQL to exclude internal comment types, but had no test pinning that behavior. Recount a post holding a regular comment, a note, and a reaction, and assert only the regular comment is counted.
…dditional-comment-type' into add-notes-emoji-reactions-try-addditional-comment-type
Use _x() with an 'emoji reaction' context so translators know bare names like Heart or Eyes refer to emoji in the reaction picker, not other UI concepts.
…er-autocomplete-grid # Conflicts: # packages/editor/CHANGELOG.md
Reviewers on the matching core change prefer not to define what counts as an "internal" comment type before a custom comment types API exists, since reactions may later apply to public comments too. Drop the gutenberg_get_internal_comment_types() helper and its filter, and list 'note' and 'reaction' at each call site, as on #76767. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01THCU4aj11hnJP5ZbUegTRh
Format the count for the user's locale in both the pill and its accessible label, as on #76767. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01THCU4aj11hnJP5ZbUegTRh
it happens automatically for text input fields. I’m not sure if it’s possible outside of that, or if there’s a reasonable way to auto-invoke it. it was just a crazy “have we tried this?” question. on everything else, it all makes sense. I would love to avoid shipping all the data, even lazily, because that could also end up introducing a lot of lag when adding the first reaction, but this is I think an inherently unfortunate scenario. if we perform search on the server, that might reduce latency but increase server load, as it would be served dynamically instead of a large static bundle. however, that might also make it more feasible for people to add their own WPmoji the way Slack does, and the grid could be simplified to a simple my comments are meant to be helpful outside questions, not demands. you are welcome to ignore me. |
The picker now outlines the emoji the user already reacted with, and names them as such, so picking one again reads as a toggle. On a resolved thread the add-reaction trigger was shown disabled on some notes and hidden until hover on others; hide it entirely, as on #76767. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01THCU4aj11hnJP5ZbUegTRh
The typecheck rejected the picker's trigger element, whose props were typed as unknown, and the grouped-or-flat items union, which matches neither of Base UI's Autocomplete.Root overloads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01THCU4aj11hnJP5ZbUegTRh
I'm not sure average users knpw about the native pickers or how to invoke them. I only know how to use them on the mac because a key on my keyboard is mapped to the picker 😀 and still I rarely if ever use the native picker. Still, I will investigate if the idea is possible.
We can host the data on .org and load it from there if shipping with wp is a concern, I believe a previous review from @t-hamano brought this up, although I can't find the conversation at the moment.
I'm working on extensibility of the emoji set in a follow up:
Thanks! I'm going to pay attention to you even if I don't always agree 😄 (colon smile colon) |
|
Merged trunk and brought over the recent review fixes from #76767. Claude ported these, summary below:
|
Do we need to allow any emoji? The linked issue says:
It would be cool (even 🆒 or 😎) if any emoji could be selected. But this seems excessive maybe, and is shying away from decisions not options. A simple picker of the fixed set would greatly simplify this. Maybe it could be extensible so a plugin could add a full emoji picker for sites that want it. (Maybe this has already been discussed. I haven't kept up with this closely.) |
|
@westonruter, the PR with the curated list of reactions is in progress here: #76767. |
I've decided to give you the choice of any emoji! Just kidding! Looks like there is more consensus on the curated list of emoji PR so we will start with that. I had always planned the full picker as a potential follow up and it was a good exploration of using the autocomplete grid. Hopefully we can revisit this in the future, I'm fairly sure people will want the full set of emoji once they start using them. |
…er-autocomplete-grid # Conflicts: # packages/editor/CHANGELOG.md # packages/editor/src/components/collab-sidebar/README.md # packages/editor/src/components/collab-sidebar/hooks.js
Move the Autocomplete.Trigger changelog entry back under Unreleased, since the 0.24.0 release stranded it under a published heading. Claude-Session: https://claude.ai/code/session_01RmMwj4CN9cF8b1Sx58Pvik
The changelog validator requires Unreleased subsections in a fixed order, and the trunk merge put Enhancements first. Claude-Session: https://claude.ai/code/session_01RmMwj4CN9cF8b1Sx58Pvik
The picker set no status while searching, so screen reader users heard nothing as results changed. The async Autocomplete pattern this follows puts a visually hidden result count in `Status`; do the same for search results, leaving the empty state to `Autocomplete.Empty`. Claude-Session: https://claude.ai/code/session_01RmMwj4CN9cF8b1Sx58Pvik
…icker-name-footer Bring in the emoji search result count announcement from #83538. Claude-Session: https://claude.ai/code/session_01RmMwj4CN9cF8b1Sx58Pvik
…eaction-filters Bring in the emoji search result count announcement from #83538. Claude-Session: https://claude.ai/code/session_01RmMwj4CN9cF8b1Sx58Pvik
The picker test merged in from #83538 mocks `useEmojibaseConfig` with only `baseUrl`, but this branch adds `labelOverrides` to the config, so the mock no longer type checks. Claude-Session: https://claude.ai/code/session_01RmMwj4CN9cF8b1Sx58Pvik
#76767 landed on trunk with Mamaduka's reactions refactor (NoteReactions and useNoteReactions replace ReactionDisplay, AddReactionButton and useReaction), so this keeps trunk's structure and moves the picker onto it: - NoteReactions opens EmojiPicker instead of the curated Menu, and pills name non-curated emoji through Emojibase. - reaction-emojis keeps trunk's label/sentenceLabel entries and regains the hex key helpers the picker needs. - The comment controller accepts any valid emoji hex key again instead of the five curated keys, with PHPUnit cases for both. - Picker styles, e2e tests and changelog entries move onto trunk's versions; changelog entries go under Unreleased. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01335F6jiRhAVVxJxKHsLxgW
…er-autocomplete-grid # Conflicts: # packages/editor/src/components/collab-sidebar/reactions/note-reactions.tsx
Trunk now requires use-recommended-components suppressions to sit on a single import specifier, and new tool scripts to be TypeScript, so scope the skin tone picker's Menu suppression and rename the emojibase copy script to .mts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LvpAdxaahosj8kTQtm3NXW
Trunk now publishes @wordpress/block-editor type declarations, so the directive is unused and fails the typecheck. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LvpAdxaahosj8kTQtm3NXW
Reverts c153d3a. Filtering is synchronous, so a live count announces on every keystroke; mirka asked for it to be removed in review and Autocomplete.Empty already covers no results. The test now guards against the count coming back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LvpAdxaahosj8kTQtm3NXW
|
I just wanted to reiterate something I suggested in the past, I can dig up my old comment if you like. Two things:
|
|
Fully agree with @jasmussen on indexing towards positive sentiment reactions here! 👍🏼 (thumbs up emoji used very purposely here) |
|
I want to bring back a suggestion from the past which maybe deserves more consideration: instead of immediately opening the full emoji search experience, we could start with a limited subset that ends in a (+) (plus/more) button that brings up the full picker. the initial list could be dynamic gaining items you select from the full picker automatically like the current favorites section. Discussed here: #78176 (comment) and if you expand the thread there is some discussion on the approach. |
An alternate take on #78176, so the two approaches can be compared side by side. Standalone against trunk: includes the changes from #76767 (previously stacked on it).
Fixes #75144
To keep this one reviewable, the filters for customizing the emoji set, exclusions, and labels moved to a stacked follow-up, #84043.
This round addresses the feedback from @mirka and @fcoveram. The picker now follows the documented
Autocompletepatterns without patching the component's behavior, and the rough edges we hit are collected below under "Autocomplete notes" to pass back.Claude reworked the picker and wrote up the changes:
pr83538-autocomplete-screencast-v3.mp4
AI Use
Code and description came out of a few rounds of back and forth with 🤖 Claude Code. I will review and test.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Jx9yDR5xxMRczChFCjzBNu