Load the External Product resolver only where a product can appear.
Read full notes3 changes
Product pages, product archives and categories, the store pages, and any post embedding a FluentCart block or shortcode are unaffected.
Its script and stylesheet were enqueued on every request, so about 11KB downloaded and a DOM scan ran on posts, pages, and the home page that never render a FluentCart button.
The decision now runs through the shared storefront check every other module already uses, so External Product also recognises the configured cart, checkout, shop and account pages and FluentCart shortcodes.
FluentCart blocks rendered outside post content, such as template parts and saved page blocks, still get the resolver.
The library shows what each block looks like. Every thumbnail was an empty frame: the preview document was escaped into an HTML attribute by a helper that handles &, < and > but not quotes, so each one was cut off at the quote in…
Read full notes8 changes
The library shows what each block looks like. Every thumbnail was an empty frame: the preview document was escaped into an HTML attribute by a helper that handles &, < and > but not quotes, so each one was cut off at the quote in its own charset tag. Blocks with no markup to render — CSS-only token blocks, PHP-only blocks — say so instead of showing an empty rectangle, and thumbnails render at a desktop width and scale down, so a section built for 1200px shows the layout it actually produces.
Each block reports how many posts and pages place it, counted across both the editor block and the shortcode. That is the number that tells you whether a block is safe to delete. Counted in a single pass and cached, so a library of two hundred blocks does not mean two hundred table scans to draw one screen.
Bulk selection with duplicate, trash, restore and delete forever; sorting by recently updated, title, most used or least used; and a grid/list switch that remembers which you prefer. The shortcode is now a chip you can see and click to copy rather than an action that copied it silently.
Import in Page Settings is two buttons, add or replace, instead of one dialog whose Cancel meant "replace every section". Only replace confirms, and it says how many blocks the builder cannot rebuild are about to go.
A Build button in the block editor saves the page and opens it in the visual builder. The builder reads from the database, so saving first is what keeps unsaved editor changes from being silently absent.
The library and settings screens now follow the admin around them: WordPress' own status filters, search box, view switcher and row actions, real section headings instead of headings faked with table cells, and no inline styles left in the settings template.
Fixed block toolbar buttons were two different heights, 26px and 29.6px, because one of them was never given a height — it inherited whatever the editor's line-height produced, and WordPress 7 changed that. Both are pinned now, and the icons are 18px rather than 14px.
Fixed the settings screen still documented the old md_ filter name for preview injection. It shows gt_page_blocks_builder_preview_injection.
Fixed: the section panel vanished off the right edge once the AI assistant had filled the code editors. Nothing was hiding it — the builder had grown wider than the window, and the panel is the last column of that row. The code area was…
Read full notes4 changes
Fixed the section panel vanished off the right edge once the AI assistant had filled the code editors. Nothing was hiding it — the builder had grown wider than the window, and the panel is the last column of that row. The code area was reporting its own content width as a minimum, which with the assistant open kept the whole builder above 1552px; a 14-inch laptop is 1512. It now fits from 1920px down to 1000px with the panel fully visible, and the tag-snippet toolbar scrolls inside its own bar instead.
Import in Page Settings is two buttons, Import & add and Import & replace. It was one button behind a dialog whose Cancel meant "replace every section" — the destructive choice sitting on the dismissive button. Only replace asks for confirmation, and it says how many blocks the builder cannot rebuild are about to be removed.
Page Settings opens with a count of what is on the page, reports the result of an import or export inline instead of through a browser alert, and stays open while you work. Exported files are named after the page rather than its post ID.
The AI model list keeps only the GPT-5.6 family: Sol, Terra and Luna. A site still set to an older model falls back to the default rather than sending one the API would reject.
The two builder filters and the four helper functions now carry the gt_ prefix, matching everything else the plugin exposes: gt_page_blocks_builder_preview_injection, gt_page_blocks_builder_post_types, gt_page_blocks_builder_url()…
Read full notes3 changes
The two builder filters and the four helper functions now carry the gt_ prefix, matching everything else the plugin exposes: gt_page_blocks_builder_preview_injection, gt_page_blocks_builder_post_types, gt_page_blocks_builder_url(), gt_page_blocks_builder_post_types(), gt_page_blocks_builder_nonce_action() and gt_page_blocks_preview_nonce_action().
Every old md_-prefixed name still works. The filters run immediately before their replacements, so the current one has the last word, and the functions delegate to theirs. Each points at its replacement under WP_DEBUG while in use and is silent otherwise, so existing snippets keep working and nobody gets a notice for a hook they never touched.
gt_page_blocks_builder_nonce_action() still returns its original string. It identifies nonces already issued into open builder tabs and saved URLs, and renaming the value would invalidate them for nothing.
Page settings live in one dialog. Title, slug, template, import and export are page-level rather than section-level, so they sit together behind one button instead of being spread across the top bar and the section panel. The slug shows the…
Read full notes10 changes
Page settings live in one dialog. Title, slug, template, import and export are page-level rather than section-level, so they sit together behind one button instead of being spread across the top bar and the section panel. The slug shows the permalink it will produce, and WordPress' own sanitised result is adopted after saving rather than what was typed.
Sections get an id. Where the outermost element has none, the builder writes one in — an id you wrote yourself is never touched. The edit is made on the opening tag as text rather than by reparsing the markup, so quote style, self-closing tags and indentation survive, a > inside an attribute value does not end the tag early, and data-id= is not mistaken for id=. Sections named after a generated id are listed by their first ten characters of text instead.
Every section drags, from anywhere on its row. Reordering was HTML5 drag-and-drop on a row covered by buttons, and a mousedown on a form control does not start its draggable ancestor's drag — in practice only a 14px grip worked. Locked and linked sections reorder too, a drop can land past the last row, and the drop point is shown while dragging.
Clicking anything in the preview selects the section it belongs to, without disturbing the inline edit the same click opens.
The AI assistant applies its own answers. A reply lands in the section and the preview updates, rather than arriving as code to copy into the editor beside it; bundled CSS and JS are unpacked into their own panes, so one prompt can fill all three. Undo restores the section exactly. A "Whole page" toggle sends every section's code with the prompt, so generated markup matches the class names, spacing and variables already on the page.
Latest AI models, and one list behind them. The model list was duplicated in four places and had already drifted — the registered default and the request-path fallback named different models, so a saved choice could be silently replaced. GPT-5.6 Luna is the new default, alongside the Claude 5 family.
Locked blocks can be deleted from the section panel. Locked means the builder will not rewrite a block's markup, not that the block has to stay on the page. It asks first, and the save guard that refuses to drop blocks the builder cannot rebuild stays exactly as strict. Their empty code panes are hidden.
The builder keeps its own palette on any theme. It renders on a front-end route, so the active theme's stylesheet loads beside it — and themes style bare select, input, textarea, pre and details under a [data-theme] ancestor, which outranked the builder's own rules. Controls were wearing the theme's dark palette while the chrome around them stayed light, a tiled dropdown arrow filled the model picker with black triangles, and stray label and textarea margins pushed controls off their rows. The preview iframe had the mirror-image problem: a transparent body took the operating system's dark canvas while the theme supplied its light-mode text.
Detach a copy is a real button again, with a broken-link icon. It carried two class names this plugin has never defined and fell through to the browser's default.
Plainer names in the code toolbar: wpauto is Auto-format, PHP is Run PHP, and the bare "JS:" prefix is a Script label. The AI composer sends on Enter, so its button is an arrow.
Previews are now mounted only while within 800px of the viewport and unmounted beyond it. On a 12-block page that means at most 3 frames live instead of 12, and 75 stylesheets instead of 300. A holder element retains each frame's measured height whether or not it is mounted, so unmounting never collapses a block or moves the scroll position.
Read full notes2 changes
Preview fidelity is unchanged: a mounted frame still loads the full theme stylesheet set and theme.json global styles.
Fixed
Scrolling was slow, or stalled entirely, while blocks showed their visual preview. Every Page Block in preview mode is a separate document carrying the whole theme stylesheet set — measured at roughly 25 stylesheets and 2,200 CSS rules to style about 30 elements, duplicated per block. A page of eight blocks therefore kept around 200 stylesheets and 17,600 rules alive at once, each in its own compositing context, and scrolling paid for all of it. Switching a block to code mode destroyed its frame, which is why code mode always felt fine.
Code editors were unusable inside the block canvas. wp.codeEditor loads into the admin document, but the block canvas has been an iframe since WP 6.3. A CodeMirror mounted there resolves focus and key events against the outer document, so…
Read full notes6 changes
Block editor UI shipped unstyled. Styles enqueued for the editor only reach the outer admin document, so the block's own chrome had no CSS and the device-preview controls rendered as blank squares with no dashicons. The stylesheet is now registered as the block's editor_style, which is what gets it into the canvas.
Inline block CSS could be silently dropped. Whether a block emitted its <style> was gated on a request-global flag raised by any block on the queried post. A block rendered from a widget, synced pattern, or template part was silenced by a flag raised on another block's behalf. CSS is now tracked per block.
minify_css corrupted quoted strings and math functions.[style*="font-weight: 300"] and [style*="font-weight:300"] are different selectors; collapsing the space merged them. Separately, clamp(6.75rem, 6rem + 2.2vw, 9rem) requires the spaces around +, while the sibling combinator requires collapsing them. Both are now handled.
Fixed: Code editors were unusable inside the block canvas.wp.codeEditor loads into the admin document, but the block canvas has been an iframe since WP 6.3. A CodeMirror mounted there resolves focus and key events against the outer document, so it rendered correctly and could not be clicked into or typed in. The plain textarea is now used there, as core's Custom HTML block does. Tab in that textarea also silently discarded its indent; it now commits properly.
Changed
Previews load theme.json global styles alongside every theme stylesheet, so var() resolves as it does on the front end.
The CSS editor suggests the custom properties the active theme defines, harvested from its stylesheets and theme.json.
The block editor chrome now follows the WordPress admin: its palette, 2px radii, system font, and standard button treatment.
The format toggle applies wpautop(), matching its label, instead of running the whole the_content filter chain from inside a block that is itself rendering during the_content.
Uninstalling with data removal enabled deleted this plugin's options and tables but never touched the filesystem, so wp-content/cache/gt-performance/ survived in full: cached HTML, generated CSS and JavaScript, logs, and both configuration…
Read full notes2 changes
Fixed
Uninstalling with data removal enabled deleted this plugin's options and tables but never touched the filesystem, so wp-content/cache/gt-performance/ survived in full: cached HTML, generated CSS and JavaScript, logs, and both configuration files. redis-config.json.php holds a host, username, and password. The guard kept those unreadable over HTTP, but someone who asked for their data to be removed should not be left with credentials in wp-content. Uninstall now removes the directory, resolving the path the way Core\Paths does and confirming with realpath() that it still sits inside wp-content before deleting anything. wp-content/cache itself is left for other plugins.
All upgrade compatibility carried since 1.0.1. DropinRuntime::serve() no longer loads ConfigFile on behalf of a drop-in published before 1.0.1, Settings::compile() no longer deletes the configuration files those releases wrote…
Read full notes4 changes
Removed
All upgrade compatibility carried since 1.0.1. DropinRuntime::serve() no longer loads ConfigFile on behalf of a drop-in published before 1.0.1, Settings::compile() no longer deletes the configuration files those releases wrote, Database::install() no longer drops their tables, and uninstall.php no longer lists their names.
Upgrade note
A site running 1.0.0 or earlier still has that release's generated advanced-cache.php on disk. It loads a fixed list of runtime files that predates ConfigFile, so on the first request after this update it raises a fatal from wp-settings.php, before WordPress can catch it, taking the front end and wp-admin down together. Replace the drop-in before or during the update. The build distributed from gauravtiwari.org carries a migrator that does this automatically; for any other route, run the standalone migration snippet first:
The License screen's Activate, Deactivate, and Check buttons returned a blank page in the store build. Identical cause to the controls fixed in 1.0.2 - the handlers were still registered as admin_post_gtp_license_* while the buttons…
Read full notes3 changes
Fixed
The License screen's Activate, Deactivate, and Check buttons returned a blank page in the store build. Identical cause to the controls fixed in 1.0.2 - the handlers were still registered as admin_post_gtp_license_* while the buttons submitted gtperf_license_* - in a file the 1.0.2 sweep did not reach. The WordPress.org build has no licensing code and was never affected.
AdminActionWiringTest now discovers every PHP file under src/ instead of checking a hardcoded list of four. The hardcoded list was the same mistake the test exists to catch: it could not see the licensing module, which only ships in the store build, so 1.0.2 shipped believing the wiring was fully verified.
Every admin control in 1.0.1 returned a blank page. The 1.0.1 rename moved the action names the controls submit from gtp_ to gtperf_, but left all 21 add_action( 'admin_post_gtp_...' ) and add_action( 'wp_ajax_gtp_...' ) registrations…
Read full notes5 changes
Fixed
Every admin control in 1.0.1 returned a blank page. The 1.0.1 rename moved the action names the controls submit from gtp_ to gtperf_, but left all 21 add_action( 'admin_post_gtp_...' ) and add_action( 'wp_ajax_gtp_...' ) registrations untouched, so nothing was hooked to the names being submitted. WordPress does not error in that case: it fires an action with no listeners and exits, which the browser renders as an empty response and which leaves no trace in the error log. Purge, Cloudflare connect/sync/preview/diagnose/token, Redis test and install, page-cache drop-in install, xCloud refresh, purge verification, Commerce Safety Lab, CSS training and regeneration, Fleet export and import, database cleanup, the admin-bar quick actions, the CSS report poll, and the Private Islands fragment endpoint were all dead.
The rename missed these because it matched \bgtp_, and in admin_post_gtp_purge the gtp_ is preceded by an underscore, which is a word character, so the boundary never applied. Hook strings are the one place that flaw could hide, and nothing compared the two sides.
Added
AdminActionWiringTest asserts that every action an admin control submits, every admin-bar action, and every AJAX action posted by the bundled JavaScript has a matching handler registered, and that no hook is registered under the retired prefix. A silent-blank-page regression of this shape now fails the test suite.