Skip to content

[BUGFIX] DocumentFragment support — {{#in-element}} with DocumentFragment targets#21253

Open
Copilot wants to merge 8 commits intomainfrom
copilot/extract-document-fragment-support
Open

[BUGFIX] DocumentFragment support — {{#in-element}} with DocumentFragment targets#21253
Copilot wants to merge 8 commits intomainfrom
copilot/extract-document-fragment-support

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 26, 2026

Adds full DocumentFragment support to {{#in-element}}, including correct behaviour after the fragment's children have been moved into a real DOM container via appendChild().

Runtime Fixes (@glimmer/runtime)

  • bounds.tsclear(): Use firstNode().parentNode (the live current parent) instead of the stored parentElement(). After a DocumentFragment is appended to the DOM, the rendered nodes' actual parent is the container, not the now-empty fragment.
  • element-builder.tsNewTreeBuilder.resume(): Capture the live parent from firstNode().parentNode before resetting the block, so subsequent renders are inserted into the real container rather than the detached fragment.
  • element-builder.tsRemoteBlock destructor: Changed the guard from parentElement() === firstNode().parentNode (always false after fragment attachment, silently skipping cleanup) to firstNode().parentNode !== null (node is still attached to some parent), so content is correctly cleaned up on destroy in both the normal and DocumentFragment cases.

Test

Added a test (InElementDocumentFragmentSuite) that:

  1. Renders reactive content into a detached DocumentFragment via {{#in-element}}.
  2. Moves the fragment's children into a real DOM container with container.appendChild(fragment).
  3. Calls rerender and verifies that text-node updates and new conditional elements both appear correctly in the container — using assert.step called from within the template and assert.verifySteps to document each reactive update.

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Agent-Logs-Url: https://github.com/emberjs/ember.js/sessions/b223b6e1-6551-4db1-bb11-99dac1313bec

Co-authored-by: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 26, 2026

📊 Package size report   0.03%↑

File Before (Size / Brotli) After (Size / Brotli)
dist/ember.debug.js 2.1 MB / 321.1 kB 0.05%↑2.1 MB / 0.04%↑321.3 kB
dist/ember.debug.js.map 3.4 MB / 635.1 kB 0.04%↑3.4 MB / 0.03%↑635.2 kB
dist/ember.prod.js 1.9 MB / 291.7 kB 0.06%↑1.9 MB / 0.07%↑291.9 kB
dist/ember.prod.js.map 3.3 MB / 607.8 kB 0.04%↑3.3 MB / 0.08%↑608.3 kB
dist/packages/shared-chunks/dynamic-DKOGdHsM.js 119.4 kB / 24.1 kB 0.3%↑119.7 kB / 0.3%↑24.2 kB
dist/packages/shared-chunks/element-builder-DRDkXgl3.js 11.7 kB / 2.6 kB 6%↑12.4 kB / 7%↑2.8 kB
Total (Includes all files) 17.1 MB / 3.2 MB 0.03%↑17.1 MB / 0.04%↑3.2 MB
Tarball size 3.9 MB 0.06%↑3.9 MB

🤖 This report was automatically generated by pkg-size-action

@NullVoxPopuli
Copy link
Copy Markdown
Contributor

I'm impressed that adding document-fragment support is such a small change it doesn't bust past our noise filter on the size comment (the map files do tho haha)

… appended to DOM

Agent-Logs-Url: https://github.com/emberjs/ember.js/sessions/aec4e27c-bdb9-4439-8efe-a010eef96826

Co-authored-by: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com>
…sertions

Agent-Logs-Url: https://github.com/emberjs/ember.js/sessions/509a66ed-dd19-4b09-aadc-523e8e19e5f6

Co-authored-by: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com>
@NullVoxPopuli

This comment was marked as resolved.

…ix verifySteps failures

Agent-Logs-Url: https://github.com/emberjs/ember.js/sessions/8126c5ce-0399-4465-98ee-8b9eaf691b8c

Co-authored-by: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com>

This comment was marked as resolved.

@NullVoxPopuli

This comment was marked as resolved.

…ng to DOM

Agent-Logs-Url: https://github.com/emberjs/ember.js/sessions/509016a2-4d3c-4fa7-9143-d386e636ec22

Co-authored-by: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com>

This comment was marked as resolved.

@NullVoxPopuli

This comment was marked as resolved.

- bounds.ts clear(): use first.parentNode (live parent) instead of stored
  parentElement(), so removal targets the real container after fragment append
- element-builder.ts resume(): capture live parent from firstNode().parentNode
  before resetting, so new content renders into the container not the fragment
- element-builder.ts RemoteBlock destructor: check firstNode().parentNode !== null
  (node still attached) instead of === parentElement() (node in original parent),
  so in-element/DocumentFragment targets get cleaned up correctly on destroy
- Update test to attach fragment first, then rerender and verify updates work

Agent-Logs-Url: https://github.com/emberjs/ember.js/sessions/6f1bf405-84ab-4bd5-b42d-f061fadcf4df

Co-authored-by: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com>

This comment was marked as resolved.

@NullVoxPopuli

This comment was marked as resolved.

Agent-Logs-Url: https://github.com/emberjs/ember.js/sessions/54039a52-25ed-4859-8971-19493ebbd060

Co-authored-by: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com>

This comment was marked as resolved.

@NullVoxPopuli
Copy link
Copy Markdown
Contributor

NullVoxPopuli commented Mar 27, 2026

I made a GH workflow for more easily testing these out:
NullVoxPopuli/limber#2101

(including PR deploy)

@NullVoxPopuli
Copy link
Copy Markdown
Contributor

Demo with some info: https://test-ember-source-copilot-ex.limber-glimdown.pages.dev/edit?c=JYWwDg9gTgLgBAYQuCA7Apq%2BAzKy4DkAAgOYA2oI6UA9AMbKQZYEDcAUKJLHAN5xo4AXzi58xdCABG1GiAgATYNmDU2nFD34woAQzoBrdAoDyUgFbo68EWJCEikmbSjp9MYADd09CGTJWHmgAzursDKjB8FG6MOhwALxwOvpGphaBABS87HBwDACuWABccAAMADS5cMCodKWZAJSJAHxwMegwAHSFWADUfVVCjRzs4SE4eiRUWIlwChB0BTPddK6x6AAii8uYMABiUytNo8GdACqg6BAFMJlNrXzVeRHBfuhdZBAkmbi603tGuwhBU4ABGMplEZjAA8cXAZA2LWqdAAFlYDMl0fkQu84GM8jDUVBkXldME4Lp2ugwLo9HE4AAFABKcAA7vEMMZkhA4CBdEZREc9hTXKgFNRdFIAtUYIpdABPUHYPwGClsm5kBRwLnaqnyJQqag8ylgMCYbUwbF-AGzOVY%2BKbEwAWVB1TZqOAaJxBS1%2BV0BTOcAA4hQQFQoCaSJ0cahsIHuVIFe1kNcMHB0GQg-zUP9aiQ4E7ncVqrxeDaVkIhAS%2BLwAMS1AC0mckeyF-0r1byeTiAA94LVKe3bTBnrWvgWAERNlsrOBSRQKydVsdlmJxHo3LArscwqS3OWoAR1CiGBJrmAbLq1OhVlo3mE0fcwQ%2Bk2s0GcBTvsR-wsCIuI2iAA&format=gjs

import Component from '@glimmer/component';
import { on } from '@ember/modifier';
import { trackedObject } from '@ember/reactive/collections';

const state = trackedObject({
  count: 0,
  inc: () => staet.count++,
});


const fragment = document.createDocumentFragment();

setTimeout(() => {
    console.log(fragment)
}, 100);

<template>
  check the console 

  <hr>
  as a separate PR we need to make fragments renderable
  today, folks would need a modifier to append the fragment to the DOM, 
  which could cause Glimmer to get confused by someone else managing DOM:
  {{fragment}}

  {{#in-element fragment}}
    text in a fragment
    {{log "in-element body"}}
    {{state.count}}

    <button onclick={{state.inc}}>inc</button>
  {{/in-element}}
</template> 

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.

3 participants