diff --git a/docs/decisions/0009-documentation-channels.md b/docs/decisions/0009-documentation-channels.md index 0444891..365ac78 100644 --- a/docs/decisions/0009-documentation-channels.md +++ b/docs/decisions/0009-documentation-channels.md @@ -134,8 +134,24 @@ history has to survive between builds, and an existing page is never overwritten `docsRef`, `docsRefKind`, `docsVersion`). A tag-pinned page links to the file at that tag ("View source") rather than offering an edit that cannot work, and is not flagged stale: its content is the release, not drift. -- The sidebar shows one Starlight group per channel (`Live 2.4.2`, - `Prerelease main`); single-channel projects keep the flat list. +- Channels are labelled with the **version they document**, not the ref they read. + A tag channel names its own version, and a `prerelease` channel that follows + `main` is labelled with the newest published prerelease + (`Prerelease 3.0.0-prerelease.2`): a reader chooses between versions, and a + branch name says nothing about which one the page describes. A `live` channel + that fell back to a branch has no stable release to name and keeps the branch, + as does a commit SHA. The resolved `ref` stays on the record, so the branch is + still visible and the edit links still target it. +- The page switcher is a native `
` drop-down: the summary names the + channel being read, the menu lists every version, the current one carries + `aria-current`, and the ref is each option's tooltip. It needs no JavaScript, + so the versions stay reachable, and every option is a real link. It exists only + while two channels are published: promotion withholds the prerelease channel, + so the switcher disappears with it and nothing in the UI advertises a version + that is no longer published (its retired URLs redirect instead). +- The sidebar shows one Starlight group per channel with the same labels + (`Live 2.4.2`, `Prerelease 3.0.0-prerelease.2`); single-channel projects keep + the flat list. - The per-project `llms-full.txt` corpus publishes the live channel only, and the concise `llms.txt` links the prerelease channel when it exists. Two versions of one page in a single corpus is ambiguous to a model. diff --git a/src/scripts/check-projects.ts b/src/scripts/check-projects.ts index b092af5..ae2c78f 100644 --- a/src/scripts/check-projects.ts +++ b/src/scripts/check-projects.ts @@ -195,7 +195,23 @@ function checkDocs( ? prereleaseLinePromoted(project, data) : { publish: true }; if (!channel) { - if (gate && !gate.publish) { + if (gate === null) { + // No release data, so nothing can say whether this channel should exist. + // Reporting it against `docs.channels` points the reviewer at the manifest + // and the project's documentation, which are the two things that are + // usually fine: the missing input is the release cache. + warn({ + project: project.id, + field: `docs.channels.${id}`, + observed: + 'not present in the aggregated documentation, and no release data to explain it', + expected: 'a docs mirror built from the release data this check reads', + remediation: + 'run `just data-sync` so the mirror and the catalogue are built from the same release data.', + }); + continue; + } + if (!gate.publish) { warn({ project: project.id, field: `docs.channels.${id}`, diff --git a/src/scripts/sync-docs.ts b/src/scripts/sync-docs.ts index 5f8a43c..861c861 100644 --- a/src/scripts/sync-docs.ts +++ b/src/scripts/sync-docs.ts @@ -3,11 +3,13 @@ import { resolve } from 'node:path'; import { aggregateDocs, + isMirrorManifestCurrent, readDocsManifest, writeDocs, writeDocsManifest, } from '../src/lib/docs/aggregate'; import { loadProjects } from '../src/lib/manifest/load'; +import { getReleaseIndex } from '../src/lib/releases/runtime'; /** Fetch and write the aggregated documentation mirror. */ export async function syncDocs(): Promise<{ projects: number; pages: number }> { @@ -37,13 +39,22 @@ export function hasDocsMirror(): boolean { } /** - * True when the mirror and its manifest agree with the current schema. A - * manifest written by an older schema reads as absent (`readDocsManifest` - * returns null), which is what forces a re-sync after a schema bump instead of - * building a sidebar for page paths the mirror no longer has. + * True when the mirror can be reused: it exists, its manifest was written by the + * current schema, and it was aggregated from the release data that is in hand. + * + * A manifest written by an older schema reads as absent (`readDocsManifest` + * returns null), which forces a re-sync instead of building a sidebar for page + * paths the mirror no longer has. The vintage check covers the other half: the + * schema can be current while the release data is not, and channels are decided + * from release data — so a mirror that predates the latest fetch documents a + * release line that has since moved on. Reusing it publishes a stale channel + * decision, and `check:projects` then reports the missing channel as drift. */ export function hasCurrentDocsMirror(): boolean { - return hasDocsMirror() && readDocsManifest() !== null; + if (!hasDocsMirror()) { + return false; + } + return isMirrorManifestCurrent(readDocsManifest(), getReleaseIndex().data.retrievedAt); } if (import.meta.main) { diff --git a/src/src/components/starlight/PageTitle.astro b/src/src/components/starlight/PageTitle.astro index 05dd00b..1002986 100644 --- a/src/src/components/starlight/PageTitle.astro +++ b/src/src/components/starlight/PageTitle.astro @@ -58,21 +58,39 @@ const llmsHref =

{data?.title}

{ - showChannelSwitcher && ( + showChannelSwitcher && activeChannel && ( ) } @@ -181,9 +199,11 @@ const llmsHref =