Skip to content

Notes: Refactor floating board measurement - #83485

Merged
Mamaduka merged 9 commits into
trunkfrom
refactor/floating-board-store
Sep 28, 2026
Merged

Mamaduka merged 9 commits into
trunkfrom
refactor/floating-board-store

Conversation

@Mamaduka

@Mamaduka Mamaduka commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

What?

Fixes #83508.
Follow-up to #79877 (review thread).

Rewrites createBoardStore and useFloatingBoard so all DOM reads happen in the store's ResizeObserver callback, React reads a value-compared snapshot through useSyncExternalStore, and note positions are derived during render. Moves the inline note helpers from hooks.js to utils.js.

Additional fixes:

  • Threads follow a noted block moved up or down, including during the move animation. Before, they kept their old order until the selection changed.
  • Threads stay aligned when content above the canvas moves it (editor notices, device preview).
  • A remount of the floating sidebar no longer paints one frame with every thread at the same spot.
  • A note inside a closed Details block aligns with the Details block instead of floating over the next block.
  • Threads ease into place on selection change, unless the user prefers reduced motion.
  • A thread without an anchor (block hidden with Block Visibility) stays hidden instead of dropping to the top of the panel. Re-selecting a hidden block loses its note position #73565 stays open.

Note

I know it's a sizable change, but it's mostly internal cleanup + bug fixes. Floating notes behavior should be the same, if not better.

Why?

The extra requestAnimationFrame after the ResizeObserver caused two paints per change and hid resize loops. The block move animation offsets moved blocks with a transform, which doesn't resize anything, so the only measurement read their old positions.

How?

The observer exists only while the board has subscribers, and requestMeasure() re-observes the root to get another pass before the next paint, on register and on threads changes. Anchors are stored in canvas content-space, so scrolling never re-renders; --canvas-scroll and --canvas-offset CSS variables move the threads instead. A MutationObserver on style attributes under the root re-measures while blocks animate.

Testing Instructions

  1. Open a post and switch to the floating notes sidebar.
  2. Add two paragraphs, "Alpha" and "Bravo", and add a note to each.
  3. Select "Bravo" and click Move up. The threads swap with the blocks and follow them during the animation.
  4. Move the block up and down a few times. The thread order always matches the block order.
  5. Scroll the canvas and add a reply to a thread. Threads stay aligned with their blocks, and the thread below is pushed down.
  6. Select the other paragraph. Threads ease into their new spots.
  7. Switch the preview to Tablet. Threads stay aligned with their blocks.

Closed Details block

  1. Add a Details block with a summary and a paragraph inside it.
  2. Add a note to the paragraph. The thread aligns with the paragraph.
  3. Click the summary to close the Details. The thread aligns with the summary.
  4. Click the summary again. The thread aligns with the paragraph.

Automated: collab-sidebar unit tests (146) pass. block-notes, block-notes-floating and collaboration-notes e2e on Chromium: 67 passed, 1 skipped (existing fixme).

Testing Instructions for Keyboard

Same as above.

Screencast

Before

CleanShot.2026-09-24.at.13.25.18.mp4

After

CleanShot.2026-09-24.at.13.23.19.mp4

Use of AI Tools

Assisted by Claude.

@Mamaduka Mamaduka self-assigned this Sep 24, 2026
@Mamaduka Mamaduka added [Type] Enhancement A suggestion for improvement. [Feature] Notes Phase 3 of the Gutenberg roadmap around block commenting labels Sep 24, 2026
@github-actions github-actions Bot added the [Package] Editor /packages/editor label Sep 24, 2026
@Mamaduka

Copy link
Copy Markdown
Member Author

Please let me know if you can think of additional edge cases for floating notes. I would like to improve our e2e test coverage for it.

@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

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 props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: Mamaduka <mamaduka@git.wordpress.org>
Co-authored-by: jasmussen <joen@git.wordpress.org>
Co-authored-by: t-hamano <wildworks@git.wordpress.org>
Co-authored-by: adamsilverstein <adamsilverstein@git.wordpress.org>
Co-authored-by: i-am-chitti <iamchitti@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

