Summary
If a string literal in a <script server> block contains a complete opening script tag, renderTemplate() silently drops the page's entire <script client> block. No setup function is emitted, no server→client bridge is generated, and the response is still 200.
The server block itself runs correctly — template expressions reading its bindings resolve and escape properly — so the page looks right and is completely inert. This is the "answered 200 and did nothing" class from the August sweep handoff, in a new consumer.
The serve path handles the same input correctly. This is specific to renderTemplate().
Reproduction
<script server>
const alpha = "VAL <script>x"
defineClientPayload({ alpha })
</script>
<script client>
const n = state(0)
const use = alpha
</script>
<div :text="n()">0</div>const html = await renderTemplate('repro.stx', {})
/const n = state\(0\)/.test(html) // false — client block gone
/__stx_setup_\d+_\d+/.test(html) // false — no setup function
/server -> client data bridge/.test(html) // false — no bridgeReplace the value with "VAL <div>x" and all three are true.
Exact trigger
Only a complete opening tag triggers it. Measured on the same template, varying only the string contents:
| value contains | client block |
|---|---|
<script>x | dropped |
<SCRIPT>x | dropped (also logs 0 opened, 1 closed — 1 stray </script>) |
<script | ok |
</script | ok |
a<scriptb | ok |
script>x | ok |
<div>x | ok |
Scope
- Affects both the declared bridge (
defineClientPayload) and the implicit name-scraping bridge. Both were tested; both lose everything, so it is not a payload-declaration bug. - The server block still executes:
{{ alpha }}in the body rendersVAL <script>x, correctly escaped. - Not reproducible on the serve path. The same template served by the dev server bridges both a
<script>-containing value and a plain one, and ships the client block. Soprocess.tsgets this right andrender.tsdoes not.
renderTemplate is not a dead export — native-build.ts:197, menubar.ts:201 and the documented edge-runtime usage all go through it. An app rendered at the edge or built natively loses every @click, :text and signal on any page whose server block happens to mention a script tag in a string.
Why it is easy to hit
The natural cases are exactly the ones an app has: an install/onboarding snippet, a docs example, an embed code generator, a CSP or analytics tag builder. A settings page that hands the user a copy-paste browser snippet is the canonical shape.
Related
Same family as the closed component-scanner issue where JS string literals like '<v:shape>' were mis-resolved as components, and as the open typecheck issue where a script tag inside an HTML comment opens a real block. The pattern each time is a tag scanner reading text that is inside a string or a comment. The handoff's own note — one rule, one implementation — looks relevant: whatever process.ts uses to find script boundaries is getting this right, and render.ts is not.