Skip to content

Repoint a frozen version's API Ref links at its <major>.<minor> instead of the default/stable - #445

Merged
moritzhartmeier merged 3 commits into
mainfrom
fix/freeze-api-links
Oct 7, 2026
Merged

moritzhartmeier merged 3 commits into
mainfrom
fix/freeze-api-links

Conversation

@moritzhartmeier

@moritzhartmeier moritzhartmeier commented Sep 22, 2026 •

Copy link
Copy Markdown
Member

The bug

docusaurus docs:version snapshots docs/ verbatim, so a newly frozen
version inherits the current version's convention of linking the unversioned
API reference. It is not the current version any more, and nothing in the bump
corrected it.

That breaks during a beta window:

  • the frozen version (say 8.6.0) becomes lastVersion, served at the site root
  • the beta (8.7.0-beta.1) becomes current, served at /next/
  • both link docs.scandit.com/data-capture-sdk/

Whichever publish sets PUBLISH_DOCS_FOR_LATEST last owns that tree — so
readers of the released version silently get the beta's API reference.

It has happened. docusaurus.config.ts records the audit:

 *   version-8.5.3     210 files link docs.scandit.com/data-capture-sdk, 0 versioned
 *   version-7.6.14      0 unversioned, links docs.scandit.com/7.6/data-capture-sdk
 *   version-6.28.11     0 unversioned, links docs.scandit.com/6.28/data-capture-sdk

8.5.3 shipped frozen-but-unrewritten. 7.6.14 and 6.28.11 link their own
lines only because someone fixed them by hand — most recently #443, which
repointed 7.6's c_api links last week.

The fix

minor_beta now rewrites the snapshot's links to its own <major.minor> line,
for both data-capture-sdk/ and stable/c_api/. docs/ is deliberately
untouched — the current version should keep linking the unversioned tree.

No check that the target resolves — deliberately

An earlier revision verified that <major.minor>/data-capture-sdk/ resolved
before rewriting. That was wrong in principle: the bump runs well before the
release
, so the snapshot and the release notes can be reviewed while there is
still time to change them. The API reference for that line is published by the
release pipeline, which has not run yet — so the check would have refused every
bump.

The links are correct by construction and unresolvable by definition at the
moment they are written. Verifying them belongs after the release publishes,
where the existing post-release checks live — not here. The rewrite touches no
network.

Verified

Simulated a real freeze of the current docs/:

Rewritten 2,267 links across 214 files
Left in the snapshot's Markdown pointing at the unversioned tree 0
docs/ changed 0 files — still 210 unversioned, as intended

Also runs clean under LC_ALL=C, and a snapshot with no API links raises rather
than passing silently.

And a version whose line does not exist yet (9.9.0) is rewritten rather than
refused, which is the case the bump actually runs in.

Context

Part of the docs-deploy cleanup (data-capture-sdk!38971, !39020, !39061).
This is also a prerequisite for automating the API-reference prune on the SDK
side: once a frozen version links <major.minor>/, that line becomes
load-bearing, and any pruning rule has to know not to delete it.

What this does NOT cover

Two things surfaced in review that this PR deliberately leaves alone. Both are
real; neither is fixable by a substitution over versioned_docs/.

src/data/features.json holds 143 unversioned apiUrl values. It is shared
rather than snapshotted — docs:version copies docs/ only — so
src/components/FeatureList renders the current data into whatever page shows it.

Correcting an earlier version of this section: no frozen version renders
that component today.
<FeatureList/> is used on 10 pages, all in docs/;
7.6.14 has the 6 label-definitions.md pages and none uses it, and 6.28.11
does not have them. The risk starts at the next freeze, when the snapshot
picks up those 10 pages while still importing the shared data file. Fixing it
means resolving the version at render time rather than rewriting a file, which
is #447. The table above is scoped to the snapshot's Markdown accordingly.

The search tag moves in lockstep with the Algolia crawler.
linksToOwnApiLine() derives apiReferenceTagsByVersionTag by reading what each
version links, so after this rewrite the frozen version derives
api-reference-8.6. Until the crawler has an action producing records under that
tag, yarn verify:search-tags reports THIN — a warning on PRs, a failure on
main with --strict (THIN_PAGE_THRESHOLD = 100). docusaurus.config.ts
already predicts this for the 8.5 case.

Prerequisite of the next minor_beta, not of this PR. This is inert until
that freeze, so it merges ahead of the crawler action. But before the next freeze
runs, both of these have to be in place, or verify:search-tags --strict goes
THIN on main:

Worth putting on the release checklist rather than leaving it to whoever
remembers this thread.

