Skip to content

Notes: Open a full searchable emoji picker from the add-reaction button (stacked on #76767) - #78176

Open
adamsilverstein wants to merge 205 commits into
add-notes-emoji-reactions-try-addditional-comment-typefrom
add-notes-emoji-reactions-full-picker
Open

adamsilverstein wants to merge 205 commits into
add-notes-emoji-reactions-try-addditional-comment-typefrom
add-notes-emoji-reactions-full-picker

Conversation

@adamsilverstein

@adamsilverstein adamsilverstein commented May 11, 2026 •

Copy link
Copy Markdown
Member

Summary

Fixes #80122

Stacked on top of #76767 - review that PR first (curated 5-emoji reactions baseline). This PR replaces the curated quick-row popover with a full searchable emoji picker: the 😊 "Add reaction" trigger on a note opens the full picker directly.

Splitting this off per @t-hamano's request: library selection and bundling strategy still need wider discussion, so it shouldn't block the basic-reactions foundation.

The hover-revealed trigger and pill treatment from #82313 (merged here in 75625d0) now live in the base PR #76767, since that is where the trigger is introduced - see @draganescu's question. This PR is back to being only about the full picker.

Picker UX

The trigger opens the full searchable picker:

  • A Frequently used section leads the grid, seeded with the curated reaction set from Add emoji reactions (limited set on notes) #76767 (including any additions from the gutenberg_note_reaction_emojis filter), so the previous quick-row picks stay one click away. It reorders as the user picks emojis.
  • Search filters the full Emojibase set; results render as a single flat grid (no category headers) to avoid ragged-row keyboard dead-ends.
  • A persistent skin-tone toggle sits beside the search field.

The curated quick row from #76767 still exists as a fallback: npm consumers of @wordpress/editor that don't configure an Emojibase URL (see bundle-size strategy below) get the curated row instead of the full picker.

Native picker built from WordPress primitives

Earlier iterations used Frimousse, an MIT-licensed React picker. @t-hamano flagged that Frimousse's UI strings come from Emojibase's .po files - not on GlotPress, no coverage for many WordPress locales. Replaced with a native picker built from SearchControl over a Composite grid grouped by Emojibase category, with sticky headers and content-visibility: auto per row so all ~1949 emojis render quickly. All chrome strings (Search emoji, Loading…, No emoji found., skin tone label) route through __() and translate via the WordPress translation pipeline.

packages/editor/src/components/collab-sidebar/emojibase-data.ts:

  • resolveEmojibaseLocale() maps a BCP-47 / WordPress locale (pt-BR, fr_FR, zh-TW) to the closest of Emojibase's 28 locales, falling back to en.
  • useEmojibaseData(baseUrl, locale) fetches data.json + messages.json once per locale, with module-level cache and abort-on-unmount.

packages/editor/src/components/collab-sidebar/emoji-picker.tsx:

  • <EmojiPicker> renders the grid with full keyboard nav via Composite (same primitives WPDS uses).

Accessibility

  • Each category is a Composite.Group with role="rowgroup" labelled by a Composite.GroupLabel, so category context reaches assistive tech while arrowing cell by cell.
  • Search result counts are announced via speak() (debounced, matching the block-inserter pattern), including the zero-results case.
  • Grid cells use an inset focus ring: the rows' content-visibility: auto paint containment clips anything drawn outside the row box, so an outset ring would lose its edges.
  • The active-cell highlight only shows while the cell actually holds focus, plus a hover highlight for pointer users.
  • The skin-tone flyout is a listbox that opens with focus on the currently selected tone (APG listbox pattern) and restores focus to its toggle on close.
  • The trigger fades rather than unmounting, so it keeps its place in the tab order whether or not a pointer is anywhere near it.

Storage normalization

Picks from the full picker are stored as a lowercase hex-codepoint sequence joined by -, e.g. 1f44d for 👍 or 1f468-200d-1f4bb for 👨‍💻. Variation selector U+FE0F is stripped so visually-equivalent presentations collapse - 2764-fe0f (❤️) folds into the curated heart slug. The hex helpers (emojiToHexKey, hexKeyToEmoji, emojiToStorageKey) extend reaction-emoji-picker.tsx and remain compatible with the curated slug storage from the base PR: a full-picker pick that matches a curated (or filter-provided) reaction stores under the same slug as a quick-row pick, so both paths aggregate into a single reaction_summary bucket.

Bundle-size strategy: lazy load + same-origin data

Two layers keep the editor bundle lean:

  1. The full picker is lazy-imported via React.lazy in reaction-display.tsx. The picker module only loads when the user first clicks (or hovers/focuses) the 😊 "Add reaction" trigger in a session - for editors who never open the picker, it never loads at all.
  2. Emojibase JSON data is served same-origin from the plugin, not embedded in the bundle. A build step (tools/build-scripts/copy-emojibase-data.mjs) copies per-locale data.json + messages.json from emojibase-data (a tools/build-scripts workspace dependency, not a root dependency) into build/emojibase-data/<locale>/ after the main build runs (28 locales, ~7 MB on disk). PHP exposes the directory URL to JS via the noteEmojibaseUrl editor setting. Per-session network cost stays at one locale (~85 KB gzipped), only fetched when the user first opens the picker.

For npm consumers of @wordpress/editor outside the plugin: when the setting is unset, the trigger falls back to the curated quick row from #76767 - there's no fallthrough to a third-party CDN. Consumers opt in to the full picker by self-hosting the data and setting it.

Per-locale label overrides

gutenberg_emoji_picker_label_overrides PHP filter lets sites override Emojibase emoji labels for any locale (e.g. when curated labels in the base PR differ from Emojibase's choice for the same code point). The overrides are exposed to JS via the noteEmojiLabelOverrides editor setting and are built from the filtered reaction list, so curated and filter-provided reactions keep their curated labels (e.g. "Heart") inside the full picker. gutenberg_emoji_to_hexcode() decodes UTF-8 byte-by-byte with no mbstring dependency.

Frequently used section

The full picker opens with a Frequently used section above the category grid, matching the design mocks. It starts seeded with the curated reaction emojis (the filtered list from useReactionEmojis(), so a site's gutenberg_note_reaction_emojis additions are seeded too) and then learns from use: every pick increments a per-user counter (packages/editor/src/components/collab-sidebar/frequent-emojis.ts), stored through the preferences store in the core scope so it persists to user meta the same way as the skin tone preference.

  • Entries are ordered by frequency, with recency breaking ties.
  • The list is capped at four grid rows (32 emojis); once full, the least used entry is discarded when a new emoji is picked (a fresh pick always makes it in).
  • Usage is keyed by the base (untoned) emoji, so the section renders with the user's current skin tone preference like the rest of the grid.
  • The section hides while searching, so it never duplicates search results.
  • The seeded defaults are appended at display time rather than persisted - they fall away naturally as real usage fills the list.

Screenshots

The whole loop: hovering a note to reveal the trigger, opening the picker, searching, picking an emoji, then hovering and joining a reaction someone else left.

Hovering a note reveals the add-reaction trigger, which opens the full picker; searching for thumbs up and picking it adds a pill, and hovering the heart pill names who left it before joining takes it to 2

At rest and hovered. The note is exactly as tall either way - the trigger floats in the corner rather than claiming a row.

At rest Hovered
Note with no trigger visible The same note with the smiley trigger revealed in its top corner

Reactions render as pill buttons with a count. Your own takes the brand outline, other people's stay quiet, and hovering a pill lazy-loads the reactor names. The trigger trails the pills once there are any.

Below: ❤️ and 🎉 left by another editor with 👍 from the current user, then joining the ❤️ takes it to 2, then the tooltip naming who reacted.

Own reaction with the brand outline, other people's quiet Joining an existing reaction takes the count to 2 Tooltip reading Alex Reactor reacted with Celebration

The picker itself: opening with the seeded Frequently used row, filtering to a flat results grid, and the skin tone flyout.

Picker open with the Frequently used row Search results as a single flat grid Skin tone flyout open on the selected tone

Data storage

Reactions are rows in wp_comments, not meta on the note. Each reaction is its own comment: comment_type is reaction, comment_parent is the note's comment ID, comment_post_ID is the post, user_id is the reactor, and comment_content holds the emoji storage key. Most of this comes from the base PR #76767; this PR adds the hex-codepoint form of the storage key.

The reasoning is in the header of lib/compat/wordpress-7.1/block-comments.php: every reaction gets a real author, date and ID, so the existing comment APIs cover authorship, timestamps and deletion; the type is generic (reaction, not note_reaction) so it can attach to other resources down the road; and concurrent add/remove resolves as row inserts and deletes rather than read-modify-write on a serialized meta value.

flowchart LR
  P["wp_posts<br/>post 12"] --> N["wp_comments<br/>type: note<br/>ID 100"]
  N -->|comment_parent| R1["type: reaction<br/>content: heart<br/>user 3"]
  N -->|comment_parent| R2["type: reaction<br/>content: heart<br/>user 7"]
  N -->|comment_parent| R3["type: reaction<br/>content: 1f44d<br/>user 3"]
  R1 --> S["reaction_summary on note 100<br/>heart: count 2, reacted true<br/>1f44d: count 1, reacted true"]
  R2 --> S
  R3 --> S
Loading

What lands in comment_content. Never raw emoji bytes - the comments table is not guaranteed to be utf8mb4 on every install, and an ASCII key also groups cleanly in the count query. Curated picks store their slug (heart, celebration, smile, eyes, rocket); everything else stores the hex-codepoint key described in Storage normalization above.

Writing. create_item() requires a parent note on the same post, accepts only a curated slug or a valid hex key (code points at or below U+10FFFF, no surrogates), rejects a duplicate emoji from the same user on the same note, and persists the canonical slug rather than the raw request content. Reactions can't be edited - a PUT returns an error, and toggling one off is a force delete, so a trashed row never blocks re-adding the same emoji.

Reading. Notes carry a read-only reaction_summary field keyed by slug, eg. { "heart": { "count": 3, "reacted": true, "my_reaction_id": 412 } }. get_items() pre-fetches summaries for the whole page of notes with two queries - counts grouped by note and slug, plus the current user's own reaction IDs - so the sidebar list doesn't go N+1.

Lifecycle. Core only cascades trash, untrash and delete to note children, so block-comments.php adds handlers that carry reactions along with their note. reaction also joins gutenberg_internal_comment_types(), which keeps reactions out of admin comment lists, comment counts, and direct comment reads.

Not in the comments table. The picker's per-user state lives in the preferences store instead: emojiPickerFrequentEmojis and emojiPickerSkinTone.

Testing

Test in WordPress Playground

For multi-user testing, try https://wordpress.org/plugins/user-switching/

  • Verify the base PR (Add emoji reactions (limited set on notes) #76767) is checked out alongside this one
  • Create a note on a block
  • Hover the note and click the 😊 trigger (hover and placement are covered in Add emoji reactions (limited set on notes) #76767)
  • Click 😊 - the full searchable picker opens directly (lazy-loads on first open, search field focused), led by a Frequently used section seeded with the five curated emojis
  • Pick a curated emoji from Frequently used and a non-curated emoji (e.g. 👍) via search - verify both render correctly as reaction pills, with spacing between pills, and that the trigger trails them
  • Add enough reactions to wrap the row - the trigger follows them onto the next line
  • Pick ❤️ from the Frequently used row and again via search ("red heart") - verify both fold into the same heart reaction
  • Type "thumbs up" and press ArrowDown - focus moves straight to the first match, skipping the clear and skin tone buttons; press Enter to pick it
  • Type "rocket" and press Enter without leaving the search field - the top match is picked
  • Check the picker against the design: 24px emoji, six per row, even spacing on the left and right, uppercase category headings
  • Arrow through the grid - verify a full focus ring on every cell (including the first column and search results), and that no cell shows a grey highlight while focus is in the search field
  • Hover a reaction pill - tooltip reads " reacted with "
  • Press Escape with the picker open - verify it closes
  • Search for zzzznoresults - verify empty-state message
  • Pick a few emojis, then reopen the picker - verify your picks lead Frequently used, most used first
  • Type in the search field - verify the Frequently used section hides while searching and results render as one flat grid
  • Set a skin tone via the toggle next to the search field - verify the grid and subsequent picks use it, and that reopening the flyout focuses the selected tone
  • Switch site language to French / German / Japanese - verify emoji labels, category headers, and chrome strings all translate
  • Run E2E tests: npm run test:e2e -- test/e2e/specs/editor/various/block-notes.spec.js --grep "Emoji Reactions"
  • Storybook: npm run storybook:dev, then open Editor → Notes Reactions for the complete reaction row (smiley trigger and toggleable pills), or Editor → EmojiPicker for the full picker grid in isolation
  • Run JS unit tests for the picker: npm run test:unit -- packages/editor/src/components/collab-sidebar
  • Run PHP tests: npm run test:unit:php:base -- --filter=Emoji_Picker_Data

AI Use

Claude Code did the typing here, I did the asking. I will review and test.

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.
@annezazu

annezazu commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

I'm going to focus my feedback on #82324 as a heads up so we can get on the same page design wise rather than my spiraling out in too many PRs in an unhelpful way 😆

…dditional-comment-type' into add-notes-emoji-reactions-full-picker

# Conflicts:
#	package-lock.json
#	packages/editor/CHANGELOG.md
#	packages/editor/package.json
@github-actions

github-actions Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

🤖 PR meta 🤖

📦 Bundle size

Size Change: +42.5 kB (+0.52%)

Total Size: 8.27 MB

📦 View Changed
Filename Size Change
build/scripts/block-editor/index.min.js 506 kB +290 B (+0.06%)
build/scripts/block-library/index.min.js 365 kB -621 B (-0.17%)
build/scripts/components/index.min.js 333 kB +56 B (+0.02%)
build/scripts/customize-widgets/index.min.js 59.3 kB +1 B (0%)
build/scripts/editor/index.min.js 656 kB +40.3 kB (+6.55%) 🔍
build/styles/block-library/image/style-rtl.css 2.93 kB +4 B (+0.14%)
build/styles/block-library/image/style-rtl.min.css 1.86 kB +6 B (+0.32%)
build/styles/block-library/image/style.css 2.94 kB +5 B (+0.17%)
build/styles/block-library/image/style.min.css 1.85 kB +6 B (+0.33%)
build/styles/block-library/style-rtl.css 22.4 kB +3 B (+0.01%)
build/styles/block-library/style-rtl.min.css 18.9 kB +4 B (+0.02%)
build/styles/block-library/style.css 22.5 kB +3 B (+0.01%)
build/styles/block-library/style.min.css 18.9 kB +4 B (+0.02%)
build/styles/editor/style-rtl.css 32.5 kB +575 B (+1.8%)
build/styles/editor/style-rtl.min.css 27.9 kB +632 B (+2.32%)
build/styles/editor/style.css 32.6 kB +578 B (+1.81%)
build/styles/editor/style.min.css 27.8 kB +627 B (+2.3%)

78a52b6 Run

⚡ Performance

Show the results

Client side metrics exclude the server response time.

front-end-block-theme

Metric 8f7f24e trunk % Change
timeToFirstByte 60.75 ms +3.87% -1.56% 59.9 ms +8.35% -2% 1.42%
largestContentfulPaint 100 ms +6% -4% 100 ms +6% -4% 0%
lcpMinusTtfb 38.25 ms +10.46% -4.84% 36.1 ms +20.78% -3.88% 5.96%
wpBeforeTemplate 30.19 ms +3.97% -1.09% 30.11 ms +11.99% -1.43% 0.27%
wpTemplate 26.66 ms +3.04% -3.11% 25.54 ms +3.37% -1.21% 4.39%
wpTotal 56.92 ms +3.29% -1.69% 55.72 ms +9.37% -1.74% 2.15%
wpMemoryUsage 7.62 MB +0% -0% 7.58 MB +0% -0% 0.52%
wpDbQueries 17 +0% -0% 17 +0% -0% 0%

front-end-classic-theme

Metric 8f7f24e trunk % Change
timeToFirstByte 47.35 ms +9.93% -1.69% 51.3 ms +6.04% -3.02% -7.7%
largestContentfulPaint 100 ms +4% -0% 108 ms +1.85% -0% -7.41%
lcpMinusTtfb 52.95 ms +5.76% -4.25% 57 ms +3.07% -2.02% -7.11%
wpBeforeTemplate 28.87 ms +7.41% -2.87% 28.15 ms +7.18% -3.09% 2.56%
wpTemplate 15.55 ms +5.79% -1.48% 19.45 ms +4.27% -2.11% -20.05%
wpTotal 44.16 ms +10.55% -1.52% 47.93 ms +5.61% -3.13% -7.87%
wpMemoryUsage 6.10 MB +0% -0% 6.20 MB +0% -0% -1.64%
wpDbQueries 10 +0% -0% 14 +0% -0% -28.57%

media-processing

Metric 8f7f24e trunk % Change
mediaProcessingJpeg 403.36 ms +1.71% -1.43% 402.42 ms +0.9% -0.59% 0.23%
mediaProcessingAvif 6128.36 ms +0.11% -0.29% 6095.56 ms +0.55% -0.04% 0.54%
mediaProcessingJpegToAvif 4218.85 ms +0.16% -0.34% 4224.13 ms +0.23% -0.45% -0.12%

media-upload

Metric 8f7f24e trunk % Change
jpegUploadProcessing 1697.24 ms +15.39% -15.46% 1431.17 ms +1.14% -0.82% 18.59%
pngUploadProcessing 211.38 ms +9.09% -12.05% 222.73 ms +15.85% -8.78% -5.1%
largeJpegUploadProcessing 1421.23 ms +0.69% -1% 1427.32 ms +0.78% -0.49% -0.43%
multipleImageUploadProcessing 2059.02 ms +3.59% -21.43% 2118.24 ms +0.14% -6.73% -2.8%

post-editor

Metric 8f7f24e trunk % Change
serverResponse 512.92 ms +6.28% -3.66% 532.73 ms +2.65% -6.13% -3.72%
firstPaint 242.76 ms +5.09% -14.59% 241.8 ms +32.02% -11.48% 0.4%
domContentLoaded 1197.94 ms +1.12% -1.75% 1129.48 ms +2.96% -2.33% 6.06%
loaded 1199.4 ms +1.12% -1.74% 1130.76 ms +2.95% -2.32% 6.07%
firstContentfulPaint 480.91 ms +3.82% -2.22% 454.83 ms +6.23% -1.53% 5.73%
firstBlock 3475.11 ms +1.85% -0.17% 3384.69 ms +1.51% -0.63% 2.67%
type 20.25 ms +4.99% -6.81% 20.21 ms +1.53% -1.09% 0.2%
typeWithoutInspector 19.37 ms +3.1% -4.49% 19.42 ms +3.14% -8.44% -0.26%
typeWithTopToolbar 26.07 ms +6.29% -9.93% 25.79 ms +3.96% -1.71% 1.09%
typeContainer 9.03 ms +10.74% -6.98% 9.5 ms +4.32% -8.63% -4.95%
focus 74.97 ms +11.9% -1.13% 76.38 ms +9.39% -7.12% -1.85%
firstFocus 210.57 ms +0% -0% 247.66 ms +0% -0% -14.98%
selectAll 563.05 ms +0.21% -2.64% 548.01 ms +8.65% -2.47% 2.74%
listViewOpen 67.57 ms +5.18% -3.57% 64.31 ms +10.05% -7.2% 5.07%
inserterOpen 26 ms +1.12% -12.62% 24.55 ms +12.95% -9.61% 5.91%
inserterHover 2.37 ms +8.86% -8.44% 2.3 ms +5.22% -8.26% 3.04%
inserterSearch 8.73 ms +13.75% -6.07% 8.17 ms +2.33% -5.14% 6.85%
loadPatterns 685.78 ms +4.35% -5.02% 689.77 ms +5.14% -2.44% -0.58%
wpTotal 503.01 ms +6.28% -3.76% 522.33 ms +2.77% -6.18% -3.7%
wpMemoryUsage 13.20 MB +0% -0% 13.17 MB +0% -0% 0.26%
wpDbQueries 54 +0% -1.85% 54 +0% -1.85% 0%

site-editor

Metric 8f7f24e trunk % Change
serverResponse 468.56 ms +3.04% -5.92% 488.58 ms +2.69% -7.72% -4.1%
firstPaint 249.37 ms +17.34% -18.98% 253.92 ms +11.53% -15.93% -1.79%
domContentLoaded 1163.14 ms +1.29% -0.94% 1112.1 ms +2.32% -1.08% 4.59%
loaded 1164.44 ms +1.28% -0.93% 1113.16 ms +2.32% -1.08% 4.61%
firstContentfulPaint 450.14 ms +1.16% -2.28% 440.94 ms +3.22% -1.92% 2.09%
firstBlock 4119.13 ms +0.69% -0.54% 4085.44 ms +1.09% -1.58% 0.82%
type 17.3 ms +5.43% -6.71% 17.61 ms +5.68% -5% -1.76%
navigate 104.73 ms +14.49% -10.4% 95.25 ms +12.55% -2.07% 9.95%
loadPatterns 1210.24 ms +3.95% -4.26% 1229.31 ms +19.09% -0.97% -1.55%
loadPages 1037.45 ms +6.94% -1.67% 1040.39 ms +1.75% -0.75% -0.28%
wpTotal 459.05 ms +3.15% -6.12% 478.72 ms +2.75% -7.74% -4.11%
wpMemoryUsage 12.18 MB +0% -0% 12.15 MB +0% -0% 0.31%
wpDbQueries 43.5 +1.15% -1.15% 43.5 +1.15% -1.15% 0%

78a52b6 Run

adamsilverstein and others added 6 commits September 14, 2026 23:24
…dditional-comment-type' into add-notes-emoji-reactions-full-picker

# Conflicts:
#	packages/editor/src/components/collab-sidebar/test/reaction-display.jsdom.test.tsx
Trunk emptied the Jest allowlist, so these files now run under Vitest with
globals off. Import the collectors explicitly, swap the jest APIs for vi,
opt into the matchMedia mock the editor store needs, and await the settings
reset so Composite's update stays inside act().

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WjWPxajLy99M2CM4SNukFs
…yout

The bare factory objects did not satisfy the mocked modules' types, which
the typecheck job rejects. The skin tone flyout animates from opacity 0, so
the test needs the reduced-motion matchMedia mock to see it open.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WjWPxajLy99M2CM4SNukFs
…dditional-comment-type' into add-notes-emoji-reactions-full-picker
The collab sidebar tests call `globalThis.wpVitest`, which the editor's dev
project could not see, so the typecheck job read it as an implicit any.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WjWPxajLy99M2CM4SNukFs
…dditional-comment-type' into add-notes-emoji-reactions-full-picker

# Conflicts:
#	packages/editor/CHANGELOG.md
#	packages/editor/src/components/collab-sidebar/note.jsx
@Jiwoon-Kim

Copy link
Copy Markdown
Contributor

I opened #83032 about emoji across WordPress as a whole: the picker, support detection, and fallback rendering. The picker and the reaction key in this PR are part of that picture, so I'm linking it here for anyone following. It doesn't ask for any change in this PR.

adamsilverstein and others added 3 commits September 18, 2026 09:11
…dditional-comment-type' into add-notes-emoji-reactions-full-picker
The trunk merge brought Prettier 3.9.6, which indents ternary
continuation lines with tabs instead of space alignment. Lint JavaScript
failed on seven lines across the emoji picker and its tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BT86xJSmmzfTzpuzjzMnJE
…a row (#82313)

Co-authored-by: adamsilverstein <adamsilverstein@git.wordpress.org>
Co-authored-by: jasmussen <joen@git.wordpress.org>
Co-authored-by: fcoveram <fcoveram@git.wordpress.org>
Co-authored-by: annezazu <annezazu@git.wordpress.org>
adamsilverstein and others added 5 commits September 23, 2026 11:16
…dditional-comment-type' into add-notes-emoji-reactions-full-picker

# Conflicts:
#	packages/editor/src/components/collab-sidebar/reaction-display.tsx
#	storybook/main.ts
The trigger moved to add-reaction-picker.tsx on this branch, so it kept
its own inlined smiley SVG. Swap it for the reaction icon from
@wordpress/icons (#83338) to match the base branch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GaU1KRgXC1a2DBq9NaPpgx
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017S5XPAZ3r74s5uHxYyj7n8
…dditional-comment-type' into add-notes-emoji-reactions-full-picker

# Conflicts:
#	test/e2e/specs/editor/various/block-notes.spec.js
@adamsilverstein

Copy link
Copy Markdown
Member Author

Absent any other feedback on which emoji style to pick—I'll keep them all around so we can swap out if we change our minds, I'm going to suggest going with this one for now:

I have switched to using the new reaction icon in #76767

@fcoveram

Copy link
Copy Markdown
Contributor

I'm taking a new look at this, and I found the following.

Reaction button when reactions were added

When reactions are added to a comment, the reaction button keeps disappearing when the pointer is not over the comment. It should remain visible at the end of the reactions set.

CleanShot 2026-09-25 at 12 59 00

Emoji search popover

Layout and styles

The emoji search popover displays emojis at a smaller size than in [the design](https://www.figma.com/design/3dd6oLYd29li43VB12AP2j/Notes-emoji-reaction?node-id=54-4882&m=dev). There is also a white spacing on the right side that makes the layout uneven.

CleanShot 2026-09-25 at 12 59 23@2x

Here is the design of it

CleanShot 2026-09-25 at 15 38 24@2x

Search interaction

When typing to search for an emoji, moving from the input to the emoji grid is not keyboard-friendly. I need to move the focus across several actions before reaching the grid. Here is how it works up to the latest commit.

CleanShot.2026-09-25.at.12.37.11.mp4

Here is how macOS works.

CleanShot.2026-09-25.at.15.54.55.mp4

@mirka

mirka commented Sep 25, 2026

Copy link
Copy Markdown
Member

@fcoveram Just a note on the design spec. The group headings for combobox/select/menu-like components are currently all styled like this in the design system:

Emoji picker example for Autocomplete
Grouped SelectControl

@adamsilverstein

Copy link
Copy Markdown
Member Author

@fcoveram - excellent, thanks for the careful review and feedback.

The issue with the reaction icon disappearing even a note has reactions is a regression - good catch. I've moved the hover interaction upstream into #76767 so I'll make the fix for that there (it will also apply here).

For the layouts/styles, thanks for the diagram showing exactly the formatting we are aiming for, that is super helpful. Also, thanks for the design follow up @mirka. I will work to address all of these points!

…dditional-comment-type' into add-notes-emoji-reactions-full-picker
@annezazu

Copy link
Copy Markdown
Contributor

I think this is looking solid. @draganescu are you able to give a code review soon? It would be helpful to have this land soon so we can get user feedback and shake out any bugs earlier in the cycle.

…earch

Design review found the search field several Tab stops away from the
emoji: ArrowDown now moves straight to the first match, and Enter picks
the top match without leaving the field, as the macOS picker does.

The layout follows the design spec: 24px emoji in 40px cells, six per
row, 4px gaps and 16px padding. The picker is sized by its grid instead
of a fixed width with a scrollbar allowance, which left a blank band on
the right under overlay scrollbars. Category headings use the Design
System's heading-sm Text, the label style its grouped selects and menus
share.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C2cavEpjEd24b69inEW9HG
@adamsilverstein

Copy link
Copy Markdown
Member Author

@fcoveram thanks again for the detailed review, and @mirka for the pointer on group headings.

I had Claude work through each point, here is its rundown of what changed:

Reaction button with reactions. Fixed in #76767 (133b993) and merged up here. The trigger still fades until hover while it floats alone on a note with no reactions, but once reactions land it trails the pills and stays visible. An e2e test now covers that case.

Popover layout. Updated in 78a52b6 to follow the design spec: 24px emoji (line height 1) in 40px cells, six per row, 4px gaps, 16px padding around the grid, and a default size skin tone button. The blank band on the right came from a fixed width that budgeted room for a scrollbar; with overlay scrollbars (the macOS default) nothing fills that space. The picker is now sized by its grid, so the spacing is even on both sides, and where a classic scrollbar shows, it sits in the gutter instead.

Group headings. These use the Design System Text heading-sm variant (small, uppercase, semibold) to match the grouped select and menu labels @mirka linked, rather than Heading/md from the Figma file. Happy to switch if the larger heading is preferred here.

Search to grid. ArrowDown in the search field now moves focus straight to the first matching emoji, skipping the clear and skin tone buttons, and Enter in the field picks the top match. One difference from macOS: focus moves into the grid rather than staying in the field with a highlight. A separate try is looking at rebuilding the picker on the Design System Autocomplete in grid mode, which keeps focus in the field the way macOS does, so the two can be compared.

Can you give it another look when you get a chance?

@adamsilverstein

adamsilverstein commented Sep 25, 2026 •

Copy link
Copy Markdown
Member Author

@fcoveram Just a note on the design spec. The group headings for combobox/select/menu-like components are currently all styled like this in the design system:

@mirka Thanks for pointing to the autocomplete grid mode, I didn't realize that was possible. I wondered if we could actually just use that component for the emoji picker. I tried that out in #83538 (what changed).

The result is promising - the Autocomplete component is much faster (especially on first open) and we automatically get the component's accessibility and styles.

This branch has not been deployed

No deployments
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 Needs Design Needs design efforts. [Package] E2E Tests /packages/e2e-tests [Package] Editor /packages/editor [Type] Feature New feature to highlight in changelogs.

Projects

Status: 🔎 Needs Review

Development

Successfully merging this pull request may close these issues.

Add a full searchable emoji picker for note reactions