perf(site): migrate markdown pipeline to Sätteri#1733
Conversation
Flip `markdown.processor` from the `unified()` parity bridge to Astro 7's native Rust processor, Sätteri, and port the custom transforms to Sätteri MDAST plugins: - satteriReadingTime / satteriConditionalHeadings: factories writing `ctx.data.astro.frontmatter.*` (surfaced unchanged as `render().remarkPluginFrontmatter`). - satteriCodeFrame: wraps standalone fenced code in a new `<CodeFrame>` component, since under Sätteri the Shiki highlight step rewrites `<pre>` to raw HTML before HAST plugins run, so the old `pre`/`code` overrides no longer fire. CodeFrame reuses the existing Tabs chrome + CopyButton; the raw `.astro-code` <pre> gets its monospace font/size from a CSS rule. Title is read from fence meta, retiring shikiTransformMetadata. Shiki themes/langs/notation transformers carry over via `shikiConfig` (processor-independent); GFM + SmartyPants stay on by Astro defaults. Drops @astrojs/markdown-remark, mdast-util-to-string, unist-util-visit; adds satteri + @astrojs/markdown-satteri. Includes unit tests for the three MDAST plugins and updates site/CLAUDE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018fACaXD786B35idFc1yUhW
✅ Deploy Preview for vjs10-site ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📦 Bundle Size Report🎨 @videojs/html — no changesPresets (7)
Media (10)
Players (5)
Skins (30)
UI Components (39)
Sizes are marginal over the root entry point. ⚛️ @videojs/react — no changesPresets (7)
Media (9)
Skins (27)
UI Components (33)
Sizes are marginal over the root entry point. 🧩 @videojs/core — no changesEntries (14)
🏷️ @videojs/element — no changesEntries (2)
📦 @videojs/store — no changesEntries (3)
🔧 @videojs/utils — no changesEntries (10)
📦 @videojs/spf — no changesEntries (4)
ℹ️ How to interpretJS sizes are initial static graph totals (minified + brotli). Lazy dynamic chunks are shown separately when present.
Run |
`astro check` is stricter than the project's tsgo typecheck and flagged type-only issues the latter missed: - Cast the parsed reference JSON to the model's parameter type (the old `.js` plugin was unchecked) and pass `partOrder ?? undefined`. - Type the code-frame ancestor walk var to admit `undefined`. - Seed the full `data.astro` shape (incl. localImagePaths/remoteImagePaths) in the plugin tests to match the markdown-satteri DataMap augmentation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018fACaXD786B35idFc1yUhW
The Sätteri migration added `.astro-code { font-size: var(--text-code) }`
to restore monospace sizing for raw highlighted `<pre>`. But `--text-code`
is an `em`, and the React `Code` components (`shared.pre` = "shiki
astro-code") also put `text-code` on their inner `<code>`. Sizing the
`<pre>` as well compounded the two `em`s (0.875 × 0.875), shrinking that
code to ~12.25px next to the 14px MDX blocks.
Move the size onto `.astro-code code` so it applies to the code element
once, matching the pre-Sätteri output where MarkdownCode sized `<code>`
directly. Verified all code blocks render uniformly at 14px.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fACaXD786B35idFc1yUhW
… control Astro's Shiki highlighter writes an inline style on each highlighted `<pre>` (theme background/color plus `overflow-x: auto`). Pre-Sätteri, `Pre.astro` deliberately discarded that style so the surrounding frame owned the background and scrolling — Shiki highlighted the text, the container was ours. Under Sätteri the pre is slotted as raw HTML, so the inline style survived: code blocks gained their own scroll box inset inside the frame padding instead of scrolling at the container. Add a Shiki `pre` transformer that deletes the inline style. Astro appends its built-in transformer first, so ours runs last and removes the fully-assembled style. Token colors live on the inner spans and are unaffected; the React `Code` components already rebuild their own `<pre>`, so only the markdown/MDX path needed this. Verified the built `<pre>` now matches main (class + tabindex + data-language, no style). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018fACaXD786B35idFc1yUhW
decepulis
left a comment
There was a problem hiding this comment.
mostly just nits about what goes in the comments. please address and then mark as ready for review.
There was a problem hiding this comment.
This file requires a larger cleanup, but I'm considering that out of scope for this PR
Address review: comments should explain what the code does or why it's unusual for future maintainers, not narrate the migration. Remove references to the prior remark/rehype plugins, `Pre.astro`, and the old processor across the Sätteri plugins, `CodeFrame`, `MarkdownCode`, the shiki-transformers CSS, and the Astro config; tighten the wording. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018fACaXD786B35idFc1yUhW
mihar-22
left a comment
There was a problem hiding this comment.
Ran through a variety of pages and everything looks pretty good. Not seeing anything concerning here. Thanks for the upgrade!
…-per2ya # Conflicts: # site/src/utils/remarkConditionalHeadings.js
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 741a0d0. Configure here.
| if (frameworks) heading.frameworks = frameworks; | ||
| if (styles) heading.styles = styles; | ||
|
|
||
| headings.push(heading); |
There was a problem hiding this comment.
Missing TOC slug collision handling
Medium Severity
The removed remark plugin reserved slugs when API reference headings were injected and re-slugged prose headings on collision. The Sätteri plugin always uses slugger.slug(text) with no reserved set, so injected entries like props can share a slug with a later markdown heading. TableOfContents keys rows by heading.slug, which duplicates keys and TOC anchors.
Reviewed by Cursor Bugbot for commit 741a0d0. Configure here.


Previously, Astro used
unifiedto do its markdown processing. Now it suggests rust-basedsatteri.This PR migrates our unified plugins to satteri and works around some new satteri idioms. We relied heavily on unified plugins for some MDX magic, so, this PR isn't trivial.
Visually, output is identical, though, so I think we succeeded.
The deets
Here's what's new
remarkReadingTimesatteriReadingTimectx.data.astro.frontmatter.{minutesRead,readingTimeMinutes}remarkConditionalHeadingssatteriConditionalHeadingsfrontmatter.conditionalHeadingsrehypePrepareCodeBlockssatteriCodeFrameshikiTransformMetadataCode blocks
Satteri runs at a different time than unified did, so we needed to apply code block styling differently. Here's how Claude would explain that:
Under Sätteri the Shiki highlight step rewrites each
<pre>to raw HTML before HAST plugins run, so the oldpre/codecomponent overrides no longer fire for code blocks. Instead,satteriCodeFramewraps each standalone fenced block in a new<CodeFrame>component at the MDAST stage (blocks inside an authored<TabsPanel>are left alone).CodeFrame.astroreuses the existingTabschrome +CopyButton, and the raw.astro-code<pre>gets its monospace font/size from a rule inshiki-transformers.css(previously supplied byMarkdownCode'scodeBlockbranch).Pre.astrois removed;MarkdownCodeis simplified to inline-only.Closes #1719.
Generated by Claude Code
Note
Medium Risk
This rewrites the central MDX/markdown build path (TOC, reading time, and code framing) for all docs and blog content; regressions would be visible sitewide, though behavior is intended to be pixel-identical and is covered by new plugin tests.
Overview
Switches the docs site from Astro’s
unified()remark/rehype stack to@astrojs/markdown-satteriwith native Sätteri MDAST plugins, aimed at faster builds while keeping the same rendered output.Pipeline:
astro.config.mjsnow usesprocessor: satteri({ mdastPlugins: [...] }). The oldremarkReadingTime,remarkConditionalHeadings, andrehypePrepareCodeBlocks(plusshikiTransformMetadata) are removed and replaced bysatteriReadingTime,satteriConditionalHeadings, andsatteriCodeFrame, still writingremarkPluginFrontmatterfields (minutesRead,readingTimeMinutes,conditionalHeadings).Code blocks: Because Shiki turns
<pre>into raw HTML before HAST/component overrides run, standalone fences are wrapped at MDAST in<CodeFrame>(Tabs chrome + copy).Pre.astrois deleted;MarkdownCodeis inline-only;.astro-codetypography moves toshiki-transformers.css. Fencetitle="..."is parsed in the MDAST plugin;shikiStripPreStyledrops inline styles on highlighted<pre>.Deps/docs/tests:
@astrojs/markdown-remark→@astrojs/markdown-satteri+satteri; Vitest coverage for the three Sätteri plugins (node env).CLAUDE.mddocuments the new processor model.Reviewed by Cursor Bugbot for commit bd8dbeb. Bugbot is set up for automated code reviews on this repo. Configure here.