`docusaurus docs:version` snapshots docs/ verbatim, so a newly frozen version
inherits the CURRENT version's convention of linking the UNVERSIONED API
reference. It is not the current version any more, and nothing in the bump
corrected it.

That breaks during a beta window. The frozen version is served at the site root
while the beta owns /next/, and both link the same unversioned tree. Whichever
publish sets PUBLISH_DOCS_FOR_LATEST last owns that tree, so readers of the
RELEASED version silently get the beta's API reference.

version-8.5.3 shipped in exactly that state -- docusaurus.config.ts records it
as "210 files link docs.scandit.com/data-capture-sdk, 0 versioned". 7.6.14 and
6.28.11 link their own lines only because someone corrected them by hand
afterwards, most recently in #443 for 7.6's c_api.

minor_beta now rewrites the snapshot's links to its own <major.minor> line,
both for data-capture-sdk/ and for stable/c_api/. docs/ is untouched: the
current version should keep linking the unversioned tree.

The rewrite refuses rather than guessing. Before touching anything it samples
real links from the snapshot, applies the rewrite, and checks they resolve --
freezing a version with links to a 404 would be worse than the problem being
fixed. Only the link kinds the snapshot actually uses are required, so a
version that never mentions the linux C API is not blocked by a missing
/X.Y/c_api/. SKIP_API_TARGET_CHECK=1 bypasses it when offline.

The check probes the rewritten LINKS, not the constructed prefix.
https://docs.scandit.com/8.6/data-capture-sdk/ is a 404 -- there is no index at
that level -- while .../8.6/data-capture-sdk/ios/... resolves, so a prefix
check would have blocked every bump. Links carrying a `${...}` template
expression are skipped, since the framework segment is filled in at render
time.

Verified by simulating a freeze of the current docs against the live origin:
2,267 links across 214 files rewritten, nothing left pointing at the
unversioned tree, docs/ unchanged, and the rewritten URLs return 200. A freeze
of an unpublished line (9.9.0) is refused with the snapshot left untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-10-07 09:00 UTC

@moritzhartmeier moritzhartmeier changed the title Repoint a frozen version's API links at its own line Repoint a frozen version's API Ref links at its <major>.<minor> instead of the default/stable Sep 22, 2026
The check verified that <major.minor>/data-capture-sdk/ resolved before
rewriting a frozen snapshot's links to it. That is wrong in principle, not just
weak: the bump runs well BEFORE the release, so the release notes and the
snapshot can be reviewed while there is still time to change them. The API
reference for that line is published by the release pipeline, which has not run
yet -- so the check would have refused every bump.

The links are correct by construction and unresolvable by definition at the
moment they are written. Verifying them belongs after the release publishes,
not in the bump.

Removes sample_api_links(), api_target_published(), SKIP_API_TARGET_CHECK and
the urllib import. The rewrite itself is unchanged and no longer touches the
network.

Re-verified offline: a simulated freeze of the current docs rewrites 2,267
links across 214 files, leaves nothing pointing at the unversioned tree, leaves
docs/ unchanged, and a version whose line does not exist yet (9.9.0) is
rewritten rather than refused, which is the whole point.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread scripts/update-version.py Outdated
Comment thread scripts/update-version.py
…o-match

Two problems in the rewrite, both reported in review.

read_text()/write_text() had no encoding, so they used the locale default. 57
of the ~676 files under docs/ are not decodable as cp1252, so on a Windows
checkout this raised part-way through the walk -- AFTER `docs:version` had
already written versioned_docs/, versioned_sidebars/ and versions.json, and
with files earlier in sorted() order already rewritten. main()'s catch-all
printed "Unexpected error", leaving a half-frozen repo that cannot simply be
re-run, since the version now exists. Both calls are explicit UTF-8 now.

A zero-match logged "No API-reference links to rewrite" and continued. That is
the wrong direction: every version so far carries hundreds of these links, so
zero means the convention moved and the patterns no longer match -- in which
case the snapshot silently keeps linking the unversioned tree, which is the
8.5.3 bug this change exists to prevent. It now raises, naming the patterns
that found nothing.

Re-verified: a simulated freeze still rewrites 2,267 links across 214 files,
runs clean under LC_ALL=C, and a snapshot with no API links raises instead of
passing silently.

Reported by @eugenia-scandit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@moritzhartmeier

Copy link
Copy Markdown
Member Author

Thank you — all four reproduce, and the first two are fixed in 912677aba.

Encoding. Confirmed exactly: 57 of the 676 files under docs/ fail to decode
as cp1252, including the three you named. And your point about where it sits is
the part that made it blocking rather than cosmetic — docs:version has already
written versioned_docs/, versioned_sidebars/ and versions.json, files
earlier in sorted() order are already rewritten, and main()'s catch-all
reports it as "Unexpected error". Re-running then fails at docs:version
because the version exists. Both calls are explicit UTF-8 now, and I verified the
rewrite under LC_ALL=C (preferred encoding US-ASCII) as well as normally.

