Repository navigation
fix(cache): refresh the LiveBlogPosting JSON-LD when a coverage or entry changes - #140
Conversation
There was a problem hiding this comment.
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.
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.
|
Hey @Vrishabhsk, good job getting this PR merged! 🎉 Now, the 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! ❤️ |
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
termsanduserslast-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:
headlinein the page's JSON-LD on the next render.liveBlogUpdatelist.liveBlogUpdate._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.trunk, load<PAGE>once to warm the cache, then rename the coverage term (Rolling Coverage → the coverage → rename it).<PAGE>and view the source. TheLiveBlogPostingheadlineis still the old name.liveBlogUpdatestill lists the moved entry.liveBlogUpdatestill shows the old author name.wp cache flush), and repeat steps 2 to 4. Each change is reflected on the next render, with no flush between steps.wp_optionshas no_transient_nrc_*rows.Technical details
Root cause.
Schema::build_metadata()cached the metadata in aWEEK_IN_SECONDStransient keyed on the host post, itspost_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 (theheadline), its entry relationships, and each entry author's display name — and the plugin never deletes the transient. A coverage rename bumps onlymodified_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, soget_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)wp_cache_get_last_changed( 'terms' )andwp_cache_get_last_changed( 'users' ). Core bumps thetermssalt on a term edit and on a term-relationship change, and theuserssalt on a user update, so all three triggers rotate the key. This is the same versioned-key pattern newspack-plugin'sCollections\Cacheuses.set_transient/get_transienttowp_cache_get/wp_cache_setin 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 inwp_optionsfor 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.WEEK_IN_SECONDSTTL stays as a bound, not the invalidation path.Tests.
tests/test-schema.phpgains four cases, each of which fails without its change: renaming the coverage refreshes the cachedheadline; moving a non-newest entry out refreshesliveBlogUpdate; 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.phpgains an optional author argument tocreate_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