Skip to content

fix scanPrototypesFor cache when class init is in flight#37

Merged
bemky merged 1 commit into
mainfrom
fix-scan-prototypes-cache
May 7, 2026
Merged

fix scanPrototypesFor cache when class init is in flight#37
bemky merged 1 commit into
mainfrom
fix-scan-prototypes-cache

Conversation

@bemky
Copy link
Copy Markdown
Owner

@bemky bemky commented May 7, 2026

Summary

Fixes a regression introduced by 7d67a95 (memoize scanPrototypesFor to avoid per-instance prototype walks). The memoization caches the merged prototype-chain values per (constructor, key) pair, but when customElements.define (called by static { this.define() }) upgrades a pre-existing element mid-class-body, the constructor fires before the subclass's static <key> = ... field initializers run. The first scan caches a result missing the subclass's own value, and every subsequent instance then inherits the wrong merged metadata — e.g. an empty assignableAttributes.

Discovered when adding the new Masonry component (#36): if a <komp-masonry> element exists in the DOM at script load, the upgrade triggers the constructor before static assignableAttributes = {...} runs, and the cache locks in [{}] (just KompElement's default).

The fix tracks obj.hasOwnProperty(key) alongside each cached entry and invalidates when it flips. Plugin-style in-place mutations to the cached arrays still propagate, so the existing this.events.push(...) pattern continues to work.

Test plan

  • npm test — 37 passing, including new supportTest.js regression coverage
  • In a project that hits the original bug (e.g. <komp-masonry> rendered before the script imports), confirm assignableAttributes are now resolved on first instance

🤖 Generated with Claude Code

The memoization in 7d67a95 caches the merged prototype-chain values per
(constructor, key) pair. When `customElements.define` upgrades a
pre-existing element mid-class-body, the constructor runs before the
subclass's `static <key> = ...` fields have initialized, so the first
scan caches a result missing the subclass's own value. All subsequent
instances then inherit the wrong merged metadata (e.g. empty
assignableAttributes).

Track the constructor's own-property status alongside each cached entry
and recompute when it flips. Plugin-style in-place mutations to the
cached arrays still propagate.

Add a regression test in test/supportTest.js.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bemky bemky merged commit 0606b0b into main May 7, 2026
bemky added a commit that referenced this pull request May 7, 2026
The memoization in 7d67a95 caches the merged prototype-chain values per
(constructor, key) pair. When `customElements.define` upgrades a
pre-existing element mid-class-body, the constructor runs before the
subclass's `static <key> = ...` fields have initialized, so the first
scan caches a result missing the subclass's own value. All subsequent
instances then inherit the wrong merged metadata (e.g. empty
assignableAttributes).

Track the constructor's own-property status alongside each cached entry
and recompute when it flips. Plugin-style in-place mutations to the
cached arrays still propagate.

Add a regression test in test/supportTest.js.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bemky added a commit that referenced this pull request May 11, 2026
* main:
  fix scanPrototypesFor cache when class init is in flight (#37)
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