🤖 PR meta 🤖

📦 Bundle size

Size Change: +730 B (+0.01%)

Total Size: 8.24 MB

📦 View Changed
Filename Size Change
build/scripts/editor/index.min.js 614 kB +409 B (+0.07%)
build/styles/editor/style-rtl.css 31.8 kB +83 B (+0.26%)
build/styles/editor/style-rtl.min.css 27.1 kB +79 B (+0.29%)
build/styles/editor/style.css 31.8 kB +82 B (+0.26%)
build/styles/editor/style.min.css 27.1 kB +77 B (+0.29%)

e8c9374 Run

⚡ Performance

Show the results

Client side metrics exclude the server response time.

front-end-block-theme

Metric cee60be trunk % Change
timeToFirstByte 62 ms +7.1% -7.02% 58.05 ms +3.88% -2.58% 6.8%
largestContentfulPaint 100 ms +8% -6% 94 ms +10.64% -2.13% 6.38%
lcpMinusTtfb 37.1 ms +19.14% -7.14% 35.35 ms +19.8% -4.38% 4.95%
wpBeforeTemplate 32.34 ms +8.13% -11.78% 28.4 ms +8.7% -1.62% 13.87%
wpTemplate 25.65 ms +6.98% -2.73% 25.32 ms +3.32% -2.29% 1.3%
wpTotal 58.11 ms +6.11% -7.12% 54.4 ms +4.06% -2.67% 6.82%
wpMemoryUsage 7.61 MB +0% -0% 7.57 MB +0% -0% 0.51%
wpDbQueries 17 +0% -0% 17 +0% -0% 0%

front-end-classic-theme

Metric cee60be trunk % Change
timeToFirstByte 44.2 ms +8.71% -2.83% 43.95 ms +5.23% -2.62% 0.57%
largestContentfulPaint 96 ms +2.08% -4.17% 96 ms +0% -4.17% 0%
lcpMinusTtfb 50.2 ms +2.89% -3.19% 50.1 ms +5.79% -2.89% 0.2%
wpBeforeTemplate 26.79 ms +8.51% -1.75% 26.74 ms +2.73% -2.06% 0.19%
wpTemplate 14.13 ms +3.61% -2.55% 13.99 ms +4.15% -1.36% 1%
wpTotal 41.21 ms +9.12% -2.55% 40.85 ms +5.9% -2.01% 0.88%
wpMemoryUsage 6.09 MB +0% -0% 6.05 MB +0% -0% 0.59%
wpDbQueries 10 +0% -0% 10 +0% -0% 0%

media-processing

Metric cee60be trunk % Change
mediaProcessingJpeg 407.31 ms +1.32% -1.02% 399.9 ms +2.4% -0.24% 1.85%
mediaProcessingAvif 6117.61 ms +0.43% -0.04% 6072.74 ms +0.38% -0.09% 0.74%
mediaProcessingJpegToAvif 4243.83 ms +0.6% -0.34% 4206.93 ms +0.27% -0.3% 0.88%

media-upload

Metric cee60be trunk % Change
jpegUploadProcessing 1435.36 ms +36.67% -1.68% 1433.6 ms +0.92% -0.46% 0.12%
pngUploadProcessing 202.47 ms +14.26% -15.17% 236.38 ms +15.66% -22.43% -14.35%
largeJpegUploadProcessing 1415.52 ms +0.87% -1.04% 1425.59 ms +0.57% -0.37% -0.71%
multipleImageUploadProcessing 1572.35 ms +2.99% -0.38% 1596.27 ms +32.34% -0.77% -1.5%

post-editor

Metric cee60be trunk % Change
serverResponse 250.69 ms +5.99% -6.67% 238.02 ms +7.71% -5.73% 5.32%
firstPaint 129.77 ms +27.78% -7.41% 182.61 ms +43.78% -25.27% -28.94%
domContentLoaded 645.83 ms +1.94% -1.69% 623.85 ms +1.35% -4.52% 3.52%
loaded 646.79 ms +1.93% -1.72% 624.96 ms +1.3% -4.56% 3.49%
firstContentfulPaint 291.85 ms +0.7% -5.24% 280.25 ms +3.56% -6.31% 4.14%
firstBlock 1861.77 ms +2.39% -0.3% 1848.71 ms +1.83% -1.44% 0.71%
type 12.02 ms +2.91% -1.08% 11.63 ms +15.74% -0.95% 3.35%
typeWithoutInspector 11.98 ms +6.18% -4.26% 12.22 ms +3.44% -4.17% -1.96%
typeWithTopToolbar 15.2 ms +4.28% -2.96% 15.63 ms +1.47% -2.5% -2.75%
typeContainer 0.64 ms +32.81% -37.5% 0.62 ms +35.48% -38.71% 3.23%
focus 7.96 ms +14.57% -3.64% 7.58 ms +20.45% -2.64% 5.01%
firstFocus 16.64 ms +0% -0% 17.22 ms +0% -0% -3.37%
selectAll 847.59 ms +1.5% -4.59% 804.74 ms +5.77% -4.07% 5.32%
listViewOpen 35.16 ms +7.91% -7.31% 35.82 ms +13.26% -5.5% -1.84%
inserterOpen 14.96 ms +17.25% -7.29% 15.09 ms +11.53% -10.47% -0.86%
inserterHover 1.52 ms +3.29% -15.13% 1.53 ms +9.15% -11.11% -0.65%
inserterSearch 5.8 ms +1.21% -2.59% 5.93 ms +6.91% -7.42% -2.19%
loadPatterns 399.74 ms +1.79% -3.15% 385.89 ms +6.14% -2.63% 3.59%
wpTotal 244.03 ms +6.05% -6.95% 231.24 ms +7.98% -5.78% 5.53%
wpMemoryUsage 13.14 MB +0% -0% 13.11 MB +0% -0% 0.28%
wpDbQueries 54 +0% -0% 54 +0% -0% 0%

site-editor

Metric cee60be trunk % Change
serverResponse 324.66 ms +3.86% -3.63% 341.89 ms +1.93% -7.82% -5.04%
firstPaint 197.8 ms +18.62% -12.66% 220 ms +22.43% -8.62% -10.09%
domContentLoaded 877.38 ms +1.29% -1.33% 926.59 ms +2.64% -2.86% -5.31%
loaded 878.42 ms +1.29% -1.34% 927.77 ms +2.62% -2.87% -5.32%
firstContentfulPaint 358.72 ms +1.66% -20.35% 385.82 ms +1.63% -4.65% -7.02%
firstBlock 3156.36 ms +0.3% -0.88% 3306.9 ms +0.62% -1.35% -4.55%
type 16.38 ms +5.56% -1.89% 17.8 ms +4.66% -4.38% -7.98%
navigate 110.15 ms +5.42% -10.86% 137.12 ms +4.24% -16.56% -19.67%
loadPatterns 1138.93 ms +21.68% -5.23% 1136.99 ms +5.11% -15.03% 0.17%
loadPages 1103.59 ms +3% -1.01% 1140.02 ms +2.21% -0.32% -3.2%
wpTotal 316.25 ms +3.93% -3.75% 333.51 ms +1.92% -8.1% -5.18%
wpMemoryUsage 12.11 MB +0% -0% 12.09 MB +0% -0% 0.13%
wpDbQueries 44 +0% -2.27% 43.5 +1.15% -1.15% 1.15%

e8c9374 Run

@Mamaduka

Copy link
Copy Markdown
Member Author

I've been doing some stress testing with Claude, and I think I managed to fix all the paper cuts we could find. Updated the PR summary to include a list of all fixes and improvements.

Comment thread packages/editor/src/components/collab-sidebar/style.scss Outdated
@adamsilverstein

adamsilverstein commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

I've also been doing some testing and will post the results soon. manual testing seemed fine, but Claude found a few edge cases worth reviewing.

@adamsilverstein

Copy link
Copy Markdown
Member

