fix: use target receiver for scoped proxy accessors#607
Conversation
jrusso1020
left a comment
There was a problem hiding this comment.
Reviewed against #606. Fix is correct and well-scoped.
What I checked
-
Root cause matches the issue.
Reflect.get(target, prop, receiver)invokes accessor getters withthis === proxy, and host getters likeDocument.prototype.bodyreject the proxy withTypeError: Illegal invocation. The fix passestargetso host getters see the rightthis. -
All four host-relevant proxy sites are patched in
packages/core/src/compiler/compositionScoping.ts:- document proxy (line 141 → target)
- window proxy (line 179 → target)
- gsap.utils inner proxy (line 252 → utilsTarget)
- gsap outer proxy (line 257 → target)
#606 specifically called these out as the load-bearing ones (the others — timeline-registry proxy on a plain object — were noted as not load-bearing).
-
Single source path. The runtime IIFE shipped in
core/dist/hyperframe.runtime.iife.jsis bundled fromcompositionLoader.tswhich importswrapScopedCompositionScriptfrom this same file. So one fix here propagates to both producer-side wrapping AND the runtime IIFE on next build. No duplicate template to keep in sync. -
Regression test is honest.
compositionScoping.test.ts:90-173stubsdocument/window/gsap/gsap.utilswith explicitIllegal invocationgetter guards (if (this !== fakeDocument) throw). The test fails on the old code (proxy asthis) and passes on the new (target asthis). Asserts both successful reads (__bodyTag === "BODY",__href,__gsapVersion,__utilsMarker) AND noconsole.error— symmetric proof. -
Browser verification in PR body. Confirmed via
agent-browseragainst generated repro pages: pre-fixbodyRead: false / titleOpacity: "0" / timelineRegistered: false / errorCount: 1, post-fix all four values flipped. Cross-checked the bug exists in published[email protected]AND[email protected]before shipping. -
CI green on all required: Lint, Typecheck, Build, Test, Test: runtime contract, CLI smoke, all perf jobs. Regression-shards still running but the changed surface is narrow.
Minor (non-blocking)
The __hfTimelineRegistryProxy get/set traps still pass receiver. #606's author flagged this as non-load-bearing (the registry is a plain HF object, no host getters), so functionally this is correct. If you want consistency across the whole file, applying the same target/utilsTarget/etc. pattern there is zero-risk and would prevent confusion if anyone ever swaps the registry for a host-y target. Not worth a separate round-trip.
LGTM, this fixes #606 cleanly. No event=APPROVE from me — flagging as a review-comment per the team-base policy on stamping unless directly authorized.
— Rames Jusso
Merge activity
|
Problem
wrapScopedCompositionScriptwraps composition scripts with scopeddocument,window, andgsapproxies. The current publishedlatestandalphapackages still pass the proxy as theReflect.getreceiver, so browser host accessors likedocument.bodycan throwTypeError: Illegal invocation.When that happens, the wrapper catches the error and aborts the rest of the composition script. For components that start hidden and reveal themselves through GSAP/timeline setup, that means the timeline is never registered and the render can stay visually empty.
Closes #606.
What this fixes
Reflect.getreceiver.Root cause
The previous proxy traps called
Reflect.get(target, prop, receiver). For accessors, that invokes the getter withthis === receiver, and in this wrapper the receiver is the proxy. Browser host getters such asDocument.prototype.bodyvalidate their receiver and reject the proxy, which makes ordinary composition code likedocument.bodyfail before timeline registration can run.Verification
Local checks
npm view @hyperframes/core version dist-tags versions --jsonandnpm view @hyperframes/producer version dist-tags versions --json:latestis0.4.42,alphais0.5.0-alpha.14.@hyperframes/coreand@hyperframes/producerat bothlatestandalpha; all four packed artifacts still contained the badReflect.get(target, prop, receiver)/utilsReceiverwrapper patterns before this fix.bun run --cwd packages/core test -- src/compiler/compositionScoping.test.tsbunx oxlint packages/core/src/compiler/compositionScoping.ts packages/core/src/compiler/compositionScoping.test.tsbunx oxfmt --check packages/core/src/compiler/compositionScoping.ts packages/core/src/compiler/compositionScoping.test.tsbun run --cwd packages/core buildbun run --cwd packages/core typecheckbun run --cwd packages/producer typecheckbun run --cwd packages/producer buildbun test packages/producer/src/services/htmlCompiler.test.tsReflect.get(..., receiver)/Reflect.set(..., receiver)scoped-wrapper patterns.git diff --checkBrowser verification
Used
agent-browseragainst generated local repro pages:core latest 0.4.42:bodyRead: false,titleOpacity: "0",timelineRegistered: false,errorCount: 1core alpha 0.5.0-alpha.14:bodyRead: false,titleOpacity: "0",timelineRegistered: false,errorCount: 1bodyRead: true,titleOpacity: "1",timelineRegistered: true,errorCount: 0Notes
agent-browserrecordings are local-only undertmp/issue-606/browser/, includingissue-606-browser-proof.webmandissue-606-after-comment.webm.dist/artifacts are committed.