Repository navigation
fix(deps): take fumadocs 16.15.17, which links "View as Markdown" under the base path - #413
Conversation
…er the base path From 16.15.13 the page actions popover handed out its "View as Markdown" link as given, while Copy Markdown on the same page prefixed Vite's base. A portal hosted under a path linked readers to the host's root, where the Markdown companion is not (fuma-nama/fumadocs#3620). 16.15.17 runs the link through the same withBasePath, and Copy Markdown now fails on an error response instead of copying the error page. A new test renders the real popover through Vite with base '/docs/' and '/' and reads the link off the markup; it fails on 16.15.15. The loader now decodes both sides of a slug lookup, so a decoded splat finds its page without slugsFromSplat; page-slugs.test.ts says so. Part of apimatic/apimatic-io#2275. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
How to testBoth checks run on a portal served from this branch. Setup
1. View as Markdown under
|
mrafnadeem-apimatic
left a comment
There was a problem hiding this comment.
Reviewed against .ai/instructions.md; comments inline. Please squash and merge. The second commit, fix comment, doesn't pass commitlint (type-empty, subject-empty), and squashing leaves only the PR title on dev.
| "fumadocs-mdx": "15.4.0", | ||
| "fumadocs-openapi": "11.4.1", | ||
| "fumadocs-ui": "16.15.15", | ||
| "fumadocs-ui": "16.15.17", |
There was a problem hiding this comment.
With this bump, Copy Markdown throws when the .md fetch fails (layouts/shared/page-actions.js:26), but fumadocs-ui's useCopyButton has no .catch. A failed copy is now an unhandled rejection, and the reader sees nothing: no check mark, no error, and the clipboard keeps its old contents.
It should show Failed. Try Again. Retrying is real, because 16.15.16 stopped caching failed responses, so another click fetches again. The same catch also covers a denied clipboard write.
MarkdownCopyButton has no failure state or label to hook into, so the portal needs to own the button. npx @fumadocs/cli add ai/page-actions installs LLMCopyButton as source: catch there, show the label, and leave the button enabled. An installed copy no longer picks up upstream fixes, so it has to keep fumadocs' withBasePath prefix itself.
There was a problem hiding this comment.
Not done in this PR, moved to a follow-up. Reported upstream as fuma-nama/fumadocs#3634, since the missing .catch is in fumadocs' shared useCopyButton.
| # Quarantined until 2026-10-06, and taken early: 16.15.17 links "View as Markdown" under | ||
| # the base path (apimatic/apimatic-io#2275), on top of 16.15.15's stringifier fix for | ||
| # apimatic/apimatic-io#2260, without which no portal page may contain bold text. | ||
| - 'fumadocs-core' | ||
| - 'fumadocs-ui' | ||
| - 'mdast-util-to-markdown' |
There was a problem hiding this comment.
.ai/instructions.md (Adding / upgrading dependencies) says only @apimatic/* is exempt from minimumReleaseAge, and that a fresh release comes in by waiting it out or with a one-off pnpm install --ignore-minimum-release-age. These entries are bare names, so they exempt every future fumadocs-core, fumadocs-ui and mdast-util-to-markdown release, not just the ones taken here, and nothing enforces "until 2026-10-06". The comment is also three lines of release history, where the instructions allow one line of why.
The lockfile already pins 16.15.17, and a locked version doesn't hit the gate again, so the entries and their comment can go:
| # Quarantined until 2026-10-06, and taken early: 16.15.17 links "View as Markdown" under | |
| # the base path (apimatic/apimatic-io#2275), on top of 16.15.15's stringifier fix for | |
| # apimatic/apimatic-io#2260, without which no portal page may contain bold text. | |
| - 'fumadocs-core' | |
| - 'fumadocs-ui' | |
| - 'mdast-util-to-markdown' |
If they have to stay, pin them to exact versions ('fumadocs-core@16.15.17', 'fumadocs-ui@16.15.17', 'mdast-util-to-markdown@2.1.3'), give them a one-line reason (# Taken before the 7-day gate for apimatic/apimatic-io#2260 and #2275.), and update instructions.md to match.
There was a problem hiding this comment.
Done in dc85447. Removed the three entries and the comment; pnpm install stays up to date with the lockfile as is.
| micromark-util-sanitize-uri: 2.0.1 | ||
| micromark-util-symbol: 2.0.1 | ||
| micromark-util-types: 2.0.3 | ||
| micromark-util-types: 2.0.2 |
There was a problem hiding this comment.
The re-resolution moved the seven micromark-extension-gfm-* packages to micromark-util-types 2.0.2, while micromark stays on 2.0.3, so the lockfile now carries both. 2.0.3 was published 2026-09-26 and is still inside the 7-day gate, which is why pnpm picked 2.0.2 here. Run pnpm dedupe once it clears (2026-10-03), or note in the description that the split is expected, so it doesn't surface as unexplained churn in a later PR.
There was a problem hiding this comment.
Noted the split in the description. pnpm dedupe can't run until 2.0.3 clears the gate on 2026-10-03, so I'll do it then.
| /** | ||
| * Renders the page actions the content route renders, for a page whose Markdown companion is | ||
| * at `markdownUrl`. The popover is stubbed to render its content inline, as an open popover | ||
| * would, so the links can be read off the server-rendered markup. | ||
| */ |
There was a problem hiding this comment.
.ai/instructions.md allows a comment only when the design can't carry it, and then as one line of why. The names can carry this one. Delete the JSDoc, and rename popoverStub → alwaysOpenPopover and POPOVER_STUB → ALWAYS_OPEN_POPOVER (lines 5, 29, 44, 49). That keeps the one non-obvious fact: the popover renders its content inline, so the links are in the server-rendered markup.
| /** | |
| * Renders the page actions the content route renders, for a page whose Markdown companion is | |
| * at `markdownUrl`. The popover is stubbed to render its content inline, as an open popover | |
| * would, so the links can be read off the server-rendered markup. | |
| */ |
| }; | ||
| } | ||
|
|
||
| /** The `href` of the popover's "View as Markdown" link when the site is built with `base`. */ |
There was a problem hiding this comment.
viewAsMarkdownHref(base, markdownUrl) already says this, and .ai/instructions.md rules out comments that restate the code.
| /** The `href` of the popover's "View as Markdown" link when the site is built with `base`. */ |
| // Externalized, fumadocs-ui would be loaded by Node as is, and `import.meta.env.BASE_URL` | ||
| // -- what the page actions prefix their links with -- would never be defined. |
There was a problem hiding this comment.
One line is enough for this why (.ai/instructions.md):
| // Externalized, fumadocs-ui would be loaded by Node as is, and `import.meta.env.BASE_URL` | |
| // -- what the page actions prefix their links with -- would never be defined. | |
| // Bundled, so Vite defines import.meta.env.BASE_URL for fumadocs-ui. |
| server: { middlewareMode: true, hmr: false, ws: false }, | ||
| // Externalized, fumadocs-ui would be loaded by Node as is, and `import.meta.env.BASE_URL` | ||
| // -- what the page actions prefix their links with -- would never be defined. | ||
| ssr: { noExternal: ['fumadocs-ui'] }, |
There was a problem hiding this comment.
For the description: this checks fumadocs-ui under a hand-built Vite config, not the portal's vite.config.ts, and through SSR, where the real popover is closed and the link only renders on the client. It works as a pin on the upstream fix. Once vite.config.ts sets base, the base-path work should verify the link in a built portal.
There was a problem hiding this comment.
Added to the description.
| try { | ||
| const { render } = (await server.ssrLoadModule(ENTRY)) as { render: (url: string) => string }; | ||
| const markup = render(markdownUrl); | ||
| const link = /<a href="([^"]*)"[^>]*>(?:(?!<\/a>).)*View as Markdown/s.exec(markup); |
There was a problem hiding this comment.
This only matches when href is the anchor's first attribute. If fumadocs moves rel/target ahead of it, the test fails with the popover renders a "View as Markdown" link, which isn't what it guards. Match href anywhere in the tag:
| const link = /<a href="([^"]*)"[^>]*>(?:(?!<\/a>).)*View as Markdown/s.exec(markup); | |
| const link = /<a [^>]*href="([^"]*)"[^>]*>(?:(?!<\/a>).)*View as Markdown/s.exec(markup); |
| // fuma-nama/fumadocs#3620: from 16.15.13 the link was handed out as given, so a portal hosted | ||
| // under a path sent the reader to the host's root, where the Markdown companion is not. |
There was a problem hiding this comment.
.ai/instructions.md rules out comments that narrate a change. The description already cites fuma-nama/fumadocs#3620, and the describe/it names say what's checked.
| // fuma-nama/fumadocs#3620: from 16.15.13 the link was handed out as given, so a portal hosted | |
| // under a path sent the reader to the host's root, where the Markdown companion is not. |
| // Since fumadocs-core 16.15.17 the loader decodes both sides of the lookup, so the splat | ||
| // finds the page without slugsFromSplat too. | ||
| it('is found from the splat as it arrives too', () => { |
There was a problem hiding this comment.
This narrates a version change, which .ai/instructions.md rules out. The test name can carry it:
| // Since fumadocs-core 16.15.17 the loader decodes both sides of the lookup, so the splat | |
| // finds the page without slugsFromSplat too. | |
| it('is found from the splat as it arrives too', () => { | |
| it('is found from the splat as it arrives, without slugsFromSplat', () => { |
Drop the fumadocs release-age excludes, since the lockfile already pins the versions taken. Stop the page-actions test's Vite server from watching the checkout and writing a dependency cache, match the link's href in any attribute position, and trim comments the names now carry. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… dev merge The merge renamed the 12.1.0 entry to 12.1.1 by hand, so it kept 12.1.0's dependencies and missed @fumadocs/json-schema, and its peers still named fumadocs-core and fumadocs-ui 16.15.15, which the lockfile no longer has. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
|
🎉 This PR is included in version 2.0.0-beta.3 🎉 The release is available on: Your semantic-release bot 📦🚀 |



Fixes fuma-nama/fumadocs#3620 ("View as Markdown" link ignores the Vite base path since 16.15.13) by taking fumadocs 16.15.17, the release that carries its fix.
The bug
From fumadocs-ui 16.15.13, the page actions popover handed out its View as Markdown link exactly as given, while Copy Markdown on the same page prefixed Vite's
base. Withbase: '/docs/', a reader on/docs/guides/introwas linked to/guides/intro.md, which resolves against the host's root and 404s; Copy Markdown fetched/docs/guides/intro.mdand succeeded.The change
fumadocs-coreandfumadocs-uigo from 16.15.15 to 16.15.17. Inlayouts/shared/page-actions.js:href: markdownUrlhref: withBasePath(markdownUrl)fetch(url).then(res => res.text())— a 404 page is copied as Markdown!res.okCopy Markdown under
portal serveThe
res.okcheck above is the fumadocs-ui 16.15.16 fix for fuma-nama/fumadocs#3612: an error response is no longer copied or cached as the page's Markdown.Copy Markdown still caches a successful response for the life of the page, so under
portal serveit keeps copying a page's Markdown from before an edit until the browser is refreshed. That was reported as fuma-nama/fumadocs#3628, and the maintainer closed it as expected in dev mode: the cache lasts until the next refresh, and a built portal is unaffected.Tests
New
test/portal-template/page-actions.test.ts. Renders the realViewOptionsPopover, imported assrc/routes/$.tsximports it, through a Vite server built with a givenbase, and reads the link off the server-rendered markup.fumadocs-uiis kept out of SSR externals soimport.meta.env.BASE_URLis defined, and the Radix popover is stubbed to render its content inline. The server doesn't watch the checkout or write a dependency cache, so the two cases take about 1 s in the default suite.This checks fumadocs-ui under a hand-built Vite config, not the portal's
vite.config.ts, and through SSR, where the real popover is closed and the link only renders on the client. It pins the upstream fix. Oncevite.config.tssetsbase, the base-path work should verify the link in a built portal.basehref/docs//docs/guides/intro.md/guides/intro.md//guides/intro.mdtest/portal-template/page-slugs.test.ts. 16.15.17 also changed the loader to decode both sides of a slug lookup (slugsKey), so a decoded splat now finds its page withoutslugsFromSplat. The test that pinned the old miss now asserts the hit.Lockfile
The re-resolution moved the seven
micromark-extension-gfm-*packages tomicromark-util-types2.0.2, whilemicromarkstays on 2.0.3, so the lockfile carries both. 2.0.3 is still inside the 7-day gate until 2026-10-03; apnpm dedupeafter that merges them.The
minimumReleaseAgeExcludeentries forfumadocs-core,fumadocs-uiandmdast-util-to-markdownare gone: the lockfile pins the versions taken, and a locked version doesn't hit the gate again.Follow-up
Copy Markdown now throws on an error response, but fumadocs-ui's
useCopyButtonhas no.catch, so a failed copy is an unhandled rejection with no feedback to the reader. Reported upstream as fuma-nama/fumadocs#3634; showing a failure state is left to a separate PR.Checked
pnpm test(tsc, mocha, ESLint): 1503 passing, 44 pending.🤖 Generated with Claude Code