fix(search): restore the API reference to search and stop docs-version renames from breaking it - #428
fix(search): restore the API reference to search and stop docs-version renames from breaking it#428eugenia-scandit wants to merge 7 commits into
Conversation
Releasing 8.5.3 renamed the docs version from `current` to `8.5.3`. Docusaurus builds `docusaurus_tag` from the version NAME, so every page at the site root flipped from `docs-default-current` to `docs-default-8.5.3` without a single URL changing. Two things broke, both silently: 1. The generated API reference under /data-capture-sdk/ is crawled into the same index but is not a Docusaurus version - it is tagged `docs-default-current`. While lastVersion was "current" it happened to share the guides' tag and rode along for free. After the rename it no longer matched the contextual filter, and ~3,200 pages left every search result. A representative query returned 86 hits where the index held 106 reachable ones. 2. buildVersionTagByMajor let `current` win its major unconditionally. With 8.6.0-beta as current and 8.5.3 as lastVersion, typing "v8" routed readers at the unreleased beta's tag - which still returned results, because that tag held the API reference, so nothing looked wrong. Neither failed loudly. Search just got worse, and the monitoring could only report "too few records". Changes: - docusaurus.config.ts is now the single source of truth for versions and every tag derived from them. DOCS_LAST_VERSION is declared once beside docsVersions instead of being restated in the preset, docVersionTag is the only place a `docs-default-*` string is built, and ALWAYS_ON_SEARCH_TAGS names the indexed content Docusaurus cannot tag for us. - buildVersionTagByMajor now follows lastVersion and skips unreleased versions, so a typed major routes where the reader actually is. - SearchBar widens the contextual OR group with the always-on tags, and the typed-version rewrite no longer clobbers them. - A build plugin writes build/search-tags.json, and scripts/verify-search-tags.cjs (yarn verify:search-tags, wired into CI) checks it three ways: the routed tag for the served major must be the served tag, the build must actually emit every tag the config names, and no tag holding real content may sit outside what search can reach. Crawl lag warns; config drift fails. The gate reproduces both bugs on the pre-fix config and passes on this one. It also surfaces two orphan cohorts left by earlier renames - docs-default-8.0.0 (10 pages) and docs-default-6.28.1 (18) - which want purging from the index. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
My previous commit restored the API reference to search by OR-ing its tag in on every page - including the frozen 7.6.14 and 6.28.11 trees, where it had never appeared before. Measured on a 7.6.14 page, one query went from 53 hits to 73, and all 20 extras were current-SDK API pages that do not apply to a reader on that version. Before the 8.5.3 rename this scoping happened by accident: the API reference shared the served version's tag, so it appeared there and nowhere else. This makes it explicit - alwaysOnScopeTags names the versions the extra tags apply to (the served version and the in-development one), and the widget injects them only when the page's own tag is in that set. Also starts the crawler migration. The API reference should not be tagged with a version name at all: `docs-default-current` is the 8.6.0 beta's own tag, which is exactly why a release could rename the API reference out of search. ALWAYS_ON_SEARCH_TAGS now lists `api-reference` alongside it, so retagging the Algolia crawler's /data-capture-sdk/** action can land before or after this commit without breaking search either way. The gate reports an empty always-on tag as a pending migration, and only fails if no always-on tag carries the content. Adds scripts/test-search-facets.cjs (yarn test:search-facets, wired into CI). It reads the two functions out of the shipped module rather than copying them, and pins both failure modes: too narrow and content vanishes from search, too wide and legacy readers get results that do not apply. Neither throws in production, so only an assertion catches them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
I had this wrong twice, and the correction matters: the API reference IS versioned. It is published per major.minor line at /<major.minor>/data-capture-sdk/<framework>/ - /6.28/, /7.6/, /8.4/, /8.5/, /8.6/ all return 200. My earlier probes used /6.28.11/, which 404s, and I concluded from that there was only one API reference. There is one per line, and each docs version has a matching one. That makes the previous design wrong in both directions: OR-ing one API reference into every version polluted legacy search with current-SDK pages, and scoping it to the served version alone hid the 6.28 and 7.6 API references from the readers they belong to. So the API reference is now mapped per version rather than bolted on. buildApiReferenceTags derives, from docsVersions alone, the tag each docs version's API reference carries: docs-default-6.28.11 -> api-reference-6.28, docs-default-7.6.14 -> api-reference-7.6, docs-default-8.5.3 -> api-reference-8.5, docs-default-current -> api-reference-8.6. A reader on 6.28.11 gets the 6.28 API reference and never the 8.x one, and typing "v7" moves the guides and the API reference together. Nothing here needs maintaining per release. The repo reads the line out of docsVersions; the crawler is expected to read it out of the URL (/8.5/data-capture-sdk/... -> api-reference-8.5). Neither side hard-codes a version, which is the whole point: tagging the API reference with a docs version NAME is what broke search when 8.5.3 renamed it. Also fixes a regression I introduced. Hoisting DOCS_LAST_VERSION left scripts/update-version.py reading the `lastVersion:` line, whose last quoted string is now "current" - so the next release would have aborted with "Already in production state". All four read/write sites now target the constant, which is the value both the docs plugin and the tag derivation actually use. Tests rewritten around the version mapping: a legacy version gets ITS API reference and not the current one, an unknown version adds nothing rather than guessing, and a typed version moves both together. 7 passing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…links
Correcting my own previous commit. It mapped the served version to
`api-reference-8.5`, on the assumption that every docs version has a
matching /<major.minor>/data-capture-sdk/ tree the crawler can reach. It
does not, and the assumption would have dropped the current API reference
out of search a second time.
What the site really does, verified against production:
- the sitemap contains ZERO /data-capture-sdk/ URLs, so the crawler can
only find that tree by following links
- the 7.6.14 guides link to /7.6/data-capture-sdk/...
- the guides served at the root link to the UNVERSIONED
/data-capture-sdk/... - nothing anywhere links to /8.5/
So /8.5/data-capture-sdk/ exists but is undiscoverable, and pointing the
served version at it would have pointed at an empty tag.
The mapping now mirrors the site's own linking instead of assuming a
symmetry that isn't there: the version served at the root and the
in-development one take the unversioned tree (`api-reference-latest`),
every frozen version takes its own line (`api-reference-7.6`,
`api-reference-6.28`). That is what the crawler will actually produce from
the URL, and it stays automatic - this file reads the version out of
docsVersions, the crawler out of the URL, neither hard-codes one.
Records are only added by this change, never removed: the unversioned tree
keeps its URLs and just changes tag. maxLostRecordsPercentage does not need
raising.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
moritzhartmeier
left a comment
There was a problem hiding this comment.
Two blocking bugs. Everything else is smaller.
Verified by execution: ran both new scripts, queried the live Algolia index, simulated a release bump through the gate, dry-ran both update-version.py transitions on this branch, and typechecked (tsc clean). All 5 CI checks pass and the 7 facet tests pass — the two bugs below are ones the tests are shaped not to catch.
What holds up: the buildVersionTagByMajor priority fix is correct and is a genuine independent bug — confirmed major 8 now resolves to docs-default-8.5.3 rather than the unreleased beta. The update-version.py fix works: I ran 8.5.3 -> 8.5.4 and then the 8.6.0 promotion, and the constant, the docsVersions key and versions.json all land correctly, ending at DOCS_LAST_VERSION = "current". The per-version API mapping is also cycle-stable — it still derives correctly after promotion, when current becomes the served version again. And checking a build artifact against the live index rather than re-deriving config against itself is the right instinct.
Blocking
1. rewriteVersionTag ANDs the API-reference tag against the whole query, so typing "v7" returns zero guides. Reproduced; detail inline. Lines 141-142 of the same file warn against exactly this.
2. The gate hard-fails on every release PR — the change it exists to protect. Simulated 8.5.3 -> 8.5.4 against the live index: two blocking FAILs, neither gated by --strict. Detail inline.
One factual correction
Releasing 8.5.3 didn't rename the docs version. That happened at e92c1b16 Release 8.6.0-beta.1 (#423), which set lastVersion: "8.5.2" and made current the unreleased beta; 8.5.3 was a later patch bump. The wrong cause is stated in the docusaurus.config.ts SSOT block, in the gate's docstring, and in three commit messages, so it's worth correcting where it will be read later.
It also isn't a one-off: the root-served tag moves on the production->beta transition, on patches during the beta window, and again on beta->production. That's a cycle, which strengthens the case for something here — but it means the migration shim in particular needs to go rather than be carried forward (see inline).
| reachable.has(tag) | ||
| ? "reachable " | ||
| : routable.has(tag) | ||
| ? "typed-only " |
There was a problem hiding this comment.
api-reference-7.6 / api-reference-6.28 print as typed-only, but they're contextually reachable from those versions' pages — the comment just above says as much. Mislabel in the operator-facing report.
There was a problem hiding this comment.
Partially fixed — the API-reference tags now read contextual, which is right. But the frozen docs version tags still print typed-only:
2501 contextual api-reference-7.6
343 typed-only docs-default-7.6.14
264 typed-only docs-default-6.28.11
A reader browsing the 7.6.14 guides reaches docs-default-7.6.14 from that page's own contextual filter with nothing typed — exactly the case the new contextual set was introduced for. It only covers values of apiReferenceTagsByVersionTag, not the version tags that key it.
Cheap fix: add the non-served keys of that map to contextual alongside their values. Report-only, so entirely up to you whether it is worth the line.
…e shim BLOCKING 1 - rewriteVersionTag ANDed the API tag against the whole query. `swap` appended targetApi at every array level it was called on, including depth 0, where facetFilters entries are ANDed. Typing "v7" produced three top-level entries, the last being docusaurus_tag:api-reference-7.6, so only 7.6 API pages could match and every guide was filtered out - the exact opposite of the intent, and what lines 141-142 already warn about. Now appended only when recursing, and only into the group that already carries the page's own docusaurus_tag. The second condition matters independently: appending to the ["language:en"] group would OR the API tag against the language filter and quietly widen it. Results deduped, which also removes the doubled 7.6 tag the reviewer saw. The test that should have caught this could not: tagsOf() only inspects the nested OR group, so a stray top-level entry was invisible to every assertion. Added `assert out.length === 2` plus an explicit "no API tag at the top level" check - the same guard the sibling test at line 89 already had. Verified in both directions: against the old implementation the suite fails with `actual: 3, expected: 2`. BLOCKING 2 - the gate hard-failed on every release PR. Simulated 8.5.3 -> 8.5.4 exactly as update-version.py writes it. Both failures were "the live index has not caught up with this build", which the PR body says should warn: the new served tag holds nothing yet, and the outgoing tag is unreachable because this build renamed it. Added `releasePending` - detected, not declared: the served tag having no records at all means the index predates this build. While it holds, both classes warn instead of failing, with the reason printed. --strict still fails both, so main and the schedule see them. Post-crawl they are real problems again and fail as before. Also deduped `missing` (lastVersionTag is in `routable` too, so it printed twice). Release simulation now: WARN + exit 0 without --strict, exit 1 with. Normal PR against the live index: exit 0. SHIM DELETED - buildApiReferenceTags no longer pushes the legacy tag. The premise is gone: the crawler retag is done. The live index holds api-reference-latest with 4,376 pages and docs-default-current with one stale record. The shim's only remaining effect was injecting that orphan into every query on the served docs AND, by naming it always-on, making the gate print it as reachable - hiding the drift the gate exists to surface. It also degenerated after promotion, and would have pulled 616 unreleased-beta pages into normal search if the crawler's /next/ exclusion ever changed. The gate now reports it correctly: `1 UNREACHABLE docs-default-current`. Test MAP updated to match; it was modelling the shim. FACTUAL CORRECTION - the cause was e92c1b1, not the 8.5.3 release. Release 8.6.0-beta.1 set lastVersion: "8.5.2" and made `current` the unreleased beta; that is what stopped the guides emitting docs-default-current. 8.5.3 was a later patch bump. Corrected in the config's SSOT block and the gate's docstring, and both now state that this is a CYCLE - the root-served tag moves on production -> beta, on patches during the beta window, and again on beta -> production - which is why the gate has to stay green through the build that renames it. Commit messages already merged cannot be corrected without a rewrite; the code comments are where it will be read. SMALLER FINDINGS - tagsEmittedByBuild returns { found, exhausted }. Hitting the file cap now reports INCONCLUSIVE rather than blaming the config for a truncated walk. - api-reference-7.6 / -6.28 print as `contextual`, not `typed-only`: they are reachable from those versions' pages with nothing typed. - Transport errors exit 0 unless --strict, so an Algolia outage or a rate limit cannot block an unrelated docs PR. - maxValuesPerFacet raised to Algolia's 1000, and pagesByTag fans out with a concurrency of 4 instead of one request per tag at once. - effectiveLastVersion declared once beside DOCS_LAST_VERSION, replacing four copies of `isPreviewBuild ? "current" : DOCS_LAST_VERSION`. - test-search-facets reads EMPTY_TAG_LIST and API_TAG_PREFIX out of the module instead of redeclaring them, so the eval'd functions no longer close over the test's copies. - Workflow comments moved above the steps they describe. Gates: test:search-facets (7), verify:search-tags, tsc --noEmit, full build. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
moritzhartmeier
left a comment
There was a problem hiding this comment.
Re-verified 046d9720 by execution rather than by reading the commit message. Both blockers are genuinely fixed, and I have resolved 11 of the 12 threads.
rewriteVersionTag — my probe now returns two top-level entries with the duplicate gone. And the regression guard is real, not decorative: I ran the new test suite against the old SearchBar and it fails with actual: 3, expected: 2. The isTagOrGroup guard is a better fix than I suggested — it also blocks appending into a non-tag group, which I had not considered.
The gate — behaves exactly as claimed:
| run | result |
|---|---|
| current state | OK, exit 0 |
| 8.5.3 -> 8.5.4 | WARN x2, exit 0 |
8.5.4 with --strict |
FAIL x2, exit 1 |
missing prints once now, and a typo'd lastVersionTag is still caught by the separate build-emission check, so releasePending does not open the hole I was worried about.
Also confirmed: shim gone and the gate now prints 1 UNREACHABLE docs-default-current; the factual correction landed in both the config SSOT block and the gate docstring with the cycle explanation; tsc clean; 7 facet tests pass; all 5 CI checks pass.
One thing left, and I do think it should be fixed here
The --strict gap, inline. It is not a bug in what ships to readers, but it hollows out both of the fixes above: the release-PR fix and the transport-error fix are each justified by a strict run that does not exist anywhere. Fifteen lines of YAML and the contract the comments describe is actually true.
Left unresolved separately: the typed-only label, which is only partially fixed — report-only, your call.
Two figures for the record, since they moved: api-reference-latest is now 4,376 pages, not the 3,407 I measured on the 21st. The commit message has the current number; the code comment in test-search-facets.cjs still cites mine.
| # Not --strict on PRs: a crawl still catching up after a deploy, a release | ||
| # build that renames the served version, or an Algolia outage all warn | ||
| # rather than blocking unrelated work. |
There was a problem hiding this comment.
Nothing in CI ever runs --strict, and there is no scheduled workflow — so both of the new downgrades are unconditional.
verify-search-tags.cjs justifies its two escape hatches on --strict existing somewhere, and refers to "the schedule" three times:
releasePending→ "--strictfails either way so main and the schedule still see them"- transport errors → "Under
--strict(main, or the schedule, where someone is watching) it still fails"
Neither exists. This workflow runs the same non-strict yarn verify:search-tags on pull_request and on push: main, and no workflow in .github/workflows/ has a schedule: trigger. grep -r -- --strict .github/ package.json matches only the comment above. So every condition the gate now downgrades is downgraded everywhere, permanently.
What that costs, using the incident this gate was built for: the release build WARNs (correctly). Push-to-main also runs pre-crawl, so it WARNs too. The unreachable API reference only becomes a FAIL on some later unrelated PR, once the crawl has caught up — and nobody reads WARN in a green log. The gate degrades from "catches it" to "might mention it eventually."
Adding --strict to the push-to-main run is not the fix — that run is also pre-crawl, so release merges would go red for exactly the reason releasePending exists. The fix is the daily --strict run the code already assumes:
on:
schedule:
- cron: "0 6 * * *"with a job that builds and runs yarn verify:search-tags --strict. Post-crawl, once a day, someone watching — which is precisely the contract the comments describe.
Related, same theme: the two new escape hatches overlap. If the file cap is hit (INCONCLUSIVE, no failure) while releasePending is true (both index checks → WARN), a genuine config drift passes fully green with no signal at all. Low probability at ~2,250 pages against a 4,000 cap, but the holes line up, and --strict is the only thing that would catch it.
HIGH - releasePending tested the wrong thing.
It asked "is the served tag absent from the index", but on the beta ->
production build DOCS_LAST_VERSION becomes "current", so lastVersionTag
becomes docs-default-current - which the index already holds with ONE stale
record (a leftover /sdks/linux/barcode-capture/get-started/). releasePending
came out false, the outgoing docs-default-8.5.3 with its 559 pages tripped
`unreachable`, and the build failed with no path to green. That is one of
the three cycle events the header says the gate must survive.
Now tested as "holds no meaningful content" against THIN_PAGE_THRESHOLD.
Nothing is masked: a served tag genuinely below the threshold is what the
THIN check reports, and it reports it either way. Verified against the live
index: promotion warns and exits 0, with `WARN "docs-default-current" -> 1
pages` printed underneath.
MEDIUM
- --strict was never passed anywhere, so every "--strict still fails"
promise in this script was empty: THIN, transport errors and Algolia-key
drift could not fail any job. Now passed on refs/heads/main only.
- The served-major assertion silently no-opped whenever lastVersion is
"current" - the entire production half of every cycle - because the major
was parsed out of a tag that carries no number. The build now states
lastVersionMajor in the manifest; the old derivation stays as a fallback
and prints a NOTE. Verified: a manifest with lastVersionTag
docs-default-current and deliberately wrong v8 routing now FAILs.
- A single empty API-reference tag only produced a NOTE, because
alwaysOnEmpty required EVERY api tag to be empty and `missing` excluded
them. If the crawler stops emitting api-reference-7.6, 2,501 pages leave
7.6 search with CI green. Now any empty api tag fails, except while a
release is pending. Verified with api-reference-8.0 in the manifest.
- assertConfigInSync drift and a missing manifest were swallowed as
"transport" and exited 0, so the gate could degrade to a no-op while
passing CI. Both now match FATAL_PATTERNS and always fail. Non-Error
throws no longer print `undefined`.
LOW
- docs-default-current (the /next/ tree) and frozen docs-version tags now
count as `contextual`, not UNREACHABLE: a reader on those pages reaches
them through their own contextual filter, exactly like the API tags. It
was tolerated only because the stale record count is 1 - at 50 crawled
/next/ pages every PR would have hard-failed on a correct config.
- withApiReferenceTags gated on CONTENT rather than position. It latched on
the first array it saw, which works only because
useAlgoliaContextualFacetFilters returns the language filter as a bare
string; if that ever gets wrapped, the API tags land in the language group
and are silently dropped. No test caught it - all of them pass a string
first.
- rewriteVersionTag's prefix filter now skips arrays. String() on a nested
array joins its elements, so an OR group whose first element was an
api-reference tag stringified to "docusaurus_tag:api-reference-…,…" and
the whole group was dropped from the top-level AND - removing the
docusaurus_tag filter entirely and returning every version's results.
- Remediation text pointed at `alwaysOnSearchTags`, which exists neither
here nor in Docusaurus. It now names buildApiReferenceTags / docsVersions.
- Deleted the orphaned JSDoc block that described a constant removed with
the migration shim and contradicted the very next block ("is NOT a
Docusaurus docs version" vs "it IS versioned"). The history it carried is
kept on buildApiReferenceTags, which is what survives the cycle.
- Corrected the preview-build comment: buildVersionTagByMajor reroutes only
the major containing `current`, so majors 6 and 7 still resolve to frozen
tags a preview has no pages for.
NOT fixed here, needs a decision: "See all results" goes to the stock
Docusaurus SearchPage, which refines docusaurus_tag on default + version
tags only, so the full-page results miss all ~4,376 API-reference pages and
its count disagrees with the modal's. Fixing it means either swizzling a
large third-party component or dropping searchPagePath. Both are product
calls, not something to slip into this PR.
Gates: test:search-facets (7), verify:search-tags across normal / patch
release / beta-promotion / --strict, tsc --noEmit, full build.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
HIGH 1 - `unreachable` never consulted the `contextual` set I had just built. A tag could print as `contextual` in the table and appear under "cannot reach" in the same run. It bites at the next minor-production release: docs-default-8.5.x loses major 8 to docs-default-current in versionTagByMajor, drops out of `routable`, stays in `contextual`, and hard-fails every PR once the crawler populates the new served tag and releasePending stops masking it. Added `&& !contextual.has(tag)`. HIGH 2 - the API tag was keyed off who is SERVED, not off what the pages LINK to. Counted in the sources, which settles it: docs/ (current) 2258 links to /data-capture-sdk, 0 versioned version-8.5.3 2258 links to /data-capture-sdk, 0 versioned version-7.6.14 0 unversioned, 2883 to /7.6/data-capture-sdk version-6.28.11 0 unversioned, 3430 to /6.28/data-capture-sdk A snapshot keeps linking to the unversioned tree until the freeze process rewrites it. `servedAtRoot` was right only by coincidence: the moment 8.5.x stops being served it emits api-reference-8.5 - a tag nothing links to and the index does not hold - so 8.5 readers get an OR-branch matching zero records. Exactly the regression this file exists to prevent. Now driven by UNVERSIONED_API_TREE_VERSIONS, with the counts recorded beside it and the condition for removing an entry stated. It lives outside docsVersions because that object goes to the docs plugin, which rejects unknown keys - "versions.current.apiTree is not allowed" failed the build when I first put it there. MEDIUM - --strict on main turned every post-release build red until the crawl finished, because it overrode the releasePending exemption. releasePending is a fact about the index, not a severity preference, so --strict no longer overrides it, and the THIN check is skipped while it holds - a thin served tag IS that state, not a separate problem. Verified: promotion exits 0 with and without --strict; an empty API tag still exits 1. - A preview build contains only `current`, so expecting the frozen majors' tags produced "the config names tags this build never emits" against a correct config. The manifest now records isPreviewBuild and the check narrows accordingly. LOW - `--manifest` with no value left manifestArg undefined, silently falling back to the local build AND re-enabling the emission scan, so a CI invocation with an empty path variable checked a different source and printed OK. Now validated. - assertConfigInSync matched the first appId/apiKey/indexName anywhere in the config, comments included. Scoped to the `algolia:` block, and a missing block is an error rather than a silent pass - this check is in FATAL_PATTERNS, so a false positive fails CI unconditionally. - current.label is load-bearing (the only source of the served major once lastVersion is "current", and buildApiReferenceTags skips a version whose number it cannot resolve). Asserted non-empty at config load. - Corrected the test's MAP comment, which claimed docs-default-current was absent while the line below added it as a key. - extract() counts braces with no string/comment awareness. Documented, and the three extractions are now asserted to be functions, so a truncated read fails by name instead of as a confusing SyntaxError inside eval. AND A DEFECT THE REVIEW SURFACED INDIRECTLY Lifting the THIN check onto releasePending created a temporal-dead-zone read - `thin` is computed before releasePending is declared - so the gate threw `Cannot access 'releasePending' before initialization`. The throw was then SWALLOWED as a transport failure and exited 0: the gate crashed and CI stayed green. Block moved above its first use, and a ReferenceError / TypeError / SyntaxError is now always fatal, because a defect in this script is never an Algolia outage. Scenario matrix, all against the live index: normal PR exit 0 patch release / --strict exit 0 / 0 beta -> production / --strict exit 0 / 0 preview build exit 0 frozen version out of routable exit 0 empty api-reference tag exit 1 --manifest with no value exit 1 Gates: test:search-facets (7), tsc --noEmit, full build. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
What was broken
Releasing 8.5.3 renamed the docs version, so
docusaurus_tagon every page atthe site root changed from
docs-default-currenttodocs-default-8.5.3—while not a single URL changed.
Two consequences, both silent:
it carried the tag
docs-default-current. While that tag matched the guides'tag, it was found alongside them for free. After the rename ~3,200 pages left
every result. A query with 106 matching pages in the index returned 86.
buildVersionTagByMajorletcurrentwin its major unconditionally. With8.6.0-beta as
currentand 8.5.3 aslastVersion, typing "v8" sent readersat the unreleased beta's tag — and it still returned results, so nothing
looked wrong.
The monitoring could only report "too few records".
What this changes
docusaurus.config.tsis the only place versions are declared and tags arebuilt.
DOCS_LAST_VERSIONis declared once besidedocsVersions(it used tobe restated in the preset), and
docVersionTagis the only constructor ofdocs-default-*strings.buildVersionTagByMajorfollowslastVersionand skips unreleased versions.version gets the API tree it belongs with —
7.6.14 → api-reference-7.6,6.28.11 → api-reference-6.28, and the version served at the root → theunversioned tree. A reader on 6.28.11 no longer gets 8.x API pages.
DOCS_LAST_VERSIONleft
scripts/update-version.pyreading thelastVersion:line, so the nextrelease would have aborted with "Already in production state". Verified by
running a real release end to end.
Why the mapping mirrors the site rather than assuming symmetry
The sitemap contains zero
/data-capture-sdk/URLs, so the crawler can onlyreach that tree by following links. The 7.6.14 guides link to
/7.6/data-capture-sdk/…, but the guides served at the root link to theunversioned
/data-capture-sdk/…— nothing anywhere links to/8.5/.Pointing the served version at
api-reference-8.5would have pointed at a treethe crawler never reaches, dropping the current API reference out of search a
second time.
Preventing a repeat
yarn verify:search-tags(in CI) compares the build against the live index:the tag a typed major routes to must be the tag the site serves; the build must
emit every tag the config names; and content under a tag search cannot reach is
an error. Crawler lag warns instead of failing.
yarn test:search-facets(in CI) — 7 assertions on what the widget actuallysends to Algolia, including that a legacy version gets its API reference and
not the current one.
Both reproduce both bugs against the pre-fix config.