Skip to content

fix(core): an added sub-composition keeps its declared variable defaults when bundled - #4539

Merged
miguel-heygen merged 3 commits into
mainfrom
fix/core-bundle-keeps-sub-comp-variable-defaults
Sep 26, 2026
Merged

miguel-heygen merged 3 commits into
mainfrom
fix/core-bundle-keeps-sub-comp-variable-defaults

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

What changes

A sub-composition installed with hyperframes add keeps its declared variable defaults in two places: when the project is bundled with bundleToSingleHtml, and in Studio's standalone preview of that file. Inside the sub-composition, getVariables() returns those defaults again. The carousel blocks, for example, load their images from assets/carousel-images/ instead of 404ing on bare file names and drawing white cards.

Why it happened

parseHTMLContent decided whether its input was a full document or a fragment by checking whether it started with <!doctype or <html. hyperframes add writes a <!-- hyperframes-registry-item: ... --> marker comment above the doctype of every file it installs. Those files were therefore parsed as fragments and wrapped in a fresh <html>, which dropped their own <html data-composition-variables>.

The sub-composition inliner then read no declared defaults and emitted no __hfVariablesByComp entry for the instance, so the scoped getVariables() had nothing to return. The CSS custom properties for the same sub-composition were unaffected, because that path reads the defaults differently.

The fix

  • isFullHtmlDocument in core's htmlDocument.ts decides document versus fragment once. It skips leading whitespace and comments (an empty <!--> included), then looks for <!doctype or <html followed by whitespace or >. So a <html-card> element no longer counts.
  • parseHTMLContent uses it. Every compiler path that parses a composition goes through parseHTMLContent, so the bundler, the sub-composition inliner and the template mount all see the file's real <html> element.
  • Studio-server's preview had its own copy of the same check and hit the same bug. The preview of an installed block lost data-composition-variables from <html> and nested a second <html> inside <body>. That copy is deleted, and the preview imports the core predicate through @hyperframes/core/compiler/html-document.

Tests

  • htmlDocument.test.ts: a document that starts with a comment keeps its <html> attributes.
  • htmlDocument.test.ts: document versus fragment past leading comments, an empty comment, and a <html-card> element.
  • htmlBundler.test.ts: a sub-composition that starts with the install marker has its declared default in __hfVariablesByComp after bundling.
  • subComposition.test.ts (studio-server): an installed block's preview keeps data-composition-variables on <html> and nests no <html> in <body>.

The first bundler and document tests fail on main, and the studio-server test fails on the previous studio-server code. The core suite passes (3199 tests), as do the studio-server helper tests (348) and both typechecks.

Before

carousel-orbit-1 added with hyperframes add, its snippet wrapped in a root index.html, bundled with bundleToSingleHtml (runtime inline) and loaded in Chromium on main. There are 48 failed image requests for bare names such as /artist-bob-seger.jpg, and every card is white.

Before: bundled carousel-orbit-1 on main, 48 image 404s, white cards

After

The same project bundled on this branch. The bundle carries the block's 24 image defaults, there are 0 failed requests, and the cards show their images.

After: the same bundle on this branch, images load

Not in this PR

  • The lint rule root_composition_missing_html_wrapper makes its own start-of-file check and would flag a root index.html that begins with a comment. Installed blocks are not root files, so they do not reach it.
  • The carousel blocks fall back to a bare file name when getVariables() returns nothing for an image. It should keep the assets/carousel-images/ folder. That affects 25 registry blocks and the generated catalog pages, so it is a separate PR.

…lts when bundled

parseHTMLContent only treated input as a full document when it started with
a doctype or <html>. Files installed by `hyperframes add` start with a marker
comment, so they were wrapped as a fragment in a new <html> and lost their own
<html data-composition-variables>. Leading comments are now skipped first.
…iables

isFullHtmlDocument moves to core beside parseHTMLContent and both callers use
it, so the standalone preview of a file that `hyperframes add` installed (a
marker comment above the doctype) is treated as the full document it is. The
leading-comment skip also handles an empty `<!-->` comment, and `<html-card>`
style elements no longer count as a document.

@terencecho terencecho 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.

Reviewed 41b7f8196805bebde4a74ca1e7fca7e1d3c4c24e. Approved on code merit. The CLI's add marker precedes the sub-composition document; the shared isFullHtmlDocument now skips leading comments/whitespace before checking for the doctype or <html>. The bundler parses that file as a document, reads its declared defaults, merges the variables into the bundled composition table, and Studio uses the same document predicate.

In an isolated worktree, 113 focused parser, bundler and Studio-preview tests passed. Bundling the actual marker-prefixed carousel-orbit-1 yielded all 24 image defaults with their assets/carousel-images/ paths (26 defaults total); the previous fragment wrapper lost its <html data-composition-variables> carrier. I inspected the supplied before/after captures and saw white cards become populated image cards. I did not independently run a browser/network trace to verify the asserted 48→0 request count. Parser probes covered whitespace/comments, case variants, and fragment-like <html-card> starts. A malformed <!DOCTYPEhtml><div…> prefix can be misclassified as a full document in Studio; that invalid-input edge is worth tightening but is not a blocker for the valid marked-document fix. At review time completed required checks were green, with some test/Windows shards still running; approval is not a claim that CI had fully settled.

— Review by tai (pr-review)

@miguel-heygen
miguel-heygen added this pull request to the merge queue Sep 26, 2026
Merged via the queue into main with commit 89fe898 Sep 26, 2026
122 checks passed
@miguel-heygen
miguel-heygen deleted the fix/core-bundle-keeps-sub-comp-variable-defaults branch September 26, 2026 14:29
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.

2 participants