Summary
The HTML-export content-root model cannot satisfy two valid ways a model references the document root's identity at once, because root identity is transferred onto the [data-he-content] wrapper itself while all other model selectors are descendant-scoped under [data-he-content].
Given a model document with identity on the root, e.g. <body class="dark" id="app" dir="rtl">:
- Global-root-qualified selectors work (already fixed):
body.dark, html#app, body[dir="rtl"] are rewritten by the global-selector pass to [data-he-content].dark / [data-he-content]#app / [data-he-content][dir="rtl"], which match the wrapper (identity was transferred onto it).
- Descendant-identity selectors do NOT work (this bug): a plain
.dark .card, #app .card, or [dir="rtl"] .card — where the model treats the root class/id/attr as an ancestor — is descendant-scoped to [data-he-content] .dark .card, i.e. it looks for a .dark descendant of the content root. But dark lives on [data-he-content] itself, not on a descendant, so the rule never matches and the themed/directional styling finalizes dead.
Root cause: identity on [data-he-content] serves compound/global-root selectors but not descendant selectors; the sanitizer's blind [data-he-content] <selector> descendant scoping cannot know that a leading .dark/#app/[dir] refers to the root.
Proposed fix (design-level)
Represent the model's <body> as an inner root proxy element carrying all transferred root identity, inside the content root:
<div data-he-content><div data-he-root class="dark" id="app" dir="rtl" lang="ko" style="...">…body children…</div></div>
Then:
- Descendant-identity selectors work:
[data-he-content] .dark .card matches (the proxy has .dark; .card is its descendant).
- Global-root selectors retarget to the proxy: the global-selector rewrite maps
body/html/:root (and *) and the transferred root inline style from [data-he-content] to the [data-he-root] proxy (e.g. [data-he-content] [data-he-root].dark), so body.dark etc. still match.
data-he-root becomes a reserved attribute (like data-he-content): model CSS/HTML cannot forge it.
Scope / risk
- Touches the hardened global-selector rewrite in
html-export-css-sanitize.ts (CRLF-escape, functional :root, sibling-combinator, :nth-child(of S) hardening; architect-CLEAR) and the root class/id/attr/style transfer in html-export-sanitize.ts + html-export-shell.ts, and changes the finalized-bytes structure (new inner wrapper). Requires re-running the full global-selector adversarial suite + direct harness + Grok-QA/architect security re-review, because it moves the reserved scoping boundary.
- Deliberately not hot-patched under review pressure: the single-wrapper model was hardened over three rounds; the inner-proxy change should be designed and reviewed as a unit.
References
Summary
The HTML-export content-root model cannot satisfy two valid ways a model references the document root's identity at once, because root identity is transferred onto the
[data-he-content]wrapper itself while all other model selectors are descendant-scoped under[data-he-content].Given a model document with identity on the root, e.g.
<body class="dark" id="app" dir="rtl">:body.dark,html#app,body[dir="rtl"]are rewritten by the global-selector pass to[data-he-content].dark/[data-he-content]#app/[data-he-content][dir="rtl"], which match the wrapper (identity was transferred onto it)..dark .card,#app .card, or[dir="rtl"] .card— where the model treats the root class/id/attr as an ancestor — is descendant-scoped to[data-he-content] .dark .card, i.e. it looks for a.darkdescendant of the content root. Butdarklives on[data-he-content]itself, not on a descendant, so the rule never matches and the themed/directional styling finalizes dead.Root cause: identity on
[data-he-content]serves compound/global-root selectors but not descendant selectors; the sanitizer's blind[data-he-content] <selector>descendant scoping cannot know that a leading.dark/#app/[dir]refers to the root.Proposed fix (design-level)
Represent the model's
<body>as an inner root proxy element carrying all transferred root identity, inside the content root:Then:
[data-he-content] .dark .cardmatches (the proxy has.dark;.cardis its descendant).body/html/:root(and*) and the transferred root inline style from[data-he-content]to the[data-he-root]proxy (e.g.[data-he-content] [data-he-root].dark), sobody.darketc. still match.data-he-rootbecomes a reserved attribute (likedata-he-content): model CSS/HTML cannot forge it.Scope / risk
html-export-css-sanitize.ts(CRLF-escape, functional:root, sibling-combinator,:nth-child(of S)hardening; architect-CLEAR) and the root class/id/attr/style transfer inhtml-export-sanitize.ts+html-export-shell.ts, and changes the finalized-bytes structure (new inner wrapper). Requires re-running the full global-selector adversarial suite + direct harness + Grok-QA/architect security re-review, because it moves the reserved scoping boundary.References
src/main/html-export-css-sanitize.ts:307("Preserve root identity selectors without body/html").feat/he-r1-cutover-reland: ea9a11d (class/id), 2591b69 (inline style), eea2f3f (lang/dir/title/role).[data-he-content]reserved attr (css-sanitize.ts), shell wrapper (html-export-shell.tscontentRootOpenTag).