From 6e9f3f8b34e86be112f3b2ddfa63a3e227b7e868 Mon Sep 17 00:00:00 2001 From: sliang-code Date: Tue, 28 Apr 2026 16:00:04 -0400 Subject: [PATCH 1/4] Fix: remove the excessive class when style exists --- .../agents/svelte-head-style-triage.agent.md | 38 +++++++++++++++++++ .../phases/2-analyze/css/css-prune.js | 9 +++++ .../src/compiler/phases/2-analyze/index.js | 13 +++++++ .../head-css-no-class/_expected_head.html | 1 + .../samples/head-css-no-class/main.svelte | 10 +++++ .../head-elements-not-scoped/_config.js | 19 ++++++++++ .../head-elements-not-scoped/main.svelte | 19 ++++++++++ tmp-repro-head-class.mjs | 20 ++++++++++ 8 files changed, 129 insertions(+) create mode 100644 .github/agents/svelte-head-style-triage.agent.md create mode 100644 packages/svelte/tests/hydration/samples/head-css-no-class/_expected_head.html create mode 100644 packages/svelte/tests/hydration/samples/head-css-no-class/main.svelte create mode 100644 packages/svelte/tests/runtime-browser/samples/head-elements-not-scoped/_config.js create mode 100644 packages/svelte/tests/runtime-browser/samples/head-elements-not-scoped/main.svelte create mode 100644 tmp-repro-head-class.mjs diff --git a/.github/agents/svelte-head-style-triage.agent.md b/.github/agents/svelte-head-style-triage.agent.md new file mode 100644 index 0000000000..ed77dba898 --- /dev/null +++ b/.github/agents/svelte-head-style-triage.agent.md @@ -0,0 +1,38 @@ +--- +name: Svelte Head Style Triage +description: "Use when investigating Svelte issues where svelte:head elements (meta/link/script) unexpectedly get class attributes, especially when components contain style blocks. Great for reproduction, root-cause hints, and high-quality bug reports." +tools: [read, search, execute] +argument-hint: "Describe the suspected Svelte bug, expected behavior, and minimal reproduction." +user-invocable: true +--- +You are a specialist for Svelte compiler/runtime issue triage focused on svelte:head behavior and style-scoping edge cases. + +## Mission +- Reproduce and validate whether the reported behavior is real. +- Determine expected vs actual behavior based on Svelte semantics. +- Provide actionable findings for maintainers and users. + +## Constraints +- Do not propose speculative fixes as facts. +- Do not broaden scope into unrelated styling issues. +- Prefer minimal reproductions over large app context. +- If uncertain, clearly label assumptions and open questions. + +## Approach +1. Restate the report in precise technical terms. +2. Build or locate a minimal reproduction component. +3. Verify output HTML/compiled behavior for head tags and class injection. +4. Check whether behavior has functional impact vs semantic/bloat impact. +5. Search for prior issues/tests touching svelte:head class or style scoping. +6. Propose next actions: issue report text, likely fix area, regression test idea. + +## Output Format +Return sections in this exact order: +1. Verdict +2. Reproduction +3. Expected vs Actual +4. Impact +5. Likely Compiler Area +6. Suggested Issue Comment +7. Suggested Regression Test +8. Open Questions diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js index 39f485a9f7..3ec9c4622a 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js @@ -122,6 +122,13 @@ const any_selector = { */ const seen = new Set(); +/** + * @param {Compiler.AST.RegularElement | Compiler.AST.SvelteElement | Compiler.AST.RenderTag | Compiler.AST.Component | Compiler.AST.SvelteComponent | Compiler.AST.SvelteSelf} node + */ +function is_inside_svelte_head(node) { + return node.metadata.path.some((ancestor) => ancestor.type === 'SvelteHead'); +} + /** * * @param {Compiler.AST.CSS.StyleSheet} stylesheet @@ -248,6 +255,8 @@ function apply_selector( from = 0, to = relative_selectors.length ) { + if (is_inside_svelte_head(element)) return false; + if (from >= to) return false; const selector_index = direction === FORWARD ? from : to - 1; diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index dec0081aa9..71510a1212 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -205,6 +205,13 @@ const visitors = { VariableDeclarator }; +/** + * @param {AST.RegularElement | AST.SvelteElement} node + */ +function is_inside_svelte_head(node) { + return node.metadata.path.some((ancestor) => ancestor.type === 'SvelteHead'); +} + /** * @param {AST.Script | null} script * @param {ScopeRoot} root @@ -876,6 +883,12 @@ export function analyze_component(root, source, options) { } for (const node of analysis.elements) { + // Elements rendered through are not style-scopable. + // Prevent css hash injection (class="s-...") on tags like , , \ No newline at end of file diff --git a/packages/svelte/tests/hydration/samples/head-css-no-class/main.svelte b/packages/svelte/tests/hydration/samples/head-css-no-class/main.svelte new file mode 100644 index 0000000000..17889a34a4 --- /dev/null +++ b/packages/svelte/tests/hydration/samples/head-css-no-class/main.svelte @@ -0,0 +1,10 @@ + + + + + + +
dummy
+ + \ No newline at end of file diff --git a/packages/svelte/tests/runtime-browser/samples/head-elements-not-scoped/_config.js b/packages/svelte/tests/runtime-browser/samples/head-elements-not-scoped/_config.js new file mode 100644 index 0000000000..127d14ce6b --- /dev/null +++ b/packages/svelte/tests/runtime-browser/samples/head-elements-not-scoped/_config.js @@ -0,0 +1,19 @@ +import { test } from '../../assert'; + +export default test({ + test({ assert, window }) { + const head = window.document.head; + + const meta = head.querySelector('meta[name="author"]'); + const link = head.querySelector('link[rel="author"]'); + const script = head.querySelector('script[type="application/ld+json"]'); + + assert.ok(meta); + assert.ok(link); + assert.ok(script); + + assert.equal(meta.getAttribute('class'), null); + assert.equal(link.getAttribute('class'), null); + assert.equal(script.getAttribute('class'), null); + } +}); diff --git a/packages/svelte/tests/runtime-browser/samples/head-elements-not-scoped/main.svelte b/packages/svelte/tests/runtime-browser/samples/head-elements-not-scoped/main.svelte new file mode 100644 index 0000000000..378ad3b802 --- /dev/null +++ b/packages/svelte/tests/runtime-browser/samples/head-elements-not-scoped/main.svelte @@ -0,0 +1,19 @@ + + + + + + +
+ Site by + + {author} + +
+ + + + diff --git a/tmp-repro-head-class.mjs b/tmp-repro-head-class.mjs new file mode 100644 index 0000000000..9b5ffe0611 --- /dev/null +++ b/tmp-repro-head-class.mjs @@ -0,0 +1,20 @@ +import { compile } from './packages/svelte/src/compiler/index.js'; + +const source = ` + + + + + +
Site by {author}
+ + + +`; + +const { js } = compile(source, { generate: 'server' }); + +console.log(js.code); From aab70ef571dcc5df71422c9d323d9d3c75c274d7 Mon Sep 17 00:00:00 2001 From: sliang-code Date: Wed, 29 Apr 2026 17:52:47 -0400 Subject: [PATCH 2/4] Delete unused agent file --- .../agents/svelte-head-style-triage.agent.md | 38 ------------------- 1 file changed, 38 deletions(-) delete mode 100644 .github/agents/svelte-head-style-triage.agent.md diff --git a/.github/agents/svelte-head-style-triage.agent.md b/.github/agents/svelte-head-style-triage.agent.md deleted file mode 100644 index ed77dba898..0000000000 --- a/.github/agents/svelte-head-style-triage.agent.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: Svelte Head Style Triage -description: "Use when investigating Svelte issues where svelte:head elements (meta/link/script) unexpectedly get class attributes, especially when components contain style blocks. Great for reproduction, root-cause hints, and high-quality bug reports." -tools: [read, search, execute] -argument-hint: "Describe the suspected Svelte bug, expected behavior, and minimal reproduction." -user-invocable: true ---- -You are a specialist for Svelte compiler/runtime issue triage focused on svelte:head behavior and style-scoping edge cases. - -## Mission -- Reproduce and validate whether the reported behavior is real. -- Determine expected vs actual behavior based on Svelte semantics. -- Provide actionable findings for maintainers and users. - -## Constraints -- Do not propose speculative fixes as facts. -- Do not broaden scope into unrelated styling issues. -- Prefer minimal reproductions over large app context. -- If uncertain, clearly label assumptions and open questions. - -## Approach -1. Restate the report in precise technical terms. -2. Build or locate a minimal reproduction component. -3. Verify output HTML/compiled behavior for head tags and class injection. -4. Check whether behavior has functional impact vs semantic/bloat impact. -5. Search for prior issues/tests touching svelte:head class or style scoping. -6. Propose next actions: issue report text, likely fix area, regression test idea. - -## Output Format -Return sections in this exact order: -1. Verdict -2. Reproduction -3. Expected vs Actual -4. Impact -5. Likely Compiler Area -6. Suggested Issue Comment -7. Suggested Regression Test -8. Open Questions From 8b20371ce1586994083e0be410a38ea3e6bd4db5 Mon Sep 17 00:00:00 2001 From: sliang-code Date: Wed, 29 Apr 2026 18:17:10 -0400 Subject: [PATCH 3/4] Delete tmp-repro-head-class.mjs --- tmp-repro-head-class.mjs | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 tmp-repro-head-class.mjs diff --git a/tmp-repro-head-class.mjs b/tmp-repro-head-class.mjs deleted file mode 100644 index 9b5ffe0611..0000000000 --- a/tmp-repro-head-class.mjs +++ /dev/null @@ -1,20 +0,0 @@ -import { compile } from './packages/svelte/src/compiler/index.js'; - -const source = ` - - - - - -
Site by {author}
- - - -`; - -const { js } = compile(source, { generate: 'server' }); - -console.log(js.code); From 9198d2696e0b74d9c8f8bdde3d7de99a42d00a85 Mon Sep 17 00:00:00 2001 From: sliang-code Date: Wed, 29 Apr 2026 18:26:25 -0400 Subject: [PATCH 4/4] Add null check for meta, link, and script elements --- .../runtime-browser/samples/head-elements-not-scoped/_config.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/svelte/tests/runtime-browser/samples/head-elements-not-scoped/_config.js b/packages/svelte/tests/runtime-browser/samples/head-elements-not-scoped/_config.js index 127d14ce6b..e9c6e7520f 100644 --- a/packages/svelte/tests/runtime-browser/samples/head-elements-not-scoped/_config.js +++ b/packages/svelte/tests/runtime-browser/samples/head-elements-not-scoped/_config.js @@ -12,6 +12,8 @@ export default test({ assert.ok(link); assert.ok(script); + if (!meta || !link || !script) return; + assert.equal(meta.getAttribute('class'), null); assert.equal(link.getAttribute('class'), null); assert.equal(script.getAttribute('class'), null);