[docs-infra] Normalize hast className to the array shape - #1797
Conversation
`className` was written as a plain string by `createFrame`, `addLineGutters` and `diffHast`, but every other producer — starry-night, `rehype-parse`, `fallbackToHast` — emits the array shape, and the compression dictionary encodes `"className":["frame"]`. Class checks were split across four ad-hoc normalizations that disagreed with each other, and the bare `=== 'line'` comparisons silently missed array-shaped trees. `getInitialVisibleSourceLines` was already broken by this: it guards frames with the shape-tolerant `isFrameSpan` but tested lines with `=== 'line'`, so on any tree through `fallbackToHast` it returned an empty visible set. Write the array shape everywhere, drop the four normalizations in favour of `hasClassName`, and check membership with `includes`. Serialized HTML is unchanged; the emitted hast now matches the compression dictionary entries. `@types/hast` 3.0.5 declares `className?: Array<string>` rather than letting it fall through the index signature, so it is bumped here to typecheck the new shape.
Deploy previewBundle sizeTotal Size Change: ▼-1.01KB(-0.03%) - Total Gzip Change: ▼-109B(-0.01%) Show details for 69 more bundlesCodeHighlighterClient parsed: 🔺+5B(+0.01%) gzip: 🔺+3B(+0.01%) PerformanceTotal duration: 18.95 ms +2.24 ms(+13.4%) | Renders: 5 (+0) | Paint: 63.41 ms +1.44 ms(+2.3%)
6 tests within noise — details Check out the code infra dashboard for more information about this PR. |
`transformMarkdownCode` set `hProperties.className` to a plain string in three of its five write sites. That reaches hast through `remark-rehype`, so markdown code blocks carried the string shape too — invisible to the compiler because mdast `Data` is untyped. Write the array shape there as well. The build caches under `.next/cache/docs-infra` are validated by hashing their *inputs*, so a pipeline change that alters the output for unchanged input is invisible to that hash, and `@mui/internal-netlify-cache` restores that directory between Netlify builds. A warm cache would therefore keep serving string-shaped `className` to the new `includes()` readers — silently, since nothing throws. Add CACHE_SCHEMA_VERSION and fold it into the two caches whose value embeds hast (`types-enhanced`, `types-text`); `pages-index` stores plain metadata and is left alone. Also drop an orphaned `loadServerTypesMeta` snapshot (no matching test since #1269) and correct the hast example in the prop-compression doc, both of which still showed the string shape.
|
@claude review |
This comment was marked as resolved.
This comment was marked as resolved.
`objectHash` guarded its class check with `Array.isArray`, which read as if the string shape could still occur. It could not: the guard was only there to narrow `unknown`, because the value was cast to an ad-hoc local type. Cast to `Element` instead and reuse `hasClassName`, so this reads like the other call sites. Also reword a stale mention of the string shape further down the `deltaContainsCollapse` doc block.
`deltaContainsCollapse` still had the string branch the rest of the PR removed, so `embedTransforms` and `diffHast` disagreed about whether the string shape was possible. It now casts to `Element` and uses `hasClassName`, matching `objectHash`. Nothing pinned CACHE_SCHEMA_VERSION into either cache-content hash: the existing tests only compare two calls to each other, so deleting the version kept them green. That version is the only thing keeping a Netlify-restored `.next/cache` from handing old-shape hast to the new readers. Extract `buildTypesEnhancedCacheContent` so the types-enhanced content is reachable from a test, and assert the version in both. Both new tests were confirmed to fail with the version removed. Two docs examples still taught the string shape, one of them in the same file whose other example this PR already converted. `getInitialVisibleSourceLines` kept `as Element` assertions on nodes `type === 'element'` had already narrowed.
It had one caller in its own file and was extracted only to give a test something to import. AGENTS.md 5.4 asks for the opposite: extract when callers benefit, not to reach internals. Inline it again and drop the test that needed it. `buildTypesTextCacheContent` keeps its test because it was already exported for `syncTypes`, whose hash must match the reader's.
The hast spec says
classis a list:<div class="alpha bravo">is['alpha', 'bravo'].rehype-parse,fallbackToHast, starry-night and the compression dictionary all produce that already.createFrame,addLineGutters,diffHastandtransformMarkdownCodewrote a plain string instead. Four separate workarounds had grown up to handle both, and they didn't agree with each other. This writes the list shape everywhere, drops the workarounds in favour ofhasClassName, and checks withincludes. The HTML we output doesn't change.This was already causing a bug. In
getInitialVisibleSourceLinesthe frame check handled both shapes but the line check only matched a plain string. Trees fromfallbackToHastuse lists, so no lines matched and the function returned nothing. Nothing showed when the block was collapsed. Master is green on this because the test fixtures were built with strings too, so they agreed with the bug. Switching those fixtures to lists breaks 5 of the 8 tests against the old implementation.The build caches under
.next/cache/docs-infrastore hast and are keyed on a hash of their inputs, so this change is invisible to them, and Netlify restores that directory between builds. A warm cache would carry on handing the old string shape to the new code without erroring.CACHE_SCHEMA_VERSIONinvalidates the two caches that hold hast.@types/hastgoes to 3.0.5, which spells outclassName?: Array<string>instead of leaving it to the catch-all index signature. The new shape needs that to typecheck, and it unblocks #1754, whose CI is red on these errors.