diff --git a/inc/helpers/aksrt-single-aside.php b/inc/helpers/aksrt-single-aside.php
index 1fc4379b..551bb03a 100644
--- a/inc/helpers/aksrt-single-aside.php
+++ b/inc/helpers/aksrt-single-aside.php
@@ -16,13 +16,10 @@ if (!function_exists('aksrt_single_render_aside')) :
function aksrt_single_render_aside(array $ctx): void {
$post_id = (int) ($ctx['post_id'] ?? get_the_ID());
- if (get_post_type($post_id) !== 'post') {
+ if (get_post_type($post_id) !== 'post' || empty($ctx['show_aside'])) {
return;
}
- // The body site-layout-* class owns visibility. Always render the post
- // sidebar so User-Agent or request-specific context cannot remove widgets
- // from an otherwise two-column article page.
?>
$post_id,
diff --git a/inc/helpers/template-tags/site-layout.php b/inc/helpers/template-tags/site-layout.php
index 65f61298..4014321f 100644
--- a/inc/helpers/template-tags/site-layout.php
+++ b/inc/helpers/template-tags/site-layout.php
@@ -61,9 +61,7 @@ if (!function_exists('aksrt_has_current_sidebar_widgets')):
* sidebar, so any of these zones makes the column meaningful.
*/
function aksrt_has_current_sidebar_widgets(): bool {
- $sidebar_id = function_exists('aksrt_resolve_sidebar_id')
- ? aksrt_resolve_sidebar_id()
- : 'home_sidebar';
+ $sidebar_id = aksrt_resolve_sidebar_id();
$sidebar_ids = array_unique(['all_sidebar_top', $sidebar_id, 'all_sidebar_bottom']);
foreach ($sidebar_ids as $sidebar_id) {
@@ -122,7 +120,7 @@ endif;
if (!function_exists('aksrt_is_show_sidebar')):
/**
- * legacy-strict sidebar visibility (settings + post meta, not widget presence).
+ * Sidebar visibility from settings, post meta, and available article widgets.
*
* Filter: aksrt_is_show_sidebar
*
@@ -148,6 +146,10 @@ function aksrt_is_show_sidebar_filter(): bool {
return false;
}
+ if (is_single() && !aksrt_has_current_sidebar_widgets()) {
+ return false;
+ }
+
// Single / page: post meta can force show/hide (legacy show_layout).
if (is_singular()) {
$layout = aksrt_get_post_show_layout(get_queried_object_id());
@@ -189,8 +191,7 @@ function aksrt_is_show_sidebar_filter(): bool {
}
}
- // Scene switches above already gated; when enabled, show the column
- // (widgets may be empty — matches legacy empty sidebar column policy).
+ // Scene switches above already gated; when enabled, show the column.
if (is_home() || is_front_page()) {
return !empty($cfg['home']);
}
diff --git a/tests/widget-system/post-visual-contract.test.cjs b/tests/widget-system/post-visual-contract.test.cjs
index 9d56bd0f..cf8f3fdc 100644
--- a/tests/widget-system/post-visual-contract.test.cjs
+++ b/tests/widget-system/post-visual-contract.test.cjs
@@ -271,6 +271,16 @@ test('the pinned visual baseline records all source checksums', () => {
}
});
+test('single posts reserve a sidebar column only when its widget stack has content', () => {
+ const layout = read('inc/helpers/template-tags/site-layout.php');
+ const single = read('inc/helpers/aksrt-single.php');
+ const aside = read('inc/helpers/aksrt-single-aside.php');
+
+ assert.match(layout, /if \(is_single\(\) && !aksrt_has_current_sidebar_widgets\(\)\) \{\s*return false;/);
+ assert.match(single, /\$show_aside = get_post_type\(\$post_id\) === 'post'[\s\S]*?&& aksrt_is_show_sidebar\(\);/);
+ assert.match(aside, /get_post_type\(\$post_id\) !== 'post' \|\| empty\(\$ctx\['show_aside'\]\)/);
+});
+
test('article visual matrix pins every breakpoint, state and strict threshold', () => {
const matrix = JSON.parse(read('tests/visual/article-visual-matrix-v1.json'));
assert.deepEqual(matrix.viewports.map((item) => item.width), [1440, 1024, 768, 767, 641, 640, 390]);