Content Guidelines: Add AI-powered generate/improve UI via Jetpack#47959
Conversation
Adds a wpcom proxy endpoint that forwards guideline generation requests to the wpcom AI suggest-guidelines API. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…es proxy - Accept and forward optional filters param to wpcom endpoint - Extensible structure for adding new filter types Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.
Interested in more tips and information?
|
This comment was marked as outdated.
This comment was marked as outdated.
Code Coverage SummaryCoverage changed in 1 file.
2 files are newly checked for coverage.
Full summary · PHP report · JS report If appropriate, add one of these labels to override the failing coverage check:
Covered by non-unit tests
|
|
Thanks for getting this started @aagam-shah. Here are a few comments based on what I see right now: 1. Badges 2. Suggestions The suggestions are presented in what appears like a disabled field: The container should look and behave like a regular input (but have a green border). We need to match the height of the input (and visa versa) to prevent layout shift. We also can accept a suggestion if someone clicks on the suggestion to start editing it. When the section is open, we don't need to show the suggestion badge any longer since the input + buttons communicate the suggestion. This is what I had in the design:
3. Banner behaviour The main banner dismisses if you generate a suggestion from within a section. The causes the layout to shift and feels jarring. The banner should only dismiss if the user dismisses it or the main call to action is used to generate guidelines for all the sections. 4. Block suggestions I'm not seeing any block suggestions being added. Are you planning on adding those separately? We should have suggestions for the block section as well as the blocks themselves.
|
dfbee81 to
3c42428
Compare
|
Thanks for the detailed feedback @fditrapani! Here's where we're at on each point: 1. Badges 2. Suggestions 3. Banner behaviour 4. Block suggestions |
|
Thanks for the updates @aagam-shah!
Sounds good to me if this is the badge we're already using. I understand we're updating components but hopefully this will get updates along with the others when that happens.
Thanks for the updates. This is looking better. We still have some shifting happening if the text area isn't the default size: Screen.Recording.2026-04-08.at.12.51.15.PM.mov
Nice! looking better here too. I noticed a bug where the banner unexpectedly goes away if you accept a suggestion. Screen.Recording.2026-04-08.at.12.53.02.PM.movJetpack logoI got some feedback from Devin that the logo should be reversed instead of the regular colour. (p1775665181650579/1775564866.152469-slack-C08NFD7RA58) Can we update the logo to look like this:
|
0b4325a to
e03390e
Compare
|
Thanks @fditrapani! Here's an update on the latest changes: 1. Suggestions UX — Updated:
2. Banner behaviour — Fixed:
3. Block suggestions — Still planned as a follow-up. 4. Upgrade notice — Added a persistent warning Notice (instead of snackbar) with a "View plans" button when AI is unavailable. Matches the standard WordPress notice pattern. |
Injects an AI-powered "Generate with Jetpack" button next to each "Save guidelines" button on the Content Guidelines settings page. Uses DOM injection via MutationObserver to find accordion forms and render React buttons that call the suggest-guidelines API endpoint. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Pass AI availability, connection status, and upgrade URL from PHP to JS - Show contextual snackbar with action link when AI is unavailable: - Not connected: "Connect" action linking to Jetpack dashboard - Connected but no AI: "Upgrade" action linking to checkout - wpcom vs self-hosted sites get appropriate upgrade URLs - Disconnect MutationObserver once all buttons are injected - Inline buttons next to Save guidelines in a flex row Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Content Guidelines page is registered with slug 'guidelines-wp-admin', not 'content-guidelines-wp-admin'. Without this fix the script never enqueues. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split monolithic 146-line JS file into: - constants.js (store name, sections, API path) - lib/api.js (suggest-guidelines API wrapper) - lib/inject.js (DOM injection + MutationObserver) - components/generate-button.jsx (per-section button) Entry point is now a thin import + init. Button label changes to "Improve with Jetpack" when content exists. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New SuggestAllButton component generates all 4 guideline sections at once. Injected into the .admin-ui-page__header via DOM injection. Label adapts: "Generate guidelines" when empty, "Improve guidelines" when content exists. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shows a Jetpack-branded card above the guideline list when all sections are empty, encouraging users to generate their first set of guidelines. Hides automatically when content exists. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Target .admin-ui-page__header-actions instead of .admin-ui-page__header so the button renders in the header row next to the title, not below the subtitle. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Option B design only has the top-level "Generate/Improve guidelines" button and the empty state banner. Per-section buttons are not part of the design. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add wp.data store (jetpack/content-guidelines-ai) for shared suggestion state across injected React roots - Show "Suggestion" badge in accordion headers when a suggestion exists - Show loading spinner in accordion headers while generating - Show suggestion preview with Accept/Dismiss inside expanded accordions - Accept writes to core/content-guidelines store, Dismiss discards - Header button now writes to suggestion store instead of directly to the guideline store Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add per-section "Improve/Generate guidelines" tertiary button in each accordion's button row (triggers single-section AI generation) - When suggestion exists: hide Gutenberg's DataForm + Save/Clear via CSS class toggle, show suggestion textarea + Accept/Dismiss instead - Add Jetpack icon SVG to the header "Improve guidelines" button - Add per-section loading state to the store so each accordion shows its own spinner independently - Update suggestion badge to use per-section loading Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Review fixes: - Extract shared availability check to lib/availability.js - Centralize config in constants.js - MutationObserver disconnects once all injections complete - Remove unused store actions/selectors - Add useEffect cleanup for DOM class manipulation - Use Jetpack CSS custom properties for colors Banner upgrade: - Rich dark gradient background with green orb decorations - Matches exploration-b prototype design - Shows when all guidelines empty, hides automatically - No localStorage persistence — purely data-driven Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
b0d8c3f to
7c3c46c
Compare
The header "Suggest all / Generate guidelines" button regressed to the top-left: when adapting to the latest Gutenberg, the original target (`.admin-ui-page__header-actions`) no longer exists — the wp-admin Page header only renders an actions slot when the page passes `actions` to <Page>, which the Guidelines page does not. The interim fix placed the button at the top of `.guidelines__content`, i.e. top-left. Instead, inject into the header-content row itself (flex, justify: space-between) that holds the page title, as its second child, so it right-aligns exactly where native header actions render. Header classes are hashed CSS-module names, so the row is located structurally: the space-between flex row containing the page <h1>, scoped to the .admin-ui-navigable-region. Verified live: the button's right edge aligns to the header's right edge, the title stays left, and re-injection is idempotent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…elect Two cleanups in the injection engine, no behavior change: Performance — the MutationObserver runs runAll() on every mutation frame across the whole admin page, and runAll re-queried the DOM each time even when everything was already injected: - Per-section loop now skips its DOM queries once a section's badge, actions, and button slots are all connected (the common steady state). If <Navigator> removes the screen, the containers disconnect and we fall through to re-inject, so correctness is preserved. - The block-modal block name (which reads the block registry and scans all block types) is only resolved when the modal is open AND its slots are not already mounted — instead of on every frame while the modal is open. Cleanup — getBlockNameFromModal used the global `wp.data`; switch to the imported `select` from `@wordpress/data` for consistency (also clears the no-undef lint). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Hey @fditrapani 👋 - quick design question on the Content Guidelines AI banner (the "Generate your guidelines in seconds" card). What's the expected behavior:
I built it as 1 (persisted so it doesn't come back once dismissed), but want to confirm that's what you intended. Also, how should "dismissed" be scoped?
|
|
Thanks for the ping @aagam-shah. #1 is exactly how I envisioned it to work. Glad that you went in that direction. I think the per-user approach makes most sense. Once you understand how it works, there's no need to see it again. |
|
Sure, @fditrapani - I’ll handle the per-user part in a separate PR, since this one is already fairly large. |
The header "Suggest all" button mounts into the page header, which renders during Gutenberg's loading spinner — before fetchGuidelines() resolves and populates the core/guidelines store. On a site with existing guidelines that meant the label briefly read "Generate guidelines" (empty default store) and then switched to "Improve guidelines" once data loaded. Gate the header injection on the guideline list being present. Gutenberg renders `.guidelines__list` only after the fetch resolves (a spinner shows until then), so the button now mounts with the store already populated and shows the correct label on first paint. Per-section buttons already mount inside the list, so they were unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| function jetpack_content_guidelines_ai_is_automattician() { | ||
| // Allow access via query parameter. | ||
| // phpcs:ignore WordPress.Security.NonceVerification.Recommended | ||
| if ( isset( $_GET['enable_ai_generation'] ) && 'true' === $_GET['enable_ai_generation'] ) { |
There was a problem hiding this comment.
Just a reminder that we should probably remove this
…es-plugin-extensibility
- Treat a generate response with no usable suggestions as a failure: throw so it routes through the existing catch and surfaces the error notice, instead of completing silently. Applied to the all-sections, per-section, and per-block generate paths for consistency. - REST proxy: guard against a malformed upstream JSON body — return a WP_Error (502) when json_decode fails, and make the non-200 message/code extraction null-safe. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the `?enable_ai_generation=true` access override (a temporary internal-testing escape hatch). The gate is now strictly the Automattician check (Simple: wpcom_is_proxied_request() + is_automattician(); Atomic: Visitor::is_automattician_feature_flags_only()), so it can't be flipped on by anyone appending a query param. The associated phpcs:ignore for the $_GET access is no longer needed and is removed too. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…es-plugin-extensibility
…changes In create mode, the block name was resolved once at first injection and frozen as a prop — inject() short-circuits while containers are connected, so switching the combobox (e.g. Paragraph to Heading) kept generating and accepting suggestions for the previously selected block. Now runAll() resolves the block name on every pass while the modal is open and tears down the block slots when it changes, re-injecting with the new name. Unmounting also clears the stale suggestion from the store, so a diff generated for the previous block can't be accepted into the new one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Switch the enqueue to Assets::register_script() with a textdomain so wp_set_script_translations() is called for the bundle — JS strings were previously never translated. The same call reads dependencies from the asset file, enqueues the CSS with its .rtl.css variant, and derives style dependencies, replacing the hand-rolled fallback deps array (which listed the nonexistent wp-ui handle). Bail early when build artifacts are missing. Also enqueue the Tracks client (w.js) explicitly via Tracking::register_tracks_functions_scripts() instead of relying on whichever wpcom platform widgets happen to inject it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…es-plugin-extensibility
…er-user Resolves add/add conflicts caused by #47959 (Content Guidelines AI base feature) landing on trunk as a squash, which removed the shared history with this branch's lineage. Resolution: - store.js: kept this branch's per-user dismissal (apiFetch + preloaded global), dropping trunk's localStorage approach — the point of this PR. - content-guidelines-ai.php: took trunk's Assets::register_script + Tracks enqueue and the removal of the ?enable_ai_generation bypass; re-applied this branch's banner-dismissed preload (wp_add_inline_script) on top. - block-suggestion-buttons.jsx, section-generate-button.jsx, use-generate-all.js, inject.js, agent-guidelines-ai endpoint: took trunk's canonical #47959 versions (this PR never intended to change them). Net result vs trunk: only the per-user banner-dismissal changes + the new guidelines-banner-dismissed endpoint remain. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>









Summary
Adds AI-powered guideline generation to the WordPress Content Guidelines admin page via Jetpack. Supports both section-level (site, copy, images, additional) and block-level (per-block) guidelines with word-level diffs for review.
What it does
Section-level AI
localStorage. The header button stays hidden while the banner is visible.Block-level AI
Plan gating
useAiFeature()hook from@automattic/jetpack-ai-client— checkshasFeatureto gate to Jetpack AI / Complete plans only (no free tier access)useAICheckout()provides checkout URL for upgrade CTA!hasFeatureAutomattician gate
wpcom_is_proxied_request()+is_automattician()Visitor::is_automattician_feature_flags_only()Shared components
lib/dom.js$cg-green,$cg-green-light,$cg-diff-added,$cg-diff-removedTracking (Tracks events)
jetpack_ai_guidelines_generate— section/block generate or improve (type,slug,action)jetpack_ai_guidelines_generate_all— global suggest all (action)jetpack_ai_guidelines_accept— accept a suggestion (type,slug)jetpack_ai_guidelines_dismiss— dismiss a suggestion (type,slug)jetpack_ai_upgrade_button— reuses existing event withplacement: "content-guidelines"API proxy
wpcom/v2/jetpack-ai/suggest-guidelinescategoriesobject through to wpcom (sections as top-level keys, blocks undercategories.blocks)Architecture
Uses DOM injection — Jetpack enqueues a standalone JS+CSS bundle on
settings_page_guidelines-wp-admin, uses MutationObserver to inject React components into Gutenberg's Content Guidelines page, and shares state viawp.datastores (jetpack/content-guidelines-aifor suggestions,wordpress-com/plansfor plan detection).Related
Testing instructions
pnpm jetpack build plugins/jetpackTesting on an Atomic site:
Out of the box this won't work on Atomic during the internal rollout: the wpcom endpoint's development gate requires an Automattician user, but the proxy authenticates with a blog token (no user attached) — so generation fails with
401 rest_forbiddeneven on a site with an AI plan (#47959 (comment)). The UI's Automattician gate also only passes when browsing through the proxy. Apply the patch below to test.🩹 Patch for testing on Atomic (click to expand)
What it does:
?enable_ai_generation=truequery-param bypass for the Automattician UI gate (removed from the shipped code in fc8d4b7).wpcom_json_api_request_as_blogtowpcom_json_api_request_as_user, so the request is authenticated as your connected WordPress.com user and passes the wpcom-side Automattician gate. Your a11n WordPress.com account must be connected to Jetpack on the site for this to work.SSH into the site and run this from the plugin directory (
jetpack-devwhen installed via Jetpack Beta,jetpackotherwise):Then open Settings → Guidelines with the query param appended:
/wp-admin/options-general.php?page=guidelines-wp-admin&enable_ai_generation=trueTo undo, re-run the same
patchcommand with-Radded.Section-level:
4. Verify the onboarding banner with "Get started" button (shown on first visit until dismissed or used once; clear the
jetpack_content_guidelines_banner_dismissedlocalStorage key to see it again)5. Click "Get started" — shimmer on textareas, spinners in headers
6. After generation: diff view with Accept / Dismiss
7. Accept — content moves to textarea. Dismiss — suggestion removed.
8. Navigate to history and back — injections re-appear
Block-level:
9. Open Blocks section, click Edit on a block
10. Click "Improve guidelines" in modal — shimmer on textarea
11. Diff appears, Accept/Dismiss below. Click-to-accept on diff works.
Plan gating:
12. On a site without Jetpack AI plan: upgrade notice shown, buttons disabled
13. On a site with Jetpack AI/Complete: full access
Does this pull request change what data or activity we track or use?
Yes. This PR adds Tracks events for guideline generation, acceptance, dismissal, and upgrade clicks (see Tracking section above). It also calls the Jetpack AI suggest-guidelines endpoint when users explicitly click generate/improve buttons.