Breakdance version: 2.7.0
Last known working version: 2.6.1
Theme: Blocksy 2.1.38 (but this affects any theme that uses the standard WordPress template lifecycle)
Host: Pantheon (not host-specific)
"Disable Theme" setting: Off
Summary
In Breakdance 2.7, when the "Disable Theme" option is off and a non-Breakdance theme template handles the request (i.e. maybe_override_the_theme_with_a_breakdance_template() returns the original $file_to_include), Breakdance's frontend asset and <script> tags are not output on the page. The generated CSS files exist on disk and are directly accessible, but no tags are emitted into the HTML, so the frontend renders unstyled.
Setting "Disable Theme" to on, or switching the page template to "Breakdance (no header/footer)", works around the issue — any path where Breakdance's template-simulator renders the page directly is fine.
Steps to reproduce
Install Breakdance 2.7.0 on a site using a standard WordPress theme (reproduced with Blocksy; expected to reproduce with any theme that renders via get_header(); while (have_posts()) { the_post(); the_content(); } get_footer(); — i.e. nearly all themes).
Ensure "Disable Theme" in Breakdance → Settings → Theme is off.
Build a page with Breakdance. Confirm the generated CSS exists at wp-content/uploads/breakdance/css/post-{ID}.css and loads fine when accessed directly by URL.
Visit the page on the frontend.
View source.
Expected
tags for the global Breakdance CSS (variables.css, global-settings.css, selectors.css, elements.css, etc.) and per-post CSS (post-{ID}.css, post-{ID}-defaults.css) are present in . Breakdance JS dependencies (Swiper, etc.) are present before .
Actual
No Breakdance asset tags are emitted. Only selectors.css and global-settings.css appear in some configurations; the rest (including per-post CSS and all JS dependencies) are missing entirely. The post-{ID} filename does not appear anywhere in the page source.
Root cause
In 2.6.1, plugin/render/capture-wordpress-output.php used a placeholder-and-replace mechanism:
registerAssetPlaceholdersInHeaderAndFooter() hooked wp_head / wp_footer to emit literal placeholder strings (%%BREAKDANCE_HEADER_DEPENDENCIES%% / %%BREAKDANCE_FOOTER_DEPENDENCIES%%).
getWordPressHtmlOutputWithHeaderAndFooterDependenciesAddedAndDisplayIt() buffered the entire template output with ob_start(), then ran renderHtmlFromScriptAndStyleHolder() after the_content had fired and populated ScriptAndStyleHolder, then str_replace()'d the placeholders with the rendered asset HTML.
In 2.7.0, this mechanism was removed. loadAssetsInHeaderAndFooter() now hooks wp_head / wp_footer directly and outputs $assets['headerHtml'] / $assets['footerHtml'] at hook time. But ScriptAndStyleHolder is populated inside \Breakdance\Render\getRenderedPost(), which is reached via replace_the_content_with_breakdance_content() in plugin/actions_filters/the_content.php — i.e. through the the_content filter. In the standard WordPress template lifecycle, wp_head fires before the_content, so at the moment 2.7's wp_head callback runs, the holder is empty and renderHtmlFromScriptAndStyleHolder() returns effectively empty asset HTML.
The 2.6 buffer-and-replace worked around this timing by deferring the replace to after the full render. 2.7 no longer does.
Why template-simulator paths still work
Paths like breakdance-no-template.php, breakdance-blank-canvas.php, and breakdance-template.php call \Breakdance\Render\render($post->ID) explicitly before wp_head() runs (see e.g. the position in plugin/themeless/util.php). So on those paths the holder is populated in time and 2.7's direct-output approach works. The regression only manifests when a theme's standard get_header(); the_content(); get_footer(); flow is used.
Relevant files / diff points
plugin/render/capture-wordpress-output.php — diff between 2.6.1 and 2.7.0 shows removal of BREAKDANCE_HEADER_ASSETS_PLACEHOLDER / BREAKDANCE_FOOTER_ASSETS_PLACEHOLDER constants, removal of getWordPressHtmlOutputWithHeaderAndFooterDependenciesAddedAndDisplayIt(), and rename of registerAssetPlaceholdersInHeaderAndFooter() → loadAssetsInHeaderAndFooter() with direct output.
plugin/actions_filters/template_include.php — loadAssetsInHeaderAndFooter() is now called but no longer wraps the template output.
plugin/actions_filters/the_content.php — replace_the_content_with_breakdance_content at priority -2147483648 is the only thing populating ScriptAndStyleHolder when a theme handles rendering, and it fires after wp_head.
Suggested fix
Either:
Restore the 2.6 buffer-and-replace mechanism when a theme template (rather than a Breakdance template-simulator) is handling rendering.
Populate ScriptAndStyleHolder earlier — e.g. trigger post CSS registration from template_redirect or wp rather than waiting for the_content — so the direct wp_head output in 2.7 actually has content to emit.
Workaround
Reinstating the 2.6 mechanism in a child theme / mu-plugin fixes the issue:
add_action('template_redirect', function () {
if (is_admin() || is_feed() || is_robots() || is_trackback()) return;
if (! function_exists('Breakdance\\Render\\renderHtmlFromScriptAndStyleHolder')) return;
$h = '%%BD_FIX_HEADER_ASSETS%%';
$f = '%%BD_FIX_FOOTER_ASSETS%%';
add_action('wp_head', function () use ($h) { echo $h; }, 1000001);
add_action('wp_footer', function () use ($f) { echo $f; }, 1000001);
ob_start(function ($buffer) use ($h, $f) {
if (! class_exists('\Breakdance\Render\ScriptAndStyleHolder')) return $buffer;
$assets = \Breakdance\Render\renderHtmlFromScriptAndStyleHolder(
\Breakdance\Render\ScriptAndStyleHolder::getInstance()
);
return str_replace([$h, $f], [$assets['headerHtml'], $assets['footerHtml']], $buffer);
});
}, 0);
Breakdance version: 2.7.0
Last known working version: 2.6.1
Theme: Blocksy 2.1.38 (but this affects any theme that uses the standard WordPress template lifecycle)
Host: Pantheon (not host-specific)
"Disable Theme" setting: Off
Summary
In Breakdance 2.7, when the "Disable Theme" option is off and a non-Breakdance theme template handles the request (i.e. maybe_override_the_theme_with_a_breakdance_template() returns the original $file_to_include), Breakdance's frontend asset and <script> tags are not output on the page. The generated CSS files exist on disk and are directly accessible, but no tags are emitted into the HTML, so the frontend renders unstyled.
Setting "Disable Theme" to on, or switching the page template to "Breakdance (no header/footer)", works around the issue — any path where Breakdance's template-simulator renders the page directly is fine.
Steps to reproduce
Install Breakdance 2.7.0 on a site using a standard WordPress theme (reproduced with Blocksy; expected to reproduce with any theme that renders via get_header(); while (have_posts()) { the_post(); the_content(); } get_footer(); — i.e. nearly all themes).
Ensure "Disable Theme" in Breakdance → Settings → Theme is off.
Build a page with Breakdance. Confirm the generated CSS exists at wp-content/uploads/breakdance/css/post-{ID}.css and loads fine when accessed directly by URL.
Visit the page on the frontend.
View source.
Expected
tags for the global Breakdance CSS (variables.css, global-settings.css, selectors.css, elements.css, etc.) and per-post CSS (post-{ID}.css, post-{ID}-defaults.css) are present in . Breakdance JS dependencies (Swiper, etc.) are present before .Actual
No Breakdance asset tags are emitted. Only selectors.css and global-settings.css appear in some configurations; the rest (including per-post CSS and all JS dependencies) are missing entirely. The post-{ID} filename does not appear anywhere in the page source.
Root cause
In 2.6.1, plugin/render/capture-wordpress-output.php used a placeholder-and-replace mechanism:
registerAssetPlaceholdersInHeaderAndFooter() hooked wp_head / wp_footer to emit literal placeholder strings (%%BREAKDANCE_HEADER_DEPENDENCIES%% / %%BREAKDANCE_FOOTER_DEPENDENCIES%%).
getWordPressHtmlOutputWithHeaderAndFooterDependenciesAddedAndDisplayIt() buffered the entire template output with ob_start(), then ran renderHtmlFromScriptAndStyleHolder() after the_content had fired and populated ScriptAndStyleHolder, then str_replace()'d the placeholders with the rendered asset HTML.
In 2.7.0, this mechanism was removed. loadAssetsInHeaderAndFooter() now hooks wp_head / wp_footer directly and outputs $assets['headerHtml'] / $assets['footerHtml'] at hook time. But ScriptAndStyleHolder is populated inside \Breakdance\Render\getRenderedPost(), which is reached via replace_the_content_with_breakdance_content() in plugin/actions_filters/the_content.php — i.e. through the the_content filter. In the standard WordPress template lifecycle, wp_head fires before the_content, so at the moment 2.7's wp_head callback runs, the holder is empty and renderHtmlFromScriptAndStyleHolder() returns effectively empty asset HTML.
The 2.6 buffer-and-replace worked around this timing by deferring the replace to after the full render. 2.7 no longer does.
Why template-simulator paths still work
Paths like breakdance-no-template.php, breakdance-blank-canvas.php, and breakdance-template.php call \Breakdance\Render\render($post->ID) explicitly before wp_head() runs (see e.g. the position in plugin/themeless/util.php). So on those paths the holder is populated in time and 2.7's direct-output approach works. The regression only manifests when a theme's standard get_header(); the_content(); get_footer(); flow is used.
Relevant files / diff points
plugin/render/capture-wordpress-output.php — diff between 2.6.1 and 2.7.0 shows removal of BREAKDANCE_HEADER_ASSETS_PLACEHOLDER / BREAKDANCE_FOOTER_ASSETS_PLACEHOLDER constants, removal of getWordPressHtmlOutputWithHeaderAndFooterDependenciesAddedAndDisplayIt(), and rename of registerAssetPlaceholdersInHeaderAndFooter() → loadAssetsInHeaderAndFooter() with direct output.
plugin/actions_filters/template_include.php — loadAssetsInHeaderAndFooter() is now called but no longer wraps the template output.
plugin/actions_filters/the_content.php — replace_the_content_with_breakdance_content at priority -2147483648 is the only thing populating ScriptAndStyleHolder when a theme handles rendering, and it fires after wp_head.
Suggested fix
Either:
Restore the 2.6 buffer-and-replace mechanism when a theme template (rather than a Breakdance template-simulator) is handling rendering.
Populate ScriptAndStyleHolder earlier — e.g. trigger post CSS registration from template_redirect or wp rather than waiting for the_content — so the direct wp_head output in 2.7 actually has content to emit.
Workaround
Reinstating the 2.6 mechanism in a child theme / mu-plugin fixes the issue: