Repository navigation
fix: avoid a duplicate H1 when a hidden title's page provides one elsewhere - #4658
HardeepAsrani wants to merge 4 commits into
Conversation
…ewhere The screen-reader H1 fallback only scanned post_content, so an H1 from a synced pattern, a page builder or a Neve Pro custom layout produced a second H1. The main area is now buffered and the fallback is kept only when no other H1 rendered. Fixes #4652 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes requested
The fix scans only the main area, so a custom layout H1 in the header or footer still gives two H1s. The H1 check, three tests and one type also need fixes.
Validation details
- Files reviewed: 5/5 changed files. Unchecked history rows: Footer Menu duplicates
footer-menuID across responsive footer layouts, Page title disappears on post category pages, Accessibility changes to match EU accessibility requirements (EN 301 549) that become mandatory in June 2025, Submenu toggle does not open through screen-reader activation, Page Title visible, even when it's disabled from Neve options, Issue with archive title, Featured post section enabled makes page titles disappear, Pass TA guidelines. - The PR's PHPUnit class passes on HEAD with PHP 8.3 and WordPress 7.1.3:
mkdir -p /workspace/checks/R/head && cd /workspace/checks/R/head && rm -rf neve && cp -a /repo neve && cd neve && set -o…printedOK (8 tests, 16 assertions)(run R2). - The same class on the base tree:
cd /workspace/checks/R && rm -rf basetree && mkdir basetree && cp -a /repo basetree/neve && cd basetree/neve && for f in…printedTests: 8, Assertions: 11, Errors: 2, Failures: 2.(run R4) Only the two hooked-H1 tests fail there. - An H1 on a header or footer hook still duplicates: `mkdir -p /workspace/checks/K; cat > /workspace/checks/K/test-zz-review.php <<'EOF'
Untested areas
- The e2e spec did not run. The sandbox has no built assets or served site. - Neve Pro was not available. Its custom layout rendering was not exercised. - These history rows were not re-verified by a dedicated check: Footer Menu duplicates `footer-menu` ID across responsive footer layouts, Page title disappears on post category pages, Accessibility changes to match EU accessibility requirements (EN 301 549) that become mandatory in June 2025, Submenu toggle does not open through screen-reader activation. These rows were checked only with ad-hoc unit runs: Page Title visible, even when it's disabled from Neve options, Issue with archive title, Featured post section enabled makes page titles disappear, Pass TA guidelines.Start at wp_body_open and resolve at wp_footer so header and footer custom layouts count, ignore H1s inside HTML comments, and strip scripts before comments so a lone "<!--" in a script cannot swallow real markup. Tests now cover header/footer hooks, patterns, comments, and each fails on the base tree. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes requested
The earlier findings are fixed, but an H1 on the body start or body end hook still gets a second H1. Fix that, one stale comment and two test issues.
Validation details
- Files reviewed: 5/5 changed files. Unchecked rows: Optional setting off: Producer, Setting unset, Override, Stale data.
- The PR's PHPUnit class passes on HEAD with PHP 8.3 and WordPress 7.1.3:
grep -E "^[0-9]+\) |Error: |Tests:" /tmp/sandbox-phpunit-ab.zVTgcX/base.log; mkdir -p /workspace/checks/R/head && cd /wo…printedOK (10 tests, 26 assertions)(run R1). - On the base tree, eight tests fail on their assertions:
cd /workspace/checks/R && rm -rf basetree && mkdir basetree && cp -a /repo basetree/neve && cd basetree/neve && for f in…printedTests: 10, Assertions: 9, Errors: 2, Failures: 8.(run R2) - Real template renders give one H1 for header and footer hooks:
cd /workspace/checks/R/head/neve && phpunit --do-not-cache-result --filter 'TestZzReview::test_real_page with data set "…printed[after_header_hook] h1=1 sr=0(run R20),cd /workspace/checks/R/head/neve && phpunit --do-not-cache-result --filter 'TestZzReview::test_real_page with data set "…printed[before_footer_hook] h1=1 sr=0(run R21). - The body start hook still duplicates:
cd /workspace/checks/R/head/neve && phpunit --do-not-cache-result --filter 'TestZzReview::test_real_page with data set "…printed[body_start_after] h1=2 sr=1(run R15).
Untested areas
- The e2e spec did not run. The theme needs built assets before a served site loads it.
- Neve Pro was not available. Its custom layout hook list was not read.
test_unbuffered_path_keeps_content_scanerrors on the base tree on a missing method. Its base result is inconclusive.- These setting rows were not settled: Optional setting off: Producer, Setting unset, Override, Stale data.
🤖 Automated review · run code-review-agent_6ad1ce22a74bb3.68194372.
🤖 Review agent — review posted ✅ on 937dfe56 · changes requested · 4 findings · 46 min
Run code-review-agent_6ad1ce22a74bb3.68194372 · trail
Open on neve_body_start_after (wp_body_open as fallback) and resolve on neve_body_end_before so custom layouts on the body start and end hooks count. Tests: one data-provider case per body hook, a stray open buffer, the wp_body_open fallback, and a placement test that fails on the base tree. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes requested
The earlier findings are fixed. Three test issues remain: one order-dependent test, one weak assertion and one duplicated helper body.
Validation details
- Files reviewed: 5/5 changed files. Unchecked rows: Optional setting off: Producer, Setting unset, Override, Empty data.
- The PHPUnit class ran on HEAD with PHP 8.3 and WordPress test library
mkdir -p /workspace/checks/R/head /workspace/checks/R/html /workspace/checks/T && cd /workspace/checks/R/head && rm -rf …printedTests: 14, Assertions: 36, Failures: 1.(run R2) - With
kses_remove_filters()added, it passescd /workspace/checks/R/head/neve && cp /workspace/checks/R/orig-test.php tests/test-neve-hidden-title-h1.php && sed -i '…printedOK (14 tests, 38 assertions)(run R6) - On the base tree, twelve tests fail on assertions
cd /workspace/checks/R && rm -rf base && mkdir base && cp -a head/neve base/neve && cp orig-test.php base/neve/tests/tes…printedTests: 14, Assertions: 14, Errors: 2, Failures: 12.(run R14) - Real template renders give one H1 for the body start hook
cd /workspace/checks/R/head/neve && for ds in body_start_after body_end_before after_header_hook before_footer_hook nest…printed[body_start_after] h1=1 sr=0(run R11)
Untested areas
- The e2e spec did not run. The theme needs built assets.
- Neve Pro was not available. Inside-content layouts were not checked.
- Two tests error on the base tree on missing symbols. Their base result is inconclusive.
- These setting rows were not settled: Optional setting off: Producer, Setting unset, Override, Empty data.
🤖 Automated review · run code-review-agent_6ad1db014396c8.21985341.
🤖 Review agent — review posted ✅ on 121d8f84 · changes requested · 3 findings · 27 min
Run code-review-agent_6ad1db014396c8.21985341 · trail
The bootstrap assigns user 1 directly, so wp_set_current_user() left kses on and stripped the noscript fixture when the class ran alone. Assert the exact buffer level in the stray-buffer case and reuse render_page() for the wp_body_open-only render. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🟢 Approved
The PR passes. The three earlier test findings are fixed, and the new commit adds no problem.
Validation details
- Files reviewed: 5/5 changed files. Unchecked rows: Optional setting off: Producer, Optional setting off: Setting unset, Optional setting off: Override.
- The hidden-title test class passes alone on HEAD with PHP 8.3:
mkdir -p /workspace/checks/R1/head; cp -a /repo /workspace/checks/R1/head/neve; python3 /workspace/checks/R1/record.py R…printedOK (14 tests, 38 assertions)(run R1) - The stray-buffer test fails when the buffer-level guard is deleted:
set -o pipefail; phpunit --do-not-cache-result --filter 'TestNeveHiddenTitleH1::test_stray_open_buffer_is_left_alone' 2>…printedFailed asserting that 3 is identical to 4.(run R2) - Both modified tests fail on the base tree:
python3 /workspace/checks/R1/record.py R103 /workspace/checks/R1/head/neve "sandbox-phpunit-ab.sh /workspace/checks/R1/h…printedFailed asserting that 2 is identical to 1.(run R3) - Product-file checks are reused from run code-review-agent_6ad1db014396c8.21985341, not rerun. Those files did not change since then.
Untested areas
- These setting rows were not settled: Optional setting off: Producer, Optional setting off: Setting unset, Optional setting off: Override.
- The e2e spec did not run. The theme needs built assets.
- Neve Pro was not available. Inside-content layouts were not checked.
🤖 Automated review · run code-review-agent_6ad2132037a466.47610119.
🤖 Review agent — review posted ✅ on 0830d445 · approved · 0 findings · 25 min
Run code-review-agent_6ad2132037a466.47610119 · trail
Summary
When the page title is hidden,
views/page-header.phpprinted a screen-reader H1 unless it found an<h1in the post's rawpost_content. Anything else that renders an H1 was invisible to that check, so the page ended up with two: Neve Pro custom layouts on hooks or inside the content, synced patterns, page builders that store markup in meta, and every non-singular page (the blog page hides its title by default for sites installed after 4.0).The decision is now deferred.
Page_Headeropens an output buffer on the theme's first body hook,neve_body_start_after(withwp_body_openas a fallback for other templates), when the title is hidden. The view prints a placeholder comment where the title would be, and the buffer callback swaps it for the screen-reader H1 only if the buffered body contains no H1 (script, template and noscript blocks and HTML comments ignored). The buffer closes on the last body hook,neve_body_end_before, so every custom layout hook from body start to body end counts; if that hook never fires, PHP runs the callback at shutdown. When no buffer is active (a template without the body hooks), the previouspost_contentscan still applies.Reproduced on a local site with Neve Pro: hook layout before the loop, hook layout before content, inside-content layout, synced pattern, and the blog page. All produced an extra H1 before and none after. Beaver Builder and Brizy were not available locally; the hooked-H1 unit tests cover the same mechanism.
Will affect visual aspect of the product
NO. The fallback H1 is visually hidden; only its presence changes.
Test instructions
h1.screen-reader-textrenders.neve_before_content,neve_after_header_hookorneve_before_footer_hook, or an inside-content layout: no screen-reader H1.neve_before_loop: one H1.<h1>renders and no placeholder comment appears in the source.Check before Pull Request is ready:
tests/test-neve-hidden-title-h1.php(14 cases including every body hook, each fails on the base tree) and a synced-pattern fixture plus assertion ine2e-tests/specs/a11y-ready/structure.spec.ts.wp_kses_post(); buffered template output is passed through unchanged.Review follow-up
The automated review asked whether the test-name conventions in
CONTRIBUTING.md(lines 246 and 260-263) should be updated, since no existing test follows them. That predates this change and applies to the whole suite, so it is left for a separate docs PR rather than mixed into this fix.Closes #4652.
🤖 Generated with Claude Code