diff --git a/src/distill.js b/src/distill.js
index 001325c..64cde67 100644
--- a/src/distill.js
+++ b/src/distill.js
@@ -284,6 +284,7 @@ function isFollowableHref(href, base, pageUrl) {
*/
export function distill(html, url = '', { view = null, type = '' } = {}) {
const { document } = parseHTML(asHTML(html, url, { view, type }));
+ lowerAttributeNames(document);
const title = clean(document.querySelector('title')?.textContent ?? '');
const base = documentBase(document, url);
/** @type {Block[]} */
@@ -615,6 +616,28 @@ function number(blocks) {
*/
const bodyOf = (document) => document.querySelector('body') ?? document.documentElement;
+/**
+ * HTML attribute names are ASCII case-insensitive, but linkedom keeps them as
+ * written, so `` had no href, `` no type and
+ * `Legacy page
+ Next page
+
secret-hidden
+ + Twice + `; + const page = distill(html, 'https://example.com/old'); + const links = page.blocks.filter((b) => b.type === 'link'); + assert.deepEqual(links.map((b) => [b.text, b.href]), [['Next page', '/next'], ['Twice', '/first']]); + assert.ok(!page.blocks.some((b) => b.type === 'input'), 'hidden input numbered'); + const view = render(page, { budget: 5000 }).text; + for (const out of [view, toMarkdown(html), toHTML(html)]) { + for (const secret of ['secret-token', 'secret-style', 'secret-hidden', 'secret-aria']) { + assert.ok(!out.includes(secret), `${secret} leaked`); + } + } +}); + test('raw mode emits real markdown with hrefs an agent can follow', () => { const md = toMarkdown(html); assert.ok(md.startsWith('# Fixture News'));