Zero-match. Agreed, and the direction was wrong. Every version so far carries
hundreds of these links, so zero means the convention moved and the patterns no
longer match — in which case the snapshot silently keeps linking the unversioned
tree, which is the 8.5.3 bug this exists to prevent. It now raises and names the
patterns that found nothing.

features.json. You are right, and I overstated the result. src/data/features.json
is shared rather than snapshotted, src/components/FeatureList renders it, and
versioned_docs/version-7.6.14/barcode-scanning.md is among the frozen pages
that use it — so those 143 apiUrl values keep pointing at the unversioned tree
whatever this PR does. A substitution over versioned_docs/ cannot reach them;
it needs the version resolved at render time. I have narrowed the claim in the
description to "the snapshot's Markdown" and added a section saying so. Happy to
open that separately — it is the same class of bug in a different place.

Search tag. Also right, and this is the one I would most like your steer on.
The rewrite makes linksToOwnApiLine() derive api-reference-8.6 for the frozen
version, and until the crawler has an action for that path verify:search-tags
goes THIN — warning on PRs, failing on main under --strict.
docusaurus.config.ts already predicts this for the 8.5 case, so the behaviour is
expected rather than surprising, but it means the crawler change has to land
with the first freeze that uses this
, not with this PR. I have written that into
the description. If you would rather this not merge until the crawler action
exists, that seems reasonable to me — it is inert until the next minor_beta
either way.

And thank you for confirming the core substitution — non-overlapping, idempotent,
already-versioned links skipped. That was the part I had tested least
adversarially.

@moritzhartmeier

Copy link
Copy Markdown
Member Author

Correcting one thing I said above about features.json, because I overstated it.

I wrote that versioned_docs/version-7.6.14/barcode-scanning.md is among the
frozen pages rendering FeatureList. It is not. My grep was
FeatureList|features, and the loose features matched the word inside
features-by-framework links. Checking the component properly:

docs/                          10 pages use <FeatureList/>
versioned_docs/version-7.6.14   0
versioned_docs/version-6.28.11  0

FeatureList landed in 909d295cb (2025-12-02) and 7.6.14 froze in 7777abbe0
(2026-05-08), but those label-definitions pages did not carry the component
then — 7.6.14 has 6 of them and none uses it.

So nothing is broken today: the 143 unversioned apiUrl values are rendered
only by current-version pages, where linking the unversioned API reference is the
correct convention.

Your conclusion still holds, though, and the timing is the uncomfortable part.
docs:version copies docs/ verbatim, so the next minor_beta snapshots all 10
pages with <FeatureList/> intact, and the component imports the shared
src/data/features.json rather than a snapshot of it. The frozen version then
renders today's unversioned URLs, and once the next minor publishes they resolve
to its API reference. That is the same bug this PR fixes for Markdown, in a place
a substitution over versioned_docs/ structurally cannot reach — and it lands at
the very freeze this PR exists to make safe.

I also checked withCurrentDocsPath(), which looked like it might already cover
this. It does not:

const match = path.match(/^\/sdks\/([^/]+)/);
if (!match || !isUnreleasedFramework(match[1])) return path;

Only relative /sdks/<framework> paths for unreleased frameworks are prefixed;
an absolute https://docs.scandit.com/data-capture-sdk/... is returned untouched.

Opening it as a separate PR, taking the tidier option: version-agnostic paths in
features.json with the component building the URL from the active version —
the shape products.json already uses, so the two data files end up consistent.

One consequence to flag: that fix makes a frozen version's feature links point at
<major.minor>/ too, so it runs into the same Algolia api-reference-<line> tag
question as this PR. The two want to land alongside the crawler action rather
than independently.

@eugenia-scandit eugenia-scandit left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: check for matching links before docs:version runs, so the zero-match guard can't leave the repo half-frozen.

@moritzhartmeier
moritzhartmeier merged commit dcbf479 into main Oct 7, 2026
5 checks passed
@moritzhartmeier
moritzhartmeier deleted the fix/freeze-api-links branch October 7, 2026 09:00
moritzhartmeier added a commit that referenced this pull request Oct 7, 2026
…version

features.json stored 143 absolute apiUrl values pointing at the UNVERSIONED API
reference, and FeatureList rendered them as-is. One shared data file serves
every docs version, so there is no single correct prefix to bake into it.

