Repository navigation
Fix/79643 rtc awareness mixed block selection - #79836
ingeniumed merged 20 commits into
Conversation
|
👋 Thanks for your first Pull Request and for helping build the future of Gutenberg and WordPress, @giteshsarvaiya! In case you missed it, we'd love to have you join us in our Slack community. If you want to learn more about WordPress development in general, check out the Core Handbook full of helpful information. |
4a1a626 to
f082230
Compare
…for multi-block selections Introduces a SelectionEndpoint discriminated union to distinguish between two kinds of selection endpoints in a multi-block selection: - CursorEndpoint (kind: cursor): the endpoint lands inside a RichText field and carries a character-level Y.Text relative position. Any block type that has a text field with a known offset produces this shape. - WholeBlockEndpoint (kind: whole-block): the endpoint has no character offset, either because the block has no RichText field (e.g. image, spacer) or it is selected as a whole block via keyboard block-selection mode. The anchor is pinned to the block slot in its parent Y.Array instead. Previously both endpoint shapes were treated identically, which caused the overlay to break when a selection spanned mixed block types (e.g. paragraph to image) because the receiver tried to resolve a text cursor on a block that has no Y.Text.
When a selection spans a list block, getBlocksBetween previously returned both the list and each of its list items, causing every item to receive a duplicate CSS outline on top of the one already applied to the parent. The fix filters out any block element that is a descendant of a block already collected, so each region is highlighted exactly once regardless of nesting depth.
Fixes RTC selection awareness breaking when a collaborator selects across blocks of different types (e.g. a paragraph into an image). Before this change, multi-block selections fell through to the same code path as single-block text selections, which tried to resolve a RichText cursor on every endpoint. Non-text blocks have no Y.Text anchor, so the resolution threw and the overlay rendered nothing. Changes: - use-render-cursors: adds a dedicated branch for SelectionInMultipleBlocks that resolves each endpoint independently via its kind (cursor vs whole-block), so mixed-type combinations resolve correctly. - compute-selection: simplifies the multi-block path to return only the cursor position at the active end. Pixel-level selection rects across multiple blocks are replaced by a CSS outline approach (see below), so this function no longer needs to compute them. - use-block-highlighting: extended to cover SelectionInMultipleBlocks. Every block in the selection range receives the is-collaborator-selected CSS class and a --collaborator-outline-color custom property. The avatar label is placed on the topmost block in document order only, avoiding one label per block per collaborator.
editor: mixed visual treatment for multi-block awareness (overlay + outline)
Replaces the earlier CSS-outline-only approach with a two-track visual
system that more closely mirrors how a selection looks to the person making it:
Text blocks (paragraph, heading, list, etc.):
- Full bounding-box semi-transparent fill overlay via selectionRects
- First block gets a full bounding-box when the selection starts at offset 0;
partial text-level rects (cursor offset → end) when it starts mid-block
- Middle blocks always get the full bounding-box fill
- Last block always uses text-level rects (0 → cursor offset) so only the
selected portion is highlighted, not the unselected tail
Non-text blocks (image, spacer, etc.):
- CSS outline border only (is-collaborator-selected class +
--collaborator-outline-color custom property)
- No fill overlay so image content is not obscured
Additional fixes included:
- Stale CSS class bug: is-collaborator-selected is now reset for every
block on each render before being conditionally re-applied, preventing
it from persisting when a block transitions between selection types
- resolveTargetElement gains a richer fallback chain (contenteditable →
common text elements p/h1-h6/etc.) so partial text rects work correctly
even when the observer has not focused the block
- Text block detection uses innerText.trim() instead of querying
[contenteditable] or [data-wp-block-attribute-key], which are absent on
blocks the observer has not clicked into
@
f082230 to
ef1fbd2
Compare
When a multi-block selection starts or ends inside an inner block (e.g. a list-item inside a list), the sibling inner blocks between the endpoint and the other selection end appeared as individual middle blocks — each getting its own fill rect, producing a striped banded look instead of one rect covering the whole list. Fix: promote each selection endpoint to its nearest [data-block] ancestor before computing rects and block highlights. Middle blocks that are descendants of the promoted container are filtered out. When both endpoints promote to the same container (e.g. two list-items inside one list), a single bounding-box rect is emitted for that container. Adds a blockContainerOf helper to cursor-dom-utils and applies the promotion in both compute-selection and use-block-highlighting.
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
|
@props-bot refresh |
|
Hi @nerrad ! I'm working on this issue — the bugs reported by @alecgeatches are now resolved. |
compareDocumentPosition returns a bitmask. When b is a following descendant of a the result is FOLLOWING | CONTAINED_BY (4 | 16 = 20), not 4. The strict === 4 check returned false, silently skipping the start/end swap for any endpoint pair where one element is nested inside the other (e.g. a list-item resolved before blockContainerOf runs). Fix: use a bitwise AND so any value with the FOLLOWING bit set returns true.
The previous closest('[data-block]') call walked arbitrarily far up the
ancestor chain. A core/list nested inside core/group has a non-[data-block]
wrapper div between them, so closest() would skip past the list and
promote to the group — wrong block gets highlighted.
The correct scope is the immediate parentElement: if the direct parent is
itself a [data-block] element (as in list-item → list), promote; otherwise
leave the element as-is.
When an endpoint block is absent from the DOM (e.g. deleted by a concurrent edit), the previous code set effectiveFirstEl/effectiveLastEl to null via a ternary. null?.contains(el) evaluates to undefined and !undefined is true, so every intermediate block passed the containment filter — spuriously highlighting the entire selection range. Add an early return when either endpoint element is missing. Also remove the null-guard ternaries that are now unreachable, letting TypeScript narrow firstEl/lastEl to HTMLElement directly.
…render-cursors computeMultiBlockOverlayRects is a pure computation function; DOM-based inner-block promotion (list-item → list) was an architectural mismatch. Move the promotion step to use-render-cursors, where resolved endpoints are built before computeSelectionVisual is called. A promote() helper checks if the resolved element's immediate parentElement is a [data-block] container and rewrites localClientId + nulls out richTextOffset if so. This matches the same boundary check used by blockContainerOf. computeMultiBlockOverlayRects now trusts that its inputs are already container-level IDs: it drops the effectiveFirstEl/effectiveLastEl/wasPromoted variables and uses docFirstEl/docLastEl directly. The same-container early return becomes a simple localClientId equality check.
|
👋🏾 @giteshsarvaiya I've added @alecgeatches and I as reviewers on your PR. Could you fix the merge conflicts and we can take a look? Thanks for putting it up! |
|
@ingeniumed @alecgeatches Thanks for taking a look! I've resolved the merge conflicts. Looking forward to your feedback. |
| * Any block type can produce a CursorEndpoint — a paragraph mid-word, a | ||
| * table cell, a heading — as long as the selection carries a character offset. | ||
| */ | ||
| export type CursorEndpoint = { |
There was a problem hiding this comment.
I see the explanation in the comments, but I'm not sure why we need these two new types. CursorEndpoint appears to contain exactly the same data as SelectionCursor, and WholeBlockEndpoint also matches SelectionWholeBlock.
If we need the endpoint concept, can we use a type SelectionEndpoint = SelectionCursor | SelectionWholeBlock instead and reuse the same inner types? I think we could also drop areEndpointsEqual() and related endpoint-specific functions in this PR unless we need a new wrapping type for a good reason.
| // endEndpoint in the document. Normalise to DOM order so | ||
| // getBlocksBetween and the avatar-placement logic below always | ||
| // operate top-to-bottom. | ||
| const startEl = |
There was a problem hiding this comment.
This element-finding logic overlaps with computeMultiBlockOverlayRects() (get DOM elements, find nodes between). Could we extract shared logic and use the same in both places? Ideally this will reduce useBlockHighlighting's size at it's getting pretty big, and also make the logic between the two functions exactly the same.
| } | ||
|
|
||
| if ( overlayRect && ! usersWithAvatar.has( color ) ) { | ||
| usersWithAvatar.add( color ); |
There was a problem hiding this comment.
usersWithAvatar should probably use user IDs instead of colors, since we only have 7 colors built-in. It's okay if multiple users have the same color when more than 7 users are present, but I think this logic will skip any users after the first 7.
alecgeatches
left a comment
There was a problem hiding this comment.
@giteshsarvaiya Thank you for your work here! I left some code comments above, and also noticed that scroll-to-avatar stops working with multiblock selection:
scroll-to-avatar.mov
Above, I show clicking an avatar works for selection within a block, but when a user has multiblock selection running the jump-to-avatar button doesn't work anymore.
Overall I think this works well and didn't see any other issues in my testing. I'd like to take another run once you've had a look at the code comments, but I think this looks pretty good already.
Clicking a collaborator's avatar in the presence list did nothing when that user had a SelectionInMultipleBlocks selection active. The cursor registry only held elements for cursor/single-block states; highlight avatar elements rendered for multi-block selections were never registered, so scrollToCursor returned false silently. Fix: thread clientId through BlockHighlightData, wrap each highlight Avatar in a positioned div captured via the existing setCursorRef/ cursorRefsMap pattern, and sync both cursor and highlight elements in one unified registry effect. The pulse animation on scroll is extended to cover block-label avatars via a shared keyframe parameterised with --label-base-transform, keeping the CSS DRY.
Only 7 colors exist in the palette so with 8+ collaborators, users sharing a color would have their avatar label skipped entirely. Key by collaboratorInfo.id (the WordPress user ID) instead — consistent with how user identity is used everywhere else in this file.
…f SelectionEndpoint = SelectionCursor | SelectionWholeBlock CursorEndpoint and WholeBlockEndpoint duplicated SelectionCursor and SelectionWholeBlock with a different discriminant field (kind vs type). Reuse the existing types so SelectionEndpoint = SelectionCursor | SelectionWholeBlock, and simplify resolveEndpoint callers to pass the endpoint directly to resolveSelection. Also fixes a latent type error where BlockEntry.userId was typed as string but collaboratorInfo.id is number, and fills in the missing clientId/userId fields on the same-container early-return path.
|
Thanks @alecgeatches for the review and the walkthrough video, it made the issue easy to reproduce. I've addressed all four points:
The PR is ready for a second round of review. |
Adds getOrderedBlockRange() to cursor-dom-utils, consolidating the
repeated query + DOM-order normalisation + blockContainerOf promotion +
getBlocksBetween/filter pattern that existed independently in both
useBlockHighlighting and computeMultiBlockOverlayRects.
Returns { firstEl, firstId, lastEl, lastId, middleEls, sameContainer }
so callers get IDs alongside elements (no extra getAttribute calls) and
a clear sameContainer flag instead of comparing IDs manually.
getBlocksBetween and isNodeBefore are now internal helpers (no longer
exported) since all external callers have been replaced by
getOrderedBlockRange.
b4949df to
71cf639
Compare
alecgeatches
left a comment
There was a problem hiding this comment.
@giteshsarvaiya Good job on the fixes! I ran through testing and all is working well for me. I'm seeing one failed e2e run, doubt it's related, will see if that passes with a trunk merge. Thank you!
|
hooray! the tests passed. |
|
I've gone ahead and merged this in. Thanks for working on this! |
* core-data: add CursorEndpoint/WholeBlockEndpoint discriminated union for multi-block selections
Introduces a SelectionEndpoint discriminated union to distinguish between two
kinds of selection endpoints in a multi-block selection:
- CursorEndpoint (kind: cursor): the endpoint lands inside a RichText field
and carries a character-level Y.Text relative position. Any block type that
has a text field with a known offset produces this shape.
- WholeBlockEndpoint (kind: whole-block): the endpoint has no character
offset, either because the block has no RichText field (e.g. image, spacer)
or it is selected as a whole block via keyboard block-selection mode. The
anchor is pinned to the block slot in its parent Y.Array instead.
Previously both endpoint shapes were treated identically, which caused the
overlay to break when a selection spanned mixed block types (e.g. paragraph
to image) because the receiver tried to resolve a text cursor on a block that
has no Y.Text.
* editor: fix getBlocksBetween to skip blocks already covered by a parent
When a selection spans a list block, getBlocksBetween previously returned
both the list and each of its list items, causing every item to receive a
duplicate CSS outline on top of the one already applied to the parent.
The fix filters out any block element that is a descendant of a block
already collected, so each region is highlighted exactly once regardless
of nesting depth.
* editor: handle SelectionInMultipleBlocks in the collaborators overlay
Fixes RTC selection awareness breaking when a collaborator selects across
blocks of different types (e.g. a paragraph into an image).
Before this change, multi-block selections fell through to the same code
path as single-block text selections, which tried to resolve a RichText
cursor on every endpoint. Non-text blocks have no Y.Text anchor, so the
resolution threw and the overlay rendered nothing.
Changes:
- use-render-cursors: adds a dedicated branch for SelectionInMultipleBlocks
that resolves each endpoint independently via its kind (cursor vs
whole-block), so mixed-type combinations resolve correctly.
- compute-selection: simplifies the multi-block path to return only the
cursor position at the active end. Pixel-level selection rects across
multiple blocks are replaced by a CSS outline approach (see below), so
this function no longer needs to compute them.
- use-block-highlighting: extended to cover SelectionInMultipleBlocks.
Every block in the selection range receives the is-collaborator-selected
CSS class and a --collaborator-outline-color custom property. The avatar
label is placed on the topmost block in document order only, avoiding
one label per block per collaborator.
* @
editor: mixed visual treatment for multi-block awareness (overlay + outline)
Replaces the earlier CSS-outline-only approach with a two-track visual
system that more closely mirrors how a selection looks to the person making it:
Text blocks (paragraph, heading, list, etc.):
- Full bounding-box semi-transparent fill overlay via selectionRects
- First block gets a full bounding-box when the selection starts at offset 0;
partial text-level rects (cursor offset → end) when it starts mid-block
- Middle blocks always get the full bounding-box fill
- Last block always uses text-level rects (0 → cursor offset) so only the
selected portion is highlighted, not the unselected tail
Non-text blocks (image, spacer, etc.):
- CSS outline border only (is-collaborator-selected class +
--collaborator-outline-color custom property)
- No fill overlay so image content is not obscured
Additional fixes included:
- Stale CSS class bug: is-collaborator-selected is now reset for every
block on each render before being conditionally re-applied, preventing
it from persisting when a block transitions between selection types
- resolveTargetElement gains a richer fallback chain (contenteditable →
common text elements p/h1-h6/etc.) so partial text rects work correctly
even when the observer has not focused the block
- Text block detection uses innerText.trim() instead of querying
[contenteditable] or [data-wp-block-attribute-key], which are absent on
blocks the observer has not clicked into
@
* editor: promote inner blocks to container for multi-block highlights
When a multi-block selection starts or ends inside an inner block (e.g. a
list-item inside a list), the sibling inner blocks between the endpoint and
the other selection end appeared as individual middle blocks — each getting
its own fill rect, producing a striped banded look instead of one rect
covering the whole list.
Fix: promote each selection endpoint to its nearest [data-block] ancestor
before computing rects and block highlights. Middle blocks that are
descendants of the promoted container are filtered out. When both endpoints
promote to the same container (e.g. two list-items inside one list), a
single bounding-box rect is emitted for that container.
Adds a blockContainerOf helper to cursor-dom-utils and applies the
promotion in both compute-selection and use-block-highlighting.
* editor: fix isNodeBefore bitmask check for nested-block endpoints
compareDocumentPosition returns a bitmask. When b is a following
descendant of a the result is FOLLOWING | CONTAINED_BY (4 | 16 = 20),
not 4. The strict === 4 check returned false, silently skipping the
start/end swap for any endpoint pair where one element is nested inside
the other (e.g. a list-item resolved before blockContainerOf runs).
Fix: use a bitwise AND so any value with the FOLLOWING bit set returns true.
* editor: fix blockContainerOf to promote only direct-parent containers
The previous closest('[data-block]') call walked arbitrarily far up the
ancestor chain. A core/list nested inside core/group has a non-[data-block]
wrapper div between them, so closest() would skip past the list and
promote to the group — wrong block gets highlighted.
The correct scope is the immediate parentElement: if the direct parent is
itself a [data-block] element (as in list-item → list), promote; otherwise
leave the element as-is.
* editor: guard against missing endpoint elements in block highlighting
When an endpoint block is absent from the DOM (e.g. deleted by a
concurrent edit), the previous code set effectiveFirstEl/effectiveLastEl
to null via a ternary. null?.contains(el) evaluates to undefined and
!undefined is true, so every intermediate block passed the containment
filter — spuriously highlighting the entire selection range.
Add an early return when either endpoint element is missing. Also remove
the null-guard ternaries that are now unreachable, letting TypeScript
narrow firstEl/lastEl to HTMLElement directly.
* editor: move inner-block promotion out of compute-selection into use-render-cursors
computeMultiBlockOverlayRects is a pure computation function; DOM-based
inner-block promotion (list-item → list) was an architectural mismatch.
Move the promotion step to use-render-cursors, where resolved endpoints
are built before computeSelectionVisual is called. A promote() helper
checks if the resolved element's immediate parentElement is a [data-block]
container and rewrites localClientId + nulls out richTextOffset if so.
This matches the same boundary check used by blockContainerOf.
computeMultiBlockOverlayRects now trusts that its inputs are already
container-level IDs: it drops the effectiveFirstEl/effectiveLastEl/wasPromoted
variables and uses docFirstEl/docLastEl directly. The same-container early
return becomes a simple localClientId equality check.
* editor: fix scroll-to-avatar for multi-block selections
Clicking a collaborator's avatar in the presence list did nothing when
that user had a SelectionInMultipleBlocks selection active. The cursor
registry only held elements for cursor/single-block states; highlight
avatar elements rendered for multi-block selections were never registered,
so scrollToCursor returned false silently.
Fix: thread clientId through BlockHighlightData, wrap each highlight
Avatar in a positioned div captured via the existing setCursorRef/
cursorRefsMap pattern, and sync both cursor and highlight elements in
one unified registry effect. The pulse animation on scroll is extended
to cover block-label avatars via a shared keyframe parameterised with
--label-base-transform, keeping the CSS DRY.
* editor: key usersWithAvatar by user ID instead of color
Only 7 colors exist in the palette so with 8+ collaborators, users
sharing a color would have their avatar label skipped entirely. Key by
collaboratorInfo.id (the WordPress user ID) instead — consistent with
how user identity is used everywhere else in this file.
* core-data, editor: drop CursorEndpoint/WholeBlockEndpoint in favour of SelectionEndpoint = SelectionCursor | SelectionWholeBlock
CursorEndpoint and WholeBlockEndpoint duplicated SelectionCursor and
SelectionWholeBlock with a different discriminant field (kind vs type).
Reuse the existing types so SelectionEndpoint = SelectionCursor |
SelectionWholeBlock, and simplify resolveEndpoint callers to pass the
endpoint directly to resolveSelection.
Also fixes a latent type error where BlockEntry.userId was typed as
string but collaboratorInfo.id is number, and fills in the missing
clientId/userId fields on the same-container early-return path.
* editor: extract shared block-range DOM logic into getOrderedBlockRange
Adds getOrderedBlockRange() to cursor-dom-utils, consolidating the
repeated query + DOM-order normalisation + blockContainerOf promotion +
getBlocksBetween/filter pattern that existed independently in both
useBlockHighlighting and computeMultiBlockOverlayRects.
Returns { firstEl, firstId, lastEl, lastId, middleEls, sameContainer }
so callers get IDs alongside elements (no extra getAttribute calls) and
a clear sameContainer flag instead of comparing IDs manually.
getBlocksBetween and isNodeBefore are now internal helpers (no longer
exported) since all external callers have been replaced by
getOrderedBlockRange.
---------
Co-authored-by: giteshsarvaiya <giteshsarvaiya@git.wordpress.org>
Co-authored-by: alecgeatches <alecgeatches@git.wordpress.org>
Co-authored-by: ingeniumed <ingeniumed@git.wordpress.org>
Co-authored-by: t-hamano <wildworks@git.wordpress.org>
This updates the pinned commit hash of the Gutenberg repository from `5a0417990a9404cc2562cdd35e3054cb0d3e546e` (version `23.7.0`) to `ae8b34d78e1abc6d1c866fdc00080a1452de82ad` (version `23.8.0`). A full list of changes included in this commit can be found on GitHub: https://github.com/WordPress/gutenberg/compare/v23.7.0..v23.8.0. - Fix: New route-based admin pages are empty when no JS (WordPress/gutenberg#80628) - Report flaky tests: declare @types/node (WordPress/gutenberg#80838) - Storybook e2e: add small, compact and icon examples to button matrix (WordPress/gutenberg#80793) - ESLint: Drop @eslint/compat fixup for jest-dom and testing-library (WordPress/gutenberg#80842) - Storybook: Use SelectControl in Motion tokens story (WordPress/gutenberg#80790) - Writing flow: forward delete an empty paragraph without breaking apart the next block (WordPress/gutenberg#80813) - ToggleControl: Add "With Visual" Storybook story (WordPress/gutenberg#80803) - Bump the github-actions group across 1 directory with 5 updates (WordPress/gutenberg#80684) - Update Ariakit packages (WordPress/gutenberg#80765) - Components: Update Floating UI to 2.1.9 (WordPress/gutenberg#80761) - Improve pre-flight npm access checks (WordPress/gutenberg#80334) - Ensure static checks can run for private mirrors (WordPress/gutenberg#80817) - Components: Update react-day-picker to 9.14.0 (WordPress/gutenberg#80792) - Testing: Remove the jest-puppeteer-axe package (WordPress/gutenberg#80775) - Tools: Use native agent skill discovery (WordPress/gutenberg#80811) - Table of Contents: Replace "Convert to static list" with confirmed "Detach" action (WordPress/gutenberg#80844) - Page List: Rename the Edit action to Detach and confirm it in a dialog (WordPress/gutenberg#80847) - URLInput: Simplify keyboard handling and fix arrow keys with a text selection (WordPress/gutenberg#80780) - Upload Media: Fail the item when the /finalize request fails (WordPress/gutenberg#80725) - Jest preset: support self-referencing via exports (WordPress/gutenberg#80837) - Fix template `modified` and `date` return value for file templates (WordPress/gutenberg#80733) - Element: Make the package importable in React 19 (WordPress/gutenberg#80053) - Add CheckboxControl with-visual Storybook story. (WordPress/gutenberg#80849) - UI: Add Combobox.InputGroup primitive (WordPress/gutenberg#80869) - Block Editor: Clear the moving animation's z-index when it finishes (WordPress/gutenberg#80824) - SearchableChipSelect: Add form primitive to @wordpress/ui (WordPress/gutenberg#80779) - Playlist: Add track icon (WordPress/gutenberg#80959) - Flaky tests: fix router navigate latest HTML (WordPress/gutenberg#80178) - Components: Update polymorphism types to allow all attributes when specifying `as` (WordPress/gutenberg#80705) - Block JSON schema & PHP-only blocks: Add `autoRegister` support (WordPress/gutenberg#80173) - Boot: Adjust specificity of the image reset styles so components can size their own images (WordPress/gutenberg#80845) - Components: Opt-in ConfirmDialog for auto-documentation (WordPress/gutenberg#80866) - RichText: don't apply input transform when there's no onReplace (WordPress/gutenberg#80978) - Quote: Ensure paragraph placeholder appears after deleting nested blocks (WordPress/gutenberg#77151) - Block editor: make the Group action wrap blocks with a group transform (WordPress/gutenberg#80891) - Copy: preserve the block when its entire text is selected (WordPress/gutenberg#80994) - Add opt-out for block style state controls (WordPress/gutenberg#80956) - Upgrade Playwright to v1.62 (WordPress/gutenberg#81001) - List View: Speed up opening by removing a forced style recalculation (WordPress/gutenberg#80929) - Widget Dashboard: preserve widget chrome flex layout (WordPress/gutenberg#80570) - Widget Primitives: document actions as verbs, not links (WordPress/gutenberg#80974) - Core Data: add missing @wordpress/base-styles dependency (WordPress/gutenberg#81012) - Ignore node_modules in schema integration test globs (WordPress/gutenberg#81013) - RichText: fix paste handler crash when pasting files (WordPress/gutenberg#81010) - Docs: Add VS Code setup guidance (WordPress/gutenberg#77913) - Perf Tests: Merge selection event windows instead of summing durations (WordPress/gutenberg#81002) - Storybook: move smoke-test deps into the storybook workspace (WordPress/gutenberg#81017) - Unit Tests: Reorganise the jest config file. (WordPress/gutenberg#80940) - Performance Tests: Ignore the locally generated artifacts directory (WordPress/gutenberg#81024) - Editor: Remove sidebar tabs focus sync effect (WordPress/gutenberg#81020) - Widgets: carry a declarative `icon` through the widget pipeline (WordPress/gutenberg#80969) - RTC: Remove excess autosave notices (when not useful) (WordPress/gutenberg#80539) - UI: Use focus styles from base-styles (WordPress/gutenberg#80635) - Automated Testing: Add Storybook component documentation regression testing (WordPress/gutenberg#80655) - Pass Playlist controls to track blocks (WordPress/gutenberg#80368) - Components: Deprecate Surface (WordPress/gutenberg#80943) - Tabs: Support Home and End keys for keyboard navigation (WordPress/gutenberg#80912) - Fix typos in code `comments` and `JSDoc` (WordPress/gutenberg#81045) - Interface: Remove incorrect aria-expanded from pin-to-toolbar button (WordPress/gutenberg#79874) - List View: Speed up opening by removing a second render pass (WordPress/gutenberg#80935) - Docs: Use `gutenberg` instead of `Gutenberg` in package name (WordPress/gutenberg#81051) - Fix ESLint warnings for 'navigateRegionsProps' spread (WordPress/gutenberg#81052) - Tests: Replace page-level selectors with locators in e2e specs (WordPress/gutenberg#81053) - RTC: Disable custom autosave controller when RTC is disabled (WordPress/gutenberg#80769) - Background: Fix the legacy gradient UI where a gradient cannot be selected (WordPress/gutenberg#81056) - Rename blockStatesEnabled setting to blockStatesEditingEnabled (WordPress/gutenberg#81058) - Background: Fix the legacy gradient UI where a gradient cannot be selected - Add missing change log (WordPress/gutenberg#81060) - Declare missing prettier and stylelint workspace dependencies (WordPress/gutenberg#81065) - Notes: Display shortcut for form action buttons (WordPress/gutenberg#81064) - View config: lowercase dynamic filter names (WordPress/gutenberg#81068) - CI: cache the system packages Playwright dowloads for WebKit (WordPress/gutenberg#80846) - List View: Collapse off-window placeholder rows into a single spacer row (WordPress/gutenberg#80953) - devops: balance Playwright shards by duration (WordPress/gutenberg#79479) - Views: honor developer-defined view config overrides (WordPress/gutenberg#80832) - Components: Migrate ItemGroup to SCSS module (WordPress/gutenberg#80797) - Test: Add Vitest migration routing guardrails (WordPress/gutenberg#81036) - Visual revisions: Add a code diff view inside the editor (WordPress/gutenberg#80314) - Bump actions/labeler from 6.1.0 to 7.0.0 in /.github/workflows (WordPress/gutenberg#80966) - Visual revisions: add shareable urls (WordPress/gutenberg#79934) - Fix e2e tests that break when shard rebalancing changes their neighbors (WordPress/gutenberg#81117) - Query: guard non-scalar queryId to avoid fatal in enhanced pagination (WordPress/gutenberg#80724) - Replace deprecated useResizeObserver for useScaleCanvas (WordPress/gutenberg#67508) - CI: Add job timeouts to E2E, static checks, bundle size, and create block (WordPress/gutenberg#80801) - Allow organization or repository variable to dictate the Actions runner (WordPress/gutenberg#81087) - List View: Only subscribe to the block subtree for rows that can show images (WordPress/gutenberg#81076) - Editor: Migrate settings sidebar to Tabs from @wordpress/ui (WordPress/gutenberg#81054) - Notes: Email users mentioned in a note (WordPress/gutenberg#79606) - List View: Narrow the dependants of the memoized List View tree (WordPress/gutenberg#81129) - List View: Pass only minimal required data to sub-tree branches (WordPress/gutenberg#81111) - DataViews: Add `time` field type and control (WordPress/gutenberg#80830) - Post Author Biography: Fix horizontal overflow with long unbroken text (WordPress/gutenberg#81018) - Link Control: Restore the preview title underline (WordPress/gutenberg#81083) - Views: Add React to the package's dev dependencies (WordPress/gutenberg#81139) - feat: Add target param in proxy (WordPress/gutenberg#81141) - Fix: flaky revisions pagination e2e cross-page diff assertion (WordPress/gutenberg#81119) - Button: Suppress UA focus ring when focused and pressed (WordPress/gutenberg#81113) - View config: add reference docs (WordPress/gutenberg#81149) - Editor: Fix document tools button focus ring (WordPress/gutenberg#81115) - List View: Replace expand/collapse callbacks with reducer dispatch (WordPress/gutenberg#81138) - CI: resolve Playwright through the workspace that declares it (WordPress/gutenberg#81137) - CI: build once and reuse the build across e2e tests shards (WordPress/gutenberg#81031) - Fix: Tabs block: Start with empty tab labels with placeholders (WordPress/gutenberg#81009) - Interface: Increase footer breadcrumb height to prevent focus ring clipping (WordPress/gutenberg#81145) - Editor: clarify autosave failure notice (WordPress/gutenberg#76470) - Theme: Omit color properties when neither provided nor inherited (WordPress/gutenberg#80600) - Post editor: Add ThemeProvider for admin color schemes (WordPress/gutenberg#81112) - Core Data: saveDirtyEntities: improve messaging of server errors (WordPress/gutenberg#81151) - Global Styles: kebab-case preset slugs when converting references to custom properties (WordPress/gutenberg#80583) - Revise CODEOWNERS for package ownership (removing nerrad) (WordPress/gutenberg#81175) - Build: Wrap script bundles in an IIFE to contain 'use strict' (WordPress/gutenberg#79792) - Media: Improve the HEIC upload error and keep any upload errors up until dismissed (WordPress/gutenberg#81130) - Block Supports: Guard against non-string attribute values to avoid fatal errors (WordPress/gutenberg#80501) - Video: Hide settings for the GIF variation (WordPress/gutenberg#81142) - Video: clarify the Video variation description (WordPress/gutenberg#81181) - Media Editor: Render sidebar tabs via ComplementaryArea's render prop (WordPress/gutenberg#81133) - Bump docker/login-action (WordPress/gutenberg#80962) - Bump actions/setup-node from 6.4.0 to 7.0.0 in /.github/setup-node (WordPress/gutenberg#80963) - fix(admin-ui): reset the wp-admin li margin that misaligns Breadcrumbs (WordPress/gutenberg#81134) - Bump astral-sh/setup-uv from 8.3.2 to 9.0.0 in /.github/workflows (WordPress/gutenberg#80964) - Fix/79643 rtc awareness mixed block selection (WordPress/gutenberg#79836) - Agents: Add a defensive data design skill and route to the copy guide (WordPress/gutenberg#81131) - ESLint: Ban `@ts-ignore` in favour of `@ts-expect-error` (WordPress/gutenberg#81148) - Types: Replace remaining `@ts-expect-error` suppressions with proper typing (WordPress/gutenberg#81200) - Button: turn on the width setting by default in theme.json (WordPress/gutenberg#81196) - Notes: fix the mention notification email composition (WordPress/gutenberg#81187) - Edit Widgets: Fix header toolbar button focus ring (WordPress/gutenberg#81176) - List View: Reduce per-row store subscriptions (WordPress/gutenberg#81136) - Customizer widgets: Add ThemeProvider for admin color schemes (WordPress/gutenberg#81174) - Blocks: Declare the `@types/react-is` devDependency (WordPress/gutenberg#81203) - Widgets editor: Add ThemeProvider for admin color schemes (WordPress/gutenberg#81173) - PanelColorSettings: Restore the missing space below the panel header (WordPress/gutenberg#81155) - Remove the editableRoot opt-in from the paragraph block (WordPress/gutenberg#81184) - Media Attached to: Fix issue with the popover unexpectedly flipping, tweak wording (WordPress/gutenberg#81206) - Components: Add allowForms prop to SandBox component (WordPress/gutenberg#76471) - Ensure device preview is always accurate when window is zoomed in (WordPress/gutenberg#81193) - DataViews: Pass only eligible items to a bulk action callback (WordPress/gutenberg#81198) - DataViews: Make grid and table item preview aspect ratio configurable (WordPress/gutenberg#79329) - Block Editor: Return false from isBlockSelected when there is no client ID (WordPress/gutenberg#81212) - Blocks: Update hpq to 1.4.0 and drop the ts-expect-error suppressions (WordPress/gutenberg#81199) - DataViews: Fix the between `date` filter losing a manually typed date on blur (WordPress/gutenberg#81150) - Block Editor: Fix Select all freezing on large posts (WordPress/gutenberg#81210) - List View: Split the context and drop dead props to cut re-renders on expand and collapse (WordPress/gutenberg#81159) - Docs: Fix broken Figma links on the design resources page. (WordPress/gutenberg#81166) - Blocks: Support static block templates in block type settings (WordPress/gutenberg#80027) - AGENTS: Update development tips section (WordPress/gutenberg#81224) - URLInput: Skip search requests while an IME composition is in progress (WordPress/gutenberg#80602) - Components: Deprecate Animate (WordPress/gutenberg#80931) - Fix: Trunk e2e failures caused by the navigation spec leaving plain permalinks (WordPress/gutenberg#81238) - Use p elements for callout alerts in components docs (WordPress/gutenberg#81234) - Storybook: Enable design system theme tool globally (WordPress/gutenberg#81116) - Media: Cover client-side big image scaling and orphaned files (WordPress/gutenberg#81061) - Footnotes: guard against invalid post meta values (WordPress/gutenberg#81201) - Block editor: unify what a new sibling block inherits (WordPress/gutenberg#81250) - Global Styles: Remove unused duplicate font family preview utilities (WordPress/gutenberg#81194) - Docs: explain how the theme.json schema fixtures work (WordPress/gutenberg#81263) - theme.json schema: fix block pseudo-classes and custom states (WordPress/gutenberg#81209) - List View: Migrate label wrapper to UI Stack and drop Badge from the anchor (WordPress/gutenberg#81266) - Docs: Remove dependency group import guidance (WordPress/gutenberg#81245) - ESLint: Define dependency import rules (WordPress/gutenberg#81246) - Flaky tests: Report as a PR comment only, stop creating issues (WordPress/gutenberg#81218) - Dashboard Widgets: remove the Hello Dolly download action (WordPress/gutenberg#81272) - Block Directory: Make downloadable block item labels translatable. (WordPress/gutenberg#81237) - Icons: move the inline image icon into the library (WordPress/gutenberg#81271) - Update phpcs, wpcs dependencies (WordPress/gutenberg#81279) - fix: add optional chaining for empty reusable block (WordPress/gutenberg#81165) (WordPress/gutenberg#81177) - DataViews: Inline the kebabCase utility from components (WordPress/gutenberg#81284) - Navigation: Complete the DocBlocks for the responsive container and overlay style helpers (WordPress/gutenberg#80622) - Style states: fix state deselection when selecting the already selected block (WordPress/gutenberg#81277) - Fix missing return value in force_filtered_html_on_import footnotes filter (WordPress/gutenberg#81293) - Packages: Recover July 29 npm release metadata (WordPress/gutenberg#81303) - Global styles: render element styles set only inside a breakpoint (WordPress/gutenberg#81265) - Style states: Fix phantom pseudo element style output (WordPress/gutenberg#81291) - Docs: remove breakpoint-only element style example from global styles guide (WordPress/gutenberg#81308) - Render viewport state element styles in the editor (WordPress/gutenberg#81307) - theme.json schema: responsive states belong to blocks (WordPress/gutenberg#81253) - theme.json schema: allow responsive states on block style variations (WordPress/gutenberg#81309) - Block Inspector: Disable the 'Edit original' button when the entity ID is missing (WordPress/gutenberg#81288) - Site Editor: Decode HTML entities in Identity fields (WordPress/gutenberg#81269) - Document editEntityRecord's options.isCached param (WordPress/gutenberg#81318) - Theme: Colocate plugin tests with implementations (WordPress/gutenberg#81247) - Block Editor: Bail out of hasSelectedInnerBlock when there is no clientId (WordPress/gutenberg#81315) - ESLint: Replace strict config with bulk suppressions (WordPress/gutenberg#81248) - Block Library: Document security patch releases (WordPress/gutenberg#81295) - ESLint: Remove legacy import suppressions (WordPress/gutenberg#81338) - Components: Migrate Spinner styles from Emotion to SCSS module (WordPress/gutenberg#80511) - DataViews: Use the public `@wordpress/ui` Badge instead of the private one (WordPress/gutenberg#81236) - Pert Tests: Avoid inflating interaction metrics with startTracing (WordPress/gutenberg#81264) - Block Editor: Simplify InspectorControlsFill rendering logic (WordPress/gutenberg#81286) - Keycodes: Make `withIgnoreIMEEvents` a public API (WordPress/gutenberg#81343) - Docs: Fix broken links to the removed JavaScript build setup guide. (WordPress/gutenberg#81232) - Markdownlint: disable the MD033 (no-inline-html) rule project-wide (WordPress/gutenberg#81213) - Docs: Fix typo in block file structure guide (WordPress/gutenberg#81348) - Playlist: Normalize Waveform Player configuration handling (WordPress/gutenberg#81342) - Site editor: fix unreadable sidebar hover color (WordPress/gutenberg#81317) - Global Styles: Migrate font size presets to the shared preset layer (WordPress/gutenberg#79811) - Block Editor: Convert useBlockVisibility hook to TypeScript (WordPress/gutenberg#80390) - Scope breakpoint media queries to screen so printing does not look like a resize (WordPress/gutenberg#81367) - New `kebab-case` package: extract utility and migrate private API calls (WordPress/gutenberg#81294) - Remove content types experiment (WordPress/gutenberg#81340) - Editor: Keep the canvas height stable while resizing the canvas (WordPress/gutenberg#81163) - Dashboard Widgets: add icon and relevance to the widget action envelope (WordPress/gutenberg#81275) - Playlist: Improve audio conversion and track selection (WordPress/gutenberg#80926) - Try: Set post navigation link icon to next. (WordPress/gutenberg#81384) - UI: Derive Base UI direction from WordPress i18n (WordPress/gutenberg#80399) - DataViews: Fix `array` field type validation for empty values (WordPress/gutenberg#81378) - Bump github/codeql-action/upload-sarif (WordPress/gutenberg#81287) - Bump actions/stale from 10.4.0 to 11.0.0 in /.github/workflows (WordPress/gutenberg#81219) - Monitor all setup-related composite Actions. (WordPress/gutenberg#81192) - IconButton: Improve keyboard shortcut accessibility (WordPress/gutenberg#80402) - Interface: Stop the secondary sidebar animating into place on page load (WordPress/gutenberg#81362) - Fix: flaky list view paste block styles e2e test (WordPress/gutenberg#81229) - Docs: Add error message guidance. (WordPress/gutenberg#81143) - wp-build: Render the no-JS fallback in the generated page templates (WordPress/gutenberg#81365) - Changed Build Process Link and remove unused tags (WordPress/gutenberg#81360) - Scripts: Prevent a UTF-8 BOM in the middle of extracted CSS (WordPress/gutenberg#81383) - Global Styles: Migrate shadows presets to use the preset management layer. (WordPress/gutenberg#79812) - Global Styles: report border, shadow, outline, filter and dimension changes (WordPress/gutenberg#81407) - Dashboard Widgets: split action icons into wire and resolved forms (WordPress/gutenberg#81381) - Block Editor: Fix Escape from the block toolbar and stop redundant last focus dispatches (WordPress/gutenberg#81319) - Block Mover: Add the keyboard shortcut to the buttons' spoken description (WordPress/gutenberg#81380) - Format Library: Remove unused CSS rule (WordPress/gutenberg#77831) - Core Data: Fix post staying dirty after saving edited post meta with RTC enabled (WordPress/gutenberg#81392) - Widget Dashboard: host-tunable tile spacing via public custom properties (WordPress/gutenberg#81352) - DataForm: Fix hidden fields validation (WordPress/gutenberg#81377) - Tools: Make agent setup a workspace (WordPress/gutenberg#81418) - Test: Add private Vitest config and migrate a pilot test (WordPress/gutenberg#81037) - Interface: Let CSS own the ComplementaryArea width and use AnimatePresence custom for exit (WordPress/gutenberg#81363) - Notes: Fix text wrapping for long usernames in collaboration sidebar (WordPress/gutenberg#81406) - DataViews: vendor `ValidatedSelectControl` (WordPress/gutenberg#81391) - UI: Add `Calendar` and `RangeCalendar`, moved from `components` private APIs (WordPress/gutenberg#81337) - Feature: Tabs block: consider using the tab title in the Document Overview instead of a generic "Tab" label (WordPress/gutenberg#81427) - fix: order site identity changes predictably (WordPress/gutenberg#81283) - Docs: Fix broken link to the Node.js debugging guide. (WordPress/gutenberg#81398) - Navigation Overlay Close: Inherit typography and color from the overlay (WordPress/gutenberg#80751) - Fix: Regression: Tabs block: Start with empty tab labels with placeholders (WordPress/gutenberg#81429) - components/Menu: Restore Modal focus return when menu items close (WordPress/gutenberg#81164) - DataViews: Move the rich text control into the editor package (WordPress/gutenberg#81430) - Components: Remove ValidatedTextControl private API (WordPress/gutenberg#80680) - Cover: Avoid passing null as the featured image size (WordPress/gutenberg#81444) - DataViews: vendor `ValidatedNumberControl` (WordPress/gutenberg#81433) - DataViews: vendor ValidatedCheckboxControl (WordPress/gutenberg#81435) - DataViews: vendor ValidatedRadioControl (WordPress/gutenberg#81434) - UI: Upgrade React DayPicker to version 10 (WordPress/gutenberg#81439) - Block editor: render a real default block in place of the default appender (WordPress/gutenberg#81231) - Spacing Sizes Control: Make the control label translatable independently of the input aria label. (WordPress/gutenberg#81240) - Writing flow: stop the caret on containers that do not merge with the text flow (WordPress/gutenberg#81456) - Remove an unmodified wrapper when all its inner blocks are removed (WordPress/gutenberg#42503) - Normalize block gap values in layout (WordPress/gutenberg#81460) - Docs: Add Block Actions Readme (WordPress/gutenberg#69408) - Block editor: disclose the nested block count of a multi selection (WordPress/gutenberg#80745) - Fix: flaky autosave shareable URL revisions e2e test. (WordPress/gutenberg#81455) - Fix empty list block with anchor persisting after backspace deletion (WordPress/gutenberg#77000) - Docs: Add Block List Readme (WordPress/gutenberg#69432) - Docs: Add Block Lock Readme (WordPress/gutenberg#69434) - Backport core fixes for finalize route (WordPress/gutenberg#81465) - ESLint: Rename root eslint.config.cjs to eslint.config.mjs (WordPress/gutenberg#81468) - Update 23.8 release branch (WordPress/gutenberg#81486) - RTC: Couple RTC and polling provider as an opt-in experiment (WordPress/gutenberg#80658) - Align collaboration migration version markers with the 23.8.0 release (WordPress/gutenberg#81789) Fixes #66066. git-svn-id: https://develop.svn.wordpress.org/trunk@63534 602fd350-edb4-49c9-b593-d223f7449a82
This updates the pinned commit hash of the Gutenberg repository from `5a0417990a9404cc2562cdd35e3054cb0d3e546e` (version `23.7.0`) to `ae8b34d78e1abc6d1c866fdc00080a1452de82ad` (version `23.8.0`). A full list of changes included in this commit can be found on GitHub: https://github.com/WordPress/gutenberg/compare/v23.7.0..v23.8.0. - Fix: New route-based admin pages are empty when no JS (WordPress/gutenberg#80628) - Report flaky tests: declare @types/node (WordPress/gutenberg#80838) - Storybook e2e: add small, compact and icon examples to button matrix (WordPress/gutenberg#80793) - ESLint: Drop @eslint/compat fixup for jest-dom and testing-library (WordPress/gutenberg#80842) - Storybook: Use SelectControl in Motion tokens story (WordPress/gutenberg#80790) - Writing flow: forward delete an empty paragraph without breaking apart the next block (WordPress/gutenberg#80813) - ToggleControl: Add "With Visual" Storybook story (WordPress/gutenberg#80803) - Bump the github-actions group across 1 directory with 5 updates (WordPress/gutenberg#80684) - Update Ariakit packages (WordPress/gutenberg#80765) - Components: Update Floating UI to 2.1.9 (WordPress/gutenberg#80761) - Improve pre-flight npm access checks (WordPress/gutenberg#80334) - Ensure static checks can run for private mirrors (WordPress/gutenberg#80817) - Components: Update react-day-picker to 9.14.0 (WordPress/gutenberg#80792) - Testing: Remove the jest-puppeteer-axe package (WordPress/gutenberg#80775) - Tools: Use native agent skill discovery (WordPress/gutenberg#80811) - Table of Contents: Replace "Convert to static list" with confirmed "Detach" action (WordPress/gutenberg#80844) - Page List: Rename the Edit action to Detach and confirm it in a dialog (WordPress/gutenberg#80847) - URLInput: Simplify keyboard handling and fix arrow keys with a text selection (WordPress/gutenberg#80780) - Upload Media: Fail the item when the /finalize request fails (WordPress/gutenberg#80725) - Jest preset: support self-referencing via exports (WordPress/gutenberg#80837) - Fix template `modified` and `date` return value for file templates (WordPress/gutenberg#80733) - Element: Make the package importable in React 19 (WordPress/gutenberg#80053) - Add CheckboxControl with-visual Storybook story. (WordPress/gutenberg#80849) - UI: Add Combobox.InputGroup primitive (WordPress/gutenberg#80869) - Block Editor: Clear the moving animation's z-index when it finishes (WordPress/gutenberg#80824) - SearchableChipSelect: Add form primitive to @wordpress/ui (WordPress/gutenberg#80779) - Playlist: Add track icon (WordPress/gutenberg#80959) - Flaky tests: fix router navigate latest HTML (WordPress/gutenberg#80178) - Components: Update polymorphism types to allow all attributes when specifying `as` (WordPress/gutenberg#80705) - Block JSON schema & PHP-only blocks: Add `autoRegister` support (WordPress/gutenberg#80173) - Boot: Adjust specificity of the image reset styles so components can size their own images (WordPress/gutenberg#80845) - Components: Opt-in ConfirmDialog for auto-documentation (WordPress/gutenberg#80866) - RichText: don't apply input transform when there's no onReplace (WordPress/gutenberg#80978) - Quote: Ensure paragraph placeholder appears after deleting nested blocks (WordPress/gutenberg#77151) - Block editor: make the Group action wrap blocks with a group transform (WordPress/gutenberg#80891) - Copy: preserve the block when its entire text is selected (WordPress/gutenberg#80994) - Add opt-out for block style state controls (WordPress/gutenberg#80956) - Upgrade Playwright to v1.62 (WordPress/gutenberg#81001) - List View: Speed up opening by removing a forced style recalculation (WordPress/gutenberg#80929) - Widget Dashboard: preserve widget chrome flex layout (WordPress/gutenberg#80570) - Widget Primitives: document actions as verbs, not links (WordPress/gutenberg#80974) - Core Data: add missing @wordpress/base-styles dependency (WordPress/gutenberg#81012) - Ignore node_modules in schema integration test globs (WordPress/gutenberg#81013) - RichText: fix paste handler crash when pasting files (WordPress/gutenberg#81010) - Docs: Add VS Code setup guidance (WordPress/gutenberg#77913) - Perf Tests: Merge selection event windows instead of summing durations (WordPress/gutenberg#81002) - Storybook: move smoke-test deps into the storybook workspace (WordPress/gutenberg#81017) - Unit Tests: Reorganise the jest config file. (WordPress/gutenberg#80940) - Performance Tests: Ignore the locally generated artifacts directory (WordPress/gutenberg#81024) - Editor: Remove sidebar tabs focus sync effect (WordPress/gutenberg#81020) - Widgets: carry a declarative `icon` through the widget pipeline (WordPress/gutenberg#80969) - RTC: Remove excess autosave notices (when not useful) (WordPress/gutenberg#80539) - UI: Use focus styles from base-styles (WordPress/gutenberg#80635) - Automated Testing: Add Storybook component documentation regression testing (WordPress/gutenberg#80655) - Pass Playlist controls to track blocks (WordPress/gutenberg#80368) - Components: Deprecate Surface (WordPress/gutenberg#80943) - Tabs: Support Home and End keys for keyboard navigation (WordPress/gutenberg#80912) - Fix typos in code `comments` and `JSDoc` (WordPress/gutenberg#81045) - Interface: Remove incorrect aria-expanded from pin-to-toolbar button (WordPress/gutenberg#79874) - List View: Speed up opening by removing a second render pass (WordPress/gutenberg#80935) - Docs: Use `gutenberg` instead of `Gutenberg` in package name (WordPress/gutenberg#81051) - Fix ESLint warnings for 'navigateRegionsProps' spread (WordPress/gutenberg#81052) - Tests: Replace page-level selectors with locators in e2e specs (WordPress/gutenberg#81053) - RTC: Disable custom autosave controller when RTC is disabled (WordPress/gutenberg#80769) - Background: Fix the legacy gradient UI where a gradient cannot be selected (WordPress/gutenberg#81056) - Rename blockStatesEnabled setting to blockStatesEditingEnabled (WordPress/gutenberg#81058) - Background: Fix the legacy gradient UI where a gradient cannot be selected - Add missing change log (WordPress/gutenberg#81060) - Declare missing prettier and stylelint workspace dependencies (WordPress/gutenberg#81065) - Notes: Display shortcut for form action buttons (WordPress/gutenberg#81064) - View config: lowercase dynamic filter names (WordPress/gutenberg#81068) - CI: cache the system packages Playwright dowloads for WebKit (WordPress/gutenberg#80846) - List View: Collapse off-window placeholder rows into a single spacer row (WordPress/gutenberg#80953) - devops: balance Playwright shards by duration (WordPress/gutenberg#79479) - Views: honor developer-defined view config overrides (WordPress/gutenberg#80832) - Components: Migrate ItemGroup to SCSS module (WordPress/gutenberg#80797) - Test: Add Vitest migration routing guardrails (WordPress/gutenberg#81036) - Visual revisions: Add a code diff view inside the editor (WordPress/gutenberg#80314) - Bump actions/labeler from 6.1.0 to 7.0.0 in /.github/workflows (WordPress/gutenberg#80966) - Visual revisions: add shareable urls (WordPress/gutenberg#79934) - Fix e2e tests that break when shard rebalancing changes their neighbors (WordPress/gutenberg#81117) - Query: guard non-scalar queryId to avoid fatal in enhanced pagination (WordPress/gutenberg#80724) - Replace deprecated useResizeObserver for useScaleCanvas (WordPress/gutenberg#67508) - CI: Add job timeouts to E2E, static checks, bundle size, and create block (WordPress/gutenberg#80801) - Allow organization or repository variable to dictate the Actions runner (WordPress/gutenberg#81087) - List View: Only subscribe to the block subtree for rows that can show images (WordPress/gutenberg#81076) - Editor: Migrate settings sidebar to Tabs from @wordpress/ui (WordPress/gutenberg#81054) - Notes: Email users mentioned in a note (WordPress/gutenberg#79606) - List View: Narrow the dependants of the memoized List View tree (WordPress/gutenberg#81129) - List View: Pass only minimal required data to sub-tree branches (WordPress/gutenberg#81111) - DataViews: Add `time` field type and control (WordPress/gutenberg#80830) - Post Author Biography: Fix horizontal overflow with long unbroken text (WordPress/gutenberg#81018) - Link Control: Restore the preview title underline (WordPress/gutenberg#81083) - Views: Add React to the package's dev dependencies (WordPress/gutenberg#81139) - feat: Add target param in proxy (WordPress/gutenberg#81141) - Fix: flaky revisions pagination e2e cross-page diff assertion (WordPress/gutenberg#81119) - Button: Suppress UA focus ring when focused and pressed (WordPress/gutenberg#81113) - View config: add reference docs (WordPress/gutenberg#81149) - Editor: Fix document tools button focus ring (WordPress/gutenberg#81115) - List View: Replace expand/collapse callbacks with reducer dispatch (WordPress/gutenberg#81138) - CI: resolve Playwright through the workspace that declares it (WordPress/gutenberg#81137) - CI: build once and reuse the build across e2e tests shards (WordPress/gutenberg#81031) - Fix: Tabs block: Start with empty tab labels with placeholders (WordPress/gutenberg#81009) - Interface: Increase footer breadcrumb height to prevent focus ring clipping (WordPress/gutenberg#81145) - Editor: clarify autosave failure notice (WordPress/gutenberg#76470) - Theme: Omit color properties when neither provided nor inherited (WordPress/gutenberg#80600) - Post editor: Add ThemeProvider for admin color schemes (WordPress/gutenberg#81112) - Core Data: saveDirtyEntities: improve messaging of server errors (WordPress/gutenberg#81151) - Global Styles: kebab-case preset slugs when converting references to custom properties (WordPress/gutenberg#80583) - Revise CODEOWNERS for package ownership (removing nerrad) (WordPress/gutenberg#81175) - Build: Wrap script bundles in an IIFE to contain 'use strict' (WordPress/gutenberg#79792) - Media: Improve the HEIC upload error and keep any upload errors up until dismissed (WordPress/gutenberg#81130) - Block Supports: Guard against non-string attribute values to avoid fatal errors (WordPress/gutenberg#80501) - Video: Hide settings for the GIF variation (WordPress/gutenberg#81142) - Video: clarify the Video variation description (WordPress/gutenberg#81181) - Media Editor: Render sidebar tabs via ComplementaryArea's render prop (WordPress/gutenberg#81133) - Bump docker/login-action (WordPress/gutenberg#80962) - Bump actions/setup-node from 6.4.0 to 7.0.0 in /.github/setup-node (WordPress/gutenberg#80963) - fix(admin-ui): reset the wp-admin li margin that misaligns Breadcrumbs (WordPress/gutenberg#81134) - Bump astral-sh/setup-uv from 8.3.2 to 9.0.0 in /.github/workflows (WordPress/gutenberg#80964) - Fix/79643 rtc awareness mixed block selection (WordPress/gutenberg#79836) - Agents: Add a defensive data design skill and route to the copy guide (WordPress/gutenberg#81131) - ESLint: Ban `@ts-ignore` in favour of `@ts-expect-error` (WordPress/gutenberg#81148) - Types: Replace remaining `@ts-expect-error` suppressions with proper typing (WordPress/gutenberg#81200) - Button: turn on the width setting by default in theme.json (WordPress/gutenberg#81196) - Notes: fix the mention notification email composition (WordPress/gutenberg#81187) - Edit Widgets: Fix header toolbar button focus ring (WordPress/gutenberg#81176) - List View: Reduce per-row store subscriptions (WordPress/gutenberg#81136) - Customizer widgets: Add ThemeProvider for admin color schemes (WordPress/gutenberg#81174) - Blocks: Declare the `@types/react-is` devDependency (WordPress/gutenberg#81203) - Widgets editor: Add ThemeProvider for admin color schemes (WordPress/gutenberg#81173) - PanelColorSettings: Restore the missing space below the panel header (WordPress/gutenberg#81155) - Remove the editableRoot opt-in from the paragraph block (WordPress/gutenberg#81184) - Media Attached to: Fix issue with the popover unexpectedly flipping, tweak wording (WordPress/gutenberg#81206) - Components: Add allowForms prop to SandBox component (WordPress/gutenberg#76471) - Ensure device preview is always accurate when window is zoomed in (WordPress/gutenberg#81193) - DataViews: Pass only eligible items to a bulk action callback (WordPress/gutenberg#81198) - DataViews: Make grid and table item preview aspect ratio configurable (WordPress/gutenberg#79329) - Block Editor: Return false from isBlockSelected when there is no client ID (WordPress/gutenberg#81212) - Blocks: Update hpq to 1.4.0 and drop the ts-expect-error suppressions (WordPress/gutenberg#81199) - DataViews: Fix the between `date` filter losing a manually typed date on blur (WordPress/gutenberg#81150) - Block Editor: Fix Select all freezing on large posts (WordPress/gutenberg#81210) - List View: Split the context and drop dead props to cut re-renders on expand and collapse (WordPress/gutenberg#81159) - Docs: Fix broken Figma links on the design resources page. (WordPress/gutenberg#81166) - Blocks: Support static block templates in block type settings (WordPress/gutenberg#80027) - AGENTS: Update development tips section (WordPress/gutenberg#81224) - URLInput: Skip search requests while an IME composition is in progress (WordPress/gutenberg#80602) - Components: Deprecate Animate (WordPress/gutenberg#80931) - Fix: Trunk e2e failures caused by the navigation spec leaving plain permalinks (WordPress/gutenberg#81238) - Use p elements for callout alerts in components docs (WordPress/gutenberg#81234) - Storybook: Enable design system theme tool globally (WordPress/gutenberg#81116) - Media: Cover client-side big image scaling and orphaned files (WordPress/gutenberg#81061) - Footnotes: guard against invalid post meta values (WordPress/gutenberg#81201) - Block editor: unify what a new sibling block inherits (WordPress/gutenberg#81250) - Global Styles: Remove unused duplicate font family preview utilities (WordPress/gutenberg#81194) - Docs: explain how the theme.json schema fixtures work (WordPress/gutenberg#81263) - theme.json schema: fix block pseudo-classes and custom states (WordPress/gutenberg#81209) - List View: Migrate label wrapper to UI Stack and drop Badge from the anchor (WordPress/gutenberg#81266) - Docs: Remove dependency group import guidance (WordPress/gutenberg#81245) - ESLint: Define dependency import rules (WordPress/gutenberg#81246) - Flaky tests: Report as a PR comment only, stop creating issues (WordPress/gutenberg#81218) - Dashboard Widgets: remove the Hello Dolly download action (WordPress/gutenberg#81272) - Block Directory: Make downloadable block item labels translatable. (WordPress/gutenberg#81237) - Icons: move the inline image icon into the library (WordPress/gutenberg#81271) - Update phpcs, wpcs dependencies (WordPress/gutenberg#81279) - fix: add optional chaining for empty reusable block (WordPress/gutenberg#81165) (WordPress/gutenberg#81177) - DataViews: Inline the kebabCase utility from components (WordPress/gutenberg#81284) - Navigation: Complete the DocBlocks for the responsive container and overlay style helpers (WordPress/gutenberg#80622) - Style states: fix state deselection when selecting the already selected block (WordPress/gutenberg#81277) - Fix missing return value in force_filtered_html_on_import footnotes filter (WordPress/gutenberg#81293) - Packages: Recover July 29 npm release metadata (WordPress/gutenberg#81303) - Global styles: render element styles set only inside a breakpoint (WordPress/gutenberg#81265) - Style states: Fix phantom pseudo element style output (WordPress/gutenberg#81291) - Docs: remove breakpoint-only element style example from global styles guide (WordPress/gutenberg#81308) - Render viewport state element styles in the editor (WordPress/gutenberg#81307) - theme.json schema: responsive states belong to blocks (WordPress/gutenberg#81253) - theme.json schema: allow responsive states on block style variations (WordPress/gutenberg#81309) - Block Inspector: Disable the 'Edit original' button when the entity ID is missing (WordPress/gutenberg#81288) - Site Editor: Decode HTML entities in Identity fields (WordPress/gutenberg#81269) - Document editEntityRecord's options.isCached param (WordPress/gutenberg#81318) - Theme: Colocate plugin tests with implementations (WordPress/gutenberg#81247) - Block Editor: Bail out of hasSelectedInnerBlock when there is no clientId (WordPress/gutenberg#81315) - ESLint: Replace strict config with bulk suppressions (WordPress/gutenberg#81248) - Block Library: Document security patch releases (WordPress/gutenberg#81295) - ESLint: Remove legacy import suppressions (WordPress/gutenberg#81338) - Components: Migrate Spinner styles from Emotion to SCSS module (WordPress/gutenberg#80511) - DataViews: Use the public `@wordpress/ui` Badge instead of the private one (WordPress/gutenberg#81236) - Pert Tests: Avoid inflating interaction metrics with startTracing (WordPress/gutenberg#81264) - Block Editor: Simplify InspectorControlsFill rendering logic (WordPress/gutenberg#81286) - Keycodes: Make `withIgnoreIMEEvents` a public API (WordPress/gutenberg#81343) - Docs: Fix broken links to the removed JavaScript build setup guide. (WordPress/gutenberg#81232) - Markdownlint: disable the MD033 (no-inline-html) rule project-wide (WordPress/gutenberg#81213) - Docs: Fix typo in block file structure guide (WordPress/gutenberg#81348) - Playlist: Normalize Waveform Player configuration handling (WordPress/gutenberg#81342) - Site editor: fix unreadable sidebar hover color (WordPress/gutenberg#81317) - Global Styles: Migrate font size presets to the shared preset layer (WordPress/gutenberg#79811) - Block Editor: Convert useBlockVisibility hook to TypeScript (WordPress/gutenberg#80390) - Scope breakpoint media queries to screen so printing does not look like a resize (WordPress/gutenberg#81367) - New `kebab-case` package: extract utility and migrate private API calls (WordPress/gutenberg#81294) - Remove content types experiment (WordPress/gutenberg#81340) - Editor: Keep the canvas height stable while resizing the canvas (WordPress/gutenberg#81163) - Dashboard Widgets: add icon and relevance to the widget action envelope (WordPress/gutenberg#81275) - Playlist: Improve audio conversion and track selection (WordPress/gutenberg#80926) - Try: Set post navigation link icon to next. (WordPress/gutenberg#81384) - UI: Derive Base UI direction from WordPress i18n (WordPress/gutenberg#80399) - DataViews: Fix `array` field type validation for empty values (WordPress/gutenberg#81378) - Bump github/codeql-action/upload-sarif (WordPress/gutenberg#81287) - Bump actions/stale from 10.4.0 to 11.0.0 in /.github/workflows (WordPress/gutenberg#81219) - Monitor all setup-related composite Actions. (WordPress/gutenberg#81192) - IconButton: Improve keyboard shortcut accessibility (WordPress/gutenberg#80402) - Interface: Stop the secondary sidebar animating into place on page load (WordPress/gutenberg#81362) - Fix: flaky list view paste block styles e2e test (WordPress/gutenberg#81229) - Docs: Add error message guidance. (WordPress/gutenberg#81143) - wp-build: Render the no-JS fallback in the generated page templates (WordPress/gutenberg#81365) - Changed Build Process Link and remove unused tags (WordPress/gutenberg#81360) - Scripts: Prevent a UTF-8 BOM in the middle of extracted CSS (WordPress/gutenberg#81383) - Global Styles: Migrate shadows presets to use the preset management layer. (WordPress/gutenberg#79812) - Global Styles: report border, shadow, outline, filter and dimension changes (WordPress/gutenberg#81407) - Dashboard Widgets: split action icons into wire and resolved forms (WordPress/gutenberg#81381) - Block Editor: Fix Escape from the block toolbar and stop redundant last focus dispatches (WordPress/gutenberg#81319) - Block Mover: Add the keyboard shortcut to the buttons' spoken description (WordPress/gutenberg#81380) - Format Library: Remove unused CSS rule (WordPress/gutenberg#77831) - Core Data: Fix post staying dirty after saving edited post meta with RTC enabled (WordPress/gutenberg#81392) - Widget Dashboard: host-tunable tile spacing via public custom properties (WordPress/gutenberg#81352) - DataForm: Fix hidden fields validation (WordPress/gutenberg#81377) - Tools: Make agent setup a workspace (WordPress/gutenberg#81418) - Test: Add private Vitest config and migrate a pilot test (WordPress/gutenberg#81037) - Interface: Let CSS own the ComplementaryArea width and use AnimatePresence custom for exit (WordPress/gutenberg#81363) - Notes: Fix text wrapping for long usernames in collaboration sidebar (WordPress/gutenberg#81406) - DataViews: vendor `ValidatedSelectControl` (WordPress/gutenberg#81391) - UI: Add `Calendar` and `RangeCalendar`, moved from `components` private APIs (WordPress/gutenberg#81337) - Feature: Tabs block: consider using the tab title in the Document Overview instead of a generic "Tab" label (WordPress/gutenberg#81427) - fix: order site identity changes predictably (WordPress/gutenberg#81283) - Docs: Fix broken link to the Node.js debugging guide. (WordPress/gutenberg#81398) - Navigation Overlay Close: Inherit typography and color from the overlay (WordPress/gutenberg#80751) - Fix: Regression: Tabs block: Start with empty tab labels with placeholders (WordPress/gutenberg#81429) - components/Menu: Restore Modal focus return when menu items close (WordPress/gutenberg#81164) - DataViews: Move the rich text control into the editor package (WordPress/gutenberg#81430) - Components: Remove ValidatedTextControl private API (WordPress/gutenberg#80680) - Cover: Avoid passing null as the featured image size (WordPress/gutenberg#81444) - DataViews: vendor `ValidatedNumberControl` (WordPress/gutenberg#81433) - DataViews: vendor ValidatedCheckboxControl (WordPress/gutenberg#81435) - DataViews: vendor ValidatedRadioControl (WordPress/gutenberg#81434) - UI: Upgrade React DayPicker to version 10 (WordPress/gutenberg#81439) - Block editor: render a real default block in place of the default appender (WordPress/gutenberg#81231) - Spacing Sizes Control: Make the control label translatable independently of the input aria label. (WordPress/gutenberg#81240) - Writing flow: stop the caret on containers that do not merge with the text flow (WordPress/gutenberg#81456) - Remove an unmodified wrapper when all its inner blocks are removed (WordPress/gutenberg#42503) - Normalize block gap values in layout (WordPress/gutenberg#81460) - Docs: Add Block Actions Readme (WordPress/gutenberg#69408) - Block editor: disclose the nested block count of a multi selection (WordPress/gutenberg#80745) - Fix: flaky autosave shareable URL revisions e2e test. (WordPress/gutenberg#81455) - Fix empty list block with anchor persisting after backspace deletion (WordPress/gutenberg#77000) - Docs: Add Block List Readme (WordPress/gutenberg#69432) - Docs: Add Block Lock Readme (WordPress/gutenberg#69434) - Backport core fixes for finalize route (WordPress/gutenberg#81465) - ESLint: Rename root eslint.config.cjs to eslint.config.mjs (WordPress/gutenberg#81468) - Update 23.8 release branch (WordPress/gutenberg#81486) - RTC: Couple RTC and polling provider as an opt-in experiment (WordPress/gutenberg#80658) - Align collaboration migration version markers with the 23.8.0 release (WordPress/gutenberg#81789) Fixes #66066. Built from https://develop.svn.wordpress.org/trunk@63534 git-svn-id: http://core.svn.wordpress.org/trunk@62710 1a063a9b-81f0-0310-95a4-ce76da25c4cd
What?
Closes #79643
Fixes RTC selection awareness for multi-block selections that span mixed block types (e.g. paragraph → image, heading → video). Previously the overlay rendered nothing when a collaborator's selection included any block without a RichText field. After this PR, text blocks show a semi-transparent fill overlay and non-text blocks show a CSS outline border, with a single avatar on the topmost block.
Why?
When a collaborator selects across multiple blocks, the awareness system sends two endpoints — one for each side of the selection. Both endpoints were treated identically, as if they always carried a character-level cursor position. Blocks without a RichText field (image, spacer, button, etc.) have no character position, so resolving the endpoint threw and the overlay silently produced nothing for the entire selection.
Beyond fixing the immediate bug, this PR also makes the collaborators overlay significantly more maintainable going forward:
SelectionEndpointdiscriminated union means TypeScript will catch any misuse at compile time. Before this PR, a caller could pass a text cursor position to code expecting a block position with no error — the failure only surfaced at runtime in the overlay.getFullBlockSelectionRectsand an always-emptyMultiBlockResult.rectsfield were removed rather than left dormant.CursorEndpoint/WholeBlockEndpointalign with the existingSelectionType.Cursor/SelectionType.WholeBlockenum values already used throughout the codebase.Visual approach: why not CSS outline for all blocks?
An earlier version of this PR used a CSS outline (
is-collaborator-selectedclass +--collaborator-outline-color) applied uniformly to every block in the selection range. This was simple to implement but had meaningful drawbacks. The current PR uses a two-track approach instead.CSS outline only (previous approach)
Mixed overlay + border (current approach)
innerText.trim()as a heuristic — an empty paragraph block would be treated as a non-text block and shown with a border instead of a fill. Partial text-level rects for the last block depend on the DOM Range API and a fallback element-resolution chain, which could theoretically fail for unusual custom blocks.The heuristic tradeoffs are acceptable: empty blocks carry no content to highlight, and the fallback chain (
[data-wp-block-attribute-key]→[contenteditable]→p/h1-h6/…) covers all standard Gutenberg blocks.Bugs fixed
Mixed-type multi-block selection renders nothing — When the selection spanned a non-text block (image, spacer, video), the overlay produced nothing. The root cause: both endpoints were resolved as if they always carried a character-level cursor position. Non-text blocks have no
Y.Textanchor, sogetCursorPositionreturned null and the entire selection was dropped silently. Fixed by introducing aSelectionEndpointdiscriminated union (CursorEndpoint/WholeBlockEndpoint) so each endpoint is resolved via the correct anchor type independently.WholeBlock keyboard selection as an endpoint renders nothing — Selecting an image block via keyboard (Alt+click / arrow navigation) produces a
WholeBlockEndpointwith no character offset.use-render-cursorshad no branch for this — it fell through to single-block logic,resolveSelectionthrew, and the selection was dropped. Fixed by the same discriminated union:WholeBlockEndpointis now resolved via itsY.Arrayslot position rather than aY.Textoffset.Ctrl+A (select entire post) not visible to collaborators — Ctrl+A produces a
SelectionInMultipleBlockswithWholeBlockEndpointon both sides. The overlay had no path for this combination — the entire selection was silently dropped, so collaborators saw nothing. Fixed as a consequence of the two changes above: both endpoints are now resolvable regardless of block type.Backward selection also works — All three fixes above apply regardless of selection direction. Endpoints are normalised to DOM order internally before any rect computation, so dragging from image → paragraph is identical to paragraph → image on the observer's side.
List block showing per-item stripes instead of one unified highlight — When a selection endpoint lands inside a list, Yjs resolves it to the specific
core/list-item(the innermost block with aY.Text), not the parentcore/list. The sibling list-items between that endpoint and the other selection end then appeared as individual middle blocks, each getting its own fill rect, producing a striped banded look rather than one rect covering the whole list.The fix promotes each endpoint to its nearest
[data-block]ancestor before computing rects:blockContainerOfwalks up the DOM from the resolved endpoint element and returns the nearest element with adata-blockattribute — the parent block. If the element is already a top-level block it returns itself unchanged.Applied in
computeMultiBlockOverlayRects:With
effectiveFirstEl= the list block,getBlocksBetween(listId, joId)returns the list-items in the DOM range — but the.filterremoves every element the list block.contains(), which is all of them.middleElsbecomes empty. The list block is then rendered as a single full bounding-box rect covering all its items.When both endpoints promote to the same container (e.g. selecting from list-item 1 to list-item 3, all inside one list), an early return emits exactly one rect for the container:
Approaches considered and rejected:
Skip descendants of start/end elements in
getBlocksBetween— Would work when the list block itself is the endpoint, but not when a list-item is the endpoint: siblings of a list-item are not its descendants, so no filtering would occur.Post-process
middleElsto merge consecutive items with the same[data-block]parent — Works but requires an extra DOM traversal per middle block and couples the merging logic to the iteration order. More moving parts for the same outcome.Promote before DOM-order normalization — Direction normalization uses the raw endpoint elements for
isNodeBeforecomparison. Promoting first would compare container elements, which could give wrong ordering when both endpoints share the same container.The chosen approach — promote after normalization, filter descendants, check equality — is one helper function, two
.contains()calls, and one equality check. It generalises to any nesting depth (columns inside groups, etc.) without additional code.Video of behavior before this PR
before.mp4
Video of behavior after this PR
after-fixing-multiselect.mp4
How?
The fix is split across 5 commits, layered from the data model up to the overlay rendering.
Commit 1 — core-data: add CursorEndpoint/WholeBlockEndpoint discriminated union
Introduces a
SelectionEndpointdiscriminated union so the receiver knows how to resolve each side of a multi-block selection:CursorEndpoint(kind: 'cursor'): the endpoint lands inside a RichText field with a known character offset. Anchored to aY.Textrelative position. Any block type can produce this — a paragraph mid-word, a table cell, a heading.WholeBlockEndpoint(kind: 'whole-block'): no character offset — either the block has no RichText field (image, spacer) or it is selected as a whole block via keyboard. Anchored to the block's slot in its parentY.Array.getSelectionEndpointtries aY.Textanchor first; if the selection carries no character offset it falls back to theY.Arrayslot anchor.Also updates all test fixtures and assertions to use the renamed endpoint types.
Commit 2 — editor: fix getBlocksBetween to skip blocks already covered by a parent
getBlocksBetweenpreviously returned all[data-block]elements between the two endpoints, including nested ones. Selecting a list block would return both the list and each of its list items, causing every item to be double-highlighted.The fix: skip any block element that is a descendant of a block already in the result. JSDoc updated to reflect the new behaviour.
Commit 3 — editor: handle SelectionInMultipleBlocks in the collaborators overlay
The main fix. Three files change together as one logical unit:
use-render-cursors— a dedicated branch now resolves each endpoint independently via itskinddiscriminant, avoiding the throw that occurred for non-text endpoints.compute-selection— the multi-block path is wired up; no cursor coords are emitted (avatar comes fromuse-block-highlightingonly).MultiBlockResult.rects(always empty) andgetFullBlockSelectionRects(no callers) are removed as dead code.use-block-highlighting— extended to coverSelectionInMultipleBlocks. Resolves start/end block IDs, normalises to DOM order, collects intermediate blocks viagetBlocksBetween, applies highlights, and places the avatar on the topmost block only. Non-obvious decisions (Yjs direction normalisation, avatar placement rule,as anycast) are documented inline.Commit 4 — editor: mixed visual treatment for multi-block awareness (overlay + outline)
Replaces CSS-outline-only highlighting with a two-track visual system:
Text blocks (
innerText.trim()is non-empty — paragraph, heading, list, etc.):selectionRectsNon-text blocks (image, spacer —
innerTextempty):is-collaborator-selectedoutline only; no fill so image content is not obscuredAlso in this commit:
is-collaborator-selectedis reset for every block on each render before being conditionally re-applied, preventing it from persisting when a block transitions between selection typesresolveTargetElementgets a richer fallback chain ([data-wp-block-attribute-key]→[contenteditable]→p/h1–h6/pre/blockquote/…) so partial text rects work even when the block is not actively focused by the observerinnerText.trim()because[contenteditable="true"]and[data-wp-block-attribute-key]are absent on blocks the observer has not clicked intoCommit 5 — editor: promote inner blocks to container for multi-block highlights
Fixes the list-item striping bug described in Case 4 above. See the Case 4 section for a detailed explanation of the root cause, the fix, and the approaches considered.
Key additions:
blockContainerOfhelper exported fromcursor-dom-utils— single responsibility, reused in bothcompute-selectionanduse-block-highlightinggetBlocksBetweenand before building the CSS outline block list, so both the fill-overlay path and the CSS-border path treat the container as one unitAfter running
/code-reviewin a Claude Code (Sonnet 4.6) session, four additional bugs were found and fixed:Commit 6 —
isNodeBeforebitmaskcompareDocumentPositionreturns a bitmask, not an exact value. The strict=== Node.DOCUMENT_POSITION_FOLLOWINGcheck (=== 4) returnedfalsewhenbwas a following descendant of
a(result is4 | 16 = 20), silently skippingDOM-order normalization for nested endpoint pairs. Fixed with a bitwise AND check.
Commit 7 —
blockContainerOfover-promotionel.parentElement?.closest('[data-block]')walked arbitrarily far up the ancestorchain. A
core/listinsidecore/grouphas a non-[data-block]wrapper divbetween them, so
closest()would skip past the list and promote to the group —wrong block highlighted. Fixed by checking only the immediate
parentElement.Commit 8 — null endpoint guard in
use-block-highlightingWhen an endpoint block is absent from the DOM (concurrent deletion),
effectiveFirstElwasnull.null?.contains(el)returnsundefined,!undefinedistrue, so every intermediate block passed the containmentfilter — spuriously highlighting the entire range. Fixed with an early return
when either endpoint element is missing.
Commit 9 — move inner-block promotion to
use-render-cursorscomputeMultiBlockOverlayRectsis a pure computation function; DOM-basedinner-block promotion was an architectural mismatch. Moved the promote step to
use-render-cursorswhere resolved endpoints are built, beforecomputeSelectionVisualis called.computeMultiBlockOverlayRectsnow receivescontainer-level IDs directly and drops the
effectiveFirstEl/wasPromotedvariables.
Testing Instructions
wp-envwith RTC enabled and open a post with paragraphs, an image block, and a heading.Known issue discovered during testing
Reproduction: User A has a multi-block selection active. User B (the observer) deletes the first or last block of that selection. On User B's screen, the entire awareness overlay for User A disappears — both the selection highlight and the avatar. On User A's screen, their own selection also clears shortly after (once Yjs syncs the deletion).
Root cause — two contributing layers, both pre-existing:
Rendering layer: When an endpoint block is deleted from the DOM,
computeMultiBlockOverlayRectscan no longer find it by[data-block]selector and returns{}, clearing all rects. This is the same defensive early-return behaviour trunk already uses for single-blockWholeBlockselections — deleting a keyboard-selected block causes its CSS outline to disappear there too. Deleting a middle block does not trigger this — only the first or last block (the selection endpoints) causes the early return.Yjs layer: Once the deletion syncs to User A, their
Y.RelativePositionanchor for the deleted endpoint block can no longer be resolved.resolveSelectionthrows, thetry/catchswallows it silently, and awareness clears on User A's side too. This exists on trunk independently of this PR.Question for reviewer: Should this be fixed here, or tracked as a separate issue?
Arguments for fixing here: the rendering-layer half of the bug is in code directly touched by this PR. A partial fix — falling back to the remaining valid blocks when an endpoint block is deleted — could be made here without touching the Yjs layer.
Arguments for a separate issue: fully eliminating the bug also requires fixing the Yjs
Y.RelativePositionresolution on User A's side, which is out of scope for a rendering-focused PR. Tracking the full fix as a follow-up keeps this PR focused.I feel I should open a separate issue, hence I am opening one, but thats just what I think, let me know your thoughts.
Edit: the new issue at #79867
Notes
getBlocksBetweenbehaviour changed (descendant filtering). It has no callers outside the overlay folder and is not in the editor package's public API barrel — low risk.getFullBlockSelectionRectswas removed. Same scope caveat.Use of AI Tools (updated)
This PR was developed with AI assistance (Claude Code, Sonnet 4.6) across two sessions:
/code-reviewcommand in Claude Code surfaced 6 findings (3 correctness bugs, 1 null-safety bug, 2 efficiency/architecture issues). Bugs 1–3 and the architectural finding were fixed in commits 6–9 above. The two efficiency findings (getBlocksBetweenO(n²) scan and missing loop break) were deferred as low-priority given realistic document sizes.All changes were reviewed and verified by me before submission.