Nice work on the follow-up fixes. I ran a Playwright stress pass against 1ee2b26 and most of what came up is already covered, a few things left below. The first two also happen on trunk, so fine as follow-ups if you'd rather keep this PR scoped.

Claude ran the edge cases side by side with trunk, here is what is left:

Compared against the trunk merge-base, with about 20 scenarios measuring each floating thread's offset from its anchor. Now aligned on this PR: moving a noted block with no selection change, move + undo/redo, delete + undo, inserting a block above a transformed block, editor notices above the canvas, and Tablet preview. No console errors or ResizeObserver loop warnings, and scrolling does not trigger a re-measure.

1. A growing post title leaves threads behind (also on trunk). Typing a title long enough to wrap onto more lines scrolls the canvas, and every thread ends up ~740px above its block, so the top thread scrolls out of view. The title sits outside .is-root-container, so the root moves without resizing and nothing re-measures. Observing the scroll container's content (or the title wrapper) instead of only the root would catch it.

Typing a longer post title leaves the Alpha thread out of view

2. Duplicating a noted block (also on trunk). The copy keeps metadata.noteId, so two blocks share note 199. The sidebar then renders two threads with the same DOM id note-thread-199, stacked on top of each other ~180px below both blocks. The board is keyed by note id, so the two threads share one registration. This is probably more of a "should duplicate copy the note at all" question than a measurement bug.

Duplicating Alpha pushes its thread below the block

3. Cost of the style MutationObserver during move animations (new in this PR). Each animation frame re-measures every anchor. With 20 notes, six animated Move down presses took ~0.60s of script time and 185 layouts on this PR, vs ~0.20s and 40 layouts on trunk. With 3 notes, a single toolbar move went from 36ms to 56ms. Not a visible stutter at these sizes, but it grows with note count. Coalescing the mutation callbacks per frame, or only re-measuring while a transform is active, might keep the benefit for less work.

@adamsilverstein

Copy link
Copy Markdown
Member

Duplicating a noted block (also on trunk).

I feel like we noticed this before and its related to a deeper issue being worked on elsewhere. Do we have a notes specific issue for this? Is it worth having one @Mamaduka ?

@adamsilverstein

adamsilverstein commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

A growing post title leaves threads behind (also on trunk)

I hadn't noticed this before (but verified it manually), do you want to address it in this PR @Mamaduka? Otherwise I will open a separate issue.

@Mamaduka

Copy link
Copy Markdown
Member Author

A growing post title leaves threads behind.

Confirmed as well. This looks like an issue worth handling, since it's part of the position/layout logic.

Duplicating a noted block

This is known issue #71544 / #29693 and not necessarily a layouting problem. We need to figure out how to handle rendering a note that belongs to multiple blocks. Related: #73416. Let's handle it in a follow-up.

Cost of the style MutationObserver during move animations

I also flagged something similar when fixing the moving blocks problem here. Will double-check.

@Mamaduka
Mamaduka force-pushed the refactor/floating-board-store branch from 1ee2b26 to ec5d498 Compare September 26, 2026 05:02
@Mamaduka

Mamaduka commented Sep 26, 2026 •

Copy link
Copy Markdown
Member Author

@adamsilverstein, I fixed 1 and 3. Let's handle 2 in a follow-up.

