Skip to content

fix(tools): decode entities in post, page and media titles - #269

Merged
docdyhr merged 6 commits into
mainfrom
fix/decode-entities-in-titles
Oct 10, 2026
Merged

docdyhr merged 6 commits into
mainfrom
fix/decode-entities-in-titles

Conversation

@docdyhr

@docdyhr docdyhr commented Oct 9, 2026 •

Copy link
Copy Markdown
Owner

Pull Request

Description

wp_create_post, wp_delete_post and wp_get_post_revisions printed title.rendered exactly as WordPress returns it, so a title with & came back as Q&#038;A (open P3 in the v4.0.9 re-test results). #263 fixed list/get/update for posts only. The page and media tools have the same raw-title lines, and wp_get_media printed the caption's HTML (<p>…</p>).

Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)

Changes Made

  • displayTitle() (markup stripped, entities decoded once, (untitled) when empty) moves from PostHandlers.ts into src/utils/htmlText.ts and is exported
  • Posts: create, delete and revisions use it
  • Pages: list, get, create; delete uses htmlToPlainText() (it already falls back to the ID when there is no title)
  • Media: list, get, upload; the caption goes through htmlToPlainText()
  • src/tools/AGENTS.md: rendered fields are HTML and are printed through these helpers, never raw
  • Tests: 3 new cases in tests/tools/posts/PostOutputFormatting.test.js. Following review, displayTitle cases are in tests/utils/htmlText.test.js and the page/media cases in tests/tools/pages/PageTools.test.js / tests/tools/media/MediaTools.test.js, so each test sits in the suite that mirrors its source (tests/AGENTS.md)
  • The AGENTS.md rule names the tools it covers. The SEO engines still pass title.rendered through unchanged in their structured results; that is out of scope here

Testing

  • All 15 new tests were red before the change
  • Combined run with the other three branches from this pass: 93 files / 2740 tests, lint, typecheck, format:check, security:scan
  • Manual, read-only against the live sites: site3 page 65 News &#038; Articles → "News & Articles" in wp_get_page and wp_list_pages; site1 media 1500 …1030&#215;687.jpg → 1030×687; site2 revisions show “Take the Point…” and T’ai Chi’s

Documentation

  • Documentation updated (src/tools/AGENTS.md)
  • CHANGELOG.md updated — generated by semantic-release

Security

  • No sensitive information committed. The output is plain text that is never parsed as HTML; content written back to WordPress still goes through the validators.

Independent review follow-up

  • The over-50-post streaming path labelled an empty title "Untitled"; it now uses displayTitle() like every other path ("(untitled)").
  • displayTitle() accepts string | null | undefined, so passing the title object no longer type-checks.
  • Corrected a comment in handleCreatePost that said content goes through sanitizeHtml(); it goes through isUnsafeWordPressContent and is otherwise unchanged.
  • Tests for a markup-only page title on delete (falls back to the ID) and a media item with no caption field.

🤖 Generated with Claude Code

Summary by Sourcery

Normalize WordPress-rendered titles and captions across post, page, and media tool output.

Bug Fixes:

  • Decode and normalize rendered post, page, and media titles instead of exposing WordPress HTML entities or markup.
  • Render media captions as plain text and provide consistent untitled or ID fallbacks for empty rendered titles.

Enhancements:

  • Centralize rendered-title formatting in shared HTML text utilities and document the formatting requirement for tool output.

Documentation:

  • Document that WordPress rendered fields are HTML and must be formatted before being printed by post, page, and media tools.

Tests:

  • Add regression coverage for decoded titles, plain-text captions, empty-title fallbacks, and post revision output across posts, pages, and media.

wp_create_post, wp_delete_post and wp_get_post_revisions printed
title.rendered as WordPress returns it, so "Q&A" came back as
"Q&#38;A"; list/get/update had been fixed in #263. The page and media
tools had the same raw-title lines, and wp_get_media printed the caption
HTML. displayTitle() moves to src/utils/htmlText.ts and every one of
these now goes through it (captions through htmlToPlainText()).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 9, 2026 09:49
@sourcery-ai

sourcery-ai Bot commented Oct 9, 2026

Copy link
Copy Markdown

Sorry @docdyhr, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 5 days and 22 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-09T09:51:57.184703Z 7885650 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@sourcery-ai

