Companion to #1769/#1771/#1772 — results of a systematic investigation into why literal keeps reaching users despite 8 fixes (c571492, 4c5fb05, d411bf8, 409a2e0, bcd6f2c, da91c34, 8ebb172, 20509da).
Why it recurs
Preserved moustaches are the framework's de-facto error UI: any failure in the detect → extract → bundle → ship → rebind relay collapses into the same visible symptom. The preserve-vs-interpolate decision is re-derived at ~7 sites across 4 phases (expressions.ts, utils.ts, includes.ts, variable-extractor.ts, client-script-bundler.ts, signals.ts) — each regression fix patched one site's heuristic; no invariant anywhere asserts 'zero literal moustaches after hydration'.
The SPA whole-scope class (component renders all-dead: literal moustaches + stuck x-show)
Server-side there is NO divergence (fragments render through the same processDirectives, serve.ts:1544, extracted post-render :2294-2406). The failures are client-side lifecycle:
- D1 (prime suspect, #1700 recurrence): doFragSwap script hash-dedup (packages/router/src/client.ts:401-415) skips a scope script on REVISIT — nav-away already disposed the scope (client.ts:339 → signals.ts:5118) — when the script fails the
charAt(0)exemption sniff (client.ts:412).f348390widened the sniff; it remains one emitted-shape change from regressing. - D2:
if (!scopeVars) returnin the stx:load walk (signals.ts:~5229) silently abandons the whole subtree. - D3: container
__stx_scopeguard skips processElement for the next fragment when not cleaned (signals.ts:~5246/5113). - D4: one top-level throw in a shared emitted script unregisters all later components in it.
- D5: x-if-detached scope roots missed by cleanupContainer → stale
__mountedblocks remount.
Ranked hardening plan
- Post-hydration invariant sweep (end of stx:load + DOMContentLoaded): scan container for leftover
{{text nodes and unregistered[data-stx-scope]; console.error with scopeId + force-fallback processElement. Converts every future upstream miss into one loud, self-healing event. - Replace dedup heuristic with a contract: emit
data-stx-run="always|once"on generated scripts; key the client.ts skip off the attribute (extraction regex at client.ts:345 must keep attributes). - Make the three silent skips loud (signals.ts ~5229, ~5246; eval catch ~1670 — log once per expression).
- Per-component registration isolation (own try/catch or own script element per component in client-script-bundler.ts) — kills D4.
- Single preservation gate: compute preserve-vs-interpolate once, stamp it, have all seven sites consume it.
App-side, ts-analytics now runs a revisit-inclusive WebView sweep + a dev canary asserting the post-hydration invariant per navigation — happy to share either as a reference implementation for (1).