Add client-side "Did you mean" suggestion to the 404 page - #3024
Draft
claude[bot] wants to merge 2 commits into
Draft
Add client-side "Did you mean" suggestion to the 404 page#3024claude[bot] wants to merge 2 commits into
claude[bot] wants to merge 2 commits into
Conversation
Mintlify's native 404 page suggests pages using content semantics, which handles renames well but misses simple typos: /models1 currently suggests three /inference/* pages and never /models. This adds 404-suggest.js, which lexically matches the dead path against the site's own /sitemap.xml (fetched only after a 404 is detected) and inserts a "Did you mean /models?" line above Mintlify's suggestion list. The two mechanisms are complementary, so Mintlify's list is kept rather than replaced. The panel also offers a prefilled docs search as the primary action (search is free) and a click-only "Ask AI" affordance that opens the existing Kapa widget with submit:false, so 404 bot traffic can never auto-fire a paid assistant message. No build step, no dependencies, no hardcoded page list. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Md238DeoXErHLL2jq96xU6
Contributor
|
Preview deployment for your docs. Learn more about Mintlify Previews.
|
Mintlify documents the 404 hooks (not-found-container,
not-found-recommended-pages-list, not-found-title) under "Element
selectors", defined as targeted "with no # or . prefix" — read literally,
they are tag names. But names from that same documented list demonstrably
render as classes: this repo's own scripts/css-minify/buttons.css matches
textarea.chat-assistant-input and button.chat-assistant-send-button, with
comments written from live-DOM inspection. Which form the 404 view uses
has not been observed, and cannot be from here.
Detection tolerated the ambiguity already (the #page-title-absent gate
plus a title/text probe). The insertion anchor did not: a single
host.querySelector('.not-found-recommended-pages-list') with no fallback.
If the class form is wrong, the panel is still injected — into #content-area
— but no longer sits above Mintlify's recommendation list, so the PR's
headline behaviour ("our answer is read first") is lost silently while
everything still looks fine.
So look every documented hook up in all three forms via one queryHook()
helper, class first, then bare element, then id. Separate queries rather
than one comma-separated list, because querySelector returns the first
match in DOCUMENT order, not selector order, and could otherwise pick the
less likely form. render() and run() now share findHost(), so the
"already injected?" check and the injection target cannot diverge. The
no-anchor path still leads the host (insertBefore firstChild) rather than
appending, so placement degrades to "above the list" rather than "below it".
Behaviour is unchanged if the class form was right all along.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Md238DeoXErHLL2jq96xU6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Matt Linville · Slack thread
Description
Prototype for review. Adds one new root file,
404-suggest.js.Before. Today, visiting
docs.wandb.ai/models1shows three suggested pages, all under/inference, and never/models.After. The same URL shows a "Did you mean
/models?" link above those suggestions, plus up to two smaller alternates.Mintlify's own suggestions are kept, not replaced. Its list is generated from page content semantics, which is exactly right for a page that was renamed and exactly wrong for a slug that was mistyped. This change adds the second half: a
/models1that is one character away from/modelsis a spelling problem, and lexical matching on the path solves it deterministically. The two mechanisms are complementary, so ours is inserted above theirs rather than in place of it.How it works
On page load the script checks for Mintlify's documented 404 DOM hooks. Only after a 404 is detected does it fetch
/sitemap.xml— which Mintlify auto-generates for every site — to get the live page list. That means normal pageviews pay nothing for this, nothing is hardcoded, the list can never drift out of sync with the docs, and the ~300 servable pages that aren't indocs.json's navigation are covered for free. If the fetch fails it degrades to a 29-entry built-in list of top-level landing pages, which still resolves/models1correctly.Ranking blends a Sørensen–Dice bigram score with normalized Levenshtein distance on the last path segment, plus full-path similarity, segment-vocabulary overlap, and a nearest-existing-ancestor bonus. Typo junk (trailing digits,
_/+/%20separators, case, plurals,.htmlsuffixes, wrong path depth) is normalized away first. A cheap bigram prefilter runs ahead of any edit-distance work; it also means bot noise like/wp-adminand/.envproduces no suggestions at all.The panel is inserted immediately above
not-found-recommended-pages-list, so our answer is read first. It carries two escape hatches: the primary button opens the docs search modal prefilled with the de-junked slug (search is free), and a de-emphasized "Ask AI instead" opens the already-installed Kapa widget with the query prefilled andsubmit: false. That flag matters — 404s attract heavy bot traffic and assistant messages are billed per message, so the assistant is never auto-fired; it only ever runs from a real human click, and the button isn't rendered at all if the widget is absent.No build step, no dependencies, no config change, ~9 KB gzip. It follows the conventions of the existing root scripts (
local-links-same-tab.js,dropdown-tabs-navigate.js,code-group-language-persist.js): dependency-free IIFE,varonly, JSDoc throughout, and the sameDOMContentLoaded+ delayed-retry + debouncedMutationObserver+pageshowboot pattern. It also pushes adocs_404_suggestion_shownevent to the existing GA4/GTMdataLayer, since Mintlify fires no analytics event for 404s — without it there's no way to tell whether this helps.Accuracy — measured, not estimated
docs.json. That number is honest but mixes two different problems together. Decomposed:/models/support/dark_mode→/support/models/articles/how-do-i-enable-dark-mode. That is precisely what Mintlify's AI list is good at, and it is why that list stays.models1ranks/modelsfirst at 0.87, ahead of/support/models(0.74) and/inference/models(0.73).Open risk — please read before approving
The
not-found-*DOM hooks are not verified against the live DOM. They are officially documented by Mintlify, but documented as element selectors and explicitly marked "subject to change" — and I had no egress todocs.wandb.aito confirm the rendered markup. The preview deployment is the test: load/models1on the preview URL. If the panel doesn't appear, the selector form is wrong and that's the fix.Partial mitigations already in place: a
document.title/ body-text fallback if the hooks change, gated on#page-titlebeing absent so it cannot fire on the real articlesupport/inference/articles/api-error-code-404-model-not-found(whose title contains "404"); and suggestion hrefs use the real slug rather than the normalized one, because 80 of 1,068 pages have underscores or uppercase in their path and linking to a hyphenated form would itself 404. Both of those were live bugs caught in testing.Two smaller risks:
errors.404.redirectisn't pinned indocs.json, so if anyone turns on "Redirect to home" in the Mintlify dashboard this script silently never runs; and the Kapaopen({query, submit})signature is unverified, though it's fullytypeof-guarded and falls back to search.Known third-party error — not from this change. Clicking "Ask AI" on the preview surfaces
Error in verifying browser for feedback submission. Captcha token could not be obtained.That comes from the kapa.ai widget the site already loads. Verified against kapa's published bundle: their question-submission path requests anask_aireCAPTCHA action but reports the failure using the string from their feedback path, so the "feedback submission" wording is misleading.Kapa.open({query, submit: false})— what this script calls — only sets the query text in the widget's own React context; the user-initiated send that follows runs the identicalsubmitQuerypath a directsubmit: truecall would, so this script can neither introduce nor bypass the captcha step. The underlying failure is a reCAPTCHA Enterprise token error on kapa's side, most often caused by the reCAPTCHA script being blocked client-side. Worth re-verifying on production after merge.This is a stopgap. The durable fix is real redirects for dead paths that actually get traffic. This helps the long tail nobody will ever enumerate, and the
dataLayerevent is there to show whether it's worth keeping.Testing
node --checkpasses; a harness boots the real file in a stubbed DOM and asserts 30/30 ranking cases, the 404-detection truth table including the false-positive case, and that every suggested href is a real page/models1on the preview URL and confirm the panel renders above Mintlify's suggestions — not yet done, this is the one thing that needs a browsermint dev)