Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
"better-sqlite3",
"esbuild",
"sharp"
]
],
"patchedDependencies": {
"fumadocs-core@16.8.12": "patches/fumadocs-core@16.8.12.patch"
}
}
}
86 changes: 86 additions & 0 deletions patches/fumadocs-core@16.8.12.patch
Original file line number Diff line number Diff line change
@@ -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@<new>`), 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) {
21 changes: 13 additions & 8 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.