Skip to content

CI run for glimmer-next-fresh (do not merge) - #2

Closed
lifeart wants to merge 99 commits into
ci-base-mainfrom
glimmer-next-fresh
Closed

lifeart wants to merge 99 commits into
ci-base-mainfrom
glimmer-next-fresh

Conversation

@lifeart

@lifeart lifeart commented Oct 12, 2026

Copy link
Copy Markdown
Owner

Fork-internal PR used only to run the full CI (classic Basic Test, variants, lint, types, GXT workflows) on the glimmer-next-fresh branch of emberjs#21340. Base ci-base-gxt = the merge-base with upstream main, so CI tests the branch exactly. Do not merge.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

📊 Size report

Tarball size — 1.2 MB → 1.1 MB

dist/dev   0.3%↑

File Before (Size / Brotli) After (Size / Brotli)
Total (Includes all files) 2.1 MB / 514.6 kB 0.3%↑2.1 MB / 0.5%↑517.2 kB
Show files (21 files)
File Before (Size / Brotli) After (Size / Brotli)
./packages/@ember/-internals/glimmer/index.js 2.3 kB / 697 B 178%↑6.3 kB / 190%↑2 kB
./packages/@ember/application/instance.js 15.8 kB / 4.4 kB 1%↑16 kB / 0.8%↑4.4 kB
./packages/@ember/object/core.js 32.1 kB / 7.9 kB 2%↑32.8 kB / 3%↑8.1 kB
./packages/@ember/routing/route.js 53.8 kB / 11.6 kB 0.9%↑54.3 kB / 2%↑11.8 kB
./packages/@ember/template-compiler/lib/template.js 8.9 kB / 2.4 kB -73%↓2.4 kB / -67.2%↓803 B
./packages/@glimmer/reference/index.js 5.5 kB / 1.7 kB -89.5%↓579 B / -85.3%↓251 B
./packages/@glimmer/validator/index.js 1.6 kB / 626 B 249%↑5.6 kB / 184%↑1.8 kB
./packages/shared-chunks/api-{hash}.js 27 kB / 6.1 kB -63.2%↓10 kB / -65.6%↓2.1 kB
./packages/shared-chunks/chain-tags-C-oqUYHk.js 7.6 kB / 2.2 kB —
./packages/shared-chunks/computed-{hash}.js 34.1 kB / 7.9 kB 2%↑34.8 kB / 3%↑8.2 kB
./packages/shared-chunks/element-builder-Iw99V8A5.js — 27 kB / 6.1 kB
./packages/shared-chunks/index-{hash}.js 62.6 kB / 12.5 kB -6.9%↓58.3 kB / 15%↑14.4 kB
./packages/shared-chunks/internal-{hash}.js 4.2 kB / 1.3 kB 21%↑5.1 kB / 25%↑1.7 kB
./packages/shared-chunks/iterable-B3x9eNEd.js — 5 kB / 1.6 kB
./packages/shared-chunks/observers-{hash}.js 6.8 kB / 1.6 kB 107%↑14.1 kB / 122%↑3.5 kB
./packages/shared-chunks/property_set-{hash}.js 4.3 kB / 1.4 kB 3%↑4.5 kB / 4%↑1.4 kB
./packages/shared-chunks/setup-{hash}.js 18.1 kB / 5.1 kB 10%↑20 kB / 13%↑5.7 kB
./packages/shared-chunks/template-{hash}.js 1.1 kB / 401 B -54%↓491 B / -49.4%↓203 B
./packages/shared-chunks/textarea-{hash}.js 14 kB / 3.4 kB 9%↑15.3 kB / 10%↑3.8 kB
./packages/shared-chunks/tracked-{hash}.js 12.9 kB / 3.5 kB 2%↑13.2 kB / 2%↑3.6 kB
./packages/shared-chunks/tracked-value-Bn9fua5C.js 4.1 kB / 1.3 kB —

dist/prod   -38.7%↓

File Before (Size / Brotli) After (Size / Brotli)
Total (Includes all files) 1.9 MB / 471.4 kB -38.7%↓1.2 MB / -41.8%↓274.4 kB
Show files (187 files)
File Before (Size / Brotli) After (Size / Brotli)
./packages/@ember/-internals/container/index.js 11 kB / 2.3 kB -45.6%↓6 kB / -47.8%↓1.2 kB
./packages/@ember/-internals/deprecations/index.js 6.7 kB / 1.7 kB -38.1%↓4.1 kB / -44.6%↓929 B
./packages/@ember/-internals/environment/index.js 625 B / 269 B -20.5%↓497 B / -7.43%↓249 B
./packages/@ember/-internals/meta/lib/meta.js 12.7 kB / 2.9 kB -30.6%↓8.8 kB / -36.3%↓1.8 kB
./packages/@ember/-internals/metal/index.js 4.2 kB / 1.2 kB -12.1%↓3.7 kB / -13.5%↓1.1 kB
./packages/@ember/-internals/owner/index.js 7.3 kB / 2.3 kB -95.2%↓354 B / -92.7%↓167 B
./packages/@ember/-internals/routing/route-managers/classic/bucket.js 898 B / 404 B -63.1%↓331 B / -56.4%↓176 B
./packages/@ember/-internals/routing/route-managers/classic/manager.js 11.3 kB / 3 kB -24.9%↓8.5 kB / -32.8%↓2 kB
./packages/@ember/-internals/routing/route-managers/classic/outlet-component.js 4.1 kB / 1.4 kB -18.6%↓3.4 kB / -19.8%↓1.1 kB
./packages/@ember/-internals/routing/route-managers/classic/query-params.js 6.1 kB / 1.8 kB -29%↓4.3 kB / -34%↓1.2 kB
./packages/@ember/-internals/routing/route-managers/classic/substates.js 5.5 kB / 1.6 kB -47.6%↓2.9 kB / -51.3%↓768 B
./packages/@ember/-internals/routing/route-managers/outlet-state.js 1.8 kB / 622 B -15.6%↓1.5 kB / -19.8%↓499 B
./packages/@ember/-internals/routing/route-managers/registry.js 2 kB / 703 B -76.7%↓476 B / -72%↓197 B
./packages/@ember/-internals/routing/route-managers/root-outlet.js 5.3 kB / 1.6 kB -15.5%↓4.5 kB / -19.1%↓1.3 kB
./packages/@ember/-internals/runtime/lib/mixins/-proxy.js 3.1 kB / 1 kB -22.9%↓2.4 kB / -27.3%↓753 B
./packages/@ember/-internals/runtime/lib/mixins/action_handler.js 5.7 kB / 1.6 kB -84.8%↓868 B / -76.1%↓379 B
./packages/@ember/-internals/runtime/lib/mixins/comparable.js 1.3 kB / 505 B -60.8%↓500 B / -46.7%↓269 B
./packages/@ember/-internals/runtime/lib/mixins/container_proxy.js 1.2 kB / 424 B -35.5%↓768 B / -28.5%↓303 B
./packages/@ember/-internals/runtime/lib/mixins/registry_proxy.js 1.3 kB / 473 B -37.3%↓821 B / -38.9%↓289 B
./packages/@ember/-internals/runtime/lib/mixins/target_action_support.js 4.6 kB / 1.3 kB -51.6%↓2.2 kB / -45.5%↓701 B
./packages/@ember/-internals/string/index.js 3 kB / 920 B -57.1%↓1.3 kB / -48.2%↓477 B
./packages/@ember/-internals/utility-types/index.js 516 B / 260 B -93%↓36 B / -84.6%↓40 B
./packages/@ember/-internals/utils/index.js 2.6 kB / 957 B -29.1%↓1.8 kB / -32.5%↓646 B
./packages/@ember/-internals/views/lib/system/event_dispatcher.js 6.9 kB / 1.9 kB -43.5%↓3.9 kB / -43.7%↓1.1 kB
./packages/@ember/-internals/views/lib/system/utils.js 4.6 kB / 1.2 kB -27%↓3.4 kB / -21.9%↓965 B
./packages/@ember/-internals/views/lib/views/core_view.js 2.5 kB / 852 B -23.9%↓1.9 kB / -27%↓622 B
./packages/@ember/-internals/views/lib/views/states.js 2.6 kB / 784 B -42.8%↓1.5 kB / -42.5%↓451 B
./packages/@ember/application/index.js 32.2 kB / 8.3 kB -79.2%↓6.7 kB / -80.2%↓1.7 kB
./packages/@ember/application/instance.js 14.7 kB / 4.1 kB -58.3%↓6.1 kB / -63.3%↓1.5 kB
./packages/@ember/application/namespace.js 1.8 kB / 617 B -28.7%↓1.3 kB / -29.7%↓434 B
./packages/@ember/array/lib/is-array.js 1.9 kB / 675 B -70.5%↓547 B / -64.7%↓238 B
./packages/@ember/array/lib/make-array.js 936 B / 368 B -79.7%↓190 B / -66%↓125 B
./packages/@ember/array/proxy.js 9.4 kB / 2.3 kB -30.9%↓6.5 kB / -36.4%↓1.5 kB
./packages/@ember/canary-features/index.js 1.6 kB / 610 B -73.8%↓430 B / -65.3%↓212 B
./packages/@ember/component/helper.js 10.4 kB / 3.3 kB -73.9%↓2.7 kB / -75.4%↓819 B
./packages/@ember/component/index.js 40.4 kB / 9.4 kB -82.2%↓7.2 kB / -79.1%↓2 kB
./packages/@ember/component/template-only.js 1.2 kB / 502 B -85.6%↓174 B / -77.7%↓112 B
./packages/@ember/controller/index.js 4.9 kB / 1.6 kB -68.1%↓1.6 kB / -61.9%↓610 B
./packages/@ember/debug/container-debug-adapter.js 2.8 kB / 930 B -57.2%↓1.2 kB / -49.5%↓470 B
./packages/@ember/debug/data-adapter.js 15.9 kB / 3.9 kB -49.4%↓8.1 kB / -51.9%↓1.9 kB
./packages/@ember/debug/index.js 1.2 kB / 421 B -14.5%↓1 kB / -13.5%↓364 B
./packages/@ember/debug/lib/capture-render-tree.js 827 B / 374 B -61.1%↓322 B / -55.6%↓166 B
./packages/@ember/debug/lib/deprecate.js 2.3 kB / 775 B -84.7%↓346 B / -79.1%↓162 B
./packages/@ember/debug/lib/inspect.js 3 kB / 979 B -21.3%↓2.4 kB / -25.8%↓726 B
./packages/@ember/deprecated-features/index.js 208 B / 130 B -80.3%↓41 B / -65.4%↓45 B
./packages/@ember/destroyable/index.js 7.7 kB / 1.7 kB -92.1%↓604 B / -89.2%↓183 B
./packages/@ember/engine/index.js 14.6 kB / 3.5 kB -70.5%↓4.3 kB / -67.3%↓1.1 kB
./packages/@ember/engine/instance.js 6.5 kB / 2 kB -53.9%↓3 kB / -54.6%↓903 B
./packages/@ember/engine/lib/engine-parent.js 762 B / 234 B -66.4%↓256 B / -46.6%↓125 B
./packages/@ember/engine/lib/strict-resolver.js 3.4 kB / 996 B -15.8%↓2.8 kB / -23.3%↓764 B
./packages/@ember/enumerable/index.js 570 B / 283 B -67.9%↓183 B / -53.4%↓132 B
./packages/@ember/enumerable/mutable.js 623 B / 296 B -60.8%↓244 B / -43.6%↓167 B
./packages/@ember/helper/index.js 49.1 kB / 11 kB -97.3%↓1.3 kB / -95.9%↓454 B
./packages/@ember/instrumentation/index.js 5.5 kB / 1.6 kB -43.1%↓3.2 kB / -42.9%↓893 B
./packages/@ember/modifier/index.js 4.8 kB / 1.4 kB -94.4%↓266 B / -91.5%↓123 B
./packages/@ember/modifier/on.js 732 B / 350 B -86.6%↓98 B / -70.9%↓102 B
./packages/@ember/object/-internals.js 1.3 kB / 529 B -70.1%↓379 B / -66.2%↓179 B
./packages/@ember/object/compat.js 3.6 kB / 1.2 kB -57.9%↓1.5 kB / -54.9%↓544 B
./packages/@ember/object/core.js 26.3 kB / 6.5 kB -72%↓7.4 kB / -70.3%↓1.9 kB
./packages/@ember/object/index.js 6.4 kB / 1.9 kB -46.7%↓3.4 kB / -44.9%↓1.1 kB
./packages/@ember/object/lib/computed/computed_macros.js 20.5 kB / 3.2 kB -77.6%↓4.6 kB / -70%↓964 B
./packages/@ember/object/lib/computed/reduce_computed_macros.js 27.1 kB / 4.7 kB -71.2%↓7.8 kB / -65.7%↓1.6 kB
./packages/@ember/object/observable.js 6.2 kB / 1.7 kB -65.2%↓2.2 kB / -64.4%↓605 B
./packages/@ember/object/promise-proxy-mixin.js 4.1 kB / 1.3 kB -51.3%↓2 kB / -48.4%↓685 B
./packages/@ember/object/proxy.js 2.8 kB / 872 B -81.2%↓528 B / -70.8%↓255 B
./packages/@ember/owner/index.js 3.2 kB / 1.2 kB -94.4%↓180 B / -92.1%↓93 B
./packages/@ember/reactive/collections.js 20 kB / 3.4 kB -37%↓12.6 kB / -50.1%↓1.7 kB
./packages/@ember/reactive/index.js 254 B / 135 B -81.1%↓48 B / -61.5%↓52 B
./packages/@ember/routing/hash-location.js 3.6 kB / 1.2 kB -60%↓1.5 kB / -63.5%↓436 B
./packages/@ember/routing/history-location.js 5.7 kB / 1.6 kB -49%↓2.9 kB / -52.4%↓758 B
./packages/@ember/routing/lib/cache.js 816 B / 303 B -20.1%↓652 B / -27.1%↓221 B
./packages/@ember/routing/lib/controller_for.js 301 B / 147 B -39.9%↓181 B / -39.5%↓89 B
./packages/@ember/routing/lib/dsl.js 5.5 kB / 1.3 kB -5.39%↓5.2 kB / -7.8%↓1.2 kB
./packages/@ember/routing/lib/generate_controller.js 1.7 kB / 558 B -55.1%↓750 B / -55%↓251 B
./packages/@ember/routing/lib/location-utils.js 1.1 kB / 445 B -47.4%↓587 B / -48.3%↓230 B
./packages/@ember/routing/lib/routing-service.js 3.6 kB / 1 kB -28.9%↓2.6 kB / -35.4%↓672 B
./packages/@ember/routing/lib/utils.js 7.4 kB / 2.3 kB -29.1%↓5.3 kB / -30.8%↓1.6 kB
./packages/@ember/routing/none-location.js 2.7 kB / 850 B -65.7%↓910 B / -58.4%↓354 B
./packages/@ember/routing/route.js 49.2 kB / 10.7 kB -63.6%↓17.9 kB / -62.1%↓4.1 kB
./packages/@ember/routing/router-service.js 25.9 kB / 5.4 kB -83.4%↓4.3 kB / -81%↓1 kB
./packages/@ember/routing/router.js 47.6 kB / 11.2 kB -37.2%↓29.9 kB / -45.2%↓6.2 kB
./packages/@ember/runloop/index.js 22.2 kB / 4.8 kB -90%↓2.2 kB / -86.8%↓642 B
./packages/@ember/service/index.js 3.2 kB / 976 B -80%↓637 B / -71%↓283 B
./packages/@ember/template-compilation/index.js 920 B / 401 B -48.6%↓473 B / -50.9%↓197 B
./packages/@ember/template-compiler/lib/compile-options.js 4.9 kB / 1.6 kB -29%↓3.5 kB / -31.7%↓1.1 kB
./packages/@ember/template-compiler/lib/dasherize-component-name.js 653 B / 341 B -23%↓503 B / -13.5%↓295 B
./packages/@ember/template-compiler/lib/plugins/allowed-globals.js 1.8 kB / 647 B -76.7%↓414 B / -68.2%↓206 B
./packages/@ember/template-compiler/lib/plugins/assert-against-attrs.js 1 kB / 427 B -24.4%↓758 B / -24.6%↓322 B
./packages/@ember/template-compiler/lib/plugins/assert-against-named-outlets.js 802 B / 340 B -19.2%↓648 B / -17.4%↓281 B
./packages/@ember/template-compiler/lib/plugins/assert-reserved-named-arguments.js 949 B / 397 B -36.3%↓605 B / -36.8%↓251 B
./packages/@ember/template-compiler/lib/plugins/auto-import-builtins.js 1.1 kB / 506 B -12.2%↓1 kB / -16.6%↓422 B
./packages/@ember/template-compiler/lib/plugins/index.js 2.4 kB / 583 B -5.81%↓2.3 kB / -4.63%↓556 B
./packages/@ember/template-compiler/lib/plugins/transform-each-track-array.js 1.2 kB / 478 B -25.1%↓864 B / -25.1%↓358 B
./packages/@ember/template-compiler/lib/plugins/transform-in-element.js 1.1 kB / 461 B -31.6%↓726 B / -35.8%↓296 B
./packages/@ember/template-compiler/lib/plugins/transform-resolutions.js 2.8 kB / 785 B -28%↓2 kB / -21.4%↓617 B
./packages/@ember/template-compiler/lib/plugins/transform-wrap-mount-and-outlet.js 1.3 kB / 484 B -28.3%↓946 B / -23.4%↓371 B
./packages/@ember/template-compiler/lib/template.js 8.9 kB / 2.4 kB -77.3%↓2 kB / -73%↓661 B
./packages/@ember/utils/lib/compare.js 4.3 kB / 1.4 kB -53.4%↓2 kB / -51.1%↓696 B
./packages/@ember/utils/lib/is_blank.js 1.1 kB / 444 B -64%↓394 B / -48.2%↓230 B
./packages/@ember/utils/lib/is_empty.js 1.8 kB / 640 B -51.8%↓884 B / -46.3%↓344 B
./packages/@ember/utils/lib/is_none.js 863 B / 371 B -62.9%↓320 B / -48%↓193 B
./packages/@ember/utils/lib/is_present.js 1.2 kB / 415 B -71.2%↓346 B / -52.3%↓198 B
./packages/@ember/utils/lib/is-equal.js 1.9 kB / 653 B -74.9%↓465 B / -62.5%↓245 B
./packages/@ember/utils/lib/type-of.js 4 kB / 1.1 kB -70.5%↓1.2 kB / -59.4%↓440 B
./packages/@glimmer/destroyable/index.js 5 kB / 1.1 kB -5.13%↓4.8 kB / -9.7%↓1 kB
./packages/@glimmer/global-context/index.js 3.9 kB / 1 kB -73%↓1.1 kB / -69.4%↓320 B
./packages/@glimmer/node/index.js 1.3 kB / 488 B -30.2%↓876 B / -27.7%↓353 B
./packages/@glimmer/owner/index.js 567 B / 263 B -67.2%↓186 B / -57%↓113 B
./packages/@glimmer/program/index.js 7.5 kB / 1.7 kB -4.47%↓7.2 kB / -7.96%↓1.5 kB
./packages/@glimmer/reference/index.js 5.4 kB / 1.6 kB -89.3%↓579 B / -84.4%↓256 B
./packages/@glimmer/tracking/index.js 4.4 kB / 1.5 kB -73.6%↓1.2 kB / -70.7%↓429 B
./packages/@glimmer/util/index.js 4.6 kB / 1.6 kB -49.2%↓2.3 kB / -46.8%↓874 B
./packages/@glimmer/validator/index.js 1.7 kB / 679 B 121%↑3.8 kB / 73%↑1.2 kB
./packages/@simple-dom/document/index.js 11.1 kB / 2.3 kB -8.53%↓10.1 kB / -14.1%↓2 kB
./packages/backburner.js/index.js 27.9 kB / 5.8 kB -14.1%↓23.9 kB / -20.8%↓4.6 kB
./packages/dag-map/index.js 5.4 kB / 1.4 kB -19.9%↓4.3 kB / -25%↓1 kB
./packages/ember-template-compiler/index.js 3.2 kB / 1.1 kB -27.3%↓2.3 kB / -30.8%↓768 B
./packages/ember-testing/lib/adapters/adapter.js 1.1 kB / 392 B -81.2%↓211 B / -65.1%↓137 B
./packages/ember-testing/lib/test.js 547 B / 260 B -66.2%↓185 B / -56.9%↓112 B
./packages/ember-testing/lib/test/pending_requests.js 409 B / 179 B -82.4%↓72 B / -70.4%↓53 B
./packages/ember-testing/lib/test/waiters.js 3 kB / 923 B -59%↓1.2 kB / -61.8%↓353 B
./packages/route-recognizer/index.js 20.9 kB / 5.1 kB -19.1%↓16.9 kB / -25.6%↓3.8 kB
./packages/router_js/index.js 64.5 kB / 13.8 kB -27.9%↓46.5 kB / -39.8%↓8.3 kB
./packages/shared-chunks/alias-{hash}.js 2.8 kB / 947 B -5.68%↓2.6 kB / -10.7%↓846 B
./packages/shared-chunks/api-{hash}.js 26.5 kB / 6 kB -79.6%↓5.4 kB / -79.5%↓1.2 kB
./packages/shared-chunks/args-{hash}.js 4 kB / 970 B -29.7%↓2.8 kB / -18.3%↓793 B
./packages/shared-chunks/arguments-{hash}.js 58.6 kB / 11.5 kB -9.54%↓53 kB / -14.7%↓9.8 kB
./packages/shared-chunks/array-{hash}.js 4 kB / 1.1 kB -12.9%↓3.5 kB / -19%↓898 B
./packages/shared-chunks/array-{hash}.js 1.3 kB / 497 B -22.5%↓1 kB / -31.8%↓339 B
./packages/shared-chunks/cache-{hash}.js 14.7 kB / 4.1 kB -51.8%↓7.1 kB / -55.1%↓1.8 kB
./packages/shared-chunks/chain-tags-D2h49dxP.js 6.9 kB / 2 kB —
./packages/shared-chunks/compiler-{hash}.js 361.9 kB / 69.4 kB -13.9%↓311.5 kB / -19.7%↓55.7 kB
./packages/shared-chunks/computed-{hash}.js 28.2 kB / 6.6 kB -63.1%↓10.4 kB / -63.8%↓2.4 kB
./packages/shared-chunks/container-{hash}.js 9.8 kB / 2.2 kB -36.2%↓6.2 kB / -42.1%↓1.3 kB
./packages/shared-chunks/curly-{hash}.js 459 B / 220 B -55.8%↓203 B / -49.6%↓111 B
./packages/shared-chunks/curly-{hash}.js 17.5 kB / 4.4 kB -14.4%↓15 kB / -20.8%↓3.4 kB
./packages/shared-chunks/debug-{hash}.js 292 B / 176 B -71.6%↓83 B / -59.1%↓72 B
./packages/shared-chunks/decorator-{hash}.js 10.2 kB / 2.9 kB -40.4%↓6.1 kB / -47.5%↓1.5 kB
./packages/shared-chunks/destroyable-{hash}.js 524 B / 265 B -80.9%↓100 B / -63.8%↓96 B
./packages/shared-chunks/dictionary-{hash}.js 559 B / 254 B -70%↓168 B / -61.8%↓97 B
./packages/shared-chunks/element-builder-5J0hVW8R.js — 21.2 kB / 4.2 kB
./packages/shared-chunks/element-{hash}.js 2.1 kB / 705 B -21.2%↓1.6 kB / -25.3%↓527 B
./packages/shared-chunks/env-{hash}.js 4.8 kB / 1.4 kB -75.1%↓1.2 kB / -69.1%↓435 B
./packages/shared-chunks/evented-{hash}.js 3.3 kB / 852 B -37.7%↓2 kB / -44.1%↓476 B
./packages/shared-chunks/events-{hash}.js 5 kB / 1.5 kB -56.1%↓2.2 kB / -50%↓727 B
./packages/shared-chunks/get_properties-{hash}.js 1.3 kB / 476 B -65%↓459 B / -52.5%↓226 B
./packages/shared-chunks/guid-{hash}.js 3.2 kB / 1.1 kB -63.1%↓1.2 kB / -62.7%↓418 B
./packages/shared-chunks/hash-{hash}.js 1.7 kB / 629 B -15.5%↓1.4 kB / -19.2%↓508 B
./packages/shared-chunks/helper-{hash}.js 487 B / 252 B -61.2%↓189 B / -50%↓126 B
./packages/shared-chunks/index-{hash}.js 59.8 kB / 12.1 kB -21.4%↓47 kB / -29.7%↓8.5 kB
./packages/shared-chunks/injected_property-{hash}.js 2.8 kB / 924 B -32%↓1.9 kB / -30.4%↓643 B
./packages/shared-chunks/intern-{hash}.js 1.7 kB / 679 B -88.9%↓189 B / -81.7%↓124 B
./packages/shared-chunks/internal-{hash}.js 3.8 kB / 1.3 kB -13.8%↓3.3 kB / -16.3%↓1 kB
./packages/shared-chunks/iterable-Bs4dHmK1.js — 3.7 kB / 1.1 kB
./packages/shared-chunks/libraries-{hash}.js 1.3 kB / 437 B -14.8%↓1.1 kB / -12.8%↓381 B
./packages/shared-chunks/link-{hash}.js 15.1 kB / 3.8 kB -40.2%↓9 kB / -41%↓2.2 kB
./packages/shared-chunks/meta-{hash}.js 942 B / 364 B -13.8%↓812 B / -14.8%↓310 B
./packages/shared-chunks/mixin-{hash}.js 16.7 kB / 4.4 kB -33.6%↓11.1 kB / -38.3%↓2.7 kB
./packages/shared-chunks/mutable-{hash}.js 16.4 kB / 4.3 kB -34.4%↓10.7 kB / -42.1%↓2.5 kB
./packages/shared-chunks/namespace_search-{hash}.js 4 kB / 1.2 kB -17.5%↓3.3 kB / -21.4%↓951 B
./packages/shared-chunks/not-{hash}.js 1.7 kB / 454 B -24.5%↓1.3 kB / -12.6%↓397 B
./packages/shared-chunks/observers-{hash}.js 6.8 kB / 1.6 kB 57%↑10.7 kB / 51%↑2.4 kB
./packages/shared-chunks/on-{hash}.js 8.6 kB / 2.5 kB -61.1%↓3.3 kB / -65.8%↓849 B
./packages/shared-chunks/program-{hash}.js 5 kB / 1.4 kB -10.7%↓4.5 kB / -16.2%↓1.2 kB
./packages/shared-chunks/program-{hash}.js 3.1 kB / 1 kB -29.5%↓2.2 kB / -34.3%↓666 B
./packages/shared-chunks/property_get-{hash}.js 3.6 kB / 1.2 kB -37.5%↓2.2 kB / -39.3%↓740 B
./packages/shared-chunks/property_set-{hash}.js 3 kB / 982 B -44%↓1.7 kB / -39.3%↓596 B
./packages/shared-chunks/props-{hash}.js 2.3 kB / 735 B -53.3%↓1.1 kB / -53.2%↓344 B
./packages/shared-chunks/registers-{hash}.js 973 B / 364 B -66.1%↓330 B / -57.7%↓154 B
./packages/shared-chunks/rehydrate-{hash}.js 15.5 kB / 3.1 kB -19.1%↓12.6 kB / -23.2%↓2.4 kB
./packages/shared-chunks/render-{hash}.js 52.2 kB / 11.3 kB -18.7%↓42.4 kB / -20%↓9.1 kB
./packages/shared-chunks/renderers-{hash}.js 319 B / 176 B -84.3%↓50 B / -69.3%↓54 B
./packages/shared-chunks/route-{hash}.js 2.1 kB / 731 B -19.9%↓1.6 kB / -21.2%↓576 B
./packages/shared-chunks/rsvp-{hash}.js 63.6 kB / 12.8 kB -60.3%↓25.2 kB / -59.2%↓5.2 kB
./packages/shared-chunks/serialize-{hash}.js 3.6 kB / 1 kB -14%↓3.1 kB / -19.9%↓831 B
./packages/shared-chunks/set_properties-{hash}.js 1.2 kB / 495 B -60.1%↓481 B / -54.6%↓225 B
./packages/shared-chunks/setup-{hash}.js 15.6 kB / 4.4 kB -17.6%↓12.8 kB / -23.1%↓3.4 kB
./packages/shared-chunks/spec-{hash}.js 443 B / 216 B -67.7%↓143 B / -62.5%↓81 B
./packages/shared-chunks/super-{hash}.js 2.7 kB / 790 B -16.2%↓2.3 kB / -18.5%↓644 B
./packages/shared-chunks/template-{hash}.js 2.7 kB / 908 B -48.9%↓1.4 kB / -52%↓436 B
./packages/shared-chunks/textarea-{hash}.js 12 kB / 3.1 kB -35.1%↓7.8 kB / -43.8%↓1.7 kB
./packages/shared-chunks/tracked-value-CZfmlG1y.js 4.1 kB / 1.3 kB —
./packages/shared-chunks/tracked-{hash}.js 9.7 kB / 2.9 kB -54.1%↓4.4 kB / -54.7%↓1.3 kB
./packages/shared-chunks/transform-{hash}.js 2.2 kB / 783 B -19.8%↓1.7 kB / -20.7%↓621 B
./packages/shared-chunks/transition-{hash}.js 16.6 kB / 4.3 kB -44.2%↓9.3 kB / -45.9%↓2.3 kB
./packages/shared-chunks/unique-{hash}.js 1.4 kB / 643 B -69.9%↓410 B / -65.9%↓219 B
./packages/shared-chunks/unrecognized-{hash}.js 695 B / 224 B -16.8%↓578 B / -11.6%↓198 B

smoke-tests/v2-app-template/dist   0.2%↑

File Before (Size / Brotli) After (Size / Brotli)
./assets/main-{hash}.js 319.8 kB / 85.7 kB 2%↑326 kB / 2%↑87.2 kB
./assets/modules-{hash}.js 31.3 kB / 9.9 kB -17.3%↓25.9 kB / -14.3%↓8.4 kB
Total (Includes all files) 353 kB / 96.4 kB 0.2%↑353.7 kB / 0.09%↑96.5 kB

smoke-tests/v2-app-hello-world-template/dist   0%↑

File Before (Size / Brotli) After (Size / Brotli)
Total (Includes all files) 126.5 kB / 35.2 kB 0%↑126.5 kB / -0.01%↓35.1 kB

🤖 This report was automatically generated by wyvox/pkg-size

lifeart and others added 8 commits October 13, 2026 11:03
Adds the two GXT flavors next to the classic build, without changing the
classic one:

- `EMBER_RENDER_BACKEND=gxt` (rollup) / `GXT_MODE=true` (vite): the compat
  backend. A shared alias table (scripts/gxt-alias-map.mjs) swaps the
  @glimmer/* reactivity and manager packages for the gxt-backend shims and
  drops the Glimmer VM packages from the dist.
- `EMBER_RENDER_BACKEND=gxt-native`: the native layer. Classic Ember source
  with `__GXT_MODE__ = false`; only the modules that touch the Glimmer VM are
  aliased to gxt-backend/native (same table, native section), and templates
  compile with the gxt compiler.

`__GXT_MODE__` / `__GXT_CLASSIC_COMPONENTS__` are build-time constants
(scripts/replace-build-constant.mjs, declared in types/gxt-ambient.d.ts) that
both bundlers inline, so classic dists fold every GXT branch away.

Other pieces: the `@lifeart/gxt` 0.0.82 dependency, playwright for the GXT
browser tests, the bundle-size check and budgets, the compat package
assembler (scripts/build-gxt-package.mjs), the opt-in gxt-backend checker,
and lint / tsconfig / prettier / yuidoc exclusions for gxt-backend.

On top of main's build: the classic test build keeps main's
`treeshake: false`; GXT test pages isolate a test module the gxt compiler
cannot compile (a failing synthetic module instead of a page that fails to
load); the stable-decorators type check excludes gxt-backend like the main
one; `@glimmer/component` resolves to its source in the GXT configs too;
the published types (types/publish.mjs now fails on tsc errors) leave out
gxt-backend and @glimmer/component-gxt and see the `__GXT_MODE__`
declaration.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
packages/@ember/-internals/gxt-backend renders Ember with glimmer-next
(@lifeart/gxt) instead of the Glimmer VM:

- compile.ts / ast-transforms.ts: the Ember template dialect on the gxt
  compiler (runtime `precompileTemplate`, `plugins.ast`, the Ember keyword
  helpers) and the gxt runtime patches the dialect needs;
- manager.ts / ember-gxt-wrappers.ts / helper-manager.ts: component, helper
  and modifier managers (classic curly components behind
  `__GXT_CLASSIC_COMPONENTS__`), the `$_tag` / `$_maybeHelper` / `$_dc`
  wrappers compiled templates call;
- validator.ts / reference.ts / destroyable.ts (+ the `-vm-compat` test
  surfaces): @glimmer/validator, /reference and /destroyable on gxt cells,
  including main's standalone `trackedValue` on the shim's tags;
- gxt-bridge.ts: the side-effect-free registry the classic packages reach
  the backend through, so classic builds never load it;
- outlet.ts: route outlets. Main renders routes through route managers and a
  `RootOutlet` component; the compat backend keeps its own outlet machinery
  (the `<ember-outlet>` element, the root template's outlet path) driven by
  the `{ render, outlets }` chain that `Route#setup` records through the
  bridge's route-outlet hooks.

Also @glimmer/component-gxt and the backend's own QUnit tests (__tests__).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
gxt-backend/native replaces only the Ember modules that touch the Glimmer VM
(renderer, outlet, managers, validator / reference / destroyable, template
factories, runtime helpers) with gxt-native implementations; everything else
is classic Ember source built with `__GXT_MODE__ = false`.

Main renders routes through route managers. Native aliases
`route-managers/root-outlet` to native/root-outlet.ts: the root outlet state
holds a native `OutletView` whose levels are live views of main's
`OutletState`s (owner, controller, tracked context as `@model`, the route
template looked up once per route bucket), and `renderComponent` renders a
`RootOutlet` through the owner's `appendOutletView`. The classic route
wrapper and the classic route manager's `RouteTemplate` (both Glimmer VM
internal components) are plain records, the route-manager registry asserts
through `@ember/debug` instead of `@glimmer/global-context`, `base-renderer`
maps to the native renderer, and owners with live roots are listed in main's
`renderers` list for `captureRenderTree`. Like main, the root outlet adds no
render-tree node.

Packaging: scripts/build-gxt-native-package.mjs and
scripts/gxt-native-package (ember-source-gxt-native), assert-native-dist
(the VM-free dist check), and the modern-app bundle/boot harness
(scripts/bundle-modern-app*.mjs, smoke-tests/modern-app) plus the packaged
consumer smoke test (smoke-tests/gxt-native-package). Following main: the
package and the harness's dist snapshots carry ember-source's `sideEffects`;
@glimmer/component builds a dev and a prod dist of its own, and the package,
the preset and the dist assertion pick the one of the mode; the consumer app
compiles for main's browser targets (lib/browsers.cjs), so its bundle stays
byte-identical to the harness measurement.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The places where classic Ember hands over to the compat backend. Every hook
is behind the build-time `__GXT_MODE__` constant and reaches gxt-backend only
through gxt-bridge, so the classic dists fold them away; the surrounding code
is main's.

- glimmer: the renderer's GXT root render (ClassicRootState), its
  force-rerender / root-tag bookkeeping, and the GXT `renderComponent`
  (base-renderer); a VM-free renderer state under GXT; the GXT root template
  (templates/root.ts); GXT-aware `template()`, `unwrapTemplate`, internal
  components, Input, element helper, scheduleRevalidate.
- metal: the single gxt cell behind `@tracked`, the GXT re-render trigger in
  `notifyPropertyChange`, the CP re-entrancy guards, `set` hooks; barrel
  imports of @glimmer/validator where the rollup alias map needs them.
- routing: `Route#setup` / `teardownViews` record and drop the route's GXT
  render state, `_setOutlets` renders through the GXT outlet machinery (and
  skips main's incremental mid-transition passes, which have nothing new to
  render there), QP / meta fixes; the classic route manager keeps a raw gxt
  route template uninvoked.
- runtime / object / runloop / application / views / template-compiler:
  smaller GXT hooks (observer flush budget, proxy unknownProperty during
  renders, settled PromiseProxy fast path, rehydrate DOM handling, view-tree
  rebuild, GXT `template()`).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- scripts/gxt-test-runner: the Playwright + QUnit runner (per-module
  isolation, baselines, smoke list, native allow-list and skips, contract
  tests, baseline gate; the gate's `--subset` mode compares only the modules
  a smoke shard ran);
- index.html: the GXT test-page setup (behind `__GXT_MODE__`) on main's page;
  native.html: the gxt-native test page, which also runs main's
  `setupTestHelpers()` and loads QUnit by package name like index.html;
- internal-test-helpers: GXT compile through the compat compiler, the GXT
  teardown harness, GXT artifact stripping in equalTokens / equalInnerHTML,
  GXT run helpers; the router test case exposes its root element to GXT;
- integration-tests: the GXT rehydration delegate (two plain GXT renders;
  serialization-dependent tests skipped with a reason) on main's
  `RenderDelegate` / `RenderHandle` interface, GXT `<!---->` stripping, and
  no empty component sub-modules.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
GXT branches in shared tests (cache-counter parity, debug render tree,
resolver cache), GXT skips (hot reload, `{{mount}}`), new regression tests
(has-block-params in nested blocks, the (hash)/(array) identity tests, which
moved to helpers/gxt-hash-array-identity-test.js because main rewrote
array-test / hash-test as .gjs), and renames that keep the names `array` and
`element` out of module scope in the single-chunk GXT test bundle.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The GXT smoke (contract + sharded smoke modules, each shard gated against
the compat baseline: main rewrote tests in six smoke modules, and their new
versions are known reds there), dual-build, gxt-native (modern-app boot,
package, native suite against its baseline) and nightly full workflows; the setup action's optional Playwright install; the
ember-source-gxt scenarios and the v2 app template for them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
test-results/gxt-baseline.json (compat, 8703/9828, 954 modules) and
native-baseline.json (native, 7061/7103, 672 modules), regenerated on this
series from one local full run each; and docs-internal-gxt-packaging-design.md.

Every status change against the pre-rebase baselines is a test main added
or rewrote: Glimmer components instead of classic ones, .gjs conversions,
route managers, standalone tracked(), {{#each-in}} key=, {{on}} listener
order, and main's rewrite of the @glimmer-workspace integration harness,
which now renders the [integration] suites through Ember's renderComponent
(GXT under GXT_MODE) instead of the Glimmer VM. The notes in the files list
the known reds and the three [integration] modules that hang under GXT.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
lifeart and others added 18 commits October 13, 2026 10:15
…ever

Three compat `[integration]` modules hung the renderer once main's harness
rendered through `renderComponent` and registered every helper as a public
`setHelperManager` definition. Two loops, both in compat only:

1. A helper's value read wrote reactive state. On every evaluation of the
   formula that renders a cached custom-manager helper, `$_maybeHelper_ember`
   assigned the bucket's args, and the args setter dirtied a tag that the
   formula had consumed through the helper's cache: with no dedup when an
   argument was a function, and a forced cache miss after any classic dirty
   (which the assignment itself caused), each evaluation scheduled the next
   (`<Bar @content={{(foo)}} />`, `<Bar @content="{{foo}}" />`). The read
   also wrote the result cell.

   Helper args now follow Glimmer's args proxy: `positional` / `named` are
   accessors over plain storage; an assignment compares slot by slot
   (`Object.is`, functions by identity) and only a real change bumps a
   helper-args tag, which only the shim's caches and track frames see (no
   GXT cell, no classic bridge, no reactor, no scheduling). `getValue` stays
   the helper's cache, so compute re-runs only when an argument or a tag it
   consumed changed; the read path no longer keeps an arg-serialization
   dedup and never writes the result cell.

2. A `(hash)` / `(array)` argument with a `(component)`, `(hash)` or
   `(array)` member got a new identity on every read after any change: gxt's
   `cachedHelper` re-runs a factory that read no cell (every `(hash)`) on
   each revision change, and those members defeat its shallow-equal check.
   Each sync then saw an argument change; its write-back fired the
   `renderComponent` root's classic reactor, which re-rendered the root in the
   middle of the sync's pass over the component args, and the pass went on
   into the new components (`<X @stuff={{hash Foo=(component "foo")}} />`).

   `$__cached` now has reference semantics (re-run only when a cell the
   factory read changed, constant otherwise, previous identity kept when
   shallow-equal). The sync's arg write-back and the arg-dirty effect run as
   arg propagation (`withArgPropagation`), whose classic dirties fire no
   classic reactor: GXT is already propagating that change.

Tests: gxt-backend/__tests__/helper-args-test.ts (14 tests; 11 hang or fail
without the fix). The 5 hanging tests of the three modules now pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ment

The per-slot `Object.is` comparison of the previous commit kept a helper's
cached value when an array argument was mutated in place (`pushObject`): the
array is the same object, and the old JSON arg-serialization dedup that used
to notice the new contents is gone. In Glimmer, reading an array argument
consumes the array's `[]` tag, so such an array is a changed argument. A slot
now also records an array's elements and is unchanged only when they are
still the same (`Object.is` each).

Test: helper-args-test.ts `an Ember array argument changed in place
re-computes the helper` (passes before the previous commit, fails with it
alone, passes now).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Regenerated from one local compat full run on 996d0c9 (runner.mjs --full,
no timeouts, no harness errors, no green->red against the previous baseline):
8764/9928 tests in 959 modules (was 8703/9828 in 954).

- The three `[integration]` modules that hung the renderer are now in the
  baseline: `jit :: Components :: Glimmer` 20/42, its `> [integration] Glimmer`
  sub-module 20/38, `argument-less helper paren-less invoke` 2/5 (the five
  tests that hung now pass; the other reds are the known harness conformance
  gaps).
- New modules: `GXT helper args: renderComponent roots settle` 10/10,
  `GXT helper args: helper re-computation` 5/5.
- red -> green: Custom Helper test :: works with strict-mode; Helper Tracked
  Properties :: class based helpers are autotracked, nested tracked
  properties rerender when updated; [integration] jit :: strict mode: dynamic
  template values :: Can pass curried helper as argument and invoke
  dynamically.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`for…of` over the reference's bookkeeping Map goes through gxt's reactive
Map.prototype iterator patch, which registers the Map with the reading
formula; the rest of gxt-backend iterates such Maps with `forEach` for that
reason. No behaviour change: the three [integration] modules, the new tests
and the `--filter "[integration]"` / `--filter Helper` runs give the same
results as the baseline generated from 996d0c9.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…he same

`<X @f={{fn this.greet "x"}} />` re-rendered forever in a renderComponent
root. The compat compiler emits the argument as a getter that calls `$__fn`
on every read, and `$__fn` returned a new function each time. The manager
compares an argument by identity, so every sync saw a change: it wrote the
value back to the component and dirtied the argument, which scheduled the
next sync.

Glimmer's `fn` is one function per call site; the function reads the
callback and the partial arguments when it is called. Now:

- an `(fn …)` value records the callback it binds and calls through a
  replaceable implementation (utils.ts `createFnHelperValue`); a
  `(mut this.x)` cell records what it writes, so two cells of one call
  site bind the same callback;
- a component argument reads through a slot per component and argument
  name, shared by the instance's arg getter, the args proxy and a
  re-render's new getter. The slot keeps its `(fn …)` value while a new
  evaluation binds the same callback, and takes that evaluation's
  implementation, so a call still reads the current callback and
  arguments (`keepFnHelperValue`).

Reading an argument writes no reactive state. Tests in helper-args-test.ts:
three renderComponent-root hangs (direct, with a changing partial
argument, with `(fn (mut …))`), one function per call site for a
template-only and a classic component, a changed callback, and a stashed
function reading its current arguments.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…e run loop

`{{join-words this.list}}` (and `{{this.list.length}}`, and `{{#each}}`
over the array) kept the old value after `runTask(() => list.push('c'))`.
The formula that read the array does subscribe to the array's collection
cell, but nothing rendered the change: `dirtyCollection` invalidated the
cell and scheduled a `syncDom()` microtask, and before that microtask ran,
the run loop's DOM sync (`_gxtSyncDomNow`) — taking its non-property
branch, since no property change was recorded — cleared gxt's pending
invalidations as initial-render noise, the collection cell's with them.

Glimmer: dirtying a tag schedules a revalidation, rendered when the run
loop ends. A trackedArray change now does what a `@tracked` write does
(glimmer-tracking.ts `trackedSet`): it records a pending state change
(only the pending sync while a render is in progress) and schedules a
revalidation, besides the coalesced flush (validator.ts
`_scheduleNativeCollectionFlush`).

Scoped to the native-cell collection (trackedArray's `collection` tag).
The storage-wrapped collections (trackedMap/Set/Object, `cell()`) go
through `dirtyTag` and keep their behaviour: recording their changes too
made a `renderComponent` root compute a helper over a trackedObject four
times (the classic reactor re-renders the root, then the sync re-ran the
previous render's formulas), a regression of the Helper Managers
"plain functions entangle with any tracked data" test.

Tests in helper-args-test.ts: a trackedArray helper argument, a
trackedArray component argument, and length / plain-function /
custom-manager reads of one trackedArray changed by push and splice.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…text

A helper inside a template-only component received the component's render
context as its named arguments (`{ $fw, args, attrs }`), and one inside a
block of it the block's context, inside a Glimmer component the instance's
own arg properties. `$_maybeHelper_ember` guessed which of its third and
fourth arguments was the hash and which the context from the third one's
shape, and the shape checks missed those contexts.

gxt's emission decides it: a call by NAME passes the render context last
(`(name, positional, ctx)`, or `(name, positional, named, ctx)` with named
arguments); a call of a BINDING passes `(definition, positional, named)`;
Ember-side callers pass all four. So with four arguments the third is the
hash, a name's third argument is its context, and a binding's third
argument is a context only after compile.ts's scope rewrite turned a name
call into a binding call, recognised by gxt's component id
(`COMPONENT_ID_PROPERTY`), which every render context carries and a hash
never does. The strict-mode `$_maybeHelper` shadow now forwards the call's
own arguments, so their number survives it.

Glimmer: a helper's named arguments are exactly the call site's
`key=value` pairs. New helper-call-site-test.ts: a custom-manager and a
function helper in a template-only component, in its blocks, in a Glimmer
component, and a strict-mode scope helper.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Owner-registered helpers (a `setHelperManager` definition, a classic
`Helper` class) were cached per helper NAME: two sibling
`{{counting "a"}}{{counting this.b}}` shared one instance (`[a1][b2]`),
every `{{#each}}` row shared it, and tearing down one call site's
`{{#if}}` branch did not touch the others' state because they were the
same object.

Glimmer creates one helper instance per call site and render scope. The
compat call (`$_maybeHelper("counting", ["a"], this)`) carries the render
scope (`this`, or a block's context) but nothing that tells two call
sites in one scope apart. Now:

- an AST transform (ast-transforms.ts `gxtHelperCallSiteTransform`) gives
  each helper call site a process-unique id, passed as a `__gxtSite`
  named argument. It marks only calls gxt emits as `$_maybeHelper` by
  NAME with at least one positional argument (not keywords, block params,
  scope values, or zero-argument / named-only calls, whose emission must
  stay exact);
- `$_maybeHelper_ember` takes the id out of the named arguments and keys
  the helper bucket / classic instance by (render scope, site), in a map
  whose entries are destroyed when gxt destroys the scope (an `{{#if}}`
  branch, an `{{#each}}` row, a component); calls without an id keep the
  name-keyed cache.

New tests in helper-call-site-test.ts: two sibling call sites keep
separate state, destroying an `{{#if}}`'d call site leaves the other,
`{{#each}}` rows get one instance each, a custom manager creates one
helper per call site. Known gaps (no site id): templates compiled at
build time by gxt's own plugin, binding calls (gxt passes no render
scope with them), zero-argument and named-only calls.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Regenerated from the compat full run on 430658f (961 modules,
8804/9948, no timeouts, no harness errors, no green->red against the
previous baseline). Against it: +20 tests in the two extended GXT helper
args modules and the two new GXT helper call sites modules, all green;
20 red->green elsewhere:

- Components test: contextual components (classic component): GH#14508
  rest positional params passed as a named parameter (2);
- Components test: curly components (classic component): can use hash
  parameter instead of arbitrary positional param [GH emberjs#12444];
- Helper Tracked Properties: array properties / getters / tracked
  properties rerender when updated (3);
- Syntax test: {{#let as}}: can access alias of an array;
- The <LinkTo /> and {{link-to}} components: it works in an #each'd
  array of string route names (2);
- [integration] jit :: initial render (client): 9 helper / literal tests;
- [integration] jit :: trackedArray() (rendering): {{each}} works with
  new items / when updating old items.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… first

gxt's `cellFor(obj, key)` without `skipDefine` defines its own cell
accessor on `obj`. Over a `@tracked` key (an accessor on the class
prototype) that accessor shadows the tracked one: later writes
(`obj.key = value`) update only gxt's cell, the tracked setter's
notification never runs, the run loop's DOM sync records no change and
the DOM stays stale, with no error. Two callers did that:

- the re-render trigger's render-context fan-out (compile.ts SyncCore)
  updated the cell of a context whose target is the written object
  itself with `skipDefine` false, so the first write to a component's
  own tracked property shadowed it (the second click of a counter
  rendered nothing, for a classic and a Glimmer component alike);
- a template's block-param path read (`{{item.name}}` in an `{{#each}}`
  row, compiled to `$__cellFor(item, "name")`) shadowed the tracked
  property of the row's item during the first render, so no direct
  write to it ever rendered.

`isTrackedAccessor(obj, key)` (manager.ts, memoized per prototype for an
inherited accessor) tells both callers that the key is tracked: the
fan-out updates the cell without defining, and `$__cellFor` reads the
key through its accessor, which reads and subscribes to the same cell.
Both `@tracked` implementations of the compat build (metal's and
gxt-backend/glimmer-tracking.ts) had the bug the same way.

The new `__tests__/glimmer-tracking-test.ts` asserts the DOM after
repeated writes in each setting (classic / Glimmer / template-only
components, helpers, `{{#each}}` rows, arguments, backtracking, action /
next() / microtask writes, `@cached`, inheritance, non-component
objects) and ports the classic tracked tests with `@glimmer/tracking`
imports. Before: 6 of the 13 settings tests fail (stale DOM after a
write, no error); after: all pass. The ported modules pass before and
after.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ompat build

The compat build aliased `@glimmer/tracking` (and its
`primitives/cache`) to gxt-backend/glimmer-tracking.ts, a second
`@tracked` / `@cached`: a `trackedData` storage cell plus
`consumeTag(tagFor(obj, key))` on read; `dirtyTagFor`, `setPendingSync`
and `triggerReRender` (skipped while rendering) on write; the accessors
marked `__isTrackedGetter` / `__isTrackedSetter` for manager.ts. Ember's
own classes, the native build and upstream's `@glimmer/tracking` use
metal's `tracked`, whose GXT path keeps the property in its one gxt cell
and notifies through `notifyPropertyChange` (the re-render trigger, which
records the pending property-change sync, plus `dirtyTagFor` of the key
and SELF_TAG, observers and `PROPERTY_DID_CHANGE`).

- scripts/gxt-alias-map.mjs: the two `@glimmer/tracking` rows are gone,
  so both builds resolve the in-repo package: metal's `tracked` /
  `cached`, and the cache primitives from `@glimmer/validator/lib/tracking`
  (collapsed onto the validator shim, as before).
- glimmer-tracking.ts is deleted.
- manager.ts: `_isTrackedKey` checks only the TrackedDescriptor metal
  records in meta. The other three marker checks (the plain-getter
  collision check, the createRenderContext cell install, the render
  proxy's getter cache) already excluded every accessor with a setter,
  and a `@tracked` accessor always has one; the marker clauses go.
- @glimmer/component-gxt re-exports metal's `tracked` / `cached` and the
  validator shim's `createCache` / `getValue`; its identity test follows.
  Its ambient declarations move from gxt-backend-shims.d.ts to a `.ts`
  script: types/publish.mjs copies every hand-written `.d.ts` into the
  published types, where a second declaration of metal's real modules
  clashed (TS2451 on `cached`).
- Comments that named glimmer-tracking.ts are updated
  (compile.ts, ember-gxt-wrappers.ts, validator.ts, bundle-modern-app's
  byte category).

What `@glimmer/tracking` gains in compat: the TrackedDescriptor that
`get` / `set` and the component manager see, field initializers called
with the instance as `this`, the `equals` option, the classic-class
`tracked({ value })` form and the array (`[]`) consumption of a tracked
array value. The last module of glimmer-tracking-test.ts covers them
(red before, green after); the DOM-after-write modules are green before
and after. Metal is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Regenerated from the compat full run on c16139a (966 modules,
8846/9990, no timeouts, no harness errors, `baseline ok` against the
previous baseline); 018eac1 differs from it only by renaming
@glimmer/component-gxt's type declarations, which the test page does
not load. Against the previous baseline: +42 tests in the five new
`GXT @glimmer/tracking` modules, all green; no red->green and no
green->red elsewhere.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The classic ember-source dist never imports @lifeart/gxt, yet the package
listed it in `dependencies`, so every classic install pulled gxt and its
own dependencies (@babel/preset-typescript, @glimmer/syntax, content-tag,
decorator-transforms).

- package.json: @lifeart/gxt moves to `devDependencies`. That entry is the
  repo's single exact pin. ember-source declares no peer or optional
  dependency on it: nothing in the published package uses it.
- scripts/build-gxt-package.mjs: ember-source-gxt takes the pin from the
  root devDependency and declares it as a real, exact dependency. It
  fails if the root lists gxt in dependencies, peerDependencies or
  optionalDependencies.
- scripts/build-gxt-native-package.mjs: ember-source-gxt-native pins the
  root devDependency instead of the installed version. An npm-installed
  gxt that differs from the pin fails the build; a local gxt build still
  only warns.
- gxt-native.yml packs the gxt named by the devDependency.
- packages/@ember/-internals no longer declares gxt. It is private, and
  its gxt-backend sources resolve the root node_modules copy (Node, vite
  and tsc all land on the same store path). The vite configs alias that
  root copy, and rollup keeps gxt external.
- The gxt-consumable smoke test now asserts that the compat package
  depends on exactly the root pin, and not as a peer or optional
  dependency.
- The packaging design doc no longer claims that classic needs gxt.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…efinition

The GXT renderComponent treated every definition that is not a class as
template-only. A plain object with a custom component manager
(setComponentManager) therefore rendered with `this` = {}: the manager's
createComponent and getContext never ran. The @glimmer-workspace
integration harness renders every loose template that way, so `{{this.x}}`
was empty in those suites.

The custom manager is now looked up on any definition (own or on the
prototype chain, as getInternalComponentManager does). A definition with
one renders the context its manager creates, with lazily read named args,
calls didCreateComponent after the render when the manager asks for
asyncLifecycleCallbacks, and destroyComponent when the render is destroyed
when it asks for a destructor. The owner is added to such a context only
when it has none, and not as an enumerable property.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… real one

The "LOCAL gxt build" warning fired whenever node_modules/@lifeart/gxt was
a symlink. pnpm installs every package as a symlink into its virtual store,
so the warning also fired for the npm release. A symlink now counts as a
local gxt only when it resolves outside node_modules/.pnpm (pnpm link, a
link: dependency, a manual symlink to a checkout); a dist with build meta
still counts as local wherever it lives.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…s of plain-object definitions

The integration harness renders loose templates through renderComponent with
a plain-object definition and a custom manager. Running that manager makes
`this` the real test context: 280 more rows pass (9139/9992).

Five tests that only passed because `this` was `{}` (their code never ran)
are now known reds with real causes: the three "Element modifier with hooks"
/ "didUpdate is not called when params are constants" tests (a context write
re-renders the whole root, so the modifier re-installs instead of updating),
and the two `false` / `null` class-name tests.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The gxt-backend render-error queue is global. Several sites queue an error
and also throw it, and the renders that flush the queue (renderComponent and
a classic root's first render) threw whatever it held. So an error the caller
had already caught stayed queued, and the next unrelated render threw it: in
the @glimmer-workspace suite a test died with the error of the test before
it. The Ember test harness hid this by clearing the queue itself.

- Every queued error records when it was captured. A render takes a mark
  when it starts; when it completes it throws the first error queued since
  the mark, and when its own error propagates to the caller it drops the
  errors queued since the mark. Errors queued before the mark stay queued.
- renderComponent also flushes its errors when it renders a component that
  has no template, through the component manager.
- A renderComponent call that throws stops its classic-tag reactor. The
  caller gets no result to rerender or destroy it, and the reactor would
  render the failing template again on the next tracked write anywhere and
  queue its error inside another render. A classic root whose render failed
  stops rendering too.

Test: "Strict Mode - renderComponent (direct)" renders a strict template
with a helper used as a component (which throws), then renders a component
that writes tracked state into another element; the second render must not
throw the first render's error.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…st render

The test checks that the next render does not throw the first render's
error, not which error the first render throws. Under gxt-native the
helper-as-component render throws a TypeError instead of Glimmer's
"Expected a dynamic component definition" message, so the test no longer
matches the message.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
lifeart and others added 28 commits October 15, 2026 04:39
…er template checks

Compat full run: 9440/10078 (was 9567/10079), 982 modules, no timeouts, no
harness errors, nothing turned green. The 2 tests of the removed
`GXT correctness: compile-time template checks` module are gone; the
`compile pipeline errors` module has 1 new test (the template compiler
throws a parse error), green.

126 tests turned red. Every one expects an invalid template to be rejected
at compile time with Glimmer's error; they were green only while the checks
ran. None is a regression of a valid template.

(a) Message-only difference, known red (25): the template still throws,
with the GXT compiler's (or GXT's not-in-scope) message instead of Glimmer's
`SyntaxError` and location.
- `[integration] jit :: compile errors` (11): end tags for void elements,
  end tags with attributes, mismatched / unclosed / unmatched tags, the
  unquoted attribute, and the 5 "error line numbers include ..." tests.
- `[integration] jit :: general syntax errors` (10): the 7 "Block params in
  HTML syntax" tests, `../`, `./`, mixing `.` and `/`.
- `[integration] jit :: strict mode: general properties`:
  `{{component.foo}}` throws (append position).
- `Components test: curly components` "block with properties on attrs is
  asserted against", "non-block with properties access via attrs is
  asserted against", and `(classic component)` "using attrs for positional
  params is asserted against": `{{attrs.x}}` throws "attrs was not in
  scope" instead of the `{{attrs}}` assertion.

(b) An invalid template the GXT compiler accepts, known red (100): the
accepted trade-off of dropping the checks. Nothing is thrown at compile
time (production builds already behaved this way).
- `[integration] jit :: \`<keyword>\` keyword syntax errors` (43): keywords
  used in a position they do not support ("non-append / non-block /
  non-expr / non-modifier keywords cannot be used as ..."): `component` 1,
  `debugger` 3, `each-in` 3, `each` 3, `has-block-params` 2, `has-block` 2,
  `helper` 2, `if` 1, `in-element` 3, `let` 3, `log` 2, `modifier` 2,
  `mount` 3, `mut` 2, `outlet` 3, `readonly` 2, `unbound` 2, `unless` 1,
  `yield` 3.
- `[integration] jit :: if/unless (if)` and `(unless) keyword syntax
  errors` (11 + 11): `if` / `unless` arity and named arguments.
- `[integration] jit :: named blocks syntax errors` (10): every named-block
  rule (in "Named blocks must start with a lower case letter" one sub-case
  still throws a GXT error, the others compile).
- `[integration] jit :: yield keywords syntax errors` (8): `yield` /
  `has-block` / `has-block-params` arguments.
- `[integration] jit :: strict mode: static template values` (9) and
  `strict mode: general properties` (4: "Undefined references is an error",
  `{{component}}` with a string in append / block / expression position):
  strict-mode references that are not in scope.
- `Strict Mode <-> Loose Mode - renderComponent :: strict-mode components
  cannot lookup things in the registry` and `` `action` is not a keyword ::
  in strict mode, an unbound `action` is not in scope ``: strict-mode names
  that are not in scope (a strict-mode helper call still throws when it
  renders).
- `[integration] jit :: argument-less helper paren-less invoke` (1) and
  `dynamic modifiers in resolution mode` (1): the loose-mode resolution
  errors.

(b), consequence, known red (1): `Strict Mode <-> Loose Mode -
renderComponent :: rendering multiple times to adjacent elements` fails
with a global failure: the render-time "`a-helper` ... was not in scope"
error of the previous test's template (above), whose un-awaited render
throws after that test has ended. Its own template is valid.

(c) Regressions: none. The `{{this.attrs.x}}` deprecation, which the checks
had emitted, is kept by the previous commit (4 tests stay green).

The native suite does not load the compat compile pipeline; its baseline is
unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…th its block

Glimmer asserts an `{{#in-element}}` destination reference: when it
changes, the block is removed from the old destination and rendered into
the new one. When the block is destroyed (its `{{#if}}` branch goes, its
render is destroyed), its content leaves the destination. Now that a
renderComponent root updates in place, the compat `$_inElement` ran only
once: a new destination was never rendered into, and the content stayed in
the old one, also after its block was gone.

`$_inElement` now reads the destination in a tracking frame of its own and
keeps the block's render (its nodes and the effects of its text) per
destination. An effect owned by the block's context subscribes to the
cells that read consumed; when the destination changes, it removes the
render from the old destination and renders the block into the new one. A
destructor on the block's context removes the render when the block is
destroyed. A later `$_inElement` call that renders into the same
destination (a classic root rendering again) replaces the earlier render
and takes over following the destination.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…t rendered in

Glimmer resolves a template's modifiers once, in the owner the template
renders with, and an update calls the installed modifier's
updateModifier. The compat build resolves a modifier by name each time
its formula runs, and an update runs that formula outside of any render:
the ambient owner is another render's, or none. A modifier registered by
name then resolved to nothing on update; its formula had called the
destructor of the installed modifier and installed nothing, so
updateModifier / didUpdate never ran.

`$_maybeModifier` now records, per element, the owner of the render that
first evaluated one of its modifiers, and evaluates the element's
modifiers in that owner while it is alive.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…stroyed

When the modifier an element invocation renders changes to another
definition (an argument swap, a curried swap, `(if a b)`), or to none,
Glimmer destroys the old modifier and installs the new one on the same
element. The compat build marks a modifier pending destroy when gxt calls
its destructor before re-running the invocation, and the update path takes
it back when the same modifier runs again. The end-of-sync flush then
skipped every pending modifier whose element was still in the document, so
a modifier replaced on a live element was never destroyed: its
destructor / destroyModifier never ran.

The flush now destroys every modifier that no evaluation of its invocation
took back during the sync, whether its element was removed or not.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A modifier manager gets an args object from `createModifier` (and
`installModifier`); in Glimmer it is a proxy over the argument
references, so a manager that keeps it reads the current values when
`updateModifier` runs. The compat build gave the manager an object over
the values of the evaluation that installed the modifier, and a fresh
one to `updateModifier`; a manager that reads the args it kept (the
functional-modifier manager of the integration tests re-runs its function
with them) saw the install-time values forever.

The args objects now read the invocation's arguments from a source that
the update path points at each evaluation's arguments.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ves in its template's owner

Glimmer renders a block in the owner of its template, also when an
update renders one of its branches or rows again. The compat build
renders that branch or row during the DOM sync, outside of any render,
where the ambient owner is another render's, or none. A modifier
resolved by name in it then found nothing: an `{{#if}}` toggled back on
rendered its element without the modifier, and didInsertElement never
ran.

`{{#if}}` and `{{#each}}` now keep the ambient owner they were created in
and render their branches, rows and `{{else}}` in it while that owner is
alive, as they already keep their namespace and parent view.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…s in its text node

Glimmer updates a text binding by changing its text node's value, so the
node is the same before and after an update. For an element whose only
content is a mustache (`<p>{{this.value}}</p>`), gxt updates the text
through its DOM api's `textContent`, which sets the element's
`textContent` and replaces its children: the text node was a new node
after every update.

The compat renderers now use a DOM api whose `textContent` changes the
value of the element's text node when it is the element's only child.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…and modifiers

Compat full run: 9453/10083 (was 9440/10078), 982 modules, no timeouts,
no harness errors, no green->red.

8 tests turned green:
- `[integration] jit :: #in-element` "Updating remote element" and
  "Inside an '{{if}}'", and `Updating` "helpers passed as arguments to
  {{#in-element}} are not torn down when switching between blocks": an
  `{{#in-element}}` renders into its new destination and its content
  leaves the old one, also when its block is destroyed.
- `Updating Element Modifier` "Updating a element modifier": a modifier
  resolved by name updates when its arguments change.
- `Updating Element Modifier` "Destructor is triggered on element
  modifiers": an `{{#if}}` branch rendered again resolves its modifiers.
- `strict mode: dynamic template values` "Can use a dynamic modifier with
  a changing definition" and its curried form: the replaced modifier is
  destroyed.
- `Updating` "a simple implementation of a dirtying rerender": the text of
  a single-mustache element updates in its text node.

5 new tests in `GXT in-place updates`, all green.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…efinition is the same

`<Foo @value={{modifier this.foo "x"}} />` curried a new modifier on every
read of the argument: the compat compiler re-evaluates the call each time.
The sync reads every argument and writes a changed value back, so each sync
saw a new `@value`, wrote it, re-ran the element's modifier (whose manager
then updated it, rewriting the element's content) and scheduled the next
sync. The renderer never settled.

Glimmer's curry reference returns the same curried value while the
definition it curries stays the same; the curried arguments are references
the modifier reads when it installs or updates. A curried modifier now
records the definition it curries and installs through a replaceable
implementation (utils.ts `createCurriedModifierValue`). An argument slot
that holds one keeps it when a later evaluation curries the same
definition, and takes that evaluation's implementation (`keepArgValue`,
which also covers the `(fn …)` values the slots already kept). A rerender
without a change leaves the modifier alone; an argument change still
updates it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`(modifier @modifier "world")` with `@modifier` undefined fell through to
gxt's own helper, which returns a new function that calls the missing
definition. An argument holding it was truthy and changed on every read:
each sync wrote it back and scheduled another one, even for a component
whose render was already torn down, and the extra sync left the next
render's update one sync late.

Glimmer's curry reference is `null` for a missing definition, and an
element modifier of that value installs nothing. The `(modifier)` keyword
now returns `undefined` for a `null` / `undefined` definition.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Compat full run: 9461/10085 (was 9453/10083), 982 modules, no timeouts,
no harness errors, no green->red.

6 tests turned green:
- `[integration] jit :: dynamic modifiers in resolution mode` "Can pass
  curried modifier as argument and invoke dynamically", and its "(with
  args)", "(with args, multi-layer)", "(with named args)" and "(with named
  args, multi-layer)" forms: a curried `(modifier …)` argument keeps its
  value while its definition is the same, so a rerender without a change
  leaves the modifier and its element alone.
- `[integration] jit :: strict mode: dynamic template values` "Calling a
  dynamic modifier using if helper": a `(modifier)` of a missing definition
  is no modifier, so the argument of a finished render no longer changes on
  every sync and delays the next render's update.

2 new tests in `GXT in-place updates`, both green.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A content mustache inside an element (`<div>{{this.value}}</div>`,
`<div>a{{this.value}}b</div>`, the same inside `<svg>`) rendered every value
as text: a SafeString showed its HTML escaped, a DOM node or fragment showed
`[object HTMLParagraphElement]`. A SafeString was rendered as HTML only when
it was the value at creation, and the text of a mustache inside an SVG
element or a foreignObject never updated. A DOM node in `{{{this.value}}}`
rendered its String() as HTML.

In Glimmer, dynamic content renders what its value is after every change:
text (updated in its text node), the HTML of a SafeString (parsed in the
element it is in, so it is SVG inside an SVG element), a DOM node, or the
nodes of a fragment. A change of kind, of the node or of the HTML replaces
the content where it is.

The content mustaches of an element are now rendered as that dynamic
content: the function children of an HTML element, the mustache that is an
element's only content, and the children of SVG, MathML and custom elements.
The binding is evaluated once when it is created, when gxt renders the child,
so children are still evaluated in order after the element's attributes. A
block's render result passed as a child is rendered by gxt as before. The
content of a component definition value renders the component, as it already
did at the top level of a template, which now shares the same binding. A
DOM node in triple curlies is inserted as it is.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ue is

A content mustache at the top level of a template (`[{{this.value}}]`)
kept the kind of its first value: text that changed to a SafeString showed
the HTML escaped, a SafeString that changed to text was parsed as HTML, and
after a change to a DocumentFragment the binding stopped updating.

It now renders through the same dynamic content binding as the content of
an element, which renders what the value is after every change (text in its
text node, the HTML of a SafeString, a DOM node, a component). The binding
is still evaluated once when it is created. A DOM node that is the first
value is still inserted as it is, since a component render result reaches
this position as a node too.

The text of an object value is now taken in the tracking frame of the
binding's evaluation, so a binding depends on what the object's `toString`
reads, such as the value of a yielded block parameter (`{{yield this.bar}}`
rendered as `{{bar}}`).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A helper invoked in content position by name (`{{format-html this.text}}`,
`{{const-foobar}}`) rendered its result with String(): a SafeString showed
its HTML escaped and a DOM node showed `[object Text]`. In Glimmer a
helper's result is dynamic content like any other value.

The result now renders through the dynamic content binding: text, the HTML
of a SafeString, a DOM node or a component, rendered again when what the
helper read changes. The helper is still evaluated once on creation, and the
binding is still torn down with the enclosing branch.

The fragment the binding returns now carries its current nodes as its
rendered nodes, so a gxt block that registers it as one of its nodes removes
what it renders when the block is destroyed. Trusted HTML rendered inside an
SVG or MathML namespace before its element exists is parsed in that
namespace.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…lled

The mustache that is an element's only content (`<div {{replace}}>Nope</div>`,
`<p {{peek}}>{{this.value}}</p>`) became the element's children when it was
made dynamic content. gxt installs an element's modifiers while it creates
the element, before it renders the element's children, so the modifier saw
an empty element, and a modifier that rewrote the element's HTML got the
content appended after its own (`[Hello, world!Nope]`).

In Glimmer a modifier is installed after the element's content is rendered.
When the element has modifiers, its content is now rendered into it by an
entry ahead of the modifiers, as the text was before, so a modifier sees it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… value is

Compat full run: 9495/10097 (was 9461/10085), 983 modules, no timeouts, no
harness errors, no green->red.

22 tests turned green:
- the 18 `[integration] jit :: Updating - Content` tests (double and triple
  curlies in the HTML, SVG foreignObject and SVG contexts): a content
  mustache renders text, the HTML of a SafeString or a DOM node and renders
  what its value is after every change; a DOM node in triple curlies is
  inserted as it is;
- `[integration] jit :: Updating` "updating a curly with a safe and unsafe
  string";
- `[integration] jit :: Updating` "double curlies with const SafeString" and
  "double curlies with const Node": the result of a helper invoked by name
  renders what it is;
- `Components test: curly components` "static arbitrary number of positional
  parameters": a component invoked by name with positional arguments as the
  only content of an element renders the nodes it returns.

12 new tests in `GXT dynamic content`, all green.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…s value is

In a .gjs/.gts template, a `{{value}}` in an element (`<p>{{this.value}}</p>`,
`<p>a{{this.value}}b</p>`) rendered every value as text: a SafeString showed
its HTML escaped, a DOM node or fragment showed `[object HTMLElement]`, and
inside SVG the HTML of a SafeString was not rendered at all. A block parameter
holding a SafeString or a node (`<Show @value={{this.value}} as |v|><p>{{v}}</p>`)
rendered as text too. Runtime-compiled templates already rendered Glimmer's
dynamic content there; build-time templates call another `$_tag`, which handed
the content to gxt's text binding.

That `$_tag` now creates a plain element with its content mustaches rendered as
dynamic content, as for a runtime-compiled template: text updated in its text
node, the HTML of a SafeString, a DOM node, or the nodes of a fragment, and a
change of kind replaces the content in place. gxt picks an element's namespace
from its render context, so a mustache among the children holds its place
while gxt creates the element and renders there once the element exists: the
HTML of a SafeString in an SVG element is SVG. The content is still rendered
after the element's attributes, and ahead of its modifiers when it is the
element's only content, so a modifier sees it when it is installed. A block
parameter is a gxt cell; its value is read where the content is rendered. An
element whose tag is dynamic (the `(element)` helper) keeps gxt's rendering of
its children, which gxt renders again when the tag changes.

A `{{value}}` at the top level of a template or of a block still renders as
text in a build-time template: gxt renders it with its own text binding, which
has no host seam outside the gxt-native build. The new tests for those two
positions are skipped in compat; classic and gxt-native run them, and the
module joins the gxt-native suite.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…through the property

`<button name={{false}}>` rendered `<button>`: `name` is an attribute to gxt,
and `false` removes an attribute. Glimmer sets a bound attribute that names a
property of its HTML element through that property (`normalizeProperty`), so
the attribute is the text the property reflects: `name="false"`, as `id`,
`title` or `type` reflect any value. A bound attribute that is no property of
its element (`data-*`, `aria-*`, `name` of a `<span>`) is an attribute, where
`true` is the empty string; compat wrote `"true"`. A bound `selected` of an
`<option>` (and `value` of an `<input>`, `checked` of a checkbox) also got an
attribute next to the property in a runtime-compiled template, so
`selected={{true}}` rendered `<option selected="true">`.

What an attribute write leaves on the element is now one rule, shared by both
builds' attribute hooks: null and undefined remove the attribute, text is the
attribute's text, any other value of a property of the element is assigned to
the property and the attribute is what it reflects, and any other value of an
attribute follows Glimmer's normalizeValue. A runtime-compiled template no
longer adds an attribute for a bound value gxt set through a property of the
element; a name that is no property of the element is still written as an
attribute. The gxt-native build may now ship Glimmer's normalizeProperty, a
DOM module with no VM dependency.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…aches and bound attributes that are properties

Compat full run: 9545/10108 (was 9495/10097), 985 modules, no timeouts, no
harness errors, no green->red. 39 tests turned green: a bound attribute that
names a property of its element is set through the property
(`<button name={{has-block}}>` renders `name="false"`: the parameterized
has-block and has-block-params `(prop, ...)` tests of the Curly, Glimmer and
Dynamic suites and their parent modules), and a bound `selected` of an option
is its selectedness, with no attribute (Updating: `<option selected>` is
normalized and updated correctly; initial render (client): Dynamic selected
options, Dynamic multi-select). 11 new tests, green or skipped: the two new
`<template>` modules (content mustache, bound attributes) and two yielded block
parameter tests in GXT dynamic content. The 2 skipped tests are a content
mustache outside an element in a build-time template, which gxt still renders
with its own text binding under GXT_MODE.

Native full run (the GXT native CI job of the fix commit): 7141/7185 (was
7132/7176), 683 modules, no green->red, no timeouts: the 9 tests of the two new
`<template>` modules pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ver invoked it

gxt's `$_args` hands a component each argument as a thunk value
(`{ value: () => this.greet }`). A template-only definition invoked
through a path (`<this.Apply @value={{this.greet}} />`), with
`{{component this.Apply …}}`, or curried and then invoked, was rendered
with that object as it was, so `@value` was the thunk. A content
mustache calls a function and showed the value, but a modifier argument
used in modifier position (`<div {{@value "x"}}>`) was never installed,
a helper argument called as `{{@value "x"}}` never ran, and an `{{on}}`
handler argument returned the handler instead of calling it.

The template-only render now gives the template an args object where
such a thunk is a getter that reads it, through the argument's value
slot, as the args proxy of a component rendered by name reads it. The
two copies of the template-only render become one function.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The GXT `renderComponent` path defined every root argument (the args
handed to a manager-rendered root, a custom manager's named args, a
component's `this.args`, a template-only root's context and its `@`
args) as a getter that read the classic-validator bridge cell before
the value. Every classic tag change bumps that cell, so a binding that
read `@foo` ran again on any change anywhere, such as a write to
another argument of a `trackedObject`, and once more in the first sync
after the render.

Glimmer reads a root argument as `childRefFor` of the args' const
reference, the environment's `getProp`: the value, and the key's tag.
The getters now read through gxt-backend's `gxtLib.readProperty`, which
reads the property once and consumes its gxt property tag, as gxt's
`$__tag` does (the validator shim's `tagFor` would create a formula
over the property, which reads it a second time). A binding depends on
that argument only: a `trackedObject` write to it, or `set()` on a
plain args object, still updates it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…it reads changes

A component rendered by name gets its arguments as getters. The args
proxy kept each one in a cell that the sync's arg-cell pass updates
from the getter, and that pass keys its entries by component instance.
A template-only component has no instance, so every template-only
render replaced the previous one's entry: only the last template-only
component rendered (the last `{{#each}}` row, or the second of two
side by side) saw an argument change. In `{{#each this.rows as |root|}}
<FooBar @value={{root.value}} />{{/each}}`, writing the first row's
tracked `value` left it stale; a Glimmer component in the same place
updated.

A component without an instance now reads such an argument where its
template uses it, through the argument's value slot, as Glimmer reads
an argument reference: the binding that reads `@value` depends on what
the argument's expression reads. Template-only components no longer
have entries in the arg-cell pass, so a destroyed one is no longer read
on every sync either.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…oot arguments read their values

Taken from a GitHub Actions GXT full run on this code. 9556/10118
green (was 9545/10108), 987 modules, no timeouts, no harness errors,
no green->red. `#in-element: Inside a loop` turned green; 10 new GXT
tests (component arguments, renderComponent root arguments) are green.
The native suite is unchanged (7141/7185).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
runner.mjs exits 1 whenever any test fails, including the tests the
baseline already lists as failing, so the scheduled full run reported a
failure on every night even when it said "baseline ok". The verdict now
comes from baseline-gate.mjs, as in the native workflow: no test red that
the baseline lists green, no baseline module missing, no timeout or harness
error.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… "inverse"

Glimmer gives a component's `{{else}}` block, `<:else>` and `<:inverse>`
one name, so `{{yield to="else"}}` and `{{yield to="inverse"}}` both
render it. The GXT compiler names a curly component's `{{else}}` block
`inverse`, and its `$_slot` finds a block by its exact name: a curly
component's `{{yield to="else"}}` rendered nothing. The compiler also
built a `{{#component}}` block's component from the main block alone, so
the `{{else}}` block of a dynamic invocation was dropped: neither yield
rendered it and `(has-block "inverse")` was false.

A dialect AST transform now gives the block the one name `inverse`:
`{{yield to="else"}}` yields to `inverse`, `<:else>` is `<:inverse>`, and
a `{{#component}}` block with an `{{else}}` block passes the two blocks
as `<:default>` (with the block params) and `<:inverse>` named blocks.
A bare `@arg` component keeps its block: its `<@arg>` invocation has no
named blocks.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
In Glimmer a tag that names a block param in scope invokes the param's
value as a component, whatever its case:
`<Yielder as |comp|><comp /></Yielder>` renders the yielded component.
The block-params transform rewrote a param used as a tag only in the
dotted form (`<comp.Foo>` reads the yielded `comp`), so `<comp>` was left
as a plain element and rendered `<comp></comp>`. It now rewrites the
param tag itself to the same yielded-value read, `<this.$_bp0>`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The quoted-attribute transform turned every bare `{{name}}` in a quoted
attribute value into a helper call `{{(name)}}`, so that a helper named
there resolves. A block param is a local variable, not a helper name,
and Glimmer reads it as the variable. As a helper call its value went
through helper resolution and a falsy one was lost:
`class="index-{{index}}"` in an `{{#each}}` row rendered `index-` for
the first row while `{{index}}` rendered 0.

The transform now keeps a mustache naming a block param in scope as it
is. It tracks block params as Glimmer scopes them: a block's params cover
its body, an element's params its children, not its own attributes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Taken from a GitHub Actions GXT full run on this code. 9584/10125
green (was 9556/10118), 989 modules, no timeouts, no harness errors,
no green->red, no module with fewer passing assertions. 11 tests turned
green: yield to "else" (curly components, yield Curly and Dynamic),
yield to "inverse" (Dynamic), the five Dynamic parameterized has-block
(..., else) when else supplied tests, and has-block within a yielded
curried component invoked with angle brackets (truthy, falsy). The 7
new GXT tests (else blocks, block params) are green. The two each-index
Updating tests stay red on other assertions (`key='@index'` keying, a
keyed row reused for a new object), with 31 more of the module's
assertions passing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@lifeart

lifeart commented Oct 15, 2026

Copy link
Copy Markdown
Owner Author

Closing: CI for this branch now runs on scratch branches before each push, so this tracking PR is no longer used.

@lifeart lifeart closed this Oct 15, 2026
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