From e2d2bf01e14371e7a76f77e98bf621b9e0ecb35f Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Tue, 17 Mar 2026 15:17:26 -0400 Subject: [PATCH 01/10] Failing test for better debugRenderTree names --- .../test/debug-render-tree-test.ts | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts b/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts index 5cecd7d0852..9a7cb929557 100644 --- a/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts +++ b/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts @@ -110,6 +110,73 @@ class DebugRenderTreeTest extends RenderTest { ]); } + @test 'strict-mode components without debug symbols preserve names from scope'() { + const state = trackedObj({ showSecond: false }); + + const HelloWorld = defComponent('{{@arg}}'); + const Root = defComponent( + `{{#if state.showSecond}}{{/if}}`, + { scope: { HelloWorld, state }, emit: { moduleName: 'root.hbs', debugSymbols: false } } + ); + + this.renderComponent(Root); + + this.assertRenderTree([ + { + type: 'component', + name: '{ROOT}', + args: { positional: [], named: {} }, + instance: null, + template: 'root.hbs', + bounds: this.elementBounds(this.delegate.getInitialElement()), + children: [ + { + type: 'component', + name: 'HelloWorld', + args: { positional: [], named: { arg: 'first' } }, + instance: null, + template: '(unknown template module)', + bounds: this.nodeBounds(this.delegate.getInitialElement().firstChild), + children: [], + }, + ], + }, + ]); + + state['showSecond'] = true; + + this.assertRenderTree([ + { + type: 'component', + name: '{ROOT}', + args: { positional: [], named: {} }, + instance: null, + template: 'root.hbs', + bounds: this.elementBounds(this.delegate.getInitialElement()), + children: [ + { + type: 'component', + name: 'HelloWorld', + args: { positional: [], named: { arg: 'first' } }, + instance: null, + template: '(unknown template module)', + bounds: this.nodeBounds(this.delegate.getInitialElement().firstChild), + children: [], + }, + { + type: 'component', + name: 'HelloWorld', + args: { positional: [], named: { arg: 'second' } }, + instance: null, + template: '(unknown template module)', + bounds: this.nodeBounds(this.delegate.getInitialElement().lastChild), + children: [], + }, + ], + }, + ]); + } + @test 'strict-mode modifiers'() { const state = trackedObj({ showSecond: false }); From 9498833de2410ee3ccb6d51549ccdbbdc10e4b35 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Tue, 17 Mar 2026 16:38:09 -0400 Subject: [PATCH 02/10] glhf --- .../integration-tests/lib/compile.ts | 7 +++- .../test/compiler/compile-options-test.ts | 6 +-- .../test/debug-render-tree-test.ts | 39 +------------------ packages/@glimmer/compiler/lib/compiler.ts | 9 ++++- .../lib/compile/wire-format/api.d.ts | 2 +- .../@glimmer/interfaces/lib/template.d.ts | 2 +- .../lib/opcode-builder/helpers/shared.ts | 5 ++- packages/internal-test-helpers/lib/compile.ts | 7 +++- 8 files changed, 28 insertions(+), 49 deletions(-) diff --git a/packages/@glimmer-workspace/integration-tests/lib/compile.ts b/packages/@glimmer-workspace/integration-tests/lib/compile.ts index 012278e8765..ace69285e2c 100644 --- a/packages/@glimmer-workspace/integration-tests/lib/compile.ts +++ b/packages/@glimmer-workspace/integration-tests/lib/compile.ts @@ -24,7 +24,10 @@ export function createTemplate( ): TemplateFactory { options.locals = options.locals ?? Object.keys(scopeValues ?? {}); let [block, usedLocals] = precompileJSON(templateSource, options); - let reifiedScopeValues = usedLocals.map((key) => scopeValues[key]); + let reifiedScope: Record = {}; + for (let key of usedLocals) { + reifiedScope[key] = scopeValues[key]; + } if ('emit' in options && options.emit?.debugSymbols) { block.push(usedLocals); @@ -34,7 +37,7 @@ export function createTemplate( id: String(templateId++), block: JSON.stringify(block), moduleName: options.meta?.moduleName ?? '(unknown template module)', - scope: reifiedScopeValues.length > 0 ? () => reifiedScopeValues : null, + scope: usedLocals.length > 0 ? () => reifiedScope : null, isStrictMode: options.strictMode ?? false, }; diff --git a/packages/@glimmer-workspace/integration-tests/test/compiler/compile-options-test.ts b/packages/@glimmer-workspace/integration-tests/test/compiler/compile-options-test.ts index c166e06c1d0..7d2c5d2505e 100644 --- a/packages/@glimmer-workspace/integration-tests/test/compiler/compile-options-test.ts +++ b/packages/@glimmer-workspace/integration-tests/test/compiler/compile-options-test.ts @@ -57,7 +57,7 @@ module('[glimmer-compiler] precompile', ({ test }) => { ...WireFormat.Statement[], ]; - assert.deepEqual(wire.scope?.(), [hello]); + assert.deepEqual(wire.scope?.(), { hello }); assert.deepEqual( componentNameExpr, @@ -84,7 +84,7 @@ module('[glimmer-compiler] precompile', ({ test }) => { ...WireFormat.Statement[], ]; - assert.deepEqual(wire.scope?.(), [f]); + assert.deepEqual(wire.scope?.(), { f }); assert.deepEqual( componentNameExpr, [SexpOpcodes.GetLexicalSymbol, 0, ['hello']], @@ -218,7 +218,7 @@ module('[glimmer-compiler] precompile', ({ test }) => { _wire = compile(`{{this.message}}`, ['this'], (source) => eval(source)); }).call(target); let wire = _wire!; - assert.deepEqual(wire.scope?.(), [target]); + assert.deepEqual(wire.scope?.(), { this: target }); assert.deepEqual(wire.block[0], [ [SexpOpcodes.Append, [SexpOpcodes.GetLexicalSymbol, 0, ['message']]], ]); diff --git a/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts b/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts index 9a7cb929557..1d2cd56fb28 100644 --- a/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts +++ b/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts @@ -111,12 +111,10 @@ class DebugRenderTreeTest extends RenderTest { } @test 'strict-mode components without debug symbols preserve names from scope'() { - const state = trackedObj({ showSecond: false }); - const HelloWorld = defComponent('{{@arg}}'); const Root = defComponent( - `{{#if state.showSecond}}{{/if}}`, - { scope: { HelloWorld, state }, emit: { moduleName: 'root.hbs', debugSymbols: false } } + ``, + { scope: { HelloWorld }, emit: { moduleName: 'root.hbs', debugSymbols: false } } ); this.renderComponent(Root); @@ -142,39 +140,6 @@ class DebugRenderTreeTest extends RenderTest { ], }, ]); - - state['showSecond'] = true; - - this.assertRenderTree([ - { - type: 'component', - name: '{ROOT}', - args: { positional: [], named: {} }, - instance: null, - template: 'root.hbs', - bounds: this.elementBounds(this.delegate.getInitialElement()), - children: [ - { - type: 'component', - name: 'HelloWorld', - args: { positional: [], named: { arg: 'first' } }, - instance: null, - template: '(unknown template module)', - bounds: this.nodeBounds(this.delegate.getInitialElement().firstChild), - children: [], - }, - { - type: 'component', - name: 'HelloWorld', - args: { positional: [], named: { arg: 'second' } }, - instance: null, - template: '(unknown template module)', - bounds: this.nodeBounds(this.delegate.getInitialElement().lastChild), - children: [], - }, - ], - }, - ]); } @test 'strict-mode modifiers'() { diff --git a/packages/@glimmer/compiler/lib/compiler.ts b/packages/@glimmer/compiler/lib/compiler.ts index 089608f65f5..a712196610a 100644 --- a/packages/@glimmer/compiler/lib/compiler.ts +++ b/packages/@glimmer/compiler/lib/compiler.ts @@ -148,7 +148,14 @@ export function precompile( let stringified = JSON.stringify(templateJSONObject); if (usedLocals.length > 0) { - const scopeFn = `()=>[${usedLocals.join(',')}]`; + const scopeEntries = usedLocals.map((name) => { + // Reserved words like "this" can't use shorthand property syntax + if (name === 'this') { + return `"this":this`; + } + return name; + }); + const scopeFn = `()=>({${scopeEntries.join(',')}})`; stringified = stringified.replace(`"${SCOPE_PLACEHOLDER}"`, scopeFn); } diff --git a/packages/@glimmer/interfaces/lib/compile/wire-format/api.d.ts b/packages/@glimmer/interfaces/lib/compile/wire-format/api.d.ts index 6db2138eb87..9032916b8d1 100644 --- a/packages/@glimmer/interfaces/lib/compile/wire-format/api.d.ts +++ b/packages/@glimmer/interfaces/lib/compile/wire-format/api.d.ts @@ -397,7 +397,7 @@ export interface SerializedTemplateWithLazyBlock { id?: Nullable; block: SerializedTemplateBlockJSON; moduleName: string; - scope?: (() => unknown[]) | undefined | null; + scope?: (() => Record) | undefined | null; isStrictMode: boolean; } diff --git a/packages/@glimmer/interfaces/lib/template.d.ts b/packages/@glimmer/interfaces/lib/template.d.ts index 5e008c8039c..2990176844a 100644 --- a/packages/@glimmer/interfaces/lib/template.d.ts +++ b/packages/@glimmer/interfaces/lib/template.d.ts @@ -18,7 +18,7 @@ export interface LayoutWithContext { readonly block: SerializedTemplateBlock; readonly moduleName: string; readonly owner: Owner | null; - readonly scope: (() => unknown[]) | undefined | null; + readonly scope: (() => Record) | undefined | null; readonly isStrictMode: boolean; } diff --git a/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/shared.ts b/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/shared.ts index 640b63494b7..634fd603510 100644 --- a/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/shared.ts +++ b/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/shared.ts @@ -107,14 +107,15 @@ export function CompilePositional( export function meta(layout: LayoutWithContext): BlockMetadata { let [, locals, upvars, lexicalSymbols] = layout.block; + let scopeRecord = layout.scope?.() ?? null; return { symbols: { locals, upvars, - lexical: lexicalSymbols, + lexical: scopeRecord ? Object.keys(scopeRecord) : lexicalSymbols, }, - scopeValues: layout.scope?.() ?? null, + scopeValues: scopeRecord ? Object.values(scopeRecord) : null, isStrictMode: layout.isStrictMode, moduleName: layout.moduleName, owner: layout.owner, diff --git a/packages/internal-test-helpers/lib/compile.ts b/packages/internal-test-helpers/lib/compile.ts index 7fd317a7fe8..2266b47457e 100644 --- a/packages/internal-test-helpers/lib/compile.ts +++ b/packages/internal-test-helpers/lib/compile.ts @@ -24,12 +24,15 @@ export default function compile( ): TemplateFactory { options.locals = options.locals ?? Object.keys(scopeValues ?? {}); let [block, usedLocals] = precompileJSON(templateSource, compileOptions(options)); - let reifiedScopeValues = usedLocals.map((key) => scopeValues[key]); + let reifiedScope: Record = {}; + for (let key of usedLocals) { + reifiedScope[key] = scopeValues[key]; + } let templateBlock: SerializedTemplateWithLazyBlock = { block: JSON.stringify(block), moduleName: options.moduleName ?? options.meta?.moduleName ?? '(unknown template module)', - scope: reifiedScopeValues.length > 0 ? () => reifiedScopeValues : null, + scope: usedLocals.length > 0 ? () => reifiedScope : null, isStrictMode: options.strictMode ?? false, }; From 427b3926c3629069ba265392c86f81a3ba2cc1f4 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Tue, 17 Mar 2026 17:33:35 -0400 Subject: [PATCH 03/10] style: fix prettier formatting in debug-render-tree-test Co-Authored-By: Claude Opus 4.6 (1M context) --- .../integration-tests/test/debug-render-tree-test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts b/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts index 1d2cd56fb28..794877eb5cc 100644 --- a/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts +++ b/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts @@ -112,10 +112,10 @@ class DebugRenderTreeTest extends RenderTest { @test 'strict-mode components without debug symbols preserve names from scope'() { const HelloWorld = defComponent('{{@arg}}'); - const Root = defComponent( - ``, - { scope: { HelloWorld }, emit: { moduleName: 'root.hbs', debugSymbols: false } } - ); + const Root = defComponent(``, { + scope: { HelloWorld }, + emit: { moduleName: 'root.hbs', debugSymbols: false }, + }); this.renderComponent(Root); From 5e7eeda04c8154337d4afec70f8c70003eb6c354 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Tue, 17 Mar 2026 18:07:37 -0400 Subject: [PATCH 04/10] Add a smoke test so we can test the full e2e --- smoke-tests/scenarios/basic-test.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/smoke-tests/scenarios/basic-test.ts b/smoke-tests/scenarios/basic-test.ts index 1e575338e0e..6d49a80bf27 100644 --- a/smoke-tests/scenarios/basic-test.ts +++ b/smoke-tests/scenarios/basic-test.ts @@ -223,6 +223,29 @@ function basicTest(scenarios: Scenarios, appName: string) { }); `, + 'debug-render-tree-test.gjs': ` + import { module, test } from 'qunit'; + import { setupRenderingTest } from 'ember-qunit'; + import { render } from '@ember/test-helpers'; + import { captureRenderTree } from '@ember/debug'; + import Component from '@glimmer/component'; + + class HelloWorld extends Component { + + } + + module('Integration | captureRenderTree', function (hooks) { + setupRenderingTest(hooks); + + test('scope-based components have correct names in debugRenderTree', async function (assert) { + await render(); + + let tree = captureRenderTree(this.owner); + let names = tree.filter(n => n.type === 'component').map(n => n.name); + assert.true(names.includes('HelloWorld'), 'HelloWorld component name is preserved in the render tree (found: ' + names.join(', ') + ')'); + }); + }); + `, }, }, }); From 497f868f6520baec33a4854f794090702c048059 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Tue, 17 Mar 2026 18:26:03 -0400 Subject: [PATCH 05/10] fix: flatten render tree when checking component names in smoke test The captureRenderTree API returns a nested tree structure. Component nodes are children of other nodes, so we need to recursively flatten the tree before searching for component names. Co-Authored-By: Claude Opus 4.6 (1M context) --- smoke-tests/scenarios/basic-test.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/smoke-tests/scenarios/basic-test.ts b/smoke-tests/scenarios/basic-test.ts index 6d49a80bf27..b58d060f79a 100644 --- a/smoke-tests/scenarios/basic-test.ts +++ b/smoke-tests/scenarios/basic-test.ts @@ -230,6 +230,17 @@ function basicTest(scenarios: Scenarios, appName: string) { import { captureRenderTree } from '@ember/debug'; import Component from '@glimmer/component'; + function flattenTree(nodes) { + let result = []; + for (let node of nodes) { + result.push(node); + if (node.children) { + result.push(...flattenTree(node.children)); + } + } + return result; + } + class HelloWorld extends Component { } @@ -241,7 +252,8 @@ function basicTest(scenarios: Scenarios, appName: string) { await render(); let tree = captureRenderTree(this.owner); - let names = tree.filter(n => n.type === 'component').map(n => n.name); + let allNodes = flattenTree(tree); + let names = allNodes.filter(n => n.type === 'component').map(n => n.name); assert.true(names.includes('HelloWorld'), 'HelloWorld component name is preserved in the render tree (found: ' + names.join(', ') + ')'); }); }); From 87d55e6e91132ed7f319f9aacf73f24d5d0ef790 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Wed, 18 Mar 2026 10:42:15 -0400 Subject: [PATCH 06/10] test: add failing tests for dynamic component names in debugRenderTree Add tests for: - invocations - <@argComponent> invocations Both currently produce '(unknown template-only component)' instead of the expected component name because dynamic resolution at runtime loses the invocation-site name information. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../test/debug-render-tree-test.ts | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts b/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts index 794877eb5cc..7692b57b8a2 100644 --- a/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts +++ b/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts @@ -142,6 +142,56 @@ class DebugRenderTreeTest extends RenderTest { ]); } + @test 'dynamic component via '() { + const HelloWorld = defComponent('{{@arg}}'); + + class Root extends GlimmerishComponent { + HelloWorld = HelloWorld; + } + + const RootDef = defComponent(``, { + component: Root, + emit: { moduleName: 'root.hbs' }, + }); + + this.renderComponent(RootDef); + + const rootChildren = this.delegate.getCapturedRenderTree()[0]?.children ?? []; + const componentNode = rootChildren.find( + (n: CapturedRenderNode) => n.type === 'component' && n.name !== '{ROOT}' + ); + + this.assert.ok(componentNode, 'found a component child node'); + + this.assert.strictEqual( + componentNode?.name, + 'HelloWorld', + `dynamic component name (got "${componentNode?.name}")` + ); + } + + @test 'dynamic component via <@argComponent>'() { + const HelloWorld = defComponent('{{@arg}}'); + const Root = defComponent(`<@Greeting @arg="first"/>`, { + emit: { moduleName: 'root.hbs' }, + }); + + this.renderComponent(Root, { Greeting: HelloWorld }); + + const rootChildren = this.delegate.getCapturedRenderTree()[0]?.children ?? []; + const componentNode = rootChildren.find( + (n: CapturedRenderNode) => n.type === 'component' && n.name !== '{ROOT}' + ); + + this.assert.ok(componentNode, 'found a component child node'); + + this.assert.strictEqual( + componentNode?.name, + 'HelloWorld', + `dynamic <@X> component name (got "${componentNode?.name}")` + ); + } + @test 'strict-mode modifiers'() { const state = trackedObj({ showSecond: false }); From f4dc41a637cdfa126f4527272d92b81069e03160 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Wed, 18 Mar 2026 10:51:24 -0400 Subject: [PATCH 07/10] feat: propagate invocation-site names for dynamic components in debugRenderTree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For dynamic component invocations like `` and `<@Greeting>`, the debug render tree now shows the invocation-site name instead of '(unknown template-only component)'. This works by extracting the Reference's debugLabel in VM_RESOLVE_DYNAMIC_COMPONENT_OP and VM_RESOLVE_CURRIED_COMPONENT_OP, and setting it as the ComponentDefinition's debugName when no name is already present. - `` → name: "Foo" - `<@Greeting>` → name: "Greeting" Co-Authored-By: Claude Opus 4.6 (1M context) --- .../test/debug-render-tree-test.ts | 3 ++- .../runtime/lib/compiled/opcodes/component.ts | 23 ++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts b/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts index 7692b57b8a2..6e3b30001a0 100644 --- a/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts +++ b/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts @@ -185,9 +185,10 @@ class DebugRenderTreeTest extends RenderTest { this.assert.ok(componentNode, 'found a component child node'); + // For <@Greeting>, the invocation-site name "Greeting" is used this.assert.strictEqual( componentNode?.name, - 'HelloWorld', + 'Greeting', `dynamic <@X> component name (got "${componentNode?.name}")` ); } diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts index a8091130ec7..5fc922269cf 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts @@ -154,8 +154,9 @@ APPEND_OPCODES.add(VM_PUSH_COMPONENT_DEFINITION_OP, (vm, { op1: handle }) => { APPEND_OPCODES.add(VM_RESOLVE_DYNAMIC_COMPONENT_OP, (vm, { op1: _isStrict }) => { let stack = vm.stack; + let ref = check(stack.pop(), CheckReference); let component = check( - valueForRef(check(stack.pop(), CheckReference)), + valueForRef(ref), CheckOr(CheckString, CheckCurriedComponentDefinition) ); let constants = vm.constants; @@ -182,6 +183,14 @@ APPEND_OPCODES.add(VM_RESOLVE_DYNAMIC_COMPONENT_OP, (vm, { op1: _isStrict }) => definition = constants.component(component, owner); } + if (DEBUG && !isCurriedValue(definition) && !definition.resolvedName && !definition.debugName) { + let debugLabel = ref.debugLabel; + if (debugLabel) { + // Extract the last segment of the path (e.g. "this.Foo" → "Foo", "Foo" → "Foo") + definition.debugName = debugLabel.split('.').pop(); + } + } + stack.push(definition); }); @@ -217,6 +226,18 @@ APPEND_OPCODES.add(VM_RESOLVE_CURRIED_COMPONENT_OP, (vm) => { } } + if (DEBUG && definition && !isCurriedValue(definition) && !definition.resolvedName && !definition.debugName) { + let debugLabel = ref.debugLabel; + if (debugLabel) { + // Extract the component name from the arg path (e.g. "@Greeting" → "Greeting") + let name = debugLabel.split('.').pop()!; + if (name.startsWith('@')) { + name = name.slice(1); + } + definition.debugName = name; + } + } + stack.push(definition); }); From df2068a81f2b8ee9fc3b4bd7a3d4f152a6f947cb Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Wed, 18 Mar 2026 10:56:21 -0400 Subject: [PATCH 08/10] fix: use full invocation path for dynamic component debugRenderTree names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the full debugLabel as the component name so users see exactly how the component was invoked: - `` → name: "this.Foo" - `<@Greeting>` → name: "@Greeting" Co-Authored-By: Claude Opus 4.6 (1M context) --- .../integration-tests/test/debug-render-tree-test.ts | 5 ++--- .../@glimmer/runtime/lib/compiled/opcodes/component.ts | 10 ++-------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts b/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts index 6e3b30001a0..df4e06003a1 100644 --- a/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts +++ b/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts @@ -165,7 +165,7 @@ class DebugRenderTreeTest extends RenderTest { this.assert.strictEqual( componentNode?.name, - 'HelloWorld', + 'this.HelloWorld', `dynamic component name (got "${componentNode?.name}")` ); } @@ -185,10 +185,9 @@ class DebugRenderTreeTest extends RenderTest { this.assert.ok(componentNode, 'found a component child node'); - // For <@Greeting>, the invocation-site name "Greeting" is used this.assert.strictEqual( componentNode?.name, - 'Greeting', + '@Greeting', `dynamic <@X> component name (got "${componentNode?.name}")` ); } diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts index 5fc922269cf..28a38c3a1de 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts @@ -186,8 +186,7 @@ APPEND_OPCODES.add(VM_RESOLVE_DYNAMIC_COMPONENT_OP, (vm, { op1: _isStrict }) => if (DEBUG && !isCurriedValue(definition) && !definition.resolvedName && !definition.debugName) { let debugLabel = ref.debugLabel; if (debugLabel) { - // Extract the last segment of the path (e.g. "this.Foo" → "Foo", "Foo" → "Foo") - definition.debugName = debugLabel.split('.').pop(); + definition.debugName = debugLabel; } } @@ -229,12 +228,7 @@ APPEND_OPCODES.add(VM_RESOLVE_CURRIED_COMPONENT_OP, (vm) => { if (DEBUG && definition && !isCurriedValue(definition) && !definition.resolvedName && !definition.debugName) { let debugLabel = ref.debugLabel; if (debugLabel) { - // Extract the component name from the arg path (e.g. "@Greeting" → "Greeting") - let name = debugLabel.split('.').pop()!; - if (name.startsWith('@')) { - name = name.slice(1); - } - definition.debugName = name; + definition.debugName = debugLabel; } } From 5d8a4215650225dc1dce5e4214b3e6a0c5777733 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Wed, 18 Mar 2026 11:38:18 -0400 Subject: [PATCH 09/10] style: fix prettier formatting in component opcodes Co-Authored-By: Claude Opus 4.6 (1M context) --- .../runtime/lib/compiled/opcodes/component.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts index 28a38c3a1de..55baed8453a 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts @@ -155,10 +155,7 @@ APPEND_OPCODES.add(VM_PUSH_COMPONENT_DEFINITION_OP, (vm, { op1: handle }) => { APPEND_OPCODES.add(VM_RESOLVE_DYNAMIC_COMPONENT_OP, (vm, { op1: _isStrict }) => { let stack = vm.stack; let ref = check(stack.pop(), CheckReference); - let component = check( - valueForRef(ref), - CheckOr(CheckString, CheckCurriedComponentDefinition) - ); + let component = check(valueForRef(ref), CheckOr(CheckString, CheckCurriedComponentDefinition)); let constants = vm.constants; let owner = vm.getOwner(); let isStrict = constants.getValue(_isStrict); @@ -225,7 +222,13 @@ APPEND_OPCODES.add(VM_RESOLVE_CURRIED_COMPONENT_OP, (vm) => { } } - if (DEBUG && definition && !isCurriedValue(definition) && !definition.resolvedName && !definition.debugName) { + if ( + DEBUG && + definition && + !isCurriedValue(definition) && + !definition.resolvedName && + !definition.debugName + ) { let debugLabel = ref.debugLabel; if (debugLabel) { definition.debugName = debugLabel; From 88bb0d137d4ee2b774ca73a992949d0362dab1f2 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Wed, 18 Mar 2026 11:58:34 -0400 Subject: [PATCH 10/10] fix: skip dynamic component name tests in production builds The invocation-site name propagation relies on ref.debugLabel which is only available in DEBUG mode. Skip these tests in production builds. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../integration-tests/test/debug-render-tree-test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts b/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts index df4e06003a1..81a594e7d49 100644 --- a/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts +++ b/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts @@ -11,6 +11,7 @@ import type { import type { TemplateOnlyComponent } from '@glimmer/runtime'; import type { EmberishCurlyComponent } from '@glimmer-workspace/integration-tests'; import { expect } from '@glimmer/debug-util'; +import { DEBUG } from '@glimmer/env'; import { modifierCapabilities, setComponentTemplate, setModifierManager } from '@glimmer/manager'; import { EMPTY_ARGS, templateOnlyComponent, TemplateOnlyComponentManager } from '@glimmer/runtime'; import { assign } from '@glimmer/util'; @@ -142,7 +143,7 @@ class DebugRenderTreeTest extends RenderTest { ]); } - @test 'dynamic component via '() { + @test({ skip: !DEBUG }) 'dynamic component via '() { const HelloWorld = defComponent('{{@arg}}'); class Root extends GlimmerishComponent { @@ -170,7 +171,7 @@ class DebugRenderTreeTest extends RenderTest { ); } - @test 'dynamic component via <@argComponent>'() { + @test({ skip: !DEBUG }) 'dynamic component via <@argComponent>'() { const HelloWorld = defComponent('{{@arg}}'); const Root = defComponent(`<@Greeting @arg="first"/>`, { emit: { moduleName: 'root.hbs' },