Our sitemap went from 72 URLs to 79 this week:
curl -s https://pub-trivia.app/sitemap.xml | grep -c '<url>' # 79 Seven new pages: four guides, an attendance guide, a seasonal questions page and a calculator. One commit, one registry file, seven new entries. That part went fine.
The part that did not is what happened to the pages around them. Six existing pages now render a link that did not exist on them before, and all six still publish a last-modified date from before the link existed. Here is how that happens, and why it is not the sort of bug a test catches.
The dates are typed by hand
Every page on the content side of PubTrivia is a node in one registry file. A node looks like this:
{ path: '/guides/pub-quiz-entry-fee', title: 'How Much to Charge for a Pub Quiz', shortLabel: 'Entry fees', heading: 'How much to charge for a pub quiz', description: 'Pub quiz entry fees: what pubs usually charge, per head or per team, ...', cluster: 'guides', updated: '2026-11-04', related: [ '/guides/pub-quiz-prize-ideas', '/guides/charity-quiz-night', '/tools/quiz-night-profit-calculator', '/solutions/pubs', ], }, Two fields matter here. updated is the one freshness fact in the system: the sitemap's lastmod, the datePublished and dateModified in the Article JSON-LD, and the visible "Last reviewed" line under the heading all read from it. related is a hand-maintained adjacency list that renders as the links at the foot of the page.
So adding a page is two jobs. Write the node, and then go and edit the neighbours that ought to point at it, because nothing generates those edges.
The seven landed, and the neighbours got edited
I diffed the registry against its parent commit rather than reading the diff, because a hand-written adjacency list is exactly the thing a diff makes hard to read:
nodes before 69 nodes after 76 (+7, and the three legal pages have no node) existing nodes whose related list changed: /solutions/pubs gained /guides/pub-event-ideas 2026-10-15 -> 2026-11-04 bumped /solutions/quizmasters gained /guides/hiring-a-quizmaster 2026-09-14 -> 2026-09-14 unchanged /guides/starting-a-quiz-night gained /tools/quiz-night-profit-calculator 2026-09-14 -> 2026-09-14 unchanged /guides/how-to-promote-a-quiz-night gained /guides/quiz-night-attendance 2026-09-14 -> 2026-09-14 unchanged /guides/pub-quiz-prize-ideas gained /guides/pub-quiz-entry-fee 2026-09-14 -> 2026-09-14 unchanged /quiz-questions/music gained /guides/pub-quiz-music-licence 2026-09-14 -> 2026-09-14 unchanged /quiz-questions/christmas gained /quiz-questions/review-of-the-year-2026 2026-10-15 -> 2026-10-15 unchanged Seven neighbours were wired up. One of the seven got its date moved, because it had other copy changes in the same commit. The other six did not, and nobody noticed, including me, and I wrote the commit.
Twelve pages carry today's date in the published sitemap, and those twelve are the seven new pages plus five hub pages whose listings changed. The six neighbours are not among them:
curl -s https://pub-trivia.app/sitemap.xml | grep -o '<lastmod>[^<]*' | cut -c10-19 | sort | uniq -c # 47 2026-09-14 # 15 2026-10-15 # 12 2026-11-04 # 3 2026-11-05 (the three legal pages, which have no node and fall back to the build clock) # 1 2026-10-18 # 1 2026-10-03 What that looks like from outside
Take the prize ideas guide. Its HTML changed yesterday: there is a new link at the foot of it, to the new entry fee guide.
curl -s https://pub-trivia.app/guides/pub-quiz-prize-ideas \ | grep -c 'href="/guides/pub-quiz-entry-fee"' # 1 Now ask the same document when it last changed. It answers three times, in three places, and all three answers are wrong:
| Claim | Where | Value |
|---|---|---|
lastmod | sitemap.xml | 2026-09-14 |
dateModified | Article JSON-LD | 2026-09-14T00:00:00Z |
| "Last reviewed" | visible <time> element | 14 September 2026 |
All three read the same field, which is the design working as intended: one fact, three surfaces, no chance of them disagreeing with each other. They just all disagree with the document.
The music questions page is in the same state, to the day. The quizmasters page is in a milder version of it: solutions pages are not marked up as articles, so they have no visible review line and no dateModified, and the stale claim is confined to the sitemap.
Why this is not a test problem
The obvious reaction is to write a test. I thought about what it would assert, and could not get there.
A test can see that related changed, but only if it has something to compare against, which means a committed snapshot of the previous state. A test can compare updated against the file's git history, but the registry is one file holding 76 nodes, so every node's mtime is the same, and the whole file's history says nothing about which node changed. A test can check that updated is not in the future, or not absurdly old, and we could do that. None of those catch "this one node's rendered output changed and its date did not".
The honest framing is that updated is not a fact about the page, it is a claim a human makes about the page, and the only thing that can be verified is internal consistency. What we have instead is a field that is reliable for the thing it was written for, body copy edits, and silently unreliable for everything that reaches a page through its neighbours.
Two options I can actually see:
- Derive it. Compute each page's modification date from the content hash of everything that renders into it, including the related list, and store the mapping. That is a build step and a committed lock file, which is a lot of machinery for a date.
- Shrink the claim. Stop emitting
dateModifiedand the visible review line for pages whose freshness is not actively curated, and keeplastmod, where a crawler treats it as a hint and nothing more. A wrong date is worse than no date in both of the places where we make it look like editorial review.
I lean to the second, which is the opposite of what an SEO guide would tell you to do, on the grounds that two of those three surfaces are promises to a reader.
While I was in there: the new pages are thinly linked
The adjacency list is reciprocal when somebody remembers to make it so. Counting over all 76 nodes:
related edges 253 reciprocal edges 116 (46 per cent) And for the seven new pages, the outbound edges that point back:
/tools/quiz-night-profit-calculator 4 out, 3 reciprocated /guides/hiring-a-quizmaster 4 out, 2 reciprocated /guides/pub-quiz-entry-fee 4 out, 2 reciprocated /guides/pub-event-ideas 4 out, 1 reciprocated /guides/pub-quiz-music-licence 3 out, 1 reciprocated /guides/quiz-night-attendance 4 out, 1 reciprocated /quiz-questions/review-of-the-year 4 out, 1 reciprocated Four of the seven have exactly one inbound link from a sibling page. They are all listed on their cluster hub, so they are reachable and crawlable, but a new page that one page links to is a leaf, and a leaf is a page you have to find through a hub. The calculator is the best connected of the batch because three guides genuinely wanted it: a guide about what to charge and a calculator that works out what you clear are the same question asked twice.
Check it yourself
Everything above is readable from the outside, which is the nice thing about publishing your own freshness claims:
# a document that changed yesterday curl -s https://pub-trivia.app/guides/pub-quiz-prize-ideas | grep -o 'href="/guides/pub-quiz-entry-fee"' # and its three dates curl -s https://pub-trivia.app/sitemap.xml | grep -A1 'guides/pub-quiz-prize-ideas' curl -s https://pub-trivia.app/guides/pub-quiz-prize-ideas | grep -o '"dateModified":"[^"]*"' curl -s https://pub-trivia.app/guides/pub-quiz-prize-ideas | grep -o '14 September 2026' The new pages themselves, if you want to see what seven nodes buy: entry fees, hiring a quizmaster, music licensing, quiet midweek nights, attendance and the profit calculator, which runs entirely in the page with no backend behind it.


