ReviewOS

stacks/stx

Runtime injection lands above the doctype, putting the page in quirks mode

#1899
Closed glennmichael123 opened this 14 days ago · 2 comments
14 days ago

Summary

injectSignalsRuntime inserts the runtime <script> at the offset of the first <script> anywhere in the template, with no check that the offset is after the doctype. When a page uses a full-document layout whose <head> contains no script of its own, that offset resolves ahead of <!DOCTYPE html>, so the runtime — and the store block that anchors to it — get prepended above the doctype.

A start tag before the doctype puts the document in quirks mode.

The code

packages/stx/src/runtime-injection.ts, all three build modes:

const runtimeScript = '<script data-stx-runtime src="/_stx/runtime.js"></script>'
const firstScriptInDoc = template.indexOf('<script')
if (firstScriptInDoc !== -1)
  return template.slice(0, firstScriptInDoc) + runtimeScript + '\n' + template.slice(firstScriptInDoc)
if (template.includes('</head>')) { /* correct placement */ }
return runtimeScript + '\n' + template

The </head> branch below it is right. The problem is that the indexOf('<script') branch wins first and never asks where that script is.

process.ts then anchors the store block to the runtime tag (findScriptBlockByAttribute(result, 'data-stx-runtime')), so the stores follow it above the doctype too — in our case another 54 lines.

Reproduce

Requires the combination of:

  1. a layout that is a full document (<!DOCTYPE html> … </html>) whose <head> contains no <script>, and
  2. a page using it that has a <script client> block

Then:

$ curl -s http://localhost:3100/pricing | head -3
<!-- stx-layout: marketing -->
<script data-stx-runtime src="/_stx/runtime.js"></script>
<script data-stx-stores>;(function(){

$ curl -s http://localhost:3100/pricing | grep -n DOCTYPE | head -1
57:<!DOCTYPE html>

In Chrome over CDP:

/pricing  ->  document.compatMode = "BackCompat"   document.doctype = null
/docs     ->  document.compatMode = "CSS1Compat"   (same layout, no client script)
/login    ->  document.compatMode = "CSS1Compat"   (client script, but its layout's head has a script)

Why this is worth fixing rather than documenting

It is invisible to every check short of compatMode. The page:

  • returns 200
  • renders correctly in a full-page screenshot
  • logs zero console errors
  • passes an SPA probe

The only symptom is that one page on the site silently uses the quirks-mode box model, line-height and table cell rules while every other page uses standards mode. That is the kind of difference that gets chased as a CSS bug for hours.

Note the two conditions are individually common and the pair is not exotic — a marketing layout that deliberately keeps script out of <head>, plus one page in that section that needs interactivity, is an ordinary shape. It just needs both at once, which is why it has not surfaced before.

Suggested fix

Anchor the insert after the doctype. Roughly, in preference order: insert after <head> / before </head> when the template is a full document, and only fall back to the first-script offset for a fragment. Something like:

const headOpen = template.search(/<head\b[^>]*>/i)
if (headOpen !== -1) {
  const insertAt = headOpen + template.slice(headOpen).match(/<head\b[^>]*>/i)[0].length
  return template.slice(0, insertAt) + '\n' + runtimeScript + template.slice(insertAt)
}
// fragment: no head, keep the existing first-script behaviour

A regression test worth pinning: assert the emitted document's first non-comment token is <!DOCTYPE html> for a page with a <script client> on a full-document layout whose head has no script. Same family as the <html>-in-a-comment and <script-in-a-comment traps — a detector reasoning about raw offsets rather than document structure.

Workaround

The injector short-circuits on if (template.includes('data-stx-runtime')) return template, so the page can declare the tag itself and keep it in <head>:

@push('styles')
<script data-stx-runtime src="/_stx/runtime.js"></script>
@endpush

Pushing it from the page rather than adding it to the layout matters when the layout is shared — otherwise every page in that section downloads the signals runtime to serve the one that needs it.

Provenance

Not a recent regression. The firstScriptInDoc rule dates to at least 266220cc7e (2026-03-27), last touched in 853bebdb6d (2026-07-31). Found in bughq on stx 0.2.170 while moving a page with a <script client> onto a shared marketing layout for the first time — the app had simply never produced the triggering combination before.

14 days ago

Fixed in 880d016c54.

The offset rule is kept, because its stated reason holds. The comment said a layout script can appear before <head> and the runtime has to precede it — and that position is still after the doctype, so it goes down the same branch it always did. Fragments have no doctype and are unaffected. Only the case where the first-script offset would jump the doctype falls through now, landing just after <head>: valid, and still ahead of every script in the document.

One case needed a decision rather than a fix. Where a script already precedes the doctype the input is malformed and heading for quirks mode on its own. The runtime now goes into <head> rather than stacking in front of it — so the tag we control is not the one jumping the doctype — and such a script does not get the runtime. That is a trade, so it is pinned by a test rather than left as an accident.

The three build modes each had their own copy of the placement logic, which is why this had to be found once instead of three times. They now share one helper.

Verified end to end, not only by unit test. Patching the installed 0.2.170 with this logic and deleting the app-side workaround from the reporting app:

before:  1: <!-- stx-layout: marketing -->
         2: <script data-stx-runtime src="/_stx/runtime.js"></script>
         3: <script data-stx-stores>;(function(){
        57: <!DOCTYPE html>
        -> document.compatMode = "BackCompat"

after:   1: <!-- stx-layout: marketing -->
         2: <!DOCTYPE html>
         4:   <head>
        11:   <script data-stx-runtime src="/_stx/runtime.js"></script>
        12: <script data-stx-stores>;(function(){
       155: </head>
        -> document.compatMode = "CSS1Compat", document.doctype present, window.stx live

15 new tests in packages/stx/test/runtime-injection-doctype.test.ts, run against both non-inline build modes: doctype position, the first-tag invariant, head placement ahead of an existing head script, the before-<head> layout script the old rule existed for, fragments, the already-declared no-op, and a lowercase doctype with leading whitespace. There were no tests on injectSignalsRuntime at all before this.

9650 existing tests still pass, plus 230 in the router package.

Worth noting for anyone hitting this before a release: the injector short-circuits on if (template.includes('data-stx-runtime')) return template, so a page can declare the tag itself and keep it in <head>. Push it from the page rather than adding it to a shared layout, or every page in that section downloads the runtime to serve the one that needs it.

14 days ago

Landed in 880d016c54, on main. Closing.

I re-verified rather than taking the commit at its word, because the failure mode here is invisible to every check short of compatMode — the exact property that let it ship in the first place.

placeRuntimeTag now asks where the first-script offset actually is:

const firstScript = template.indexOf('<script')
const doctype = template.search(/<!doctype\b/i)

if (firstScript !== -1 && (doctype === -1 || firstScript > doctype))
  return template.slice(0, firstScript) + runtimeScript + '\n' + template.slice(firstScript)

so the old rule still wins whenever it lands after the doctype — which covers both the layout-script case it was written for and fragments, which have no doctype at all — and only falls through to just-inside-<head> when it would jump the doctype.

Probed each shape independently against the source, asserting the runtime offset is greater than the doctype offset and reading back the first non-comment token:

PASS  reported shape (head has no script)        <!DOCTYPE html> <html><head><title>…
PASS  layout script before <head>                <!DOCTYPE html> <script data-stx-runtime…
PASS  head already has a script                  <!DOCTYPE html> <html><head><script data-stx-r…
PASS  comment mentioning <script before doctype  <!DOCTYPE html> <html><head> <script data-stx-…
PASS  fragment, no doctype                       <div><script data-stx-runtime…

Two of those are the ones worth having:

  • "layout script before <head>" is the case the original rule existed for. It would have been easy to fix the doctype bug by simply preferring <head> — as the issue itself suggested — and silently regress that script back into throwing. The runtime still precedes it.
  • "comment mentioning <script before doctype" is the trap this codebase keeps meeting (findBodyOpenTag, the <html>-in-a-comment case). A comment reading <!-- use <script client> here --> above the doctype puts a <script offset ahead of it; the check fails safe to the head rather than being fooled into prepending.

packages/stx/test/runtime-injection-doctype.test.ts — 15 pass. Full package suite: 9654 pass, 0 fail across 469 files.

One thing worth keeping from this: the reason it survived is that every signal was green. 200, correct full-page screenshot, no console error, SPA probe passing. document.compatMode was the only thing that ever disagreed, and nothing was looking at it. A page-level assertion that compatMode === 'CSS1Compat' is cheap and would catch the whole family — worth considering wherever the render smoke checks live.

Sign in to comment on this issue.