Skip to content

fix: avoid a duplicate H1 when a hidden title's page provides one elsewhere - #4658

Open
HardeepAsrani wants to merge 4 commits into
developmentfrom
fix/hidden-title-duplicate-h1
Open

HardeepAsrani wants to merge 4 commits into
developmentfrom
fix/hidden-title-duplicate-h1

Conversation

@HardeepAsrani

@HardeepAsrani HardeepAsrani commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

Summary

When the page title is hidden, views/page-header.php printed a screen-reader H1 unless it found an <h1 in the post's raw post_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_Header opens an output buffer on the theme's first body hook, neve_body_start_after (with wp_body_open as 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 previous post_content scan 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

  • Hide a page's title (page settings or Customizer) and give it no H1: exactly one h1.screen-reader-text renders.
  • Same page with an H1 block in the content: no screen-reader H1.
  • Put the H1 in a synced pattern instead: no screen-reader H1.
  • With Neve Pro, add a Custom Layout containing an H1 on neve_before_content, neve_after_header_hook or neve_before_footer_hook, or an inside-content layout: no screen-reader H1.
  • Set a static front page and a posts page; visit the posts page with a Custom Layout H1 on neve_before_loop: one H1.
  • Leave the title visible: the normal <h1> renders and no placeholder comment appears in the source.

Check before Pull Request is ready:

  • I have written a test and included it in this PR — 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 in e2e-tests/specs/a11y-ready/structure.spec.ts.
  • I have run all tests and they pass — PHPUnit 154 tests; the three hidden-title e2e tests pass against a local site.
  • The code passes when running the PHP CodeSniffer
  • Code meets WordPress Coding Standards for PHP, HTML, CSS and JS
  • Security and Sanitization requirements have been followed — title still passes through wp_kses_post(); buffered template output is passed through unchanged.
  • I have assigned a reviewer or two to review this PR

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

…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>
@pirate-bot

pirate-bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Plugin build for 0830d44 is ready 🛎️!

@pirate-bot pirate-bot left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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-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, 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… printed OK (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… printed Tests: 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.
🤖 Automated review · run code-review-agent_6ad063fb8f1ba9.24760594. --- **🤖 Review agent** — review posted ✅ on `1f278ccd` · [changes requested · 5 findings](https://github.com//pull/4658#pullrequestreview-5528869504) · 39 min Run `code-review-agent_6ad063fb8f1ba9.24760594` · [trail](https://agents.vertistudio.com/admin/trails/github-pr/Codeinwp-neve-4658)

Comment thread inc/views/page_header.php
Comment thread inc/views/page_header.php
Comment thread tests/test-neve-hidden-title-h1.php
Comment thread tests/test-neve-hidden-title-h1.php Outdated
Comment thread tests/test-neve-hidden-title-h1.php Outdated
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>

@pirate-bot pirate-bot left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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… printed OK (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… printed Tests: 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_scan errors 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

Comment thread inc/views/page_header.php
Comment thread inc/views/page_header.php Outdated
Comment thread inc/views/page_header.php
Comment thread tests/test-neve-hidden-title-h1.php Outdated
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>

@pirate-bot pirate-bot left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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 … printed Tests: 14, Assertions: 36, Failures: 1. (run R2)
  • With kses_remove_filters() added, it passes cd /workspace/checks/R/head/neve && cp /workspace/checks/R/orig-test.php tests/test-neve-hidden-title-h1.php && sed -i '… printed OK (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… printed Tests: 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

Comment thread tests/test-neve-hidden-title-h1.php Outdated
Comment thread tests/test-neve-hidden-title-h1.php Outdated
Comment thread tests/test-neve-hidden-title-h1.php Outdated
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>

@pirate-bot pirate-bot left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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… printed OK (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>… printed Failed 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… printed Failed 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

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants