Skip to content

feat(md): render Markdown footnotes via markdown-it-footnote - #243

Merged
Brooooooklyn merged 2 commits into
mainfrom
feat/md-footnote
Jul 2, 2026
Merged

Brooooooklyn merged 2 commits into
mainfrom
feat/md-footnote

Conversation

@Brooooooklyn

@Brooooooklyn Brooooooklyn commented Jul 2, 2026 •

Copy link
Copy Markdown
Member

What

Adds markdown-it-footnote support to @void/md so Markdown footnotes render:

Here is a statement.[^1]

[^1]: And here is the footnote body.

This upstreams a yarn patch we currently apply downstream against @void/md.

Why

@void/md previously had no footnote plugin, so [^1] refs/definitions passed through as literal text.

A subtle second bug blocked a naive add: compile() shared one env object across compiler.parse() and compiler.renderAsync(). Because markdown-exit's renderAsync(src, env) re-parses internally (this.renderer.renderAsync(this.parse(src, env), …)), a stateful plugin like footnote would append its refs/definitions to the same env.footnotes twice — producing duplicated footnote lists and wrong fn/fnref ids.

How

Three edits in packages/md/src/compile.ts:

  1. import footnote from 'markdown-it-footnote'.
  2. markdown.use(footnote) — registered after the emoji full plugin and before anchor, so footnote back-reference markers aren't treated as heading-anchor targets.
  3. Give each markdown-it call its own fresh env (compiler.parse(content, {}) / compiler.renderAsync(content, {})) instead of a shared object, fixing the double-accumulation. Heading/TOC extraction is unaffected — anchor ids derive per-parse from the identical content, not from shared env state.

Supporting changes:

  • markdown-it-footnote@^4.0.0 added to @void/md dependencies (minimal lockfile addition).
  • v4 ships no types, so an ambient module shim was added to src/env.d.ts matching the existing markdown-it-attrs/emoji/container shims — no @types devDependency needed.
  • Docs updated (packages/md/README.md feature list + docs/guide/pages-routing/markdown.md Footnotes section).

Note: this repo releases via v* git tags (no changesets), so no changeset was added.

Verified

  • vp run --filter ./packages/md build — passes
  • vp run --filter ./packages/md typecheck — passes
  • vp lint packages/md — 0 warnings/errors
  • vp test run packages/md — 87 passed (10 files), including 2 new tests:
    • a footnote renders exactly once (single fn1/fnref1/class="footnotes"/footnote-item)
    • headings/TOC intact alongside footnotes (slug: 'section', id="section" present)
  • Render proof (throwaway, since deleted) confirmed the exact output:
    <p>Ref here.<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></p>
    <hr class="footnotes-sep">
    <section class="footnotes">
    <ol class="footnotes-list">
    <li id="fn1" class="footnote-item"><p>The footnote body. <a href="#fnref1" class="footnote-backref">↩︎</a></p>

🤖 Generated with Claude Code


Note

Low Risk
Scoped markdown rendering and metadata changes with targeted unit tests; no auth, security, or data-path impact.

Overview
Adds footnote rendering to @void/md via markdown-it-footnote ([^1] refs and definitions → linked footnote list), with docs in the guide and package README.

Fixes a compile pipeline bug where one shared markdown-it env was passed to both parse() and renderAsync(); because renderAsync re-parses internally, stateful plugins (footnotes) would double-accumulate. Each call now uses a fresh {} env.

Aligns lightweight metadata extraction with full compile: footnotes register before heading anchors so footnote markers in headings are stripped from @void/md/pages heading text/slugs and match rendered anchor ids. Type shim added for the untyped plugin dependency.

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

Register `markdown-it-footnote` in the `@void/md` compiler so `text[^1]`
references and `[^1]: definition` blocks render into a linked footnote list.

Three changes in the compiler:

- Import `markdown-it-footnote` and register it after the emoji plugin and
  before the anchor plugin, so footnote back-reference markers are not
  themselves treated as heading-anchor targets.
- Give each markdown-it call its OWN fresh env in `compile()`. Previously
  `parse` and `renderAsync` shared one `env` object; because markdown-exit's
  `renderAsync` re-parses internally, footnote state accumulated into the same
  `env.footnotes` twice — producing duplicated footnote lists and wrong
  fn/fnref ids. A fresh env per call keeps the two parses independent, and
  heading/TOC extraction is unaffected (anchor ids derive per-parse from the
  identical content, not from shared env state).
- markdown-it-footnote@4 ships no types, so add an ambient module shim in
  `env.d.ts` matching the existing attrs/emoji/container shims (no `@types`
  dependency needed).

Verified: `@void/md` build + typecheck + lint pass; all 87 package tests pass,
including new tests asserting a footnote renders exactly once (single
fn1/fnref1/footnotes block) and that headings/TOC are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 2, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_89979c57-e635-481f-805d-aed205d4bb9f)

@socket-security

socket-security Bot commented Jul 2, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedmarkdown-it-footnote@​4.0.010010010080100

View full report

@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: cca4c69a03

ℹ️ 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 packages/md/src/compile.ts
The metadata compiler (extractMetadata) installed only markdown-it-anchor,
so a footnote ref in a heading such as `## API[^1]` stayed literal there
while compile() (which does install footnote) strips it. That produced a
different slug/text in `@void/md/pages` than the rendered anchor id, so
sidebar/TOC/search links pointed at a missing anchor.

Register footnote before anchor to mirror compile.ts, keeping metadata
heading slugs in sync with rendered ids.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 2, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_ddf3a0c4-1da3-4071-aa3c-c46915e03fe0)

@Brooooooklyn
Brooooooklyn merged commit eaf079b into main Jul 2, 2026
7 checks passed
@Brooooooklyn
Brooooooklyn deleted the feat/md-footnote branch July 2, 2026 08:35
cpojer pushed a commit that referenced this pull request Sep 11, 2026
* fix(dispatch): harden static asset storage failures

* fix(dispatch): address asset fallback review

* fix(dispatch): preserve pre-stub timeout attribution
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