Skip to content

🐛 Fixed missing newsletter title heading - #29642

Merged
9larsons merged 2 commits into
TryGhost:mainfrom
cyphercodes:fix/newsletter-title-h1
Jul 29, 2026
Merged

🐛 Fixed missing newsletter title heading#29642
9larsons merged 2 commits into
TryGhost:mainfrom
cyphercodes:fix/newsletter-title-h1

Conversation

@cyphercodes

Copy link
Copy Markdown
Contributor

Problem

Newsletter post titles are styled visually as headings, but the rendered email marks them up as a regular link. This hides the top-level document structure from assistive technology and fails WCAG 1.3.1.

Solution

Wrap the post title link in an h1 while resetting the heading's default typography so the existing email-table layout and title styling remain unchanged. Add a focused rendering test that validates the output HTML and semantic title structure.

Fixes #29612

Test plan

  • pnpm exec vitest run test/unit/server/services/email-service/email-renderer.test.js -t 'renders the post title as the top-level heading'
  • pnpm exec eslint test/unit/server/services/email-service/email-renderer.test.js --no-cache
  • git diff --check

Please check your PR against these items:

  • I've read and followed the Contributor Guide
  • I've explained my change
  • I've written an automated test to prove my change works

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The email wrapper now renders the post title link inside a styled <h1> element. Title-specific CSS sets explicit font sizing, line height, weight, and margin. A renderBody unit test validates the generated HTML, heading location, title link text, and concrete font-size styling.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: adding the missing newsletter title heading.
Description check ✅ Passed The description is directly related to the email title heading fix and added test coverage.
Linked Issues check ✅ Passed The PR wraps the newsletter title in an h1, preserves styling, and adds a validating test, matching #29612.
Out of Scope Changes check ✅ Passed The changes stay focused on newsletter title semantics, styling, and test coverage with no obvious unrelated edits.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.45.0)
ghost/core/test/unit/server/services/email-service/email-renderer.test.js

ast-grep timed out on this file


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ghost/core/test/unit/server/services/email-service/email-renderer.test.js`:
- Around line 1565-1577: Update the test case “renders the post title as the
top-level heading” to assert that heading.find('a.post-title-link') contains
exactly one element, while retaining the existing text assertion for “Test
Post”.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0acc42fd-1d2c-4b3b-8429-e0711162f42a

📥 Commits

Reviewing files that changed from the base of the PR and between 3db41d5 and 9913bb3.

📒 Files selected for processing (2)
  • ghost/core/core/server/services/email-rendering/partials/email-wrapper.hbs
  • ghost/core/test/unit/server/services/email-service/email-renderer.test.js

Comment on lines +1565 to +1577
it('renders the post title as the top-level heading', async function () {
const post = createModel(basePost);
const newsletter = createModel(baseNewsletter);

const response = await emailRenderer.renderBody(post, newsletter, null, {});
await validateHtml(response.html);

const $ = cheerio.load(response.html);
const heading = $('.post-title > table td > h1');

assert.equal(heading.length, 1);
assert.equal(heading.find('a.post-title-link').text(), 'Test Post');
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert that the title contains exactly one link.

The text assertion alone does not reject duplicate matching links. Add a cardinality assertion so the test fully validates the intended markup.

Proposed fix
             assert.equal(heading.length, 1);
+            assert.equal(heading.find('a.post-title-link').length, 1);
             assert.equal(heading.find('a.post-title-link').text(), 'Test Post');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('renders the post title as the top-level heading', async function () {
const post = createModel(basePost);
const newsletter = createModel(baseNewsletter);
const response = await emailRenderer.renderBody(post, newsletter, null, {});
await validateHtml(response.html);
const $ = cheerio.load(response.html);
const heading = $('.post-title > table td > h1');
assert.equal(heading.length, 1);
assert.equal(heading.find('a.post-title-link').text(), 'Test Post');
});
it('renders the post title as the top-level heading', async function () {
const post = createModel(basePost);
const newsletter = createModel(baseNewsletter);
const response = await emailRenderer.renderBody(post, newsletter, null, {});
await validateHtml(response.html);
const $ = cheerio.load(response.html);
const heading = $('.post-title > table td > h1');
assert.equal(heading.length, 1);
assert.equal(heading.find('a.post-title-link').length, 1);
assert.equal(heading.find('a.post-title-link').text(), 'Test Post');
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ghost/core/test/unit/server/services/email-service/email-renderer.test.js`
around lines 1565 - 1577, Update the test case “renders the post title as the
top-level heading” to assert that heading.find('a.post-title-link') contains
exactly one element, while retaining the existing text assertion for “Test
Post”.

cyphercodes and others added 2 commits July 28, 2026 14:43
fixes TryGhost#29612

Newsletter titles were styled visually but lacked the semantic top-level heading required to expose their structure to assistive technology.
- the h1 wrapper reset its typography with `inherit`, which Outlook's Word
  rendering engine ignores, so the title would have fallen back to the default
  h1 size and weight in every Outlook client
- replaced the inline resets with a `.post-title-heading` rule so juice inlines
  concrete px/weight values matching the existing `.post-title > table td` rule
- regenerated the email snapshots; the only delta is the h1 wrapper
@9larsons
9larsons force-pushed the fix/newsletter-title-h1 branch from 9913bb3 to 3663b00 Compare July 28, 2026 19:51

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@ghost/core/core/server/services/email-service/email-templates/partials/styles.hbs`:
- Around line 164-170: Update the mobile email styles for `.post-title-heading`
so its font size is 26px and line height is 1.1, both with `!important`,
overriding the later `table.body h1` rules while preserving the existing desktop
declarations and nested-link styling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 72fa24bb-88fb-4f2b-ab57-579aa7e03581

📥 Commits

Reviewing files that changed from the base of the PR and between 9913bb3 and 3663b00.

⛔ Files ignored due to path filters (3)
  • ghost/core/test/e2e-api/admin/__snapshots__/email-previews.test.js.snap is excluded by !**/*.snap
  • ghost/core/test/integration/services/email-service/__snapshots__/batch-sending.test.js.snap is excluded by !**/*.snap
  • ghost/core/test/integration/services/email-service/__snapshots__/cards.test.js.snap is excluded by !**/*.snap
📒 Files selected for processing (3)
  • ghost/core/core/server/services/email-rendering/partials/email-wrapper.hbs
  • ghost/core/core/server/services/email-service/email-templates/partials/styles.hbs
  • ghost/core/test/unit/server/services/email-service/email-renderer.test.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • ghost/core/core/server/services/email-rendering/partials/email-wrapper.hbs
  • ghost/core/test/unit/server/services/email-service/email-renderer.test.js

@nx-cloud

nx-cloud Bot commented Jul 29, 2026

Copy link
Copy Markdown

🤖 Nx Cloud AI Fix

Ensure the fix-ci command is configured to always run in your CI pipeline to get automatic fixes in future runs. For more information, please see https://nx.dev/ci/features/self-healing-ci


View your CI Pipeline Execution ↗ for commit 3663b00

Command Status Duration Result
nx run ghost:test:ci:integration ✅ Succeeded 3m 9s View ↗
nx run ghost:test:integration ✅ Succeeded 3m 15s View ↗
nx run ghost:test:legacy ✅ Succeeded 3m 8s View ↗
nx run ghost:test:e2e ✅ Succeeded 2m 47s View ↗
nx run ghost-monorepo:lint:boundaries ✅ Succeeded 22s View ↗
nx run-many -t lint -p ghost,ghost-monorepo ✅ Succeeded 20s View ↗
nx run-many -t test:unit -p ghost ✅ Succeeded 29s View ↗
nx run @tryghost/admin:build ✅ Succeeded 7s View ↗
nx run-many --target=build --projects=tag:publi... ✅ Succeeded 1s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-07-29 14:47:46 UTC

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 75.41%. Comparing base (0c03704) to head (3663b00).
⚠️ Report is 5 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #29642      +/-   ##
==========================================
- Coverage   75.45%   75.41%   -0.05%     
==========================================
  Files        1607     1607              
  Lines      141190   141198       +8     
  Branches    17465    17451      -14     
==========================================
- Hits       106535   106483      -52     
- Misses      33611    33644      +33     
- Partials     1044     1071      +27     
Flag Coverage Δ
e2e-tests 77.56% <ø> (-0.05%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@9larsons
9larsons merged commit 7a90633 into TryGhost:main Jul 29, 2026
46 checks passed
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.

Newsletter - H1 tag missing

2 participants