Skip to content

Notes: Preserve unsent note and reply drafts - #84110

Merged
Mamaduka merged 1 commit into
trunkfrom
fix/notes-preserve-drafts
Oct 6, 2026
Merged

Mamaduka merged 1 commit into
trunkfrom
fix/notes-preserve-drafts

Conversation

@Mamaduka

@Mamaduka Mamaduka commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

What?

Closes #73473

Keeps the text typed into the new note and reply forms when the form closes because another block or thread was selected, or the editor switched to the code editor. Today, typing a note, clicking another block and coming back shows an empty form.

Why?

NoteForm holds its content in local state, and the form only renders while its note is selected, so any selection change unmounts it and drops the text. Window and tab switches were fixed in #80222; block and thread switches are what remain.

How?

Drafts live in a Map owned by NotesSidebarContainer, keyed by block client ID (new note) or note ID (reply); useNoteDraft reads it when a form mounts and writes it on change, so typing doesn't re-render the sidebar, and the owner sits above the code editor and revisions checks. Selecting a block with no threads but a draft selects 'new', which reopens the form, and a non-empty new note form stays open on focus-out (as long as the same block is selected).

Alternative to #80482, which mirrors every keystroke into the editor store.

Testing Instructions

  1. Open a post or page and add three paragraphs.
  2. Select the first paragraph, choose "Add note" from the block options menu and type some text.
  3. Click the first paragraph. The form stays open with the text. An empty form still closes.
  4. Click the third paragraph, then the first again. The form reopens with the text.
  5. Click "Add note". The note is created.
  6. Open a new note form on the third paragraph, type, click "Cancel", then "Add note" again. The form is empty.
  7. Select an existing note's thread, type a reply, click another paragraph, then the noted paragraph. The reply text is back.
  8. Type a new note draft, switch to the code editor and back, then select the paragraph. The draft is back.

Testing Instructions for Keyboard

Same.

Use of AI Tools

Assisted by Claude.

@Mamaduka Mamaduka self-assigned this Oct 6, 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 6, 2026
@github-actions github-actions Bot added the [Package] Editor /packages/editor label Oct 6, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Mamaduka
Mamaduka force-pushed the fix/notes-preserve-drafts branch from c8ff08f to c8a724d Compare October 6, 2026 11:30
@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

🤖 PR meta 🤖

🎉 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>
Co-authored-by: desrosj <desrosj@git.wordpress.org>
Co-authored-by: Mustafabharmal <mustafabharmal@git.wordpress.org>
Co-authored-by: yashjawale <yashjawale@git.wordpress.org>
Co-authored-by: t-hamano <wildworks@git.wordpress.org>
Co-authored-by: westonruter <westonruter@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.

📦 Bundle size

Size Change: +178 B (0%)

Total Size: 8.29 MB

📦 View Changed
Filename Size Change
build/scripts/editor/index.min.js 620 kB +178 B (+0.03%)

c8a724d Run

⚡ Performance

Show the results

Client side metrics exclude the server response time.

front-end-block-theme

Metric b88a35d trunk % Change
timeToFirstByte 58.2 ms +8.51% -4.55% 58.9 ms +3.48% -4.16% -1.19%
largestContentfulPaint 100 ms +2% -8% 98 ms +6.12% -4.08% 2.04%
lcpMinusTtfb 36.3 ms +21.9% -7.02% 38.4 ms +16.15% -7.16% -5.47%
wpBeforeTemplate 28.84 ms +12.1% -3.26% 28.75 ms +3.27% -3.34% 0.31%
wpTemplate 24.66 ms +3.77% -3.73% 25.51 ms +4% -2.86% -3.33%
wpTotal 54.38 ms +8.13% -4.76% 54.89 ms +3.95% -4.14% -0.93%
wpMemoryUsage 7.65 MB +0% -0% 7.61 MB +0% -0% 0.46%
wpDbQueries 17 +0% -0% 17 +0% -0% 0%

front-end-classic-theme

Metric b88a35d trunk % Change
timeToFirstByte 42.4 ms +7.31% -0.71% 50.2 ms +7.07% -1.2% -15.54%
largestContentfulPaint 92 ms +2.17% -4.35% 108 ms +1.85% -3.7% -14.81%
lcpMinusTtfb 48.15 ms +3.01% -4.15% 56.55 ms +2.39% -3.71% -14.85%
wpBeforeTemplate 26.17 ms +2.37% -0.69% 27.43 ms +11.37% -1.57% -4.59%
wpTemplate 13.58 ms +3.61% -1.33% 19.54 ms +1.13% -2% -30.5%
wpTotal 39.75 ms +6.82% -0.86% 47.17 ms +6.78% -1.89% -15.73%
wpMemoryUsage 6.13 MB +0% -0% 6.23 MB +0% -0% -1.56%
wpDbQueries 10 +0% -0% 14 +0% -0% -28.57%

