TOF-441: Flatten redirect chains and fix internal links to redirected URLs - #174
Conversation
… URLs Co-authored-by: tylergoerzen-mxp <259741734+tylergoerzen-mxp@users.noreply.github.com>
|
Preview deployment for your docs. Learn more about Mintlify Previews.
|
Co-authored-by: tylergoerzen-mxp <259741734+tylergoerzen-mxp@users.noreply.github.com>
|
The URL rewrites preserved #fragments that no longer match a heading on the new destination, so 15 links landed at the top of the right page or, in two cases, on the wrong page entirely. Each replacement was checked against the target page's actual headings. Notable: two changelog links pointed at /docs/reports for company profiles and activation metrics, which live on group-analytics; and /docs/reports#custom-buckets was a self-link to an anchor that no longer exists, now pointing at /docs/features/custom-buckets. Also repoint 17 redirect destinations aimed at onboarding-playbook subpages that do not exist. Only guides/strategic-playbooks/ onboarding-playbook.mdx is real, and the PR already sent the content links there, so the map now agrees with them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rec #22's acceptance is zero chains, loops, or duplicate sources. The earlier pass fixed the 24 caused by fragment-matching; 40 remained where a destination matched a wildcard source such as /changelogs/* or an exact source that had itself been repointed. Resolved each destination through the redirect graph to its final landing page. Where the inherited #anchor no longer exists on that page, dropped the anchor rather than hard-coding a dead one (9 cases). Rule count unchanged at 873 — flattened only, nothing deleted, so external backlinks on old routes still resolve. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Fix 13 residual chains the first pass missed (trailing slash on /docs/ and ?sdk= query strings) and 3 dangling destinations that existed on main - Repoint ~50 links from main's AI-guides reorg to their canonical /guides/mcp, /guides/mixpanel-agent, /guides/use-mixpanel-ai paths - Fix stale anchors: onboarding-playbook/plan/tracking-strategy#..., mixpanel-agent/use-cases#rca-agent and #experiments-agent, lookup-tables double-hyphen typo, pricing group-keys anchor Verified with PR #180's check_redirects.py and check_links.py (both pass). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@mbocianski conflicts are resolved: merged 8298f05: merge only. Took main's side in all 4 content conflicts, because each points at a more specific live page than the generic onboarding-playbook fallback ( bc49444: re-flatten after the merge. Main's AI-guides reorg (#182) added redirects and new links, so the PR's zeros stopped holding:
PR #180's Left alone on purpose: |
- Drop stray "?" before tab anchors on install-mixpanel links - Make the "Mixpanel format" links relative (one pointed at mixpanel.com/docs) - Fix "Mixpanel's uses" typo in single-sign-on Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@mbocianski this is current with What changed since your review
I checked your tab anchors ( Re-verified on the merged tree (script checks exact and wildcard sources, ignoring
One question for you: |
… redirect chains; validate OpenAPI examples The frontmatter gate failed on main content (362 pages missing a description, 14 duplicate-title groups), so it would have blocked every unrelated PR. Known violations now live in scripts/docs-ci-baseline.json and only new ones fail. Entries that get fixed are reported, never fatal, so TOF-439 (#172) and TOF-441 (#174) can land without touching it. - check_redirects: reject chains (destination is itself redirected); 229 existing chains baselined for #174. Loops reported once per cycle. - check_openapi: validate media-type, parameter and schema-level examples against their schemas; fix the one invalid GDPR example. - check_code_samples: tag two bare fences that landed on main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…en-redirects # Conflicts: # docs/tracking-methods/warehouse-connectors.mdx # troubleshooting/faqs.mdx
The FAQ split (#177) moved three links this PR had already fixed into new per-topic pages, where they pointed at redirect sources again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@mbocianski I merged
The new merges added no redirect chains. The redirect count is still 882. |
|
@tylergoerzen-mxp just need an approval with write access. Looks good. Changed that one link to objective C as you mentoned. |
Resolve conflict in properties.mdx by keeping the PR side, which includes main's group-key anchor fix (#261) plus the redirect-source repoint. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Resolve docs.json conflict by taking main's redirect destination from #174, which adds a valid #group-profiles anchor. Accept a trailing .md on internal links in check_links.py: Mintlify serves every page as Markdown at <path>.md, which docs-for-ai-agents.mdx links to. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…es, OpenAPI (#180) * Initial plan * Add docs CI gates: frontmatter, code samples, links, redirects Co-authored-by: tylergoerzen-mxp <259741734+tylergoerzen-mxp@users.noreply.github.com> * Fix false positives and false negatives in the docs CI gates check_redirects.py - Honour wildcard redirect sources when resolving a destination. The docstring says chained redirects are allowed, but only exact sources were matched, so the 461 wildcard sources were ignored. That produced 25 errors on main of which only 3 were real: an 88% false-positive rate that would have fired again on the next redirect anyone added. - Detect loops. Every node in a cycle is also a source, so the chained-redirect rule silently swallowed /self -> /self and /a -> /b -> /a. check_links.py - Blank out fenced and inline code before extracting links, so a page documenting an example <a href="/docs/..."> does not fail CI. Line numbers are preserved. check_code_samples.py - Track fence length so a ```python block nested in a ````mdx block does not close the outer block early. Drop the unused FENCE_OPEN_RE and report repo-relative paths instead of absolute ones. check_frontmatter.py - An empty title no longer passes. Verified: all four pass on this branch, and each rejects a deliberate bad fixture (empty title, bare fence, dead link, redirect loop). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Add OpenAPI gate, enforce description and unique titles, pin actions Completes rec #20 Part B's gate list. - New check_openapi.py: parses all 14 specs, requires openapi/info/paths, and resolves every local $ref. Uses openapi-spec-validator for full schema validation when installed, and still runs structurally without it. Passes on all 14 specs today; rejects a spec with a dangling $ref. - check_frontmatter.py now requires a non-empty description and fails on duplicate rendered titles, which rec #11 asked for. - Pin actions/checkout and actions/setup-python to commit SHAs, matching stale.yml. Collapse four near-identical jobs into one with ordered steps, and add a concurrency group. MERGE ORDER: the frontmatter gate is red until #172 (description backfill) and #178 (title dedupe) land. Verified against the #172 tree: all description errors clear, leaving only the duplicate titles that #178 resolves. Merge this last, as the plan intends. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Baseline existing violations so the gates only block new ones; reject redirect chains; validate OpenAPI examples The frontmatter gate failed on main content (362 pages missing a description, 14 duplicate-title groups), so it would have blocked every unrelated PR. Known violations now live in scripts/docs-ci-baseline.json and only new ones fail. Entries that get fixed are reported, never fatal, so TOF-439 (#172) and TOF-441 (#174) can land without touching it. - check_redirects: reject chains (destination is itself redirected); 229 existing chains baselined for #174. Loops reported once per cycle. - check_openapi: validate media-type, parameter and schema-level examples against their schemas; fix the one invalid GDPR example. - check_code_samples: tag two bare fences that landed on main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Stop tracking Python bytecode Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Note the baseline in the workflow Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fail wildcard redirect destinations that match no page Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix docs CI violations on pages added since the gates were written Add missing descriptions to agent-intelligence and wingify, and tag three bare code fences in audit-log-streaming as text. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: tylergoerzen-mxp <259741734+tylergoerzen-mxp@users.noreply.github.com> Co-authored-by: Tyler Goerzen <tyler.goerzen@mixpanel.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: no value <270450525+hywel-mixpanel@users.noreply.github.com> Co-authored-by: hywel-mixpanel <hywel.wong@mixpanel.com>
docs.jsoncarried 873 redirect rules, and hundreds of them pointed at another redirect source rather than a live page. Content files also linked directly to redirect sources instead of canonical URLs.Relates to: https://linear.app/mixpanel/issue/TOF-441/aeo-qw4-flatten-redirect-chains-and-fix-internal-links-to-redirect
Why this matters for AEO
Redirect chains are invisible to a human clicking a link and expensive for everything else.
This is the least glamorous item in the plan and the most mechanically verifiable: the acceptance criterion is a number, and the number is now zero.
Result
Rule count is deliberately unchanged. Old routes may hold external backlinks, so this flattens destinations rather than deleting anything.
Changes
docs.json— every chained destination resolved through the graph to its final landing page, in one pass rather than per-hopCorrections made during review
24 chains survived the first pass because the flattening matched on the full destination string including its
#fragment, so anything with an anchor never matched its source. A second pass strips the fragment before lookup.15 links kept an anchor that no longer exists on the new destination. These were already stale on
main— the old page was deleted and the fragment rode through the redirect — but the rewrite would have hard-coded them. Each replacement was verified against the target page's actual headings. Two were landing on the wrong page entirely:/docs/reports#company-profilesand#activation-metricsboth belong togroup-analytics.9 anchors were dropped rather than guessed where the heading is genuinely gone. A link to the top of the right page beats a link to a dead anchor.
17 redirect destinations pointed at
onboarding-playbooksubpages that do not exist. Onlyguides/strategic-playbooks/onboarding-playbook.mdxis real. The PR had already repointed the content links there; the map itself was still dangling.Note on sequencing
PR #180 adds a CI gate that fails the build on new chains, loops, and duplicate sources. That gate can only be enforced once this merges. Merge this first.