sourcery-ai Bot commented Oct 9, 2026

Copy link
Copy Markdown

Reviewer's Guide

Centralizes WordPress rendered-title normalization and applies it across post, page, and media outputs, while converting media captions to plain text and adding comprehensive regression coverage for entity decoding, markup stripping, and empty-title handling.

File-Level Changes

Change Details Files
Centralize conversion of WordPress rendered titles into safe, readable plain text.
  • Moved and exported displayTitle() alongside htmlToPlainText().
  • Decode entities, strip markup, and use “(untitled)” for blank titles.
src/utils/htmlText.ts
src/tools/posts/PostHandlers.ts
Apply consistent title formatting across post, page, and media tool output.
  • Updated post create, delete, and revision responses.
  • Updated page list, get, create, and delete responses.
  • Updated media list, get, and upload responses.
src/tools/posts/PostHandlers.ts
src/tools/pages.ts
src/tools/media.ts
Render media captions as plain text instead of exposing WordPress HTML.
  • Pass caption.rendered through htmlToPlainText() before including it in wp_get_media output.
src/tools/media.ts
Document and regression-test rendered-field formatting behavior.
  • Document that rendered WordPress fields are HTML/entity-encoded and must use the formatting helpers.
  • Add coverage for displayTitle and every page/media call site.
  • Add post create, delete, and revision regression cases.
src/tools/AGENTS.md
tests/tools/titleEntityDecoding.test.js
tests/tools/posts/PostOutputFormatting.test.js

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copilot AI 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.

🟡 Changes recommended

The new test placement violates the repository test-layout contract, and the documentation overstates the implemented formatting guarantee.

2 open findings
What changed in this PR

Centralizes plain-text title formatting and applies it across post, page, and media tool responses.

Changes:

  • Exports and reuses displayTitle().
  • Decodes titles and media captions in affected handlers.
  • Adds regression tests and rendered-field guidance.
File Description
src/​utils/​htmlText.ts Exports shared title formatting.
src/​tools/​posts/​PostHandlers.ts Formats create, delete, and revision titles.
src/​tools/​pages.ts Formats page titles.
src/​tools/​media.ts Formats media titles and captions.
src/​tools/​AGENTS.md Documents rendered-field handling.
tests/​tools/​posts/​PostOutputFormatting.test.js Tests post title formatting.
tests/​tools/​titleEntityDecoding.test.js Tests page, media, and utility formatting.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/tools/AGENTS.md Outdated
Comment thread tests/tools/titleEntityDecoding.test.js Outdated
docdyhr and others added 5 commits October 9, 2026 12:02
Review follow-up: tests/AGENTS.md wants tests to mirror src/. The
displayTitle cases move to tests/utils/htmlText.test.js and the page and
media cases into PageTools.test.js / MediaTools.test.js;
tests/tools/titleEntityDecoding.test.js is removed. The AGENTS.md rule
now names the tools it covers (post, page, media) and says the SEO
engines still pass title.rendered through unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-up:
- The >50-post streaming path labelled an empty title "Untitled" while
  every other path says "(untitled)"; it now uses displayTitle() too.
- displayTitle() takes string | null | undefined, so passing the title
  object instead of .rendered no longer type-checks.
- The comment in handleCreatePost claimed validatePostParams runs
  content through sanitizeHtml(); it uses isUnsafeWordPressContent and
  passes content through unchanged (sanitizeHtml corrupts Gutenberg
  markup). Corrected so nobody "restores" it.
- Tests for an empty/markup-only page title on delete (falls back to the
  ID) and a media item without a caption field.
- src/tools/AGENTS.md: which rendered fields carry tags; htmlText.js in
  the shared imports.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@docdyhr
docdyhr merged commit f8cfade into main Oct 10, 2026
24 checks passed
@docdyhr
docdyhr deleted the fix/decode-entities-in-titles branch October 10, 2026 22:39
docdyhr pushed a commit that referenced this pull request Oct 10, 2026
## [4.1.2](v4.1.1...v4.1.2) (2026-10-10)

### 🐛 Bug Fixes

* **tools:** decode entities in post, page and media titles ([#269](#269)) ([f8cfade](f8cfade))
@docdyhr

docdyhr commented Oct 10, 2026

Copy link
Copy Markdown
Owner Author

🎉 This PR is included in version 4.1.2 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants