Skip to content

Notes: Share thread focus recovery across actions - #85455

Merged
Mamaduka merged 4 commits into
trunkfrom
fix/notes-thread-focus-recovery
Oct 23, 2026
Merged

Mamaduka merged 4 commits into
trunkfrom
fix/notes-thread-focus-recovery

Conversation

@Mamaduka

Copy link
Copy Markdown
Member

What?

Follow-up to #84060

Moves the adjacent-thread focus logic out of handleDelete into a shared focusThreadOrAdjacent helper in Notes, used by delete, resolve, collapse, and reply cancel. NoteThread and AddNote now call useNoteActions directly instead of receiving the actions as props.

Why?

Any action can drop a thread from the list, and when the focused thread unmounts, focus falls to the document body. Only handleDelete handled this, with its own neighbor lookup, so every other action would need to repeat it.

How?

focusThreadOrAdjacent( thread, nextThread ) focuses the thread if it stays, otherwise the next or previous thread, and falls back to the block when none is left. Select the neighbor only if the leaving thread was selected. On trunk, a thread leaves only when deleted (nextThread is null); #84060 adds the filter check to this one condition.

Testing Instructions

  1. Open a post or page and add notes to three different blocks.
  2. Open "All notes", select the first note, and delete it. Focus moves to the second note and its block is selected.
  3. Select the last note and delete it. Focus moves to the previous note.
  4. Delete the remaining note. Focus moves to its block.
  5. Select a note and press Resolve. Focus stays on the note.

Testing Instructions for Keyboard

Same as above, using Tab to reach the note actions and Escape to collapse a note; focus stays on the collapsed note.

Use of AI Tools

Assisted by Claude.

Mamaduka and others added 3 commits October 23, 2026 14:22
Move the adjacent-thread pick out of handleDelete into focusThreadOrAdjacent, so any action that can drop a thread from the list reuses it. Delete handling moves into NoteThread next to resolve.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drops the action props Notes passed down only to forward them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Mamaduka Mamaduka self-assigned this Oct 23, 2026
@Mamaduka Mamaduka added [Type] Bug An existing feature does not function as intended [Feature] Notes Phase 3 of the Gutenberg roadmap around block commenting labels Oct 23, 2026
@github-actions github-actions Bot added the [Package] Editor /packages/editor label Oct 23, 2026
@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

🤖 PR meta 🤖

Preview in WordPress Playground

🎉 Props

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: adamsilverstein <adamsilverstein@git.wordpress.org>

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

Updated as activity occurs, without notifying anyone named here. Add the props-bot label to refresh.

⚡ Performance

Show the results

Client side metrics exclude the server response time.

front-end-block-theme

Metric 6e78a42 trunk % Change
timeToFirstByte 47.75 ms +15.29% -2.51% 48.05 ms +9.47% -2.19% -0.62%
largestContentfulPaint 84 ms +9.52% -4.76% 82 ms +9.76% -2.44% 2.44%
lcpMinusTtfb 32.35 ms +14.84% -4.02% 32.9 ms +19.15% -3.34% -1.67%
wpBeforeTemplate 24.17 ms +5.63% -1.28% 24.26 ms +10.14% -1.53% -0.37%
wpTemplate 20.02 ms +3.15% -5.44% 19.66 ms +3.2% -4.37% 1.83%
wpTotal 44.19 ms +13.83% -3.03% 44.07 ms +8.99% -2.09% 0.27%
wpMemoryUsage 7.67 MB +0% -0% 7.64 MB +0% -0% 0.43%
wpDbQueries 16 +0% -0% 16 +0% -0% 0%

front-end-classic-theme

Metric 6e78a42 trunk % Change
timeToFirstByte 35.95 ms +2.78% -1.53% 39.1 ms +3.07% -1.92% -8.06%
largestContentfulPaint 82 ms +2.44% -2.44% 88 ms +4.55% -0% -6.82%
lcpMinusTtfb 45.45 ms +3.96% -2.42% 49.9 ms +4.11% -0.8% -8.92%
wpBeforeTemplate 21.44 ms +2.29% -1.12% 21.09 ms +2.89% -1.75% 1.66%
wpTemplate 11.49 ms +2% -3.05% 14.75 ms +5.15% -1.76% -22.1%
wpTotal 33.17 ms +2.29% -1.93% 36.1 ms +3.6% -1.83% -8.12%
wpMemoryUsage 6.25 MB +0% -0% 6.33 MB +0% -0% -1.22%
wpDbQueries 9 +0% -0% 13 +0% -0% -30.77%

