Skip to content

feat(lint): warn when a scene's styles sit outside its template - #6317

Merged
miguel-heygen merged 1 commit into
mainfrom
fix/lint-style-outside-scene-template
Oct 25, 2026
Merged

miguel-heygen merged 1 commit into
mainfrom
fix/lint-style-outside-scene-template

Conversation

@miguel-heygen

Copy link
Copy Markdown
Collaborator

What

hyperframes lint now warns when a sub-composition file that has a <template> keeps a <style> outside it, or a <link rel="stylesheet"> outside both the template and <head>. The message says to move it inside <template>.

Why

A templated sub-composition is mounted from its template alone, so styles left outside it never apply. The scene renders unstyled in preview, snapshot and render alike, with no error. The usual shape is a full HTML page with its <style> in <head> and the composition root in a <template> in <body>.

Dropping the head is deliberate, and every mount path shares it:

  • packages/core/src/compiler/compositionAssembly.ts:238-239: assetHead = hasTemplate ? null : head, so head <style> and <script> are left out for a templated composition (styleSources, lines 247-250).
  • packages/core/src/runtime/compositionLoader.ts:795-808 (preview) and packages/parsers/src/subCompositionValidity.ts:61-63, used by packages/core/src/compiler/inlineSubCompositions.ts:417 (render): any <template> in the file makes it templated, and only its content is mounted.

Head stylesheet links are different: linkSources (compositionAssembly.ts:252-255) reads head, not assetHead, so <link rel="stylesheet"> and <link rel="preconnect"> in <head> are always hoisted. The rule leaves those alone and only flags a stylesheet link in the body outside the template.

Scoping head styles into the composition instead was ruled out: the head belongs to the host page by design (the comment at line 238), so the fix is a warning at authoring time.

How

  • New rule subcomposition_style_outside_template in packages/lint/src/rules/composition.ts, active only for sub-composition files (isSubComposition, set for compositions/* by packages/lint/src/project.ts:261).
  • It parses the whole file (comments stripped), because the lint context's tags are the template's content once the root lives inside it (packages/lint/src/context.ts:46-52).
  • Severity is a warning, one finding per dropped tag, with the tag as the snippet.
  • isInsideInertTemplate now delegates to a small isInsideTag(tag, tags, name) so the head check reuses it.
  • Documented in docs/reference/lint-rules.mdx.

Every tracked templated file under a compositions/ folder in this repo (124) lints clean with the rule.

Test plan

  • Unit tests added/updated

  • Manual testing performed

  • Documentation updated (if applicable)

  • Comments follow CONTRIBUTING.md "Comments": they say why, not what, and a bug fix says what the code must do and how to reproduce the bug

  • composition.test.ts, 4 tests: a head <style> next to a <template> warns with the fix in the hint; a <style> and a stylesheet link in the body outside the template each warn; styles inside the template and a head stylesheet link do not; a file without a template, or outside a sub-composition, does not.

  • With the rule returning nothing, the two warning tests fail. Removing each guard in turn (the head-link exclusion, the template exclusion, the sub-composition check, the template check) fails the matching negative test.

  • packages/lint suite: 22 files, 1,169 tests, 3 runs in a row, exit 0. Pre-commit lint, format, typecheck and fallow pass; check-comment-citations and comment-ratchet pass.

A sub-composition file with a <template> is mounted from the template alone, so a <style>
in its <head> (or a stylesheet link in its body) never applies in preview, snapshot or render.
Warn on it, with the fix in the message: move it inside <template>.
@mintlify

mintlify Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
hyperframes 🟢 Ready View Preview Oct 25, 2026, 3:08 PM

💡 Tip: Enable Automations to automatically generate PRs for you.

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE · 0 blocker · 0 important · 0 nits · head 4210097e

I checked the rule against all three mount paths: planCompositionAssembly, which sets assetHead = hasTemplate ? null : head and hoists linkSources from head; the preview compositionLoader, where the first or #<id>-template makes the file templated; and the render path's resolveSubCompositionContent. They agree with what the rule flags. The four new tests and the 300 tests in composition.test.ts pass locally. I also probed edge cases: <style> text inside a <script> string is not flagged, a <style> inside <noscript> in <head> is flagged (correct, it is dropped too), and so is a bare <link><template> fragment with no <head> (correct for render, because linkedom puts no head links there).

Scope

I read the whole diff (3 files). CI was still running at review time and no check had failed. There were no prior reviews.

@github-actions

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 271 (base branch 271), smooth 144 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (0)

Unstable (3)

  • seqplainkeys-none-center-r0-root-z100: tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo false, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true
  • seqheldtwice-tlhold-px-r0-root-z100: tracking 0.41, pressJump 60.41, drop 0.35, reload 0.35, render 0.02, renderKey -, undo true, teleport true / tracking 0.08, pressJump 0.11, drop 0.35, reload 0.35, render 0.02, renderKey -, undo true, teleport true / tracking 0.08, pressJump 0.11, drop 0.35, reload 0.35, render 0.02, renderKey -, undo true, teleport true
  • seqplainkeys-none-pct-r0-nested-z100: tracking 0.05, pressJump 0, drop 0, reload 0, render 0.02, renderKey -, undo false, teleport true / tracking 0.05, pressJump 0, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true / tracking 0.05, pressJump 0, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true

@miguel-heygen
miguel-heygen added this pull request to the merge queue Oct 25, 2026
Merged via the queue into main with commit ad6771c Oct 25, 2026
68 checks passed
@miguel-heygen
miguel-heygen deleted the fix/lint-style-outside-scene-template branch October 25, 2026 15:45

This branch was successfully deployed

1 active deployment
staging - docs — 4210097e Deployed Oct 25, 2026 by mintlify[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant