Companion to #1843, and a correction to its framing.
That report concludes the workarounds it found are "an adoption problem, not a missing-feature problem", because the primitives are already exported. I audited a second, independent app (loghq — 6.5k lines, SSR, 33 routes) and found the same hand-rolling. But the primitives it names are exported from the package and not reachable from the surface apps author in.
Measured
packages/stx/src/runtime-globals.ts is the single source of truth for what a <script client> block can call — it is destructured into every generated client script, and runtime-globals.test.ts already asserts every name in it exists on window.stx.
| Primitive | in src/ | in STX_RUNTIME_GLOBALS | on window.stx |
|---|---|---|---|
useFetch | ✅ | ✅ | ✅ |
defineForm | ✅ | ❌ | ❌ |
validateFields | ✅ | ❌ | ❌ |
useAsyncData | ✅ | ❌ | ❌ |
StxModalBuiltin | ✅ | ❌ | ❌ |
StxToastBuiltin | ✅ | ❌ | ❌ |
Teleport | ✅ | ❌ | ❌ |
redirect | ✅ | ❌ | ❌ |
useRouteParams | ✅ | ❌ | ❌ |
$ grep -oE "'(defineForm|validateFields|useAsyncData|Teleport|redirect|useRouteParams|useFetch)'" src/runtime-globals.ts
'useFetch'One of the nine is reachable. So an author writing defineForm(...) in a client script gets a bare ReferenceError, and the natural conclusion — "the framework doesn't have forms" — is what both apps acted on.
This is the same shape as #1804 and #1838, one layer up: the list exists, the guard exists, and these names were never added to it.
Why it produced identical workarounds in two unrelated apps
loghq, with no knowledge of bench:
- 5 forms, each re-typing loading / error / disabled by hand.
login.stxalone has 6loadingand 10errorreferences.defineFormandvalidateFieldsexist and cannot be called. - 21 raw
fetch(in views against 2 uses ofuseQuery/useMutation.useAsyncDataexists and cannot be called. - A modal with a hand-written focus trap, scroll lock and focus restore, added by hand this week.
StxModalBuiltinandTeleportexist and cannot be called.
Ask
- Add the missing names to
STX_RUNTIME_GLOBALS— or state explicitly which are server-only, so the boundary is discoverable rather than inferred from aReferenceError. - Extend
runtime-globals.test.tsto assert the inverse direction too: every public composable exported from the package is either in the list or explicitly annotated as server-only. Without that, this recurs the next time a composable is added. - #1843's ask 1 ("confirm these reach the client in a released build") should fail today for eight of the nine above, in a fork build as well as a release.
Independent of reachability
One item does not fit the "exists but unreachable" pattern: no form in either app can work without JavaScript. In loghq all five carry no action and no method — they are preventDefault + fetch. defineForm becoming reachable would remove the duplication but not this; that needs a server-side form action (Laravel's controller + @error/old(), Remix/SvelteKit form actions). Filing separately if you want it tracked apart from this.