media-processing

Metric b88a35d trunk % Change
mediaProcessingJpeg 397.17 ms +1.32% -0.4% 400.83 ms +1.76% -0.53% -0.91%
mediaProcessingAvif 6098.56 ms +0.42% -0.15% 6055.52 ms +0.19% -0.22% 0.71%
mediaProcessingJpegToAvif 4239.51 ms +0.06% -0.09% 4154.01 ms +0.79% -0.24% 2.06%

media-upload

Metric b88a35d trunk % Change
jpegUploadProcessing 1433.38 ms +3.12% -1.4% 1415.69 ms +1.12% -0.4% 1.25%
pngUploadProcessing 197.13 ms +1.44% -5.81% 206 ms +22.67% -17.33% -4.31%
largeJpegUploadProcessing 1421.12 ms +0.83% -0.31% 1402.66 ms +0.58% -0.62% 1.32%
multipleImageUploadProcessing 1588.45 ms +0.84% -0.6% 1532.78 ms +1.1% -1.67% 3.63%

post-editor

Metric b88a35d trunk % Change
serverResponse 525.31 ms +5.74% -1.31% 526.77 ms +2.68% -6.86% -0.28%
firstPaint 251.15 ms +20.74% -6.82% 282.85 ms +21.62% -24.97% -11.21%
domContentLoaded 1103.4 ms +1.19% -1.25% 1103.47 ms +1% -1.14% -0.01%
loaded 1104.58 ms +1.21% -1.25% 1104.6 ms +1% -1.13% 0%
firstContentfulPaint 456.4 ms +0.3% -2.25% 460 ms +1.34% -1.85% -0.78%
firstBlock 3360.9 ms +0.55% -1.04% 3330.28 ms +0.63% -0.84% 0.92%
type 17.41 ms +1.9% -3.68% 17.51 ms +8.05% -5.65% -0.57%
typeWithoutInspector 18.82 ms +6.22% -6.32% 17.97 ms +9.29% -4.56% 4.73%
typeWithTopToolbar 23.9 ms +4.23% -3.18% 23.73 ms +5.56% -5.86% 0.72%
typeContainer 8.82 ms +7.6% -9.52% 8.88 ms +8.11% -3.6% -0.68%
focus 106.91 ms +3.16% -2.95% 116.9 ms +2.56% -4.92% -8.55%
firstFocus 222.21 ms +0% -0% 224.23 ms +0% -0% -0.9%
selectAll 584.57 ms +1.79% -6.9% 554.4 ms +4.61% -0.52% 5.44%
listViewOpen 78.29 ms +10.31% -6.4% 78.85 ms +3.69% -15.55% -0.71%
inserterOpen 44.25 ms +4.56% -7.37% 41.51 ms +7.71% -11.15% 6.6%
inserterHover 12.05 ms +8.22% -7.97% 12.52 ms +6.79% -7.83% -3.75%
inserterSearch 7.96 ms +10.43% -4.9% 8.88 ms +0.79% -9.12% -10.36%
loadPatterns 620.22 ms +3.54% -5.93% 603.11 ms +7.54% -2.19% 2.84%
wpTotal 515.75 ms +5.76% -1.42% 516.4 ms +2.88% -6.94% -0.13%
wpMemoryUsage 13.20 MB +0% -0% 13.17 MB +0% -0% 0.28%
wpDbQueries 53.5 +0.93% -0.93% 54 +0% -1.85% -0.93%

site-editor

Metric b88a35d trunk % Change
serverResponse 527.52 ms +0.53% -5.86% 506.27 ms +1.65% -1.56% 4.2%
firstPaint 274.96 ms +21.98% -8.2% 255.4 ms +20.22% -15.04% 7.66%
domContentLoaded 1189.26 ms +1.69% -0.93% 1181.52 ms +0.58% -1.48% 0.66%
loaded 1190.46 ms +1.69% -0.93% 1182.95 ms +0.57% -1.48% 0.63%
firstContentfulPaint 471.78 ms +2.39% -1.24% 457.22 ms +5.06% -0.63% 3.18%
firstBlock 4472.32 ms +0.5% -0.5% 4450.87 ms +0.14% -0.77% 0.48%
type 20.25 ms +3.21% -4.35% 18.71 ms +12.29% -1.76% 8.23%
navigate 119.09 ms +7.41% -3.38% 121.58 ms +15.03% -3.17% -2.05%
loadPatterns 1312.12 ms +4.65% -6.5% 1331.15 ms +9.1% -8.41% -1.43%
loadPages 1036.63 ms +0.93% -1.8% 1018.93 ms +1.34% -0.89% 1.74%
wpTotal 517.32 ms +0.56% -5.97% 496.92 ms +1.53% -1.62% 4.11%
wpMemoryUsage 12.25 MB +0% -0% 12.21 MB +0% -0% 0.3%
wpDbQueries 43 +2.33% -0% 43 +2.33% -0% 0%

