Summary
On SPA navigation, a scoped component script that the emitter did not mark data-stx-run="always" can be silently deduplicated by execScripts, because the runsAlways fallback heuristic only re-runs scripts whose trimmed body starts with ( or ;. When that happens the component's scope is never re-registered and it fails to hydrate — the same class as #1773 / "content vanishes on SPA nav."
Background
execScripts dedups already-executed scripts by content hash, except scoped scripts, which must re-run every navigation (the prior scope was deleted by _cleanupContainer). The exemption is decided by runsAlways:
function runsAlways(declared, code){
if(declared==='always')return true;
if(declared==='once')return false;
var head=code.trimStart();
return head.charAt(0)==='('||head.charAt(0)===';'||code.indexOf('window.stx.mount')>-1;
}The primary path is the emitter mark (data-stx-run="always"). The fallback sniff is the safety net when the mark is missing — and it's brittle.
Repro (unmarked scoped scripts, declared="")
(function(){ /*scope*/ })() → re-runs ✅
// scope for X\n(function(){})() → DEDUPED ❌ (leading // comment)
/* scope */(function(){})() → DEDUPED ❌ (leading /* */ comment)
const s = stx.state(0); /*...*/ → DEDUPED ❌ (starts with const)
!function(){}() → DEDUPED ❌ (bang-IIFE)Each DEDUPED case is skipped on the 2nd+ navigation → scope never re-registers → :if/@event/ under that scope never re-bind → the component (including its static markup) disappears on SPA nav.
Impact
The entire re-hydration safety rests on the emitter marking every scoped script correctly. Any gap in the emitter (a new code path, a wrapper change) falls through to a fallback that misses common forms (leading comments, const-first, !function). Failures are silent — no error, just missing UI after navigation.
Suggested fix
- Strip leading
//and/* … */comments (and whitespace) before the start-char check. - Broaden the heuristic (
!function,void function,const/let/var … stx.). - Or make the emitter mark authoritative and fail loudly (dev warning) if a
data-stx-scopescript arrives without adata-stx-rundeclaration, so the brittle sniff is never the deciding factor.
Location
packages/router/src/client.ts — runsAlways + the execScripts hash-dedup guard.