ReviewOS

stacks/stx

Runtime-globals destructure list is duplicated in 3 places and has drifted — 5 APIs throw ReferenceError depending on script context

#1785
Closed glennmichael123 opened this 10 days ago ·
10 days ago

Summary

The list of runtime globals destructured from window.stx at the top of every generated client script is hardcoded in three separate places. They have drifted, so which stx APIs you can call depends on which kind of script you are writing — and the failure is a bare ReferenceError at runtime with nothing at build time.

The three copies

SiteFileNames
wrapClientScript — @included component scriptspackages/stx/src/includes.ts:26851
processScriptSetup — page setup functionpackages/stx/src/signal-processing.ts:126454
renderComponentWithSlot — <Component /> client scriptspackages/stx/src/utils.ts:115253

Union: 56 distinct names. Eight are missing from at least one site:

defineStore        missing from: renderComponentWithSlot
inject             missing from: wrapClientScript, processScriptSetup
nextTick           missing from: wrapClientScript, processScriptSetup
onBeforeUnmount    missing from: wrapClientScript
onDestroy          missing from: renderComponentWithSlot
onMount            missing from: renderComponentWithSlot
onMounted          missing from: wrapClientScript
onUnmounted        missing from: wrapClientScript

Which gaps are real

Three are masked because the runtime also assigns them as bare globals, so the identifier still resolves:

  • onMount, onDestroy (window.onMount / window.onDestroy are assigned)
  • defineStore (window.defineStore is assigned)

Five are not masked — no global fallback exists, so they throw:

  • inject
  • nextTick
  • onMounted
  • onUnmounted
  • onBeforeUnmount

Verified

Rendered through processDirectives on current main:

PAGE setup: nextTick in the window.stx destructure?  NO  -> bare nextTick() = ReferenceError
COMPONENT script: onMounted in its destructure?      NO  -> bare onMounted() = ReferenceError

So nextTick() works in a <Component /> client script but throws in a page's <script client>, and onMounted() works in a page but throws in an @included component. Same API, same syntax, different context, silent inconsistency.

Why it keeps happening

Adding a runtime global means remembering three unrelated files. Nothing links them, nothing tests that they agree, and the drift above is the natural result. A fourth copy would rot the same way — which is why composable-loader.ts deliberately detects built-in collisions at runtime against window.stx rather than hardcoding the list again (#1780).

Suggested fix

  1. Export the list once (e.g. STX_RUNTIME_GLOBALS in a small module) and build all three destructures from it, so adding a global is a one-line change.
  2. Add a test asserting the three generated destructures are identical — that is what actually prevents the drift from returning.
  3. Decide the intended surface for the currently-inconsistent eight (particularly whether the Vue-style aliases onMounted/onUnmounted/onBeforeUnmount should be available everywhere, or only where they are now).

Happy to do this, but it touches all three files, so it wants a quiet moment rather than landing mid-burst.

Sign in to comment on this issue.