venues, and a guide staleness check that could not see an arrival

Adopts bedrock/venues.md and reconciles externals.md, which dropped its four-line
venue section in favour of the one sentence that is actually about externals.

The staleness check compared only documents present in both the stamp and the
locks, so a section gaining a page never flagged its guide — and the bedrock
guide enumerates its pages by hand. It went silently incomplete the moment venues
was adopted. Now reports changed, added and gone separately, in words that fit
each case.

Found the same way as the sidebar: by adopting something real for the first time.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-08 10:56:54 -04:00
co-authored by Claude Opus 5
parent 2d766d0a72
commit c4e2a2e67d
8 changed files with 245 additions and 16 deletions
+22 -3
View File
@@ -154,13 +154,25 @@ function guidePage(repo, records, existing) {
let data = { title: repo, loom: {} }
let body = GUIDE_TEMPLATE(repo)
let stale = []
let changed = []
let added = []
let gone = []
if (existing) {
const split = splitFrontmatter(existing)
data = { ...split.data }
body = split.body
const was = new Map((data.loom?.writtenAgainst ?? []).map((s) => [s.path, s.etag]))
stale = records.filter((r) => was.has(r.path) && was.get(r.path) !== r.etag).map((r) => r.name)
// A section gaining or losing a document changes the guide too, and this
// did not notice until bedrock grew a fifth page under a guide that
// enumerates its pages by hand. Comparing only what is present in both is a
// comparison that cannot see an arrival.
changed = records.filter((r) => was.has(r.path) && was.get(r.path) !== r.etag).map((r) => r.name)
added = records.filter((r) => !was.has(r.path)).map((r) => r.name)
gone = [...was.keys()]
.filter((p) => !records.some((r) => r.path === p))
.map((p) => p.split('/').pop().replace(/\.md$/, ''))
stale = [...changed, ...added, ...gone]
}
data.title ??= repo
@@ -175,9 +187,16 @@ function guidePage(repo, records, existing) {
// only ever know that they differ. The first time it fired in real use, the
// guide was written against a version newer than the copy here, and a
// message claiming "an older X" was confidently backwards.
const which = stale.map((s) => `<code>${s}</code>`).join(', ')
const code = (xs) => xs.map((x) => `<code>${x}</code>`).join(', ')
const parts = []
if (changed.length)
parts.push(
`${code(changed)} differs from the version this page was written against — so one of the two is behind`,
)
if (added.length) parts.push(`${code(added)} was adopted after this page was written`)
if (gone.length) parts.push(`${code(gone)} is no longer adopted`)
data.banner = {
content: `Written against a different ${which} than the copy in this repository — so one of the two is behind. Run <code>loomctl external check</code>, or re-read this page, then <code>npm run ack</code>.`,
content: `${parts.join('; ')}. Run <code>loomctl external check</code>, or re-read this page, then <code>npm run ack</code>.`,
}
} else {
delete data.banner