Skip to content

fix(cache): refresh the LiveBlogPosting JSON-LD when a coverage or entry changes - #140

Merged
miguelpeixe merged 3 commits into
trunkfrom
fix/cache-bug
Oct 8, 2026
Merged

miguelpeixe merged 3 commits into
trunkfrom
fix/cache-bug

Conversation

@Vrishabhsk

Copy link
Copy Markdown
Collaborator

A page that embeds a Rolling Coverage block keeps the structured data it built the first time it rendered, for up to a week. Rename the coverage, move an entry in or out of it, or rename an entry's author, and the page's JSON-LD keeps the old name or the old entry list until that cached value expires. The value is keyed on parts of the coverage these changes don't touch, so nothing rebuilds it.

What changes

The cached LiveBlogPosting is now keyed on core's terms and users last-changed salts, so a rename, an entry move, or an author rename builds it fresh. The cache also moves from a database transient to the object cache.

With this change:

  • Renaming a coverage updates the headline in the page's JSON-LD on the next render.
  • Moving a published entry in or out of a coverage updates the liveBlogUpdate list.
  • Renaming an entry's author updates the author in liveBlogUpdate.
  • On sites without a persistent object cache, viewing a coverage page no longer writes a _transient_nrc_* row per view.

How to test

Setup: a published page embedding a coverage block, with a few published entries. <PAGE> below is its URL.

  1. On trunk, load <PAGE> once to warm the cache, then rename the coverage term (Rolling Coverage → the coverage → rename it).
  2. Reload <PAGE> and view the source. The LiveBlogPosting headline is still the old name.
  3. Move one of its entries to another coverage, reload, and check the source. liveBlogUpdate still lists the moved entry.
  4. Rename the entry's author, reload, and check the source. liveBlogUpdate still shows the old author name.
  5. Check out this branch, flush the site's object cache once (wp cache flush), and repeat steps 2 to 4. Each change is reflected on the next render, with no flush between steps.
  6. On a site with no persistent object cache, load a coverage page ten times and confirm wp_options has no _transient_nrc_* rows.
Technical details

Root cause. Schema::build_metadata() cached the metadata in a WEEK_IN_SECONDS transient keyed on the host post, its post_modified_gmt, entries_per_page, the coverage's status, its last-modified term meta, its end time, and its newest entry's timestamp. That key omits three inputs the metadata is built from — the coverage's name (the headline), its entry relationships, and each entry author's display name — and the plugin never deletes the transient. A coverage rename bumps only modified_at, a different meta key; moving an entry touches only the entry's current coverages, not the coverage it left; an author rename touches no post or term data at all. In each case the key is unchanged, so get_transient() returns the old value for up to a week. This affects the standalone script and, because the Yoast merge reads the same method, Yoast's Article on sites with Yoast.

Fix (class-schema.php)

  • The key now ends with wp_cache_get_last_changed( 'terms' ) and wp_cache_get_last_changed( 'users' ). Core bumps the terms salt on a term edit and on a term-relationship change, and the users salt on a user update, so all three triggers rotate the key. This is the same versioned-key pattern newspack-plugin's Collections\Cache uses.
  • The cache moves from set_transient/get_transient to wp_cache_get/wp_cache_set in a new group, newspack_rolling_coverage_schema. Without a persistent object cache the salts are minted per request, so a transient key would change on every page view and leave an orphaned _transient_* row in wp_options for each one. As an object-cache entry it simply expires with the request in that case, and is shared when Redis or Memcached is present.
  • The WEEK_IN_SECONDS TTL stays as a bound, not the invalidation path.

Tests. tests/test-schema.php gains four cases, each of which fails without its change: renaming the coverage refreshes the cached headline; moving a non-newest entry out refreshes liveBlogUpdate; renaming an author refreshes the author; and the metadata is never stored as a database transient. The move case uses a non-newest entry on purpose, so the "latest entry timestamp" component of the key can't mask whether the term-relationship change alone invalidated it. tests/class-rolling-coverage-testcase.php gains an optional author argument to create_dated_entry(). The full suite runs (707 tests); PHPCS is clean.

Not covered. A page cache that serves a page indefinitely (no expiry, no purge on the term or entry change) still shows the JSON-LD from when the page was cached, until the page cache turns over. This matches how Newspack's other blocks behave and is left to the page cache's own TTL and invalidation.

Self-review: validated (ollama's deepseek-v4.1-flash:cloud). Each new test was seen failing before its fix, the suite passes with and without a persistent object cache, and PHPCS is clean.

🤖 Generated with ollama's deepseek-v4.1-flash:cloud

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟢 Approval recommended

The cache invalidation paths are correctly implemented and covered by focused PHPUnit tests.

Review effort: Balanced
Findings: None

What changed in this PR

Updates LiveBlogPosting caching so coverage, entry relationship, and author changes invalidate stale JSON-LD.

Changes:

  • Uses object-cache entries keyed by WordPress term and user change salts.
  • Adds regression tests for all affected invalidation paths.
  • Extends the entry test helper with optional author assignment.
File Description
includes/​class-schema.php Moves schema caching to a versioned object-cache group.
tests/​test-schema.php Tests cache storage and invalidation behavior.
tests/​class-rolling-coverage-testcase.php Supports authored dated test entries.

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

@miguelpeixe miguelpeixe left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you, @Vrishabhsk!

Comment thread includes/class-schema.php Outdated
Comment thread includes/class-schema.php Outdated
Key the LiveBlogPosting cache on the group's own last_changed stamp, bumped on coverage rename, entry move, author rename and user deletion, so unrelated site writes don't rebuild it.
@Vrishabhsk
Vrishabhsk requested a review from miguelpeixe October 8, 2026 07:30

@miguelpeixe miguelpeixe left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you!

@miguelpeixe
miguelpeixe merged commit 4d7909d into trunk Oct 8, 2026
5 checks passed
@miguelpeixe
miguelpeixe deleted the fix/cache-bug branch October 8, 2026 12:30
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Hey @Vrishabhsk, good job getting this PR merged! 🎉

Now, the needs-changelog label has been added to it.

Please check if this PR needs to be included in the "Upcoming Changes" and "Release Notes" doc. If it doesn't, simply remove the label.

If it does, please add an entry to our shared document, with screenshots and testing instructions if applicable, then remove the label.

Thank you! ❤️

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants