Skip to content

feat: improve AcceptMarkdown support#209

Merged
dcrawbuck merged 9 commits into
mainfrom
duncan/sw-5396-docs-full-acceptmarkdowncom-support
Jun 15, 2026
Merged

feat: improve AcceptMarkdown support#209
dcrawbuck merged 9 commits into
mainfrom
duncan/sw-5396-docs-full-acceptmarkdowncom-support

Conversation

@dcrawbuck

@dcrawbuck dcrawbuck commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

This adds AcceptMarkdown-compliant content negotiation for docs routes, including Fumadocs-based q-value negotiation, Vary: Accept, alternate/canonical Link headers, and 406 responses for unsupported media types.
It adds Markdown alternate URL helpers plus root and nested /llms.mdx/docs routes so /docs/, .md, .mdx, and canonical docs URLs all resolve to Markdown where appropriate.
It switches processed Markdown generation to Fumadocs placeholder rendering for common MDX components, making Markdown cleaner for agents while keeping HTML pages unchanged.
It adds tests for negotiation and rewrite behavior; local validation covered bun test, bun run build:cf, curl checks against local dev, and agent-browser checks.


Note

Medium Risk
Touches global request middleware and content negotiation for all /docs traffic, so regressions could affect browsers, crawlers, and agent clients; scope is large but covered by new unit tests and leaves HTML rendering paths mostly unchanged.

Overview
Adds AcceptMarkdown-style behavior for Superwall Docs: canonical HTML URLs can negotiate to Markdown via Accept (with q-values), direct /docs/...md and .mdx routes, and HTML responses gain Vary: Accept plus Link: alternate for markdown. Unsupported Accept on known doc paths returns 406; unknown paths can return 404 before negotiation.

Markdown serving is centralized in new route helpers and splat routes (/{$}.md, SDK variants, nested /llms.mdx/docs). Copy/view actions and page <head> now point at buildMarkdownAlternatePath (.md for pages, /llms.mdx/docs/ for the docs index) instead of hard-coded .mdx URLs.

Exported markdown quality improves via Fumadocs processed-markdown options (MDX components as placeholders with custom renderers), a markdownImageMap remark plugin + image URL restoration, and shared normalizeDocsAssetPath.

LLM index files move to section-scoped paths like /docs/ios/llms.txt (with legacy /docs/llms-ios.txt still documented), add an Agents section, and update vibe-coding docs to describe .md URLs and the new table links.

Reviewed by Cursor Bugbot for commit 870762a. Bugbot is set up for automated code reviews on this repo. Configure here.

@linear

linear Bot commented Jun 3, 2026

Copy link
Copy Markdown

SW-5396

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 3, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
superwall-docs-staging 870762a Commit Preview URL

Branch Preview URL
Jun 15 2026, 09:34 PM

@dcrawbuck dcrawbuck changed the title Improve docs AcceptMarkdown support feat: improve AcceptMarkdown support Jun 3, 2026
Comment thread source.config.ts
@dcrawbuck dcrawbuck force-pushed the duncan/sw-5396-docs-full-acceptmarkdowncom-support branch from 35ee720 to 3bf524c Compare June 4, 2026 21:49
@dcrawbuck dcrawbuck marked this pull request as ready for review June 4, 2026 21:55

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3bf524c276

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/routes/{$}[.]md.ts
return buildMarkdownRouteResponse(includeBody ? body : null, slugs);
}

