feat(mdx): add MDX support with component embedding, routing, and content indexing - #282
Open
skafai wants to merge 167 commits into
Open
feat(mdx): add MDX support with component embedding, routing, and content indexing#282skafai wants to merge 167 commits into
skafai wants to merge 167 commits into
Conversation
…tracer - Add topcoat-mdx, topcoat-mdx-grammar, topcoat-mdx-macro to workspace members - Create three crate Cargo.toml files following macro trio pattern - Register mdx feature in topcoat facade crate with compile_mdx! re-export - Make ViewWriter, WriteView, ExprKind, MatchArmsBuilder pub in topcoat-view-grammar (required for raw HTML passthrough via write_str_unescaped) - Add Nodes::new(), Nodes::from(Vec), Nodes::into_vec() constructors - Add Attributes::default() implementation - Implement parse.rs: get_parse_options() with GFM + MDX JSX + frontmatter - Implement walker.rs: walk_node(), walk_nodes(), walk_to_writer(), mdx_to_view() covering paragraph, heading, text, emphasis, strong, inline code, blockquote, thematic break, break, and raw HTML passthrough - Add unit tests for parse options and walker (15 tests passing)
- tracer_compiles: verifies macro expands without errors - tracer_raw_html_passthrough: verifies raw HTML passes through unescaped in rendered output - Fixture: tracer.mdx with heading, paragraph, bold, italic, raw HTML
- compile_mdx! reads .mdx file at compile time, resolves path relative to CARGO_MANIFEST_DIR with path traversal guard (T-01-01) - Parses with markdown-rs using get_parse_options() (GFM + MDX + frontmatter) - Walks mdast through walk_to_writer() into ViewWriter - Raw HTML nodes pass through via write_str_unescaped() (D-03) - Text nodes go through write_text() for escaping (T-01-03) - Parser errors convert to syn::Error (OQ-3, T-01-04) - Integration tests: tracer_compiles and tracer_raw_html_passthrough - Adds markdown and topcoat-view-grammar deps to macro crate
- Add walk_link: <a href="url" title="..."> children </a>
- Add walk_image: <img src="url" alt="alt" title="...">
- Add walk_code_block: <pre><code class="language-{lang}">...</code></pre>
- Add walk_list: <ul>/<ol> with <li> children
- Add walk_list_item: <li> with optional checkbox for task lists
- Add walk_table: <table> with <thead>/<tbody>/<tr>/<th>/<td>
- Add walk_table_cell: <td>/<th> with text-align style attribute
- Add walk_delete: <del> children </del>
- Add helper functions: create_attribute, create_attribute_bool,
with_attributes, normal_element_with_attrs, void_element_with_attrs,
self_closing_element, make_ident
- All attribute values go through AttributeValue::LitStr which uses
write_attribute_value() escaping at render time (T-02-01, T-02-02)
- 32 unit tests passing (11 original + 21 new)
- commonmark.mdx fixture: headings 1-6, paragraphs, inline formatting, links, images, blockquotes, lists (ordered/unordered), code blocks with/without language, thematic breaks, hard breaks - gfm.mdx fixture: tables with alignment, strikethrough, task lists with checked/unchecked items, autolinks - raw_html.mdx fixture: block-level raw HTML passthrough (div, table) - compile_mdx.rs: 8 integration tests (4 compile + 4 render) verifying full macro expansion and rendered HTML output - Raw HTML passthrough verified end-to-end (MDX-05): block-level HTML appears verbatim in rendered output, not escaped
- is_safe_url now blocks all data: URIs, not just data:text/html - data:image/svg+xml URIs can execute JS via SVG event handlers (onload) - Update walk_link and walk_image doc comments to reflect broader blocking - Add unit tests for is_safe_url (WR-01): javascript:, vbscript:, data: blocking - Fix split closing parenthesis on walk_image (WR-03) for readability
Add comments noting that the markdown version must stay synchronized between topcoat-mdx-macro and topcoat-mdx-grammar since both use markdown-rs types directly.
Thread WalkContext through all walker functions so the component registry and error buffer are available when walking MdxJsxFlowElement and MdxJsxTextElement nodes. Adds: - WalkContext<'a> with components registry and errors RefCell buffer - coerce_attr_value() with bool/int/float/str smart coercion - walk_jsx_attributes() producing NamedArg list from mdast attributes - walk_jsx_element() and walk_jsx_text_element() for component lookup - Unit tests for coercion, attribute walking, and JSX element resolution Co-Authored-By: Claude <noreply@anthropic.com>
Introduce mdx_components! macro that expands to a braced block of Ident => Path pairs parseable by compile_mdx! as the component registry. Follows the asset! pattern: public macro delegates to internal helper. Supports trailing commas and qualified paths via $path:path constraint. Co-Authored-By: Claude <noreply@anthropic.com>
… emission
- Parse mdx_components!{Ident => Path, ...} macro invocation in compile_mdx!
- Construct WalkContext with component registry from parsed tokens
- Thread WalkContext through walk_to_writer() calls
- Drain ctx.errors into syn::Error diagnostics after walking
- Support backward-compatible one-arg form: compile_mdx!("path")
- Re-export mdx_components! from topcoat facade crate
- Add topcoat-mdx base crate as optional facade dependency
- Create components_basic.mdx integration test fixture
- Create plain .md fixtures for backward compatibility verification
- Add compile_mdx_components.rs with 10 integration tests:
- Two-arg form compiles and renders with component registry
- One-arg form backward compatible (existing .mdx)
- Plain .md files compile and render correctly
- Unknown component error propagation at walker level
- Update macro Cargo.toml dev-dependencies for grammar/view-grammar
- components_nested.mdx: NestedOuter containing NestedInner with props - components_self_closing.mdx: Empty tag pair and raw HTML passthrough - components_bare_attrs.mdx: Bare attributes coerce to boolean true - components_prop_types.mdx: All smart coercion types (bool, int, float, str) - components_mixed_content.mdx: Markdown and components at same level - components_child_content.mdx: Component wrapping child component - 12 new integration tests (compile + render per fixture) - All 22 component tests pass, 8 Phase 01 tests still pass
- Add #[must_use] to get_parse_options, WalkContext::new/empty, coerce_attr_value - Replace wildcard view::* import with explicit list - Add # Errors doc section to mdx_to_view - Simplify map_or to starts_with for uppercase check - Simplify map_or/let-else for component registry lookup - Use format! interpolation for variables in macro error messages - Compare Ident directly instead of .to_string() for mdx_components check - Merge Html/MdxjsEsm arms with #[allow(clippy::match_same_arms)] - Fix doc backticks for PascalCase references
walk_jsx_text_element used ?.map()? which silently dropped unregistered PascalCase components inside paragraphs with no compile-time diagnostic. Now matches walk_jsx_element behavior: pushes an error to ctx.errors before returning None. Adds test to verify the error is reported.
Add a span field to WalkContext so generated literals use the compile_mdx! invocation's file-path span instead of Span::call_site(). This makes compiler errors point to the macro call site rather than the macro name when a component prop type mismatches. Updates coerce_attr_value to accept a span parameter, wired through walk_jsx_attributes and the macro layer.
Clarify that raw HTML nodes are not sanitized — MDX files are trusted source content. Notes that link/image URL safety is handled separately via is_safe_url() checks.
The macro accepts both mdx_components!{...} and a bare {...} block
before the file path. Document the bare form so the API surface
matches what users can actually write.
The one-arg form was already covered by tracer_compiles and tracer_renders in compile_mdx.rs. Removes redundant tests from compile_mdx_components.rs to reduce maintenance burden.
Remove the stored fat_arrow field and #[allow(dead_code)] annotation. Parse and discard the => token directly in the Parse impl, which is the standard syn pattern for tokens consumed only for sequencing.
Tests walk mdast through the walker, not through the compile_mdx! proc-macro. Renamed to walker_reports_unknown_component and walker_reports_unknown_component_from_fixture to avoid misleading readers into thinking the macro pipeline is exercised.
Documents that the Badge component must use the tag-pair form rather than self-closing, due to markdown-rs parsing behavior.
…tter<T>, and mdx_page! - Grammar: extract_frontmatter() pulls YAML from first root child - compile_mdx!: emits YAML const + view tokens, skips Yaml node in walk - Frontmatter<T>: FromRequest extractor reading from extensions - mdx_page!: proc-macro registering .mdx as page route with frontmatter - Uses serde-saphyr for compile-time YAML deserialization
- Grammar: tests for YAML present, none, heading-first, frontmatter-only - Macro: backward compat one-arg, no-frontmatter, complex frontmatter - Fixtures: frontmatter_empty.mdx, frontmatter_complex.mdx
- Facade: replace scattered pub use lines with pub mod mdx - Base crate: remove extern crate self and feature-gate deps - Tests: update imports to mdx::compile_mdx
Scan a directory at compile time using ignore::Walk, derive kebab-case route paths from file names, and register each .mdx file as a page route via inventory::submit!. Accepts optional 'prefix' argument for route path prepending. Includes path traversal guard (T-03-04) and .gitignore respect (T-03-05). Re-exports mdx_pages! through topcoat-mdx base crate and topcoat facade.
Cover nested directories, kebab-case filename conversion, empty directory scanning, and files without frontmatter.
The `discover` feature submitted `MdxComponentMapping` entries to a global inventory that nothing ever read. It could not have worked: component resolution happens inside the proc macro, while inventory is a link-time mechanism, so a macro invocation cannot observe another compilation unit's registrations. The submission was also unreachable, since `mdx_components!` expands to a token block that is not valid Rust on its own, and its submit arm referenced a nonexistent `internal` module. Drop both mapping types, the collect/submit machinery, the feature, and the `inventory` dependency. `mdx_components!` is now only the token producer its consumers already treated it as. Correct the docs that promised global component discovery, including the `mdx-discover` feature that never existed, and fix three `mdx_components.md` doctests that bound the macro to a variable and failed to compile. Rename `examples/mdx/discover` to `examples/mdx/mdx-pages` and rewrite it around the discovery that does work: `mdx_pages!` scans a directory, registers a `PageFn` per file, and `Router::builder().discover()` mounts them, with one shared component registry and an index listing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
These files drifted from `cargo fmt` and `cargo topcoat fmt` output, so CI formatting checks fail on them independently of any behavior change. Purely mechanical: no source edits beyond what the formatters produce. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`ExprKind`'s docs linked to `Chunk::Expr`, which is private. The workspace denies `rustdoc::broken_intra_doc_links`, so this failed the whole doc build. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Neither feature could run, and no test or example exercised either one.
`frontmatter = Type` emitted `const FM: T = T { title: "..." }`, which cannot
compile: a `String` field is not const-constructible from a `&str` literal.
Its consumer was unreachable regardless, since the router exposes no mutable
extensions accessor and `mdx_page!` generates its own handler, leaving no user
handler for an extractor to run in. The conversion also panicked outright on
frontmatter keys that are not Rust identifiers and on YAML `.inf`/`.nan`.
`<!-- more -->` excerpts could never parse: MDX rejects HTML comments, and both
extensions share one parser config, so the marker was a hard parse error. Had
it parsed, the two-writer split would have dropped every pre-marker node from
the rendered page, because the excerpt tokens were computed and discarded.
Frontmatter parsing itself stays: it is still stripped from output, and
`mdx_pages!` still reads `title`, `date`, `excerpt`, and `tags` into its index.
Dropping `Frontmatter<T>` leaves the base crate with no use for `anyhow`,
`topcoat-core`, or `topcoat-router`, so those dependencies go too.
BREAKING CHANGE: the `frontmatter = Type` argument to `mdx_page!` and
`mdx_pages!` and the `Frontmatter<T>` extractor are removed. Read page
metadata from the `mdx_pages!` content index instead.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The macro path built its `WalkContext` with empty definition and footnote maps
and never ran the collection pass, which only existed inside `mdx_to_view`.
Every `[text][ref]` therefore failed with "unknown reference link target", and
footnote sections were never emitted at all. Grammar unit tests passed because
they call `mdx_to_view` directly, and the docs-site page named "Reference
Links" happens to contain only inline links.
Collect definitions before the walk and append the footnote section after it,
matching what `mdx_to_view` already did.
Footnote back-references pointed at `#fnref-{id}`, an anchor nothing emitted,
so every backlink was dangling. The referencing anchor now carries that id.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three defects in `mdx_page!`/`mdx_pages!` output: Both macros emitted `inventory::submit!` unconditionally, so a crate using `features = ["mdx"]` without `discover` failed to compile with "cannot find `inventory` in `internal`". Gate the submission behind a `discover` feature on the macro crate and forward it from the facade, mirroring how `topcoat-router` already does this. Frontmatter `tags` were emitted as `&"a", "b"` rather than `&["a", "b"]`, so any file with tags failed to compile: a single tag as a type error, several as a parse error. The existing coverage hand-constructed an `MdxIndexEntry` and so never exercised the codegen, and every fixture avoided tags. Malformed frontmatter panicked the proc macro, surfacing as a bare "proc macro panicked" with no span. Report it as a `syn::Error` naming the file instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`try_apply_override` duplicated `build_override_component` but read attribute keys with `ident.first.to_string()` instead of `html_ident_to_string`, so any test going through it validated different behavior than production for hyphenated attributes such as `data-lang`. Delegate instead of duplicating. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drop the `frontmatter = Type`, `Frontmatter<T>`, and `<!-- more -->` excerpt sections for the features removed alongside them, and point metadata readers at the `mdx_pages!` content index. Correct two standing inaccuracies: `.md` was described as "plain markdown with no component support", when both extensions share one parser config, so components work in `.md` and MDX syntax rules apply to it; and the index `excerpt` was documented as falling back to pre-marker content, when it only ever came from frontmatter. Also replace the `mdx_components!` example that bound the macro to a variable. It expands to a token block that is not valid Rust on its own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three examples for feature areas that previously only appeared mixed together inside the docs-site example, or were not demonstrated at all. `overrides` makes element overrides and the content wrapper the subject, and serves the same file with and without overrides for comparison. `content-index` drives a listing, tag pages, and a sitemap from `mdx_index_*`. `gfm` covers tables, task lists, footnotes, and code block meta attributes rendered through a highlighting component. Building these surfaced the tags and reference-link defects fixed earlier in this branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The sitemap writer was left unformatted, so `cargo +nightly fmt --all --check` failed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An audit of every example page turned up six claims that the code does not support: `release-v1.mdx` advertised parsing frontmatter into Rust structs, removed in 1d335a9, and announced a stable 1.0 release of a crate the README marks experimental. It is now `feature-roundup.mdx` and describes the content index. `roadmap.mdx` listed footnotes and code block meta strings as planned; both ship. `install.mdx` gave a feature list without `serve` or `discover`, so following it does not build the app it lives in, and named a directory the example does not scan. `self-closing.mdx` used a tag pair and claimed it parses more reliably, though self-closing tags have worked since the walker gained a test for them. `references.mdx` demonstrated reference links using only inline ones. The GFM page claimed an unknown reference is a compile error: markdown-rs only emits a LinkReference once it has matched a definition, so an undefined reference stays literal text and the walker's error path is unreachable from parsing. Also corrects the repository URL in two pages, which pointed at a `topcoat-rs` org that does not exist. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CONTRIBUTING requires plain ASCII in code and docs and no em dashes. The mdx comments, doc pages, and tests carried 43 em dashes and 8 unicode arrows; rewritten with colons, commas, or "to" rather than substituted, since the style skill rules out em dashes rather than their spelling. The blog example's post separator becomes a hyphen. The remaining non-ASCII in the tree predates this branch: the CI concurrency comment, and the accented characters and emoji in the topcoat-view escape tests, which are the data under test.
The Clone derives on ElementName, HtmlIdent, HtmlIdentSegment, HtmlIdentSeparator, HtmlIdentPart, and TemplateExpr existed for a single clone in the mdx walker that no longer exists. Nothing clones these parser types now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The headline example in both docs nested compile_mdx! inside view!, which does not compile: the macro already expands to a full view expression, so it stands in for a view! block rather than nesting inside one. Both snippets are rust,ignore, so doctests never caught it. Also correct the mdx_pages! route derivation: paths keep the subdirectory structure below the scanned directory, not just the kebab-cased filename. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Frontmatter carrying fields beyond title/date/excerpt/tags had no coverage at either level. Add grammar tests asserting custom keys survive extraction for YAML and TOML, and macro tests asserting the body still renders when unknown fields are present. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
MdxIndexEntry named four frontmatter fields and dropped the rest, so a page could not carry metadata of its own. Entries now also hold the whole frontmatter block, the syntax it was written in, and a word count of the body. The block arrives with its delimiters already stripped, so the syntax cannot be recovered from the text; frontmatter_format carries it instead of leaving callers to guess. The body does not exist at runtime, so a reading estimate has to be derived at compile time: word_count reports the count and leaves words per minute to the caller. Also removes the __MDX_FRONTMATTER_* const that compile_mdx! emitted. It lived inside the anonymous block the macro expands to, so no caller could ever name it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reading custom frontmatter meant deserializing it by hand on every access, and a wrapper component could not see it at all: it received only the compiled body. Both macros now take `frontmatter = Type`. The macro picks the deserializer from the syntax each page used, parses once on first read, and hands the result to the index entry and the wrapper. `meta` is an Option on both, so a directory may hold pages that carry no frontmatter; those pass None rather than being rejected. Behind the `mdx-frontmatter` feature, separate from `mdx`, because this is the only frontmatter handling that runs in the built program. Rendering resolves frontmatter while the macro expands and keeps none of it, so a site that does not name a type should not carry serde and both format parsers. Naming one without the feature is a compile error that says so. The macro sees only the name of the type, and serde does not run during expansion, so a page that does not match panics on first read rather than failing to compile. Both macros also now accept a trailing comma. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A post that keeps its assets in a directory of its own got a route ending in a repeated segment: posts/my-post/my-post.mdx became /blog/my-post/my-post. A file named index now stands for the directory holding it, so posts/my-post/index.mdx serves /blog/my-post. Its slug is the directory name for the same reason, since every index file would otherwise answer to "index". Flat files and ordinary nested files keep the routes they had, so both layouts can live in one directory. Deriving a route is not injective, and this adds a second way for two files to claim one: an index file beside a same-named sibling. Colliding routes are now a compile error naming both files, where before the winner was whichever the directory walk reached last. That also catches the collision kebab-casing already allowed between my_post.mdx and my-post.mdx. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The mdx crates pinned markdown, serde, serde-saphyr, serde-value, and toml inline, against the convention that every dependency is declared once in the workspace. That left toml at 0.8 while the workspace had 1.1.2, so both were built. Move all five to the workspace, adding entries for the three it lacked, and bring toml to the workspace version. The lockfile drops toml 0.8 along with its own copies of toml_edit, toml_datetime, toml_write, serde_spanned, and winnow. markdown carried a comment in each crate asking that the versions stay synchronized by hand; the shared entry now does that. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…h helpers Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…h, td overrides Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR is the first step toward a complete mdx package (similar to
@next/mdx). It fulfills the Markdown support item on the roadmap.Adds
topcoat-mdx, a compile-time MDX pipeline.compile_mdx!reads a.mdxor.mdfile relative toCARGO_MANIFEST_DIR, parses it withmarkdown-rs, walks the mdast intoview!AST nodes, and emits tokens. Nothing is parsed at runtime.The crate follows the usual trio layout:
topcoat-mdxholds the runtime types (MdxIndexEntry, themdx_components!macro_rules!),grammar/holds the parser options and the walker, andmacro/is the proc-macro entry point. The facade gains anmdxmodule behind anmdxfeature that pulls inrouter, and forwardsdiscoverto the macro crate.Macros
compile_mdx!(path), or with a leading component registry -- either a braced block ormdx_components! { Callout => path::to::callout }.overrides = { "a" => ... }routes HTML elements through components;wrapper = Pathwraps the compiled content in a layout component.mdx_page!(route, path, ...)compiles one file and registers it as a page.mdx_pages!(dir, prefix = ..., ...)walks a directory, compiles every.mdxand.mdfile, and registers a handler per file with kebab-cased slugs derived from the path. It also emits a&'static [MdxIndexEntry]const and anmdx_index_{dir}()accessor carrying each file's slug, route path, and frontmattertitle,date,excerpt, andtags.Both page macros submit a
PageFnto the link-time inventory behind thediscoverfeature, soRouter::builder().discover()mounts them.Walker
Covers CommonMark plus the GFM extensions (tables, strikethrough, task lists, autolinks). HTML passthrough is disabled, so only MDX JSX tags reach the component path; raw HTML renders as text. Reference links, reference images, and footnotes need the definitions before the nodes that use them, so the walk runs in two passes: one to collect definitions, one to render. Footnotes render as a numbered section at the end with backlinks.
Headings get an
idderived from their text, deduplicated with-1/-2suffixes. Fenced code block meta strings becomedata-lang,data-lines,data-title, anddata-emphasisattributes. Overridable elements area,h1--h6,img,pre, andhr. Link and image URLs are checked before an override component sees them: alldata:URIs are rejected and C0 control characters are stripped, so neither can smuggle a scheme past the check.Changes outside
topcoat-mdxThe walker builds
view!AST nodes programmatically, which needs a few additions intopcoat-view/grammar:DefaultonAttributesandViewWriter;Nodes::new(); andViewWritermadepubrather thanpub(crate).#[must_use]on the constructors. No change to parsing or to the codeview!generates.topcoat-core/grammargains atopcoat_mdxpath helper so generated code can reach the crate through the facade or standalone.Examples
Eight, under
examples/mdx/:simple-- two pages,compile_mdx!, manual registration.components-- component embedding: props, children, self-closing tags, nesting.overrides-- element overrides andwrapper =, serving the same file with and without overrides for comparison.gfm-- tables, task lists, footnotes, and code block meta driving a highlighting component through apreoverride.blog--mdx_pages!over a nestedposts/directory alongsidemodule_router!, with a listing built from the index.content-index-- listing, tag pages, and a sitemap driven entirely bymdx_index_*.mdx-pages-- directory scan plus.discover()end to end.docs-site-- footnotes, reference links, overrides, wrappers, code meta, and heading IDs on dedicated pages.Testing
cargo clippy --workspace --all-targets --all-features --locked -- -D warningscargo test --workspace --all-featuresRUSTDOCFLAGS="--cfg docsrs -Dwarnings" cargo +nightly doc --workspace --all-features --no-deps --lockedcargo +nightly fmt --allandcargo topcoat fmtBuilding the three new examples surfaced three defects that are fixed here with regression tests:
mdx_pages!emitted&"a", "b"for frontmatter tags rather than&["a", "b"], so any tagged file failed to compile;WalkContextwas constructed without its collected definitions in the file-compiling path, so reference links and footnotes silently degraded there while passing in the string-based tests; and malformed frontmatter panicked the proc macro instead of reporting a spannedsyn::Error.Disclaimer
This work was done with coding assistance from Claude (Opus 5) under human supervision. The design, the requirements, and the review of every change are the author's; the model assisted with writing code, tests, docs, and examples, and drafted this description.