Skip to content

fix(docs): keep a retagged operation's URL working instead of 404ing - #417

Merged
jfrench9 merged 1 commit into
mainfrom
bugfix/docs-retag-redirects
Sep 21, 2026
Merged

jfrench9 merged 1 commit into
mainfrom
bugfix/docs-retag-redirects

Conversation

@jfrench9

Copy link
Copy Markdown
Member

Why

The API reference went live on 09-17 against the tags the spec carried then. Two days later two refactors retagged it — Extensions: RoboLedger became eight RoboLedger: * tags on the extensions surface, and Auth shed its passkey, MFA and SSO operations into three tags of their own. Every page under the old tags moved, and the old URLs answer 404.

Those URLs sat in the sitemap Google downloaded daily for the three days they were live.

The redirects added with the extensions split cover roboledger-*, roboinvestor and graphql — the slugs the spec carries now. Under /docs/api those only existed for the nineteen hours between the API's retag and this app's split. The slugs published for three days were extensions-roboledger, extensions-roboinvestor and extensions-graphql, and none of them redirected.

What changed

Resolve by slug rather than write a rule per operation. An operation slug comes from the operationId and does not move when the tag does, so when the tag in the URL no longer holds the operation, findMovedApiOperation looks for it in the whole catalog — both surfaces — and OperationReference 308s to wherever it lives now. That covers all 57 RoboLedger operations, the RoboInvestor ones, the fourteen carved out of Auth, and the next retag, with nothing to remember. Two operations sharing a slug stays a 404, because the destination would be a guess.

Bare tag pages still need rules, since a tag that became eight has no single successor: extensions-roboledger and roboledger go to the extensions hub, extensions-roboinvestor and extensions-graphql to their pages.

Verified

Against a server running on the production spec, not a test client:

URL Before After
/docs/api/extensions-roboledger 404 308 → /docs/extensions
/docs/api/extensions-roboledger/close-period 404 308 → /docs/extensions/roboledger-fiscal-close/close-period
/docs/api/extensions-roboledger/create-event-block 404 308 → /docs/extensions/roboledger-ledger-events/create-event-block
/docs/api/extensions-roboinvestor/create-security 404 308 → /docs/extensions/roboinvestor/create-security
/docs/api/extensions-graphql/graphql-endpoint 404 308 → /docs/extensions/graphql
/docs/api/auth/get-passkey-registration-options 404 308 → /docs/api/auth-passkeys/get-passkey-registration-options
/docs/api/auth/verify-mfa 404 308 → /docs/api/auth-mfa/verify-mfa
/docs/api/auth/no-such-operation 404 404
/docs/api/auth/login-user 200 200

Every destination returns 200 — no loops. Four new unit tests on the resolver, including the ambiguity case. npm run test:all green: 584 tests, format, lint, typecheck, cf-lint.

Scope

No signal has been lost yet — nothing links to the dead URLs and Search Console reports them unknown to Google. This closes the window before one is.

🤖 Generated with Claude Code

The API reference went live on 09-17 against the tags the spec carried
then. Two days later two refactors retagged it: `Extensions: RoboLedger`
became eight `RoboLedger: *` tags on the extensions surface, and `Auth`
shed its passkey, MFA and SSO operations into three tags of their own.
Every page under the old tags moved, and the old URLs — which sat in the
sitemap Google downloaded daily for those three days — answer 404.

The redirects added with the extensions split cover `roboledger-*`,
`roboinvestor` and `graphql`, which are the slugs the spec carries *now*.
Those URLs only existed under /docs/api for the nineteen hours between
the API's retag and this app's split. The slugs that were published for
three days are `extensions-roboledger`, `extensions-roboinvestor` and
`extensions-graphql`, and none of them redirect.

Rather than write a rule per operation, resolve by slug: an operation
slug comes from the operationId and does not move when the tag does, so
when the tag in the URL no longer holds the operation, look for it in
the whole catalog and 308 to wherever it lives now. That covers all 57
RoboLedger operations, the RoboInvestor ones, the fourteen carved out of
Auth, and the next retag, without anyone remembering to add a rule. Two
operations sharing a slug stays a 404 — the destination would be a guess.