export const Route = createFileRoute("/{$}.md")({

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Use a splat route for nested markdown alternates

When the alternate URL is for a nested doc such as /docs/ios/quickstart/install.md, this route will not handle it: the generated route tree for this file is /{$}.md, i.e. a single path segment with a .md suffix, while TanStack splats are the /$ segment captured in params._splat. Since shouldBypassLLMRewrite now skips all .md paths, those nested alternates fall through to the normal docs splat as ios/quickstart/install.md and 404 instead of returning the page markdown. Please make the .md/.mdx routes catch nested paths before the suffix, or stop bypassing the existing markdown rewrite for direct suffix URLs.

Useful? React with 👍 / 👎.

Comment thread source.config.ts
rehypeCodeOptions: isDevelopment ? false : undefined,
remarkPlugins: (existing) => [
remarkImagePaths,
remarkMarkdownImageMap,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Run the image map after includes are expanded

For pages whose images come from <include>d shared MDX, this plugin runs before remarkInclude, so those included image nodes are not added to markdownImageMap even though they are present in the processed markdown later. For example, content/docs/ios/guides/advanced/observer-mode.mdx includes content/shared/observer-mode.mdx, which contains /images/om_transactions.png; that page's markdown alternate can be left with an unresolved __img... placeholder instead of the restored image URL. Move the map collection after include expansion or otherwise collect images from included content.

Useful? React with 👍 / 👎.

Comment thread src/start.ts
Comment on lines +160 to +162
headers: {
Vary: "Accept",
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve CORS on markdown negotiation redirects

When a browser on another origin fetches a canonical docs URL with Accept: text/markdown, this 307 is part of the CORS redirect chain but only includes Vary. The final markdown route sets Access-Control-Allow-Origin: *, yet the browser can block before following it because the redirect response itself lacks that header. Add the same CORS header to the negotiation redirect so cross-origin consumers can use the advertised AcceptMarkdown URL, not just the direct .md URL.

Useful? React with 👍 / 👎.

Comment thread src/start.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 04d16ec766

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/source.ts
};

const htmlImagePlaceholderPattern = /<img\b([^>]*?)\bsrc=["']__img(\d+)["']([^>]*)\/?>/g;
const markdownImagePlaceholderPattern = /(!\[[^\]]*]\(\s*)<?__img(\d+)>?(\s*\))/g;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Resolve placeholders for titled markdown images

When a markdown image includes a title, the processed placeholder can be emitted as ![alt](__img0 "title"), but this pattern only matches placeholders followed immediately by ), so those images are left as __img… in the .md alternate. Several existing docs use titled images, including content/docs/ios/quickstart/install.mdx, so those pages can expose unresolved placeholders instead of usable image URLs.

Useful? React with 👍 / 👎.

Comment thread plugins/remark-markdown-image-map.ts
Comment thread src/start.ts
@dcrawbuck dcrawbuck force-pushed the duncan/sw-5396-docs-full-acceptmarkdowncom-support branch from 0f9a1a7 to da40466 Compare June 11, 2026 20:01
Comment thread src/start.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: da4046622d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/remark-markdown-image-map.ts Outdated
Comment on lines +45 to +47
const placeholder = getImagePlaceholder(node);
const url = getImageUrl(node);
if (placeholder && url) imageMap[placeholder] = url;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Capture JSX image sources in the markdown image map

For MDX <img> nodes this condition can never add an entry: with a real src, getImagePlaceholder() is undefined, and with a __imgN placeholder, getImageUrl() returns undefined. Pages such as content/docs/agents/create-an-agent.mdx use Frame-wrapped JSX images, so when the processed markdown contains <img src="__imgN">, restoreImagePlaceholders() has no URL for that placeholder and the .md alternate can expose unresolved image placeholders instead of usable image links. Collect the JSX image URLs under the placeholder index order rather than requiring both values on the same node.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5cff604ed6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/start.ts

const directRewrite = rewriteLLMMarkdown(url.pathname) || rewriteLLMMdx(url.pathname);
if (directRewrite) return directRewrite;
if (negotiateDocsRepresentation(request) !== "text/markdown") return undefined;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep llms.txt links fetchable as markdown

With this negotiation gate, a normal curl -sL or LLM crawler following the bare page URLs emitted in llms.txt no longer gets the markdown redirect, because curl sends Accept: */* and the new negotiator defaults wildcards to HTML. That makes the generated llms summaries' page links return HTML unless callers know to add an Accept: text/markdown header, so either emit .md links in the summaries or update the advertised fetch path/header before relying on Accept negotiation here.

Useful? React with 👍 / 👎.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 870762a. Configure here.

for (const url of jsxImageUrls) {
imageMap[`__img${imageIndex}`] = url;
imageIndex += 1;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

JSX image placeholder index mismatch

Medium Severity

markdownImageMap assigns __img keys to all markdown images first, then appends JSX <img> sources. Processed markdown from Fumadocs uses placeholders in document order. When a JSX image sits between markdown images, restoreImagePlaceholders can swap URLs so exported markdown shows the wrong images.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 870762a. Configure here.

@dcrawbuck dcrawbuck merged commit 803597c into main Jun 15, 2026
3 checks passed
@dcrawbuck dcrawbuck deleted the duncan/sw-5396-docs-full-acceptmarkdowncom-support branch June 15, 2026 23:00
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.

1 participant