c8a724d 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.

refuses the drop and uploads nothing in /test/e2e/specs/editor/various/single-file-placeholder-drop.spec.js, passed after 1 failed attempt.
Error: expect(received).toHaveLength(expected)

Expected length: 0
Received length: 2
Received array:  [{"_links": {"about": [{"href": "http://localhost:8889/wp-json/wp/v2/types/attachment"}], "author": [{"embeddable": true, "href": "http://localhost:8889/wp-json/wp/v2/users/1"}], "collection": [{"href": "http://localhost:8889/wp-json/wp/v2/media"}], "curies": [{"href": "https://api.w.org/{rel}", "name": "wp", "templated": true}], "replies": [{"embeddable": true, "href": "http://localhost:8889/wp-json/wp/v2/comments?post=193"}], "self": [{"href": "http://localhost:8889/wp-json/wp/v2/media/193", "targetHints": {"allow": ["GET", "POST", "PUT", "PATCH", "DELETE"]}}], "wp:attached-to": [{"embeddable": true, "href": "http://localhost:8889/wp-json/wp/v2/posts/191", "id": 191, "post_type": "post"}]}, "alt_text": "", "author": 1, "caption": {"rendered": ""}, "class_list": ["post-193", "attachment", "type-attachment", "status-inherit", "hentry", "entry"], "comment_status": "open", "date": "2026-10-06T11:46:02", "date_gmt": "2026-10-06T11:46:02", "description": {"rendered": "<p class=\"attachment\"><a href='http://localhost:8889/wp-content/uploads/2026/10/10x10_e2e_test_image_z9T8jK-1.png'><img loading=\"lazy\" decoding=\"async\" width=\"10\" height=\"10\" src=\"http://localhost:8889/wp-content/uploads/2026/10/10x10_e2e_test_image_z9T8jK-1.png\" class=\"attachment-medium size-medium\" alt=\"\" style=\"width:100%;height:100%;max-width:10px;\" /></a></p>
"}, "featured_media": 0, "filename": "10x10_e2e_test_image_z9T8jK-1.png", "filesize": 80, "guid": {"rendered": "http://localhost:8889/wp-content/uploads/2026/10/10x10_e2e_test_image_z9T8jK-1.png"}, "id": 193, "link": "http://localhost:8889/?attachment_id=193", "media_details": {"file": "2026/10/10x10_e2e_test_image_z9T8jK-1.png", "filesize": 80, "height": 10, "image_meta": {"alt": "", "aperture": "0", "camera": "", "caption": "", "copyright": "", "created_timestamp": "0", "credit": "", "focal_length": "0", "iso": "0", "keywords": [], "orientation": "0", "shutter_speed": "0", "title": ""}, "sizes": {}, "width": 10}, "media_type": "image", "meta": [], "mime_type": "image/png", "modified": "2026-10-06T11:46:02", "modified_gmt": "2026-10-06T11:46:02", "ping_status": "closed", "post": 191, "slug": "10x10_e2e_test_image_z9t8jk-2", "source_url": "http://localhost:8889/wp-content/uploads/2026/10/10x10_e2e_test_image_z9T8jK-1.png", "status": "inherit", "template": "", "title": {"rendered": "10x10_e2e_test_image_z9T8jK"}, "type": "attachment"}, {"_links": {"about": [{"href": "http://localhost:8889/wp-json/wp/v2/types/attachment"}], "author": [{"embeddable": true, "href": "http://localhost:8889/wp-json/wp/v2/users/1"}], "collection": [{"href": "http://localhost:8889/wp-json/wp/v2/media"}], "curies": [{"href": "https://api.w.org/{rel}", "name": "wp", "templated": true}], "replies": [{"embeddable": true, "href": "http://localhost:8889/wp-json/wp/v2/comments?post=192"}], "self": [{"href": "http://localhost:8889/wp-json/wp/v2/media/192", "targetHints": {"allow": ["GET", "POST", "PUT", "PATCH", "DELETE"]}}], "wp:attached-to": [{"embeddable": true, "href": "http://localhost:8889/wp-json/wp/v2/posts/191", "id": 191, "post_type": "post"}]}, "alt_text": "", "author": 1, "caption": {"rendered": ""}, "class_list": ["post-192", "attachment", "type-attachment", "status-inherit", "hentry", "entry"], "comment_status": "open", "date": "2026-10-06T11:46:02", "date_gmt": "2026-10-06T11:46:02", "description": {"rendered": "<p class=\"attachment\"><a href='http://localhost:8889/wp-content/uploads/2026/10/10x10_e2e_test_image_z9T8jK.png'><img loading=\"lazy\" decoding=\"async\" width=\"10\" height=\"10\" src=\"http://localhost:8889/wp-content/uploads/2026/10/10x10_e2e_test_image_z9T8jK.png\" class=\"attachment-medium size-medium\" alt=\"\" style=\"width:100%;height:100%;max-width:10px;\" /></a></p>
"}, "featured_media": 0, "filename": "10x10_e2e_test_image_z9T8jK.png", "filesize": 80, "guid": {"rendered": "http://localhost:8889/wp-content/uploads/2026/10/10x10_e2e_test_image_z9T8jK.png"}, "id": 192, "link": "http://localhost:8889/?attachment_id=192", "media_details": {"file": "2026/10/10x10_e2e_test_image_z9T8jK.png", "filesize": 80, "height": 10, "image_meta": {"alt": "", "aperture": "0", "camera": "", "caption": "", "copyright": "", "created_timestamp": "0", "credit": "", "focal_length": "0", "iso": "0", "keywords": [], "orientation": "0", "shutter_speed": "0", "title": ""}, "sizes": {}, "width": 10}, "media_type": "image", "meta": [], "mime_type": "image/png", "modified": "2026-10-06T11:46:02", "modified_gmt": "2026-10-06T11:46:02", "ping_status": "closed", "post": 191, "slug": "10x10_e2e_test_image_z9t8jk", "source_url": "http://localhost:8889/wp-content/uploads/2026/10/10x10_e2e_test_image_z9T8jK.png", "status": "inherit", "template": "", "title": {"rendered": "10x10_e2e_test_image_z9T8jK"}, "type": "attachment"}]
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/various/single-file-placeholder-drop.spec.js:71:45
scrolls the thread before the canvas in /test/e2e/specs/editor/various/block-notes-floating.spec.js, passed after 1 failed attempt.
Error: expect(received).toBeGreaterThan(expected)

Expected: > 221
Received:   221

Call Log:
- Timeout 5000ms exceeded while waiting on the predicate
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/various/block-notes-floating.spec.js:839:6
should indent with Space and outdent with Shift+Space at the start in /test/e2e/specs/editor/blocks/list.spec.js, passed after 1 failed attempt.
Error: Object with guid handle@9ff2e06092d67f8f8eb05180ce4e0e74 was not bound in the connection
three users concurrently edit a large post with diverse blocks in /test/e2e/specs/editor/collaboration/collaboration-stress.spec.ts, passed after 1 failed attempt.
TimeoutError: locator.waitFor: Timeout 17500ms exceeded.
Call log:
  - waiting for getByRole('button', { name: /Collaborators list/ }) to be visible

    at map (/home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/collaboration/fixtures/collaboration-utils.ts:193:7)
    at CollaborationUtils.waitForMutualDiscovery (/home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/collaboration/fixtures/collaboration-utils.ts:188:10)
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/collaboration/collaboration-stress.spec.ts:416:28

c8a724d Run

@Mamaduka
Mamaduka marked this pull request as ready for review October 6, 2026 11:52
@Mamaduka

Mamaduka commented Oct 6, 2026

Copy link
Copy Markdown
Member Author

Marking this ready to review.

Re: Inline note drafts

The tricky part is the anchor. With drafts, the form outlives the text selection, so we need to keep the range somewhere and track it while the content changes.

Google Docs and Notion handle this with a placeholder highlight on the selected text while you write the note. It goes away on cancel and becomes the real marker on submit. I'd like to experiment with something similar: a highlight that exists only in the editor and is never saved in the content.

It's more involved than block and reply drafts, so it will probably be a follow-up.

};
}, [] );

// Select the block's primary note, or clear the selection if it has none.

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.

is this comment block removal intentional?

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.

Sort of, it's more a result of my aggressive comment pruning. Happy to restore, but the L580-581 logic seems clear to me.

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

Neat

@adamsilverstein

Copy link
Copy Markdown
Member

Nice work, I like this approach. I tested this with block level and selection notes and while I see the anchor issue you mentioned, it still feels fine to land as is for now.

@Mamaduka

Mamaduka commented Oct 6, 2026

Copy link
Copy Markdown
Member Author

Thanks, @adamsilverstein!

@Mamaduka
Mamaduka merged commit 3ed98a4 into trunk Oct 6, 2026
142 checks passed
@Mamaduka
Mamaduka deleted the fix/notes-preserve-drafts branch October 6, 2026 14:50
@github-actions github-actions Bot added this to the Gutenberg 24.2 milestone Oct 6, 2026
adamsilverstein added a commit that referenced this pull request Oct 7, 2026
Trunk's draft-preserving reply form (#84110) carries the note class, so
the hover locator matched two elements.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L7rwHtVAa37GWJPC8P3Cwd
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

Development

Successfully merging this pull request may close these issues.

Drafted notes are lost when focus is removed from block

2 participants