Skip to content

HTML export: descendant root-identity selectors (.dark .card / #app / [dir]) don't match after root-identity transfer — needs inner root-proxy #33

Description

@project820

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions