Raised by CodeRabbit on #489 (docs/src/components/Footer.astro:51), and independently noticed-but-not-raised by the pre-push-reviewer gate on the same branch. Filing rather than folding in, because the fix is a design decision rather than a review nit.
The invariant, and how it breaks
Trademarks.astro states the contract in its own header:
The marks and their owners live in src/config/trademarks.ts, shared with src/plugins/rehype-trademarks.ts — the pass that puts the matching ® / ™ on the first mention in the prose above. One registry, so the footer can't promise a notice the prose didn't mark, or vice versa.
Both sides read one registry, but they scan different text:
rehype-trademarks.ts walks the rendered HTML and deliberately skips code, pre, kbd, samp, var, h1–h6, .katex, .expressive-code, and the LinkButton/LinkCard components — each for a good reason (a ® inside a copy-pasteable command is wrong; marking a heading desyncs it from the TOC that was collected before the plugin runs).
Footer.astro builds its notice list from pageText(entry.data, entry.body ?? "") — the raw Markdown/MDX body, which contains all of those.
So a mark named only inside a heading or a fenced block produces a footer notice with no matching symbol anywhere in the prose. Live example: docs/src/content/docs/architecture.md mentions OpenTelemetry only in excluded structures, and still emits an OpenTelemetry notice.
The direction is the safe one — over-attributing is better than claiming a mark the page never names — which is why it isn't blocking. But it is precisely the mismatch the component says can't happen.
Options
- Have the plugin publish the marks it actually matched (per page) and let the footer render notices from that set. Exact by construction, and the invariant becomes structural rather than a comment. Costs a build-time channel from the rehype pass to the footer component.
- Teach
pageText() the same exclusions — strip fenced/inline code and ATX/setext headings from the body before scanning. Cheaper, no plumbing, but it duplicates the eligibility rules in a second place and they will drift.
- Weaken the documented invariant to "the footer may name a mark the prose didn't symbol-mark, never the reverse," and say why. Zero code, but gives up a property worth having.
Option 1 is the one that actually holds the line; option 2 is the pragmatic version if the plumbing isn't worth it.
Also in scope while there
Trademarks.astro derives from entry.body, so a mark appearing only inside a code fence still yields a footer notice — the same root cause, and it would be fixed by the same change.
Raised by CodeRabbit on #489 (
docs/src/components/Footer.astro:51), and independently noticed-but-not-raised by thepre-push-reviewergate on the same branch. Filing rather than folding in, because the fix is a design decision rather than a review nit.The invariant, and how it breaks
Trademarks.astrostates the contract in its own header:Both sides read one registry, but they scan different text:
rehype-trademarks.tswalks the rendered HTML and deliberately skipscode,pre,kbd,samp,var,h1–h6,.katex,.expressive-code, and theLinkButton/LinkCardcomponents — each for a good reason (a ® inside a copy-pasteable command is wrong; marking a heading desyncs it from the TOC that was collected before the plugin runs).Footer.astrobuilds its notice list frompageText(entry.data, entry.body ?? "")— the raw Markdown/MDX body, which contains all of those.So a mark named only inside a heading or a fenced block produces a footer notice with no matching symbol anywhere in the prose. Live example:
docs/src/content/docs/architecture.mdmentions OpenTelemetry only in excluded structures, and still emits an OpenTelemetry notice.The direction is the safe one — over-attributing is better than claiming a mark the page never names — which is why it isn't blocking. But it is precisely the mismatch the component says can't happen.
Options
pageText()the same exclusions — strip fenced/inline code and ATX/setext headings from the body before scanning. Cheaper, no plumbing, but it duplicates the eligibility rules in a second place and they will drift.Option 1 is the one that actually holds the line; option 2 is the pragmatic version if the plumbing isn't worth it.
Also in scope while there
Trademarks.astroderives fromentry.body, so a mark appearing only inside a code fence still yields a footer notice — the same root cause, and it would be fixed by the same change.