media-processing

Metric 6e78a42 trunk % Change
mediaProcessingJpeg 391.42 ms +0.69% -0.3% 388.76 ms +1.13% -0.6% 0.68%
mediaProcessingAvif 5741.14 ms +0.54% -0.15% 5764.6 ms +0.23% -0.23% -0.41%
mediaProcessingJpegToAvif 3945.87 ms +0.28% -0.1% 3891.35 ms +0.35% -0.08% 1.4%

media-upload

Metric 6e78a42 trunk % Change
jpegUploadProcessing 1688.53 ms +15.62% -16.68% 1436.16 ms +0.67% -0.33% 17.57%
pngUploadProcessing 205.08 ms +4.66% -1.83% 205.96 ms +3.35% -3.48% -0.43%
largeJpegUploadProcessing 1413.02 ms +0.53% -1.03% 1419.04 ms +1.27% -0.46% -0.42%
multipleImageUploadProcessing 2023.85 ms +2.23% -20.4% 1597.97 ms +27.76% -0.86% 26.65%

post-editor

Metric 6e78a42 trunk % Change
serverResponse 358.59 ms +1.91% -7.48% 345.39 ms +3.79% -6.32% 3.82%
firstPaint 423.12 ms +4.26% -43.5% 393.61 ms +11.62% -30.91% 7.5%
domContentLoaded 931.82 ms +4.15% -1.45% 958.73 ms +2.03% -2.79% -2.81%
loaded 932.7 ms +4.15% -1.44% 959.61 ms +2.03% -2.8% -2.8%
firstContentfulPaint 428.51 ms +2.95% -1.94% 424.04 ms +4.66% -2.61% 1.05%
firstBlock 2665.4 ms +3.38% -1.52% 2726.53 ms +2.77% -3.76% -2.24%
type 16.4 ms +3.54% -3.05% 15.99 ms +0.69% -0.81% 2.56%
typeWithoutInspector 16.39 ms +4.45% -6.47% 16.18 ms +1.11% -5.19% 1.3%
typeWithTopToolbar 20.93 ms +8.41% -1.15% 21.06 ms +4.8% -3.18% -0.62%
typeContainer 8.01 ms +3.75% -7.74% 8.01 ms +2.12% -8.74% 0%
focus 96.76 ms +6.96% -5.99% 95.64 ms +1.43% -1.65% 1.17%
firstFocus 192.84 ms +0% -0% 176.62 ms +0% -0% 9.18%
selectAll 427.77 ms +5.06% -3.47% 401.28 ms +16.24% -2.58% 6.6%
listViewOpen 69.33 ms +6.09% -9.95% 63.31 ms +3.92% -7.28% 9.51%
inserterOpen 35.22 ms +6.05% -6.79% 35.46 ms +3.61% -7.25% -0.68%
inserterHover 10.55 ms +10.24% -3.22% 9.49 ms +8.64% -3.79% 11.17%
inserterSearch 8.46 ms +6.03% -10.28% 8.36 ms +8.25% -7.66% 1.2%
loadPatterns 551.29 ms +2.21% -2.92% 533.16 ms +7.04% -1.25% 3.4%
wpTotal 349.29 ms +1.7% -7.48% 336.92 ms +3.82% -6.38% 3.67%
wpMemoryUsage 14.71 MB +0% -0% 14.67 MB +0% -0% 0.24%
wpDbQueries 55 +0% -0% 55 +0% -0% 0%

site-editor

Metric 6e78a42 trunk % Change
serverResponse 526.76 ms +0.56% -11.64% 480.08 ms +4.49% -6.14% 9.72%
firstPaint 264.42 ms +22.62% -14.15% 282.49 ms +10.44% -16.03% -6.4%
domContentLoaded 1261.56 ms +1.21% -2.07% 1283.56 ms +2.64% -2.2% -1.71%
loaded 1262.77 ms +1.2% -2.07% 1284.81 ms +2.64% -2.19% -1.72%
firstContentfulPaint 496.87 ms +4.45% -13.73% 521.98 ms +1.72% -8.52% -4.81%
firstBlock 4676.99 ms +0.71% -0.62% 4731.75 ms +1.24% -0.22% -1.16%
type 17.78 ms +8.1% -3.15% 20.05 ms +4.09% -6.38% -11.32%
navigate 118.09 ms +11.59% -3.21% 118.87 ms +15.48% -9.77% -0.66%
loadPatterns 1256.61 ms +1.6% -2.42% 1295.28 ms +5.17% -3.07% -2.99%
loadPages 991.75 ms +2.91% -3.63% 979.72 ms +3.63% -4.2% 1.23%
wpTotal 517.3 ms +0.55% -11.86% 470.75 ms +4.53% -6.26% 9.89%
wpMemoryUsage 12.72 MB +0% -0% 12.69 MB +0% -0% 0.27%
wpDbQueries 44 +2.27% -0% 44 +2.27% -0% 0%