Bare tag pages still need rules, since a tag that became eight has no
single successor; those three go to the extensions hub, RoboInvestor and
GraphQL respectively.

Nothing links to the dead URLs and Search Console reports them unknown
to Google, so no signal has been lost yet — this closes the window
before one is.
@github-actions github-actions Bot added the change:normal Normal change: automated review documented on the pull request before merge label Sep 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Change classification: normal — platform or application code changed.

A normal change carries a documented automated review on this pull request before merge, in place of a second human approver. The change-classification job requests that review and passes once it has been posted. Recorded by the change-classification job.

@jfrench9

Copy link
Copy Markdown
Member Author

@claude please review this PR

@claude

claude Bot commented Sep 21, 2026

Copy link
Copy Markdown

Claude finished @jfrench9's task in 2m 24s —— View job


Review of PR #417

  • Gather context (PR description, changed files list)
  • Read changed files (next.config.js, OperationReference.tsx, openapi.ts, openapi.test.ts)
  • Check redirect/resolver logic for correctness and edge cases
  • Check tests
  • Post review feedback

Overall: looks solid, ready to merge. Resolving by operation slug instead of a rule per moved operation is the right call — it's derived from operationId (spec-guaranteed unique), degrades safely to notFound() on a genuine collision (covered by the "declines to guess when two tags share a slug" test in src/lib/__tests__/openapi.test.ts:489), and it automatically covers the Auth carve-out with the same code path that fixes the RoboLedger retag. I traced the manual verification table in the PR body against the code and it checks out logically:

  • findMovedApiOperation (src/lib/openapi.ts:552) is correctly called with the unnarrowed catalog (full, not the surface-scoped catalog) in OperationReference.tsx:176, so it can find an operation that moved to the other surface (e.g. RoboLedger operations landing on /docs/extensions).
  • The static next.config.js redirects only cover the bare tag URLs, which is correct since a retagged tag like Extensions: RoboLedger → 8 tags has no single successor page (so it must go to the hub); everything with an operation segment correctly falls through to the dynamic resolver.
  • No overlap/double-redirect between the new static rules and the existing roboledger-.*|roboinvestor rule — extensions-roboledger doesn't match the roboledger-.* regex, so there's a single clean 308, not a chain.

Two minor, non-blocking observations:

  1. generateMetadata doesn't know about the moved-operation fallback. In both src/app/(docs)/docs/api/[tag]/[operation]/page.tsx:26-30 and the extensions equivalent, load() only calls findApiOperation/findApiTag, not findMovedApiOperation. For a URL that's about to 308 (e.g. /docs/api/auth/verify-mfa), generateMetadata will compute and return the "Page Not Found" title before the page component's permanentRedirect fires. Since Next discards the rendered output (metadata included) once a redirect is thrown during the render pass, this is harmless in practice — just a bit of wasted/misleading-if-read-in-isolation work. Not worth blocking on.
  2. The bare-tag rule at next.config.js:95 matches both extensions-roboledger and plain roboledger. The PR's narrative and verification table only account for extensions-roboledger as a slug that was actually live in the sitemap — I couldn't find where bare roboledger would have been a real published URL. It's harmless today (redirects a URL nobody has to the hub), but worth a quick gut-check: if a future spec ever adds a top-level tag that slugifies to exactly roboledger, this permanent (308) redirect would silently hijack that page. If there's no evidence bare roboledger was ever live, consider dropping it from the regex.

Testing: I wasn't able to execute npm run test:all in this environment — both git fetch and npm ci require interactive approval that isn't available here, so I read the diff and tests statically rather than running them. The new tests in src/lib/__tests__/openapi.test.ts:455-513 look thorough: they cover the cross-surface move, the "no such operation" case, and the ambiguous-slug case, which are exactly the three branches findMovedApiOperation needs.

@jfrench9
jfrench9 merged commit 53189a1 into main Sep 21, 2026
7 checks passed
@jfrench9
jfrench9 deleted the bugfix/docs-retag-redirects branch September 21, 2026 00:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:normal Normal change: automated review documented on the pull request before merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant