Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Hydration relay: taxonomy of literal-{{ }} regressions + ranked hardening plan (deep-dive results) #1773

Copy link
Copy link

Description

@glennmichael123
Issue body actions

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, stx-router fragment-swap path silently drops scope IIFEs on revisit — dedup check is missing the isAlreadyScoped escape #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). f348390 widened the sniff; it remains one emitted-shape change from regressing.
  • D2: if (!scopeVars) return in the stx:load walk (signals.ts:~5229) silently abandons the whole subtree.
  • D3: container __stx_scope guard 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 __mounted blocks remount.

Ranked hardening plan

  1. 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.
  2. 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).
  3. Make the three silent skips loud (signals.ts ~5229, ~5246; eval catch ~1670 — log once per expression).
  4. Per-component registration isolation (own try/catch or own script element per component in client-script-bundler.ts) — kills D4.
  5. 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).

Reactions are currently unavailable

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    Morty Proxy This is a proxified and sanitized view of the page, visit original site.