stripDocumentWrapper decides whether a string is a full HTML document or a fragment using two regexes. One is anchored to the start of the string, the other is not. As a result, the literal text <!DOCTYPE appearing anywhere in a fragment — including inside an HTML comment, a <pre> block, or a JS string — causes that fragment to be treated as a full document.
Location
dist/app-shell.js:117-119 (v0.2.113)
export function stripDocumentWrapper(html, options = {}) {
const trimmed = html.trim();
if (!/<!DOCTYPE\b/i.test(trimmed) && !/^<html[\s>]/i.test(trimmed))
return html;
// ...strips <head>/<body>/document wrapper from here on/^<html[\s>]/i— anchored. Correct./<!DOCTYPE\b/i— not anchored. Matches anywhere in the string.
Reproduce
A fragment that is unambiguously not a document:
<div class="docs">
<!-- every page starts with <!DOCTYPE html> -->
<p>hello</p>
</div>stripDocumentWrapper treats this as a full document and proceeds into wrapper-stripping, rather than returning it untouched.
This is easy to hit in practice on any page that documents HTML — docs sites, code samples in <pre><code>, a changelog entry mentioning a doctype.
Suggested fix
Anchor the doctype test the same way the <html> test is anchored:
if (!/^<!DOCTYPE\b/i.test(trimmed) && !/^<html[\s>]/i.test(trimmed))
return html;A document's doctype is required to be the first thing in the file, so anchoring loses nothing.
Possibly related
Downstream we've seen a symptom where a literal <html> inside an HTML comment in a page fragment causes SPA navigation to fall back to a full page reload. I have not confirmed that's the same code path — dist/router.js contains no <html reference, so the reload decision appears to be made elsewhere. Filing this separately since the anchoring asymmetry above is demonstrable on its own; happy to open a second issue with a repro for the reload behaviour if that's a distinct path.
Environment
@stacksjs/stx0.2.113- Found while auditing template/fragment handling ahead of a migration.