@Mamaduka
Mamaduka force-pushed the refactor/floating-board-store branch from ec5d498 to 5d42246 Compare September 26, 2026 05:32
Mamaduka and others added 4 commits September 28, 2026 16:01
- Measure only in ResizeObserver callback; drop rAF, state and heavy effect.
- Observer lifetime follows subscriptions; resubscribe re-observes refs.
- Content-space anchors, value-compared snapshot; no emit on scroll.
- Derive positions in render; re-measure on block reorder.
- Move anchor resolution to getNoteAnchorRect util.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Move readInlineSelection, wrapInlineNote and clearInlineNoteMarker
from hooks.js to utils.js and fix their JSDoc type warnings. Move
NOTE_FORMAT_NAME to constants.js so utils.js stops duplicating it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The move animation offsets blocks with a transform, which resizes
nothing, so threads kept their old order until the next change.
Watch style changes under the root and request a measurement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Anchors are read in the canvas frame's viewport, but threads are positioned
in the sidebar panel. Content above the canvas (editor notices, device
preview inset) moved the frame away from the panel and left threads offset.
Measure the frame's offset from the threads' container and shift threads by it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mamaduka and others added 5 commits September 28, 2026 16:01
Threads register with the board after the first paint, so a remount of the
floating sidebar painted one frame with every thread at the same spot.
Move the hide-until-positioned rule from AddNote into FloatingContainer so
threads and the new note form share it. Uses opacity, since visibility would
block focusing the new note form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A note inside a closed Details still measured the box its block would have
when expanded, so the thread sat below the summary, over the next block.
When the anchor fails checkVisibility(), climb to the closest visible block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Threads jumped to their new spots on selection change. Transition `top`,
unless the user prefers reduced motion. A thread's first positioning starts
from `top: auto`, which doesn't animate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Re-measuring on every animation frame re-rendered the board each frame.
Skip style mutations while a transform is set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The title sits outside the block-list root, so a wrapping title moves
the root without resizing it and threads were left behind. Observe the
root's parent, which grows with it.

Rebuild the observers when the root changes instead of swapping each
target, and share the floating element cleanup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Mamaduka
Mamaduka force-pushed the refactor/floating-board-store branch from 5d42246 to e8c9374 Compare September 28, 2026 12:05
Comment on lines +21 to +23
// Threads mount before the first measurement. Not
// `visibility`, which would block focusing the new note form.
opacity: floating.y === undefined ? 0 : undefined,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Currently, opacity: 0 is applied to all notes without block anchors, as well as the new note form. As a result, even when a note is visually hidden because its linked block is unselected, it can still receive focus. Should we apply the inert attribute to these notes?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Let's try to resolve the hidden block notes as part of #73565. As the inline comment states, using opacity is intentional; the user might trigger note focus before the position settles, and focus might be missed. Using inert will cause the same problem, similar to the visibility property.

@t-hamano t-hamano left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't see any other issues with this PR. Let's merge it and address the remaining concerns in follow-up tasks 👍

@Mamaduka

Copy link
Copy Markdown
Member Author

Thanks for the feedback, everyone!

@Mamaduka
Mamaduka merged commit 9ddd97f into trunk Sep 28, 2026
95 of 98 checks passed
@Mamaduka
Mamaduka deleted the refactor/floating-board-store branch September 28, 2026 14:19
@github-actions github-actions Bot added this to the Gutenberg 24.2 milestone Sep 28, 2026
widoz pushed a commit to widoz/gutenberg that referenced this pull request Oct 2, 2026
Co-authored-by: Mamaduka <mamaduka@git.wordpress.org>
Co-authored-by: jasmussen <joen@git.wordpress.org>
Co-authored-by: t-hamano <wildworks@git.wordpress.org>
Co-authored-by: adamsilverstein <adamsilverstein@git.wordpress.org>
Co-authored-by: i-am-chitti <iamchitti@git.wordpress.org>
adamsilverstein added a commit that referenced this pull request Oct 6, 2026
Take trunk's board store and floating board hook from #83485; the
hidden-block anchor is rebuilt on top of them in the following commits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFNXAzK5ogseY5tCtU2x53
adamsilverstein added a commit that referenced this pull request Oct 6, 2026
The floating board now measures in the store's ResizeObserver pass (#83485),
so the stand-in anchor for a block with no rendered element moves there
from the old rAF batch. Threads register their block's client ID and the
store keeps the document's block order, so it can place the thread between
the neighbours that are rendered. With every noted block hidden, the store
finds the canvas root from the threads' document instead.

Neighbours inside collapsed content are skipped, since they still report
their expanded box.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFNXAzK5ogseY5tCtU2x53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Feature] Notes Phase 3 of the Gutenberg roadmap around block commenting [Package] Editor /packages/editor [Type] Enhancement A suggestion for improvement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Notes: Floating threads don't move when an editor notice pushes the canvas down

4 participants