What's the problem?
With elementAttributeNameCase: "html", the react→html attribute-name conversion is applied inconsistently depending on how a hast plugin sets the property:
- Nested descendants of a subtree passed to
ctx.appendChild are converted (strokeWidth → stroke-width). ✅
- The root node passed to
ctx.appendChild is not converted — strokeWidth is emitted verbatim. ❌
ctx.setProperty names are not converted either. ❌
The same hast property name produces different JSX attribute names depending on its position, so plugin-added SVG presentation attributes leak invalid camelCase names into the rendered HTML (browsers ignore them). The root/setProperty path does apply a smaller special-case set (className → class, ariaHidden → aria-hidden), which makes the gap easy to miss until an SVG attribute is involved.
Repro (node repro.mjs):
import { mdxToJs, defineHastPlugin } from 'satteri';
const plugin = defineHastPlugin({
name: 'repro',
element: {
filter: ['p'],
visit(node, ctx) {
ctx.appendChild(node, {
type: 'element',
tagName: 'svg',
properties: { strokeWidth: 'ROOT' },
children: [
{ type: 'element', tagName: 'path', properties: { strokeWidth: 'NESTED' }, children: [] },
],
});
ctx.setProperty(node, 'strokeWidth', 'SETPROP');
},
},
});
const { code } = await mdxToJs('hello', {
hastPlugins: [plugin],
elementAttributeNameCase: 'html',
});
console.log(code);
Output (satteri 0.9.5 — identical on 0.9.1):
return _jsxs(_components.p, {
strokeWidth: "SETPROP",
children: ["hello", _jsx(_components.svg, {
strokeWidth: "ROOT",
children: _jsx(_components.path, { "stroke-width": "NESTED" })
})]
});
One property name, three spellings-in-flight, two survive to output incorrectly.
Downstream context: found while investigating why withastro/astro#17514 (which passes elementAttributeNameCase: "html" on the Sätteri MDX path, fixing withastro/astro#17512) appeared to work in Astro's test fixture but not in a real site. The fixture's attributes sat on a nested <path> (converted); the real-world plugin set them on the appended root node and via setProperty (leaked).
Environment Information
- satteri npm v0.9.5 (reproduced identically on v0.9.1), prebuilt binary
@bruits/satteri-darwin-arm64
- macOS 26.6 (arm64)
- Rust: n/a (prebuilt NAPI binary)
- Node.js v24.18.0
What's the expected result?
All three positions emit "stroke-width". Property-name conversion should depend only on the configured elementAttributeNameCase, not on whether the property arrived on an appended subtree's root, a nested child, or via ctx.setProperty.
What's the problem?
With
elementAttributeNameCase: "html", the react→html attribute-name conversion is applied inconsistently depending on how a hast plugin sets the property:ctx.appendChildare converted (strokeWidth→stroke-width). ✅ctx.appendChildis not converted —strokeWidthis emitted verbatim. ❌ctx.setPropertynames are not converted either. ❌The same hast property name produces different JSX attribute names depending on its position, so plugin-added SVG presentation attributes leak invalid camelCase names into the rendered HTML (browsers ignore them). The root/
setPropertypath does apply a smaller special-case set (className→class,ariaHidden→aria-hidden), which makes the gap easy to miss until an SVG attribute is involved.Repro (
node repro.mjs):Output (satteri 0.9.5 — identical on 0.9.1):
One property name, three spellings-in-flight, two survive to output incorrectly.
Downstream context: found while investigating why withastro/astro#17514 (which passes
elementAttributeNameCase: "html"on the Sätteri MDX path, fixing withastro/astro#17512) appeared to work in Astro's test fixture but not in a real site. The fixture's attributes sat on a nested<path>(converted); the real-world plugin set them on the appended root node and viasetProperty(leaked).Environment Information
@bruits/satteri-darwin-arm64What's the expected result?
All three positions emit
"stroke-width". Property-name conversion should depend only on the configuredelementAttributeNameCase, not on whether the property arrived on an appended subtree's root, a nested child, or viactx.setProperty.