diff --git a/package.json b/package.json index 0980599..5fc3667 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,9 @@ "better-sqlite3", "esbuild", "sharp" - ] + ], + "patchedDependencies": { + "fumadocs-core@16.8.12": "patches/fumadocs-core@16.8.12.patch" + } } } diff --git a/patches/fumadocs-core@16.8.12.patch b/patches/fumadocs-core@16.8.12.patch new file mode 100644 index 0000000..c9ebf2a --- /dev/null +++ b/patches/fumadocs-core@16.8.12.patch @@ -0,0 +1,86 @@ +# Carry `Handle.peek` onto the wrapped serializer handlers in `defaultStringifier`. +# +# WHY THIS PIN EXISTS +# ------------------- +# `page.data.getText('processed')` -- what `apps/docs/lib/source.ts` serves from +# `/llms-full.txt` and from all 79 per-page `/llms.mdx` bodies -- emitted HTML +# numeric character references into files that are not HTML, and two of them broke +# the markdown link they sat in. Measured on `main` at 50aacb9, before this patch: +# 67 numeric character references (58 encoding an asterisk, 5 a backtick, 4 a +# closing parenthesis) and 4 of 661 link targets malformed, in each of the two +# outputs. Two of those broken targets also made themselves invisible to the +# absolute-URL rewrite in `app/llms-full.txt/route.ts`, whose `SITE_LINK` pattern +# needs a literal closing parenthesis, so they were served relative in a file whose +# whole purpose is to be read somewhere else. +# +# THE DEFECT +# ---------- +# `defaultStringifier` (`dist/mdx-plugins/stringifier.js`) wraps every +# `mdast-util-to-markdown` serializer handler through `modHandler`, and the wrapper +# does not carry the handler's `peek` property. `containerPhrasing` uses +# `Handle.peek` to read the NEXT sibling's first character cheaply and WITHOUT side +# effects; with `peek` missing it invokes that sibling's full handler instead, and +# the lookahead leaves `state.attentionEncodeSurroundingInfo` set from node N+1. +# The loop then applies those flags to node N -- which is why an emphasis run's +# opening marker is encoded while its closing partner is left alone. +# +# The fix is to copy `.peek` from the handler onto the wrapper. It is upstream's +# bug, not this repo's, and this patch is byte-for-byte what upstream would ship. +# +# UPSTREAM STATE THIS TRACKS +# -------------------------- +# Measured 2026-09-08 by unpacking the published tarballs: the defect is present in +# 16.8.12 (installed here), 16.15.1, 16.15.2 and 16.15.8 (`latest` on that date). +# The anchor line is byte-identical in all four. "Upgrade" is not a fix. +# +# Not reported upstream from this repository -- this session's GitHub access is +# scoped to `objectstack-ai/*` and cannot open an issue on `fuma-nama/fumadocs`. A +# ready-to-post upstream report is filed as a comment on +# https://github.com/objectstack-ai/objectos/issues/197 and the maintainer routes +# the filing. +# +# WHEN THIS FILE CAN BE DELETED +# ----------------------------- +# When an installed `fumadocs-core` carries the fix itself: `grep peek +# node_modules/fumadocs-core/dist/mdx-plugins/stringifier.js` finds something. +# Delete this file, remove `pnpm.patchedDependencies` from the root `package.json`, +# re-run `pnpm install`, and rebuild `apps/docs` -- the reference count must stay 0. +# +# ON A VERSION BUMP, THIS PATCH IS EXPECTED TO FAIL LOUDLY +# -------------------------------------------------------- +# It is pinned to 16.8.12 by the key in `package.json`, so any other version simply +# does not receive it and the defect returns silently -- which is why the counts +# above are worth re-measuring on any bump. Beyond that, 16.15.x reformats an +# `if` in the same function two hunks up, so a patch generated here does not +# necessarily line up there. A Dependabot bump of this package therefore cannot be +# merged on green: regenerate the patch against the new version in the same PR +# (`pnpm patch fumadocs-core@`), or drop it if upstream has landed the fix. +# +diff --git a/dist/mdx-plugins/stringifier.js b/dist/mdx-plugins/stringifier.js +index cf19eea9336acb66414cf3e2e75f2b1aa2bfd138..636346eec49cd0760d84f874c2a352bd78a804fd 100644 +--- a/dist/mdx-plugins/stringifier.js ++++ b/dist/mdx-plugins/stringifier.js +@@ -66,7 +66,22 @@ function defaultStringifier(config = {}) { + } + const customToMarkdown = { handlers: { _custom(node, _, state, info) { + const handlers = state.handlers; +- for (const k in handlers) handlers[k] = modHandler(handlers[k], node.ctx); ++ for (const k in handlers) { ++ const handler = handlers[k]; ++ const wrapped = modHandler(handler, node.ctx); ++ ++ // `Handle.peek` is how `mdast-util-to-markdown`'s `containerPhrasing` ++ // reads the NEXT sibling's first character cheaply and without side ++ // effects. A wrapper that does not carry it makes that lookahead invoke ++ // the sibling's FULL handler instead, which leaves ++ // `state.attentionEncodeSurroundingInfo` set from node N+1 and applies it ++ // to node N — encoding markdown punctuation as numeric character ++ // references in plain-text output. See the header of ++ // `patches/fumadocs-core@16.8.12.patch` in this repository. ++ if (handler.peek) wrapped.peek = handler.peek; ++ ++ handlers[k] = wrapped; ++ } + return state.handle(node.root, void 0, state, info); + } } }; + return function(root, ctx) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 595e804..fea662c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,11 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +patchedDependencies: + fumadocs-core@16.8.12: + hash: 042796c0fe0ae66ccc2f1eadd4df4b8399a43c39c49facd3a0d938b182ed8d79 + path: patches/fumadocs-core@16.8.12.patch + importers: .: @@ -19,13 +24,13 @@ importers: dependencies: fumadocs-core: specifier: 16.8.12 - version: 16.8.12(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.170.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.16.0(react@19.2.7))(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3) + version: 16.8.12(patch_hash=042796c0fe0ae66ccc2f1eadd4df4b8399a43c39c49facd3a0d938b182ed8d79)(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.170.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.16.0(react@19.2.7))(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3) fumadocs-mdx: specifier: 15.0.7 - version: 15.0.7(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.17)(fumadocs-core@16.8.12(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.170.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.16.0(react@19.2.7))(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: 15.0.7(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.17)(fumadocs-core@16.8.12(patch_hash=042796c0fe0ae66ccc2f1eadd4df4b8399a43c39c49facd3a0d938b182ed8d79)(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.170.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.16.0(react@19.2.7))(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) fumadocs-ui: specifier: 16.8.12 - version: 16.8.12(@tailwindcss/oxide@4.3.3)(@types/mdx@2.0.13)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(fumadocs-core@16.8.12(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.170.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.16.0(react@19.2.7))(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(tailwindcss@4.3.3) + version: 16.8.12(@tailwindcss/oxide@4.3.3)(@types/mdx@2.0.13)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(fumadocs-core@16.8.12(patch_hash=042796c0fe0ae66ccc2f1eadd4df4b8399a43c39c49facd3a0d938b182ed8d79)(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.170.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.16.0(react@19.2.7))(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(tailwindcss@4.3.3) lucide-react: specifier: ^1.16.0 version: 1.16.0(react@19.2.7) @@ -6115,7 +6120,7 @@ snapshots: fsevents@2.3.3: optional: true - fumadocs-core@16.8.12(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.170.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.16.0(react@19.2.7))(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3): + fumadocs-core@16.8.12(patch_hash=042796c0fe0ae66ccc2f1eadd4df4b8399a43c39c49facd3a0d938b182ed8d79)(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.170.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.16.0(react@19.2.7))(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3): dependencies: '@orama/orama': 3.1.18 estree-util-value-to-estree: 3.5.0 @@ -6149,14 +6154,14 @@ snapshots: transitivePeerDependencies: - supports-color - fumadocs-mdx@15.0.7(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.17)(fumadocs-core@16.8.12(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.170.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.16.0(react@19.2.7))(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7): + fumadocs-mdx@15.0.7(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.17)(fumadocs-core@16.8.12(patch_hash=042796c0fe0ae66ccc2f1eadd4df4b8399a43c39c49facd3a0d938b182ed8d79)(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.170.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.16.0(react@19.2.7))(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7): dependencies: '@mdx-js/mdx': 3.1.1 '@standard-schema/spec': 1.1.0 chokidar: 5.0.0 esbuild: 0.28.0 estree-util-value-to-estree: 3.5.0 - fumadocs-core: 16.8.12(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.170.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.16.0(react@19.2.7))(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3) + fumadocs-core: 16.8.12(patch_hash=042796c0fe0ae66ccc2f1eadd4df4b8399a43c39c49facd3a0d938b182ed8d79)(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.170.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.16.0(react@19.2.7))(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3) js-yaml: 4.1.1 mdast-util-mdx: 3.0.0 picocolors: 1.1.1 @@ -6177,7 +6182,7 @@ snapshots: transitivePeerDependencies: - supports-color - fumadocs-ui@16.8.12(@tailwindcss/oxide@4.3.3)(@types/mdx@2.0.13)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(fumadocs-core@16.8.12(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.170.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.16.0(react@19.2.7))(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(tailwindcss@4.3.3): + fumadocs-ui@16.8.12(@tailwindcss/oxide@4.3.3)(@types/mdx@2.0.13)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(fumadocs-core@16.8.12(patch_hash=042796c0fe0ae66ccc2f1eadd4df4b8399a43c39c49facd3a0d938b182ed8d79)(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.170.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.16.0(react@19.2.7))(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3))(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(tailwindcss@4.3.3): dependencies: '@fumadocs/tailwind': 0.0.5(@tailwindcss/oxide@4.3.3)(tailwindcss@4.3.3) '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -6191,7 +6196,7 @@ snapshots: '@radix-ui/react-slot': 1.2.4(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) class-variance-authority: 0.7.1 - fumadocs-core: 16.8.12(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.170.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.16.0(react@19.2.7))(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3) + fumadocs-core: 16.8.12(patch_hash=042796c0fe0ae66ccc2f1eadd4df4b8399a43c39c49facd3a0d938b182ed8d79)(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.170.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.17)(lucide-react@1.16.0(react@19.2.7))(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(zod@4.4.3) lucide-react: 1.16.0(react@19.2.7) motion: 12.40.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) next-themes: 0.4.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7)