8996bf5 Run

🏁 Flaky tests

Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

Regression: updating a specific option will only change its value and will not corrupt others in /test/e2e/specs/editor/various/core-settings.spec.js, passed after 1 failed attempt.
Error: expect(received).toStrictEqual(expected) // deep equality

- Expected  - 1
+ Received  + 1

  Array [
    "rewrite_rules",
-   "SERIALIZED DATA",
+   "",
  ]
    at forEach (/home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/various/core-settings.spec.js:46:26)
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/various/core-settings.spec.js:43:35
pushes earlier threads above the canvas to keep the selected one aligned in /test/e2e/specs/editor/various/block-notes-floating.spec.js, passed after 1 failed attempt.
Error: expect(locator).toBeVisible() failed

Locator: getByRole('region', { name: /^(Notes|Editor settings)$/ }).getByRole('treeitem', { name: 'Note: Four: Comprehensive considerations regarding extraordinarily sophisticated implementation characteristics notwithstanding.' })
Expected: visible
Timeout: 5000ms
Error: element(s) not found

Call log:
  - Expect "toBeVisible" getByRole('region', { name: /^(Notes|Editor settings)$/ }).getByRole('treeitem', { name: 'Note: Four: Comprehensive considerations regarding extraordinarily sophisticated implementation characteristics notwithstanding.' }) with timeout 5000ms
  - waiting for getByRole('region', { name: /^(Notes|Editor settings)$/ }).getByRole('treeitem', { name: 'Note: Four: Comprehensive considerations regarding extraordinarily sophisticated implementation characteristics notwithstanding.' })

    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/various/block-notes-floating.spec.js:589:4

8996bf5 Run

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@adamsilverstein adamsilverstein left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Fantastic

@adamsilverstein

Copy link
Copy Markdown
Member

Code looks good, I'm going to do some manual testing in Playground.

@adamsilverstein

Copy link
Copy Markdown
Member

This worked well in my manual testing. I also asked Claude to go thru and try various Note actions, tracking the focus and will post the results shortly.

@adamsilverstein

Copy link
Copy Markdown
Member

This tested well and pulling the focus logic into one place makes sense. I had Claude test this in Playground and it worked as expected.

Claude reports:

Tested with three notes on three paragraphs in the "All notes" sidebar, checking focus and block selection after each action:

Scenario Result
Delete the first (selected) note Focus moves to the second note, its block is selected
Delete the last note Focus moves to the previous note
Resolve a note Focus stays on the note, which collapses
Escape on a selected note Focus stays on the note, which collapses
Cancel a reply Focus stays on the thread, which collapses
Delete a reply Focus goes to the parent thread, which stays open
Delete notes whose blocks are gone, in a row Focus moves to the next note each time

Step 4 (deleting the last note) behaved the same as the base commit in headless Chrome: the block is selected and its paragraph gets focus, then focus drops to the canvas body right away.

A few notes from reading the code:

  • Delete looks up the neighbor after the request resolves, which works because the unmounting NoteThread still holds the threads list from before the delete. Makes sense, though a short comment there might save the next reader a minute.
  • The neighbor is now only selected when the deleted thread was the selected one, where trunk always selected it. That seems more correct, and could be worth a line in the description.
  • Calling useNoteActions per thread looks cheap, since it only reads selectors and dispatchers without per-thread subscriptions.
  • No e2e test covers where focus lands after a delete yet. Steps 2-4 would make a cheap regression test before Notes: Add search and a status filter to the All Notes sidebar #84060 builds on this.

@Mamaduka

Copy link
Copy Markdown
Member Author

Thanks, Adam!

My Claude review reported similar cases. I ignored the extra description, which LLMs seem to like for some reason. The useNoteActions usage is acceptable; I reuse similar hooks all the time.

@Mamaduka
Mamaduka merged commit 0b1cdf3 into trunk Oct 23, 2026
81 checks passed
@Mamaduka
Mamaduka deleted the fix/notes-thread-focus-recovery branch October 23, 2026 16:21
@github-actions github-actions Bot added this to the Gutenberg 24.4 milestone Oct 23, 2026
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] Bug An existing feature does not function as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants