From 024a95a04637dec52d7a776951337e77d4fd20e4 Mon Sep 17 00:00:00 2001 From: yogeshbhutkar Date: Mon, 8 Jun 2026 14:58:38 +0530 Subject: [PATCH 1/5] Persist editorial note block links on review --- .../hooks/useEditorialNotes.ts | 60 +++++++++++++++---- 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/src/experiments/editorial-notes/hooks/useEditorialNotes.ts b/src/experiments/editorial-notes/hooks/useEditorialNotes.ts index c9450a4b3..660bf21b6 100644 --- a/src/experiments/editorial-notes/hooks/useEditorialNotes.ts +++ b/src/experiments/editorial-notes/hooks/useEditorialNotes.ts @@ -65,6 +65,15 @@ interface ReviewResult { suggestions: Suggestion[]; } +interface ReviewSingleBlockResult { + suggestionCount: number; + didLinkNoteId: boolean; +} + +interface CreateNoteResult { + didLinkNoteId: boolean; +} + interface NoteRecord { id: number; [ key: string ]: unknown; @@ -79,7 +88,7 @@ interface NoteRecord { * @param resolvedNoteIds Set of Note IDs that have been resolved (approved). * @param noteContentById Map of Note ID → rendered Note content (pending only). * @param pendingNotes All pending Notes for the post (for reply lookup). - * @return The number of suggestions created, or 0 if the block was skipped. + * @return The suggestion count and whether a new Note thread was linked. */ async function reviewSingleBlock( block: Block, @@ -88,19 +97,19 @@ async function reviewSingleBlock( resolvedNoteIds: Set< number >, noteContentById: Map< number, string >, pendingNotes: ExistingNote[] -): Promise< number > { +): Promise< ReviewSingleBlockResult > { // Look up any existing Note thread on this block. const existingNoteId = block.attributes.metadata?.noteId ?? null; // Skip blocks whose Note thread has been resolved. if ( existingNoteId && resolvedNoteIds.has( existingNoteId ) ) { - return 0; + return { suggestionCount: 0, didLinkNoteId: false }; } const blockText = getBlockText( block ); if ( blockText.length === 0 ) { - return 0; + return { suggestionCount: 0, didLinkNoteId: false }; } // Collect pending Note texts for this block's thread as context. @@ -148,11 +157,20 @@ async function reviewSingleBlock( } ); if ( result?.suggestions && result.suggestions.length > 0 ) { - await createNote( block, postId, result.suggestions, existingNoteId ); - return result.suggestions.length; + const { didLinkNoteId } = await createNote( + block, + postId, + result.suggestions, + existingNoteId + ); + + return { + suggestionCount: result.suggestions.length, + didLinkNoteId, + }; } - return 0; + return { suggestionCount: 0, didLinkNoteId: false }; } /** @@ -228,6 +246,7 @@ export function useEditorialNotes(): { } let totalSuggestions = 0; + let didLinkAnyNoteId = false; // Process blocks in batches. for ( @@ -252,7 +271,11 @@ export function useEditorialNotes(): { ) ) ); - totalSuggestions += results.reduce( ( sum, n ) => sum + n, 0 ); + + for ( const result of results ) { + totalSuggestions += result.suggestionCount; + didLinkAnyNoteId ||= result.didLinkNoteId; + } setProgress( Math.min( batchStart + BATCH_SIZE, reviewableBlocks.length ) @@ -262,6 +285,12 @@ export function useEditorialNotes(): { setLastRunCount( totalSuggestions ); if ( totalSuggestions > 0 ) { + if ( didLinkAnyNoteId ) { + // Save once so newly linked noteIds survive reloads and later + // review/refinement passes. + await ( dispatch( editorStore ) as any ).savePost(); + } + ( dispatch( coreStore ) as any ).invalidateResolutionForStoreSelector( 'getEntityRecords' ); @@ -330,7 +359,7 @@ export function useEditorialBlock(): { noteContentById.set( note.id, note.content?.rendered ?? '' ); } - const suggestionCount = await reviewSingleBlock( + const { suggestionCount, didLinkNoteId } = await reviewSingleBlock( block, postId, content, @@ -340,6 +369,12 @@ export function useEditorialBlock(): { ); if ( suggestionCount > 0 ) { + if ( didLinkNoteId ) { + // Save once so newly linked noteIds survive reloads and later + // review/refinement passes. + await ( dispatch( editorStore ) as any ).savePost(); + } + ( dispatch( coreStore ) as any ).invalidateResolutionForStoreSelector( 'getEntityRecords' ); @@ -393,13 +428,14 @@ export function useEditorialBlock(): { * @param postId The current post ID. * @param suggestions The suggestions to include in the Note. * @param existingNoteId The ID of an existing Note thread, or null for a new thread. + * @return Whether a new Note thread was linked to the block. */ async function createNote( block: Block, postId: number, suggestions: Suggestion[], existingNoteId: number | null -): Promise< void > { +): Promise< CreateNoteResult > { const noteContent = suggestions .map( ( s ) => `[${ s.review_type.toUpperCase() }] ${ s.text }` ) .join( '\n\n' ); @@ -430,5 +466,9 @@ async function createNote( metadata: { ...existingMeta, noteId: note.id }, } ); + + return { didLinkNoteId: true }; } + + return { didLinkNoteId: false }; } From 6ed987f121db3540bc3876b0f5d646fb850a88f0 Mon Sep 17 00:00:00 2001 From: yogeshbhutkar Date: Fri, 12 Jun 2026 13:54:09 +0530 Subject: [PATCH 2/5] Revert "Persist editorial note block links on review" This reverts commit bb546ce8b6308d99052a9a7640fea63b5e4acb16. --- .../hooks/useEditorialNotes.ts | 60 ++++--------------- 1 file changed, 10 insertions(+), 50 deletions(-) diff --git a/src/experiments/editorial-notes/hooks/useEditorialNotes.ts b/src/experiments/editorial-notes/hooks/useEditorialNotes.ts index 660bf21b6..c9450a4b3 100644 --- a/src/experiments/editorial-notes/hooks/useEditorialNotes.ts +++ b/src/experiments/editorial-notes/hooks/useEditorialNotes.ts @@ -65,15 +65,6 @@ interface ReviewResult { suggestions: Suggestion[]; } -interface ReviewSingleBlockResult { - suggestionCount: number; - didLinkNoteId: boolean; -} - -interface CreateNoteResult { - didLinkNoteId: boolean; -} - interface NoteRecord { id: number; [ key: string ]: unknown; @@ -88,7 +79,7 @@ interface NoteRecord { * @param resolvedNoteIds Set of Note IDs that have been resolved (approved). * @param noteContentById Map of Note ID → rendered Note content (pending only). * @param pendingNotes All pending Notes for the post (for reply lookup). - * @return The suggestion count and whether a new Note thread was linked. + * @return The number of suggestions created, or 0 if the block was skipped. */ async function reviewSingleBlock( block: Block, @@ -97,19 +88,19 @@ async function reviewSingleBlock( resolvedNoteIds: Set< number >, noteContentById: Map< number, string >, pendingNotes: ExistingNote[] -): Promise< ReviewSingleBlockResult > { +): Promise< number > { // Look up any existing Note thread on this block. const existingNoteId = block.attributes.metadata?.noteId ?? null; // Skip blocks whose Note thread has been resolved. if ( existingNoteId && resolvedNoteIds.has( existingNoteId ) ) { - return { suggestionCount: 0, didLinkNoteId: false }; + return 0; } const blockText = getBlockText( block ); if ( blockText.length === 0 ) { - return { suggestionCount: 0, didLinkNoteId: false }; + return 0; } // Collect pending Note texts for this block's thread as context. @@ -157,20 +148,11 @@ async function reviewSingleBlock( } ); if ( result?.suggestions && result.suggestions.length > 0 ) { - const { didLinkNoteId } = await createNote( - block, - postId, - result.suggestions, - existingNoteId - ); - - return { - suggestionCount: result.suggestions.length, - didLinkNoteId, - }; + await createNote( block, postId, result.suggestions, existingNoteId ); + return result.suggestions.length; } - return { suggestionCount: 0, didLinkNoteId: false }; + return 0; } /** @@ -246,7 +228,6 @@ export function useEditorialNotes(): { } let totalSuggestions = 0; - let didLinkAnyNoteId = false; // Process blocks in batches. for ( @@ -271,11 +252,7 @@ export function useEditorialNotes(): { ) ) ); - - for ( const result of results ) { - totalSuggestions += result.suggestionCount; - didLinkAnyNoteId ||= result.didLinkNoteId; - } + totalSuggestions += results.reduce( ( sum, n ) => sum + n, 0 ); setProgress( Math.min( batchStart + BATCH_SIZE, reviewableBlocks.length ) @@ -285,12 +262,6 @@ export function useEditorialNotes(): { setLastRunCount( totalSuggestions ); if ( totalSuggestions > 0 ) { - if ( didLinkAnyNoteId ) { - // Save once so newly linked noteIds survive reloads and later - // review/refinement passes. - await ( dispatch( editorStore ) as any ).savePost(); - } - ( dispatch( coreStore ) as any ).invalidateResolutionForStoreSelector( 'getEntityRecords' ); @@ -359,7 +330,7 @@ export function useEditorialBlock(): { noteContentById.set( note.id, note.content?.rendered ?? '' ); } - const { suggestionCount, didLinkNoteId } = await reviewSingleBlock( + const suggestionCount = await reviewSingleBlock( block, postId, content, @@ -369,12 +340,6 @@ export function useEditorialBlock(): { ); if ( suggestionCount > 0 ) { - if ( didLinkNoteId ) { - // Save once so newly linked noteIds survive reloads and later - // review/refinement passes. - await ( dispatch( editorStore ) as any ).savePost(); - } - ( dispatch( coreStore ) as any ).invalidateResolutionForStoreSelector( 'getEntityRecords' ); @@ -428,14 +393,13 @@ export function useEditorialBlock(): { * @param postId The current post ID. * @param suggestions The suggestions to include in the Note. * @param existingNoteId The ID of an existing Note thread, or null for a new thread. - * @return Whether a new Note thread was linked to the block. */ async function createNote( block: Block, postId: number, suggestions: Suggestion[], existingNoteId: number | null -): Promise< CreateNoteResult > { +): Promise< void > { const noteContent = suggestions .map( ( s ) => `[${ s.review_type.toUpperCase() }] ${ s.text }` ) .join( '\n\n' ); @@ -466,9 +430,5 @@ async function createNote( metadata: { ...existingMeta, noteId: note.id }, } ); - - return { didLinkNoteId: true }; } - - return { didLinkNoteId: false }; } From ca2b8ee257334fe21a02f7e46bf32994dc21d947 Mon Sep 17 00:00:00 2001 From: yogeshbhutkar Date: Fri, 12 Jun 2026 14:20:50 +0530 Subject: [PATCH 3/5] Enhance success notice to include save instructions --- .../editorial-notes/hooks/useEditorialNotes.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/experiments/editorial-notes/hooks/useEditorialNotes.ts b/src/experiments/editorial-notes/hooks/useEditorialNotes.ts index c9450a4b3..59be2e67a 100644 --- a/src/experiments/editorial-notes/hooks/useEditorialNotes.ts +++ b/src/experiments/editorial-notes/hooks/useEditorialNotes.ts @@ -265,6 +265,20 @@ export function useEditorialNotes(): { ( dispatch( coreStore ) as any ).invalidateResolutionForStoreSelector( 'getEntityRecords' ); + + ( dispatch( noticesStore ) as any ).createSuccessNotice( + sprintf( + /* translators: %d: number of suggestions added. */ + _n( + '%d suggestion added. Save to keep changes.', + '%d suggestions added. Save to keep changes.', + totalSuggestions, + 'ai' + ), + totalSuggestions + ), + { type: 'snackbar' } + ); } } catch ( error: any ) { ( dispatch( noticesStore ) as any ).createErrorNotice( @@ -350,8 +364,8 @@ export function useEditorialBlock(): { sprintf( /* translators: %d: number of suggestions added. */ _n( - '%d suggestion added.', - '%d suggestions added.', + '%d suggestion added. Save to keep changes.', + '%d suggestions added. Save to keep changes.', suggestionCount, 'ai' ), From d0d2261d9926f4ade0ce4d741cb7600cb58255bd Mon Sep 17 00:00:00 2001 From: yogeshbhutkar Date: Fri, 12 Jun 2026 15:26:13 +0530 Subject: [PATCH 4/5] Locate pending notes based on noteId stored in block metadata --- .../components/EditorialNotesPlugin.tsx | 1 + .../components/EditorialUpdatesPlugin.tsx | 1 + .../hooks/useEditorialUpdates.ts | 21 +++++++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/src/experiments/editorial-notes/components/EditorialNotesPlugin.tsx b/src/experiments/editorial-notes/components/EditorialNotesPlugin.tsx index a9a421659..61e11ed77 100644 --- a/src/experiments/editorial-notes/components/EditorialNotesPlugin.tsx +++ b/src/experiments/editorial-notes/components/EditorialNotesPlugin.tsx @@ -85,6 +85,7 @@ export default function EditorialNotesPlugin() { justifyContent: 'center', width: '100%', } } + accessibleWhenDisabled __next40pxDefaultSize > { buttonLabel } diff --git a/src/experiments/editorial-updates/components/EditorialUpdatesPlugin.tsx b/src/experiments/editorial-updates/components/EditorialUpdatesPlugin.tsx index 065682cd2..017e3b5f3 100644 --- a/src/experiments/editorial-updates/components/EditorialUpdatesPlugin.tsx +++ b/src/experiments/editorial-updates/components/EditorialUpdatesPlugin.tsx @@ -63,6 +63,7 @@ export default function EditorialUpdatesPlugin() { width: '100%', justifyContent: 'center', } } + accessibleWhenDisabled > { buttonLabel } diff --git a/src/experiments/editorial-updates/hooks/useEditorialUpdates.ts b/src/experiments/editorial-updates/hooks/useEditorialUpdates.ts index 82ebcc926..ee9a06fc4 100644 --- a/src/experiments/editorial-updates/hooks/useEditorialUpdates.ts +++ b/src/experiments/editorial-updates/hooks/useEditorialUpdates.ts @@ -99,6 +99,26 @@ export function useEditorialUpdates(): { if ( ! postId ) { return false; } + + // Collect Note IDs linked from block metadata, then check whether any are + // still pending. This avoids showing the action for stale Notes left behind + // after refreshing before the generated Note metadata was explicitly saved. + const allBlocks = sel( blockEditorStore ).getBlocks() as Block[]; + const linkedNoteIds = Array.from( + new Set( + flattenBlocks( allBlocks ) + .filter( ( block ) => + REVIEWABLE_BLOCK_TYPES.includes( block.name ) + ) + .map( ( block ) => block.attributes.metadata?.noteId ) + .filter( ( id ) => typeof id === 'number' ) + ) + ); + + if ( linkedNoteIds.length === 0 ) { + return false; + } + const notes = ( sel( coreStore ) as any ).getEntityRecords( 'root', 'comment', @@ -106,6 +126,7 @@ export function useEditorialUpdates(): { type: 'note', status: 'hold', post: postId, + include: Array.from( linkedNoteIds ), per_page: 1, _fields: 'id', } From 94e2f53a11bdadf3a3b29a7f25b343aae2b2a787 Mon Sep 17 00:00:00 2001 From: yogeshbhutkar Date: Fri, 12 Jun 2026 16:36:37 +0530 Subject: [PATCH 5/5] Fix CI --- .../experiments/editorial-updates.spec.js | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/tests/e2e/specs/experiments/editorial-updates.spec.js b/tests/e2e/specs/experiments/editorial-updates.spec.js index c79416314..ca61feb1a 100644 --- a/tests/e2e/specs/experiments/editorial-updates.spec.js +++ b/tests/e2e/specs/experiments/editorial-updates.spec.js @@ -232,8 +232,14 @@ test.describe( 'Editorial Updates Experiment', () => { await admin.createNewPost( { title: 'Grouped Editorial Controls Test', - content: - 'This post has enough content to meet the minimum character requirement for the summarization feature and show the editor sidebar controls.', + } ); + + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + content: + 'This post has enough content to meet the minimum character requirement for the summarization feature and show the editor sidebar controls.', + }, } ); await editor.saveDraft(); @@ -296,6 +302,19 @@ test.describe( 'Editorial Updates Experiment', () => { await page.reload(); await editor.openDocumentSettingsSidebar(); + // Re-inject noteId after reload. + await page.evaluate( ( id ) => { + const blocks = window.wp.data + .select( 'core/block-editor' ) + .getBlocks(); + + window.wp.data + .dispatch( 'core/block-editor' ) + .updateBlockAttributes( blocks[ 0 ].clientId, { + metadata: { noteId: id }, + } ); + }, noteId ); + const notesButton = page.getByRole( 'button', { name: 'Generate Editorial Notes', } );