ReviewOS

stacks/stx

Slot content handlers lose page scope when a component template has no reactive binding (ReferenceError on click)

#2139
Closed chris opened this 3 days ago ·
3 days ago

Observed with @stacksjs/stx 0.2.719 while adding a confirmation dialog to the Settlers remake (stacksjs/the-settlers-iii-remake#226).

A <DialogPanel> whose slot holds only static markup plus @click="pageFunction()" buttons compiles those buttons into the component's own client script (__stx_evt_stx_dialog_panel_...). That script resolves the handler through the nearest [data-stx-scope], which is the dialog's scope and has no __stx_execute, then falls back to the bare name pageFunction(). The name lives in the page's setup closure, so every click throws ReferenceError: pageFunction is not defined.

The same slot works as soon as any element in it carries a reactive binding (for example :disabled="x()", :if, or x-model), because templateHasReactiveContext then leaves @click for the signals runtime. The existing restart dialog in the same page works only for that reason. This is the same class as stacksjs/stx#1824, reached through slot content instead of a useStore-only page.

Expected: slot content authored in a page keeps the page's scope regardless of what the receiving component's template contains, or the compiler reports the unresolved handler at build time.

Reproduction: a page with const open = state(true); function close() { open.set(false) } and <Dialog :open="open()"><DialogPanel><button type="button" @click="close()">Close</button></DialogPanel></Dialog>. Click Close and read the console.

The game currently carries a harmless :disabled binding on its confirm button as a workaround; it is not a fix. Please verify against current main (0.2.751) before changing code.

3 days ago

Verified the exact reported reproduction on current main and on a fresh unmodified npm installation of STX/components/bun-plugin-stx 0.2.753: an initially open Dialog/DialogPanel containing only a static Close button with @click="close()" closes successfully in real Chrome, without a dummy reactive binding in its slot.

This is already corrected by the caller-event compiler fix dc47421 tracked in #2126. Added the exact initially-open regression to packages/components/test/dialog-caller-events-browser.test.ts in 75e437f. Disabling the caller-event correction makes this new case fail, so it detects the reported defect. The existing cancellation, confirmation, repeated opening, aliased useRef and opener-focus cases also pass.

Validation: 137 source checks passed; 13 fresh-installed npm 0.2.753 checks passed (including all three real Chrome dialog scenarios); types, lint and format pass. All three original npm archives were SHA-512 verified. Closing as fixed; no additional compiler workaround or Craft change was necessary. This does not substitute for the game's separate dependency-adoption/full-browser acceptance gate in #2126/#2127.

3 days ago

Verified against current main (0.2.754): the same page builds with the slotted @click kept for the page runtime and no __stx_evt_stx_dialog_panel binding, while 0.2.719 still strips it (2 generated bindings). The defect is already fixed upstream; the regression test in packages/stx/test/site-builder-slot-handlers.test.ts now keeps it fixed. Separately, a static build of a page whose client script imports a relative module ('./parse') hung for 60 seconds in that harness; I am not chasing it here.

Sign in to comment on this issue.