Adds a credential converter beside the MCP connection instructions. Enter your WordPress username and a dedicated Application Password, then copy the Base64 token, full Basic Authorization value or complete MCP configuration.
Read full notes
Conversion stays in the browser. The helper accepts grouped Application Passwords and UTF-8 usernames, masks generated tokens and clears sensitive values when inputs change or the page is left. Site Agent does not submit or store converter credentials.
Validation: 24 WordPress integration tests with 106 assertions; 6 converter tests; JavaScript syntax, WordPress coding standards, PHP lint, translation freshness and package checks. Rendered-browser checks covered exact encoding, masking, copy controls and clearing. The official MCP runtime pin and access settings are unchanged.
Site Agent connects MCP-compatible clients directly to WordPress, with ten named tools for content, source inspection, source editing, PHP execution and foreground WP-CLI.
Read full notes
Access starts disabled. Tool groups are independent opt-ins; developer execution is full-privilege access and is not sandboxed.
This public release adds free FluentCart update-license activation, encrypted site-bound credentials, protected package downloads, fresh download URLs for single/bulk upgrades, and extracted plugin identity checks. Licensing controls automatic updates only; all functionality remains available without activation.
The bundled official WordPress MCP Adapter is the upcoming 0.7.0 prerelease at a pinned source commit, with PHP MCP Schema 0.2.0. No Novamira application code, assets or dependencies are included.
Requires WordPress 6.9+ and PHP 8.0+. PHP 8.0, 8.3 and 8.5 CI passed, including 24 integration tests and 106 assertions. Use the complete release ZIP below; it includes the scoped official runtime.
Every field added in 2.12.0 is optional and off when empty. Rendering blocks saved by 2.11.x through the new template gives the same markup with one exception: the price, discount and price note are now wrapped in…
Read full notes53 changes
What's Changed
Fixed
Product Box: the Top Image style cropped the product. The hero was forced into a 16:9 frame with object-fit: cover, so a tall or square product lost its top and bottom, and a very wide one lost its sides. The hero now shows the whole image (contain) at the image's own shape, capped at 420px tall (260px on phones).
Product Box: every image was the full-size original. The template asked for product-box-image (550×550) and product-box-wide (800×450), but extra.php registered those sizes on after_setup_theme and is itself loaded on acf/init, after that hook has fired. The sizes were never generated, so WordPress fell back to the original upload: a 3000px photo went into a 300px column. The side image now uses core's medium_large (768px) and the Top Image large (1024px), both uncropped and with srcset. The dead size registration is removed, so no extra files are generated per upload.
The corner badge no longer overlaps a long title or the new label in the default and No Image styles. Above a contained Top Image, above a stacked side image on phones, and above a No Image box on phones, it gets its own strip instead of covering the product or squeezing the title.
Two phone-width rules never applied: the smaller badge and the smaller price lost to the higher-specificity base selectors. Both now take effect.
The CSS minifier broke descendant selectors that start with a colon.tools/build-assets.php dropped whitespace before every :, so .box :is(*, ::before) shipped as .box:is(*, ::before), which matches the box itself instead of what's inside it. The source files were right and only the .min.css files the plugin actually serves were wrong. Rules that were silently dead in production now apply as written: The space before : is now always kept (it costs a byte in the rare color : red and never changes meaning), and three regression cases cover it.
Added
Image Fit (Display tab): "Show full image" (default) or "Fill & crop". Fill & crop restores the old cropped look: a square frame in the side layout, and an edge-to-edge image in Top Image. Cropping is CSS-only, so the full image is always what's downloaded.
Top Image Frame (Display tab): Auto (the image's own shape), 16:9, 4:3 or 1:1, so several Top Image boxes in a grid line up. With "Show full image" the product sits inside the frame; with "Fill & crop" it fills it.
Highlight Label: a pill above the title ("Best Overall", "Editor's Choice"). Setting it also gives the box an accent border so the pick stands out in a list.
Open Links in New Tab: applies to the title, image and button links, and adds rel="noopener" without duplicating tokens already present.
Title & Image Link Rel: the title link had no rel at all, so an affiliate URL in the title shipped without sponsored. It now takes its own rel value.
Disclosure: optional small print under the buttons for affiliate disclosures.
The rating now shows its number (for example, 4.5) next to the stars. Screen readers get one label on the stars instead of an aria-label on a plain div, which most of them ignore.
The Top Image hero links to the title URL, like the side image always has.
Images served from the media library carry width and height, so the box no longer jumps when the image loads.
Rank: a "#1", "#2" tile beside the title for "best of" lists.
Review Score: your own score as a ring beside the title, out of 10, 5 or 100, with an editable label ("Our score" by default). Screen readers get one label: "Our score: 9.2 out of 10".
Verdict: a one-line "Why it wins" callout above the features, with an editable label.
Spec Chips: up to eight short facts (49", 144Hz) as pills under the title.
"You save" amount: worked out from Original and Current Price and printed in the current price's own format ("$60.57", "₹1,20,000" with Indian grouping, "300,00 €"). It stays hidden rather than guessing when either price isn't a plain amount ("Free", "From $99", "$9.99/mo") or the two use different currencies.
Price Checked On: a date under the price, shown in the site's date format inside a <time> element. Amazon's Associates rules expect displayed prices to carry one.
Perks: up to six icon-and-text items (shipping, returns, warranty, secure, deal, fast, gift, check). The icons are inline SVG, so they don't depend on the theme's icon font.
Amazon Yellow button style.
Emphasize First Button: the first button gets larger and filled with the accent color, and the rest become outlines. Amazon Yellow and Custom buttons keep their own colors.
Arrow on First Button: an arrow that nudges forward on hover or keyboard focus.
Shine on First Button: Off, On hover, or Repeat (a sweep every few seconds, which also works on phones). The shine is its own element, so it can't collide with theme icon classes like md-icon-external that use ::before or ::after.
Box Style: Spotlight: a gradient border, a soft accent tint at the top and an accent glow. On browsers that support relative colors, the gradient's second color is derived from the accent.
Accent Color: one color for the label, rank, score ring, verdict, Spotlight border, primary buttons and discount chip. The text on it is picked automatically for contrast, and only #rgb/#rrggbb values reach the style attribute.
The field group is split into Product, Highlights, Price & Buttons, and Display tabs, and the score and verdict label fields only appear once a score or verdict is entered.
Tests for price parsing across US, Indian and European formats, the savings math, date handling, accent sanitizing and contrast, link attributes and image-size selection. Other tests assert that every new field is optional and defaults to off, and that the product box registers no image sizes of its own.
Compatibility: Every field added in 2.12.0 is optional and off when empty. Rendering blocks saved by 2.11.x through the new template gives the same markup with one exception: the price, discount and price note are now wrapped in .acf-product-box__price-block. All existing class names are unchanged.
Changed
In the bottom section, the description now sits above the price and buttons, which share one row. Before, the price, the price note, the description and the buttons were separate items in one wrapping row, so the price note and description floated between the price and the buttons, and the buttons often wrapped onto a line of their own.
Image frames are transparent. The gray panel behind the side and Top Image pictures is gone, so a product cutout (a transparent PNG or WebP) sits directly on the card, and the divider under the Top Image is gone with it. Images with their own background get 8px rounded corners.
Phone layout: a stacked side image is capped at 220px tall and a Top Image at 260px, so the title and price stay near the top of the screen. Buttons are full width with 44px touch targets and 14px text. Spacing is tighter, and the disclosure is centered under the buttons.
The image zoom on hover only runs on devices that can hover, so tapping on a phone no longer leaves the image stuck zoomed.
Button focus rings use :focus-visible, so they show for keyboard users and not after a mouse click.
Turn on Hide Price Until Login for individual products. Each product gets a "Login to purchase" entry in its FluentCart menu, and the module's "Apply to" setting chooses between those products and all products. Stores that already used the…
Read full notes
Turn on Hide Price Until Login for individual products. Each product gets a "Login to purchase" entry in its FluentCart menu, and the module's "Apply to" setting chooses between those products and all products. Stores that already used the module keep applying it to all products, so updating exposes no price; new setups are opt-in. Buy Now, Add to Cart, and quantity controls are now left out of the page per product, so a shop grid can mix hidden and visible products.
Fixes: guest modal checkout for a login-only product now shows the login prompt instead of the HTTP 500 it returned since 2.6.4, and orders containing a login-only product are refused when the buyer must log in, closing carts created before the product required login. Invoice and existing-order payment links are unaffected.
Validation: 21 per-product checks and the scope migration on MariaDB, 13 store-wide checks on FluentCart 1.6.6, real HTTP tests of Buy Now, modal checkout, and order placement for guests and logged-in customers in both modes, a failing pre-fix run, existing checkout cache-buster, currency, and invoice contract checks, PHP/JavaScript syntax, generated assets, and package integrity.
Keep Hide Price Until Login prices off the page for guests. Product pages, shop grids, product cards, variant data, pricing tables, search results, and the public product API now leave the server with the login prompt instead of the amount…
Read full notes
Keep Hide Price Until Login prices off the page for guests. Product pages, shop grids, product cards, variant data, pricing tables, search results, and the public product API now leave the server with the login prompt instead of the amount. Buy Now and Add to Cart controls are hidden on FluentCart 1.6 markup, and guest instant-checkout links are refused. Unchecked login-link and redirect settings are now respected, and the prompt holds on Multi-Currency stores.
Validation: 13 hide-price checks on FluentCart 1.6.6 (guest and admin rendering, REST, pricing table, instant checkout, signed-in REST without a nonce) with a failing pre-fix run, a guest scan of 9 storefront and API surfaces, existing checkout cache-buster, currency, and invoice contract checks, PHP/JavaScript syntax, generated assets, and package integrity.
Core Tables 0.2.0 adds read-only Google Sheets refresh, indexed datasets up to 10,000 rows, line charts, and simpler table/chart block workflows.
Read full notes5 changes
Requires WordPress 6.9+ and PHP 8.3+. Back up the database before upgrading; version 0.2.0 adds revision indexes and source/job tables while retaining existing datasets and publications. Preserve WordPress AUTH_KEY to retain access to encrypted connections.
The product remains early access. Google Sheets uses a customer-managed service account. Full assistive-technology certification, automated personal-data detection, and unsupported external-cache invalidation are not claimed.
Validation: PHP/static/JavaScript checks, 19 unit tests, 237 integration checks, 10 concurrent save/publication races, and actual Block Editor/public table workflows. The prior 0.1.1 package remains available for rollback.
Delivery verified: GitHub and FluentCart serve the same 203,617-byte ZIP (e7c5c8fcc947e291119f27c36083871351469f2fa00774e6bfd9e3a4b71261cf). The licensed updater returned 0.2.0 with a direct signed R2 package; invalid activation received no package. A real WordPress bulk upgrade from 0.1.1 succeeded with maintenance mode active. The temporary verification license was removed, and the prior 0.1.1 artifact is retained for rollback. All six release CI jobs passed.
Connect a Google Sheet with explicit column mapping, encrypted credentials, draft review, refresh scheduling, pause/resume, cancellation, and retry status.
Search and browse all accessible datasets, restore older revisions, and manage retained history or permanently delete reviewed datasets from Trash.
Use typed filters, rich cells, line/bar charts, and optional public CSV exports.
Choose datasets in a focused dialog, adjust presentation in the native Styles tab, and keep dataset editor actions visible while scrolling.
Pagination and CSV buttons default off. Without pagination, all published rows are displayed. Enabling pagination restores page controls; downloads also require dataset-level permission.
Star Rating votes never saved. Since 2.9.0 the public acf-blocks/v1/ratings route used floatval as the sanitize_callback for rating and initialRating. WordPress calls sanitize callbacks with three arguments ($value, $request, $param), and…
Read full notes21 changes
What's Changed
Fixed
Star Rating votes never saved. Since 2.9.0 the public acf-blocks/v1/ratings route used floatval as the sanitize_callback for rating and initialRating. WordPress calls sanitize callbacks with three arguments ($value, $request, $param), and an internal PHP function given more arguments than it accepts throws ArgumentCountError on PHP 8 (PHP 7 warned and returned null, which the range check then rejected). Every submission failed before reaching storage: a 500 on PHP 8, a 400 on PHP 7. Both arguments now use acf_star_rating_sanitize_float(), which also turns non-numeric input, arrays included, into 0, so the existing 1 to 5 range check answers it with a 400 instead of counting it.
The Star Rating widget had no REST URL on block themes. The script was registered on wp_enqueue_scripts, but a block theme renders its template, and every block in it, before wp_head fires that hook. The render template's wp_localize_script() call hit an unregistered handle and returned false, and a static flag stopped later renders from retrying, so window.acfStarRating was never defined and the submit handler returned without sending anything. Registration and localization now happen once on init, and the template calls the same helper before enqueueing so renders outside the normal hook order are covered too. Classic themes, where wp_enqueue_scripts runs before the content, were only affected by the first bug.
Added
Regression tests: every sanitize callback on the ratings route must be a PHP function that tolerates WordPress's three arguments, and the Star Rating script must be registered and localized on init, with no localization left in the render template.
Commits
2.11.4: fix Star Rating votes on PHP 8 and block themes (8676090)
docs add Buy Me a Coffee support links to README (2ebb43d)
A fix for "Don't cache this page" in optimize-only mode, and admin and CLI text that now matches the screens it describes.
Read full notes4 changes
Fixes
In optimize-only mode, a page marked "Don't cache this page", or one that defines DONOTCACHEPAGE, is now sent no-store as 1.2.0 described. It was left unoptimized but sent no cache headers, so a host cache (LiteSpeed, Hostinger, Kinsta) could store it for its own default lifetime.
The CSS status panel no longer reports "Needs cache setup" in optimize-only mode. Unused-CSS builds run in that mode without the drop-in or WP_CACHE.
cache explain and cache verify accept every hostname --page-url accepts, including aliases added with the gt_performance_canonical_hosts filter. The page-url check let an alias through and the inspector then refused it as another site. The Diagnostics explainer and the explain ability accept the same hostnames.
The language and currency plugin advice names the "Cookie prefixes that bypass cache" field by its label, and the database --all-revisions help names the "Run selected optimization" button.
Works out of the box: a guided setup, optimization for sites whose host already caches pages, and a plugin that cleans up after itself.
Read full notes18 changes
Setup and cache modes
Caching
Cloudflare
Compatibility and diagnostics
A Setup tab walks a fresh install to a verified cached page: AUTH_KEY, OpenSSL, the cache directory, and who owns advanced-cache.php; active page-cache plugins and host caches (from the environment, or from the home page's headers fetched once as a visitor: LiteSpeed, Kinsta, Hostinger, SiteGround, WP Engine, Varnish, Nginx, and others); the cache mode; Cloudflare; store and language plugins; and a verification that requests the home page as a visitor, up to four times two seconds apart, until GT Performance answers HIT. Cloudflare's answer is shown beside the result rather than deciding it, because right after a full purge the edge can keep answering MISS for a minute while the origin already serves HITs. Nothing runs until a button is pressed, and activation does not redirect. The Dashboard points to Setup until verification passes.
Optimize-only mode runs the optimization pipeline on responses the eligibility rules would cache and hands them to the host's cache without storing them. The drop-in is not required, and the compiled configuration tells an installed one never to serve. Bypassed requests start no output buffer. Responses must pass the same checks a stored page must, and warming and stale refresh are skipped. Cache headers for eligible pages are left to the host. A response the eligibility rules would not cache is sent no-store, as in store mode: a shared cache that receives no Cache-Control applies its own default lifetime (two hours at Cloudflare), so staying silent would have shared pages this plugin refused to cache. The mode is chosen in Setup or on the Cache tab and shown on the Dashboard, in the health report, cache status, and the site-status ability.
"Cache each value separately" lists query parameters whose values each get their own stored copy, instead of bypassing the cache as unknown parameters. Values over 100 characters are not cached, and a page holds at most 100 variants; beyond that, new values are served uncached. Each variant is recorded in a per-page index, so purging the page removes every variant at the origin and passes their URLs to the edge purge.
The editor's GT Performance box adds "Don't cache this page" (never stored or optimized, sent no-store, reported by Explain as page-option) and "Use original CSS" (full stylesheets, no unused-CSS build queued).
Query parameters sent as arrays (name[]=) bypass the cache as query_array:<name> and are sent no-store, like never-cache parameters. parse_str() made them arrays and the request context kept only scalars, so ?preview[]=1 or ?s[]=x was judged and keyed as the page with no query, in WordPress and in the drop-in.
A preload that gets a redirect or a 4xx is recorded as skipped (redirect_301, http_404) instead of failing and retrying three times. On a large site, sitemap URLs of moved posts had made up most of the queue's failed jobs. Server errors and transport failures still fail and retry.
A settings save advances the cache generation, which purges the origin and sends purge-everything to Cloudflare, only when a setting that reaches cached pages changed. Credentials, connection status, background work, and admin-only settings are listed as output-neutral; anything else, including any new setting, still purges.
Unused CSS kept a block theme's fluid font sizes. The bundled parser (Sabberworm 9.4 and 9.5) silently drops any declaration with a bare parenthesised group inside a math function, such as clamp(1rem, 1rem + ((1vw - 0.2rem) * 0.196), 1.125rem), which is how WordPress writes every fluid font size; on Twenty Twenty-Five the page title fell from 43.8px to 16px. Such values are now hidden from the parser and restored byte for byte, and a stylesheet whose round trip loses any declaration is left untouched rather than pruned. Builds made before 1.2.0 are not reused after updating.
A price or stock change saved through the store's own tools purges the product, its shop page, and its categories immediately, at the origin and at the edge. Only that purge fires after the new price is written; it used to be queued, so a WooCommerce price change reached visitors only after the next queue run, and Cloudflare could re-store the old price in between. Listings found through recorded dependencies stay queued, since bulk stock syncs touch thousands of products.
Deactivation deletes this site's managed Cache Rule, leaving every other rule, and purges this site's hostnames, best effort and without blocking deactivation. After reactivation the Cloudflare tab says the rule is gone until the next sync. wp gt-performance cloudflare disconnect [--forget] and a Disconnect button do the same and turn the integration off; --forget also deletes the credentials and Zone ID.
Sites that share a Cloudflare zone (example.com and shop.example.com on separate installs) no longer take over each other's cache rule. Every site used one fixed rule ref, and refs are unique per zone, so a second site's sync overwrote the first site's rule and its deactivation deleted it. New rules get a ref per hostname; a rule under the old shared ref still belongs to the site whose host its expression names and keeps that ref when updated, since Cloudflare refuses to change a ref. Conflict detection no longer mistakes a subdomain's rule for this host's.
A full edge purge (a cache-relevant settings save, disconnect, deactivation, cloudflare purge without --page-url) purges this site's hostnames instead of sending purge_everything, which also emptied Cloudflare's cache for every other site and subdomain in the zone. Purge by hostname is available on every plan.
Deactivation also empties the local page store. Nothing purges while the plugin is off, so a page edited in the meantime came back from the store as a HIT on reactivation.
Every sync, including the one Setup runs, purges this site's hostnames after writing the rule. A request that reached Cloudflare in the seconds before a just-deleted rule stopped applying could store the page WordPress sent without the plugin, and recreating the rule served that copy. Callers report a failed purge.
The ruleset backup written on every sync was never read, and restoring it would undo the site owner's later rule changes, so it is no longer written. Uninstall still removes the old option.
The connection check adds two stages that warn rather than fail: whether the site host's DNS records are proxied (falling back to the site's own CF-Ray header when the token cannot read DNS), and whether APO is on. The summary says when a stage needs attention.
WPML, Polylang, TranslatePress, Weglot, WooCommerce Multilingual & Multicurrency, CURCY, FOX (WOOCS), Aelia Currency Switcher, and Price Based on Country are detected. Integrations flags each active one with what to set, and the health report warns while any is active. Nothing is changed automatically.
The health report and cron checks are translatable, CronHealth leaves the server path out of exported reports through a flag instead of string replacement, and languages/gt-performance.pot ships with the plugin.
Fixes found while verifying the documentation against the 1.1.0 code. Each one was reproduced before it was fixed.
Read full notes16 changes
Cache serving
Purging
YouTube previews
Administration and WP-CLI
Removed
The page-cache drop-in never read GTPERF_SAFE_MODE, so already-cached pages kept being served with safe mode on. It now falls through to WordPress, which reports X-GT-Cache: SAFE-MODE.
A cache hit sent only six fixed headers. CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, the cross-origin policies, X-Robots-Tag, Content-Language, and Link headers set in PHP disappeared from cached pages, including a noindex X-Robots-Tag. Capture stores an allowlist of them (at most 20 lines of 8 KB) and the drop-in replays them after re-validating each line. Pages cached before this release get their headers when they are rebuilt; purge once to refresh them all.
Any client could send X-GT-Preload and turn every stale page into a full WordPress render. Preload requests now carry a ten-minute HMAC token derived from AUTH_KEY, which the drop-in verifies without a database.
X-GT-Cache-Reason uses one sanitizer in the drop-in and in WordPress, so path:/cart/ no longer arrives as pathcart or path:cart.
The edge modules load only for admin, cron, AJAX, REST, CLI, and signed-in requests, so a purge from an anonymous request (a visitor's auto-approved comment, a classic WooCommerce checkout that sells a product out) cleared the origin while Cloudflare and xCloud kept serving the old page. The plugin now loads them the moment a purge fires.
WordPress core, plugin, and theme updates purge the cache. Translation updates and plugin installs do not.
--page-url for cache purge|explain|verify and cloudflare purge must be on one of the site's canonical hosts. verify fetched any URL, and purges handed foreign URLs to the edge.
In a theme with responsive embeds, a YouTube embed block showed an empty band the height of the video above the preview. Core reserves the 16:9 box with a padding ::before on the block wrapper and pins the iframe over it; the preview kept its own inline 16:9 box, so it stacked under the reserved one. Its styles now live in one stylesheet in the head, which pins the preview over the wrapper with core's own selector, and nothing is inline except the thumbnail, so a theme's embed wrapper can position it too.
The preview shows YouTube's red play button instead of a browser-styled "Play" button, and a click anywhere on the thumbnail starts the video. Pages cached before this release keep the old preview until they are rebuilt.
Tools → Explain this page is new: wp-admin had no view for it, and the admin-bar link opened Tools with a URL nothing read. The panel shows the decision and reason, the origin copy's state and times, the cache key, and whether Cloudflare agrees, with a link to the page in per-request safe mode. The admin-bar link now encodes the URL and jumps to the panel.
wp gt-performance database run ignored "Scheduled revisions to retain" and deleted every revision, which is how a server cron calling it behaved. It now keeps them; --all-revisions matches the Run cleanup button.
wp gt-performance doctor and health exit 1 when a check fails. Warnings, such as an integration that is not in use, exit 0.
The autosave interval setting never took effect: WordPress defines AUTOSAVE_INTERVAL right after plugins_loaded, and the plugin defined it on init.
The credential-name guard dropped bloat.disable_password_strength_meter from settings exports, read-only abilities, and proposals. A reviewed exception keeps it; any new credential-like key is still hidden.
Corrected the Diagnostic logging description (entries are kept in the database, not a log directory) and the Purge GT cache description (it removes stored pages, not generated assets).
Commerce\PolicyAudit, Optimization\Css\SelectorObservation, and uncalled methods left from removed features.