Nothing is broken today: the component is used on 10 pages, all in docs/, and
for the current version the unversioned tree is the right target -- the same
convention its Markdown uses. The problem is the next freeze. `docs:version`
copies docs/ verbatim, so the snapshot gets all 10 pages with <FeatureList/>
intact while still importing the SHARED features.json. The frozen version then
renders whatever the unversioned tree documents, which after the following minor
publishes is the newer release. That is the bug #445 fixes for Markdown, in a
place a substitution over versioned_docs/ cannot reach.

features.json now stores the path WITHIN the API reference
("ios/label-capture/api/serial-number-barcode.html#..."), and a new
apiReferenceUrl() applies the version at render time: the unversioned tree for
`current`, <major.minor>/data-capture-sdk/ for a frozen version. FeatureList
reads the active version through useActiveDocContext(), the pattern
FrameworkSelectorMobile already uses.

withCurrentDocsPath() did not cover this and still does not: it prefixes
relative /sdks/<framework> paths for unreleased frameworks only, and returns an
absolute URL untouched.

Verified: apiReferenceUrl() across current / frozen / already-absolute inputs,
tsc clean, a full build passes, and `yarn verify:frameworks` still passes --
that gate reads features.json, so the shape change had to keep it happy.

NOT verified, stated plainly: how a FROZEN page renders, because no frozen
version uses the component today, and FeatureList is client-rendered -- the
static HTML carries "Loading features..." and the data ships in the JS bundle,
so a build cannot observe those links either way. The frozen path rests on
useActiveDocContext() reporting the version name, which the existing selector
component already depends on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
moritzhartmeier added a commit that referenced this pull request Oct 20, 2026
…#447)

* fix(features): resolve API-reference links against the reader's docs version

features.json stored 143 absolute apiUrl values pointing at the UNVERSIONED API
reference, and FeatureList rendered them as-is. One shared data file serves
every docs version, so there is no single correct prefix to bake into it.

Nothing is broken today: the component is used on 10 pages, all in docs/, and
for the current version the unversioned tree is the right target -- the same
convention its Markdown uses. The problem is the next freeze. `docs:version`
copies docs/ verbatim, so the snapshot gets all 10 pages with <FeatureList/>
intact while still importing the SHARED features.json. The frozen version then
renders whatever the unversioned tree documents, which after the following minor
publishes is the newer release. That is the bug #445 fixes for Markdown, in a
place a substitution over versioned_docs/ cannot reach.

features.json now stores the path WITHIN the API reference
("ios/label-capture/api/serial-number-barcode.html#..."), and a new
apiReferenceUrl() applies the version at render time: the unversioned tree for
`current`, <major.minor>/data-capture-sdk/ for a frozen version. FeatureList
reads the active version through useActiveDocContext(), the pattern
FrameworkSelectorMobile already uses.

withCurrentDocsPath() did not cover this and still does not: it prefixes
relative /sdks/<framework> paths for unreleased frameworks only, and returns an
absolute URL untouched.

Verified: apiReferenceUrl() across current / frozen / already-absolute inputs,
tsc clean, a full build passes, and `yarn verify:frameworks` still passes --
that gate reads features.json, so the shape change had to keep it happy.

NOT verified, stated plainly: how a FROZEN page renders, because no frozen
version uses the component today, and FeatureList is client-rendered -- the
static HTML carries "Loading features..." and the data ships in the JS bundle,
so a build cannot observe those links either way. The frozen path rests on
useActiveDocContext() reporting the version name, which the existing selector
component already depends on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(features): hide features newer than the docs version the reader is on

features.json records the SDK version each feature arrived in, and one shared
file serves every docs version -- so a frozen version lists features that did
not exist in it. 86 framework entries are newer than 7.6.

That was harmless while their links pointed at the unversioned API reference,
which always documents the newest release. Resolving links against the reader's
own line turns every one of them into a 404: /7.6/data-capture-sdk/ has no page
for a class added in 8.2. The fix would have introduced the breakage it exists
to prevent.

Availability is now version-aware at all three sites: the feature filter, the
isAvailable flag, and the per-framework chip list.

The comparison is numeric PER COMPONENT, which matters more than it looks:
"6.5" is earlier than "6.28", and both a string comparison ("6.5" > "6.28") and
a float one (6.5 > 6.28) get that backwards. 6.28 is a real docs version, so it
is not hypothetical. `current` shows everything, and an unparseable version on
either side shows the feature rather than hiding it, so a data-entry slip
degrades to the previous behaviour rather than silently emptying a table.

Also rebased onto main for #448: features.json is regenerated from main with
the host-stripping re-applied, so the dotnet.ios / dotnet.android corrections
are preserved rather than hand-merged (32 values checked).

Verified: the comparison across both frozen versions and `current` including
the 6.5/6.28 case, tsc clean, build passes, verify:frameworks passes.

Reported by @eugenia-scandit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants