Skip to content

feat: warn on invalid HTML nesting at render time#42

Merged
jonlaing merged 1 commit into
mainfrom
feat/nesting-validation-warnings
Jul 24, 2026
Merged

feat: warn on invalid HTML nesting at render time#42
jonlaing merged 1 commit into
mainfrom
feat/nesting-validation-warnings

Conversation

@jonlaing

Copy link
Copy Markdown
Owner

Summary

Certain HTML parent/child pairs get silently normalized by the browser's parser — the live DOM ends up different from what SSR emitted, and Effex hydration reports a confusing `Expected but not found in Y` mismatch far from the actual bug's source. This PR catches the class at render time with a targeted `console.warn`.

Motivating case

Portfolio project had:

```ts
// Card.ts
$.p({ class: "text-sm..." }, children)

// HomePage.ts
Card({...}, $.p($.of("..."))) //

inside


```

SSR emits `

...

`. Browser parses that as `

...

` (auto-closes outer `

` at the inner one). Hydration walker expects `

` inside `

`, finds the sibling, reports "Expected

but not found in P" — nowhere near the actual bad callsite.

Approach

New shared helper `packages/dom/src/Render/validateNesting.ts`:

  • `checkNesting(parent, child)` — returns a warning string if invalid, else null. Codifies the HTML spec's content-model rules for the categories that cause silent normalization.
  • `warnIfInvalidNesting(parent, child)` — dedupes with a `Set` so a bad pair rendered in a loop doesn't spam. Only warns once per pair per process.

Wired into both renderer `appendChild` implementations:

  • `DOMRenderer` (client mount): checks Element parents/children via `tagName`.
  • `StringRenderer` (SSR): checks VElement parents/children via the `type` field.

Hydration renderer's `appendChild` is a no-op, so no wire-up needed there.

Covered nestings

  • `

    ` inside `

    ` plus all block-level tags inside `

    `: `div`, `section`, `article`, `aside`, `ul`, `ol`, `form`, `table`, `h1`–`h6`, `hr`, `blockquote`, `address`, `figure`, `fieldset`, `pre`, `main`, `nav`, `header`, `footer`, `details`, `dialog`, `menu`

  • `` inside `` (nested anchors)
  • Interactive content inside ``: `a`, `button`, `embed`, `iframe`, `input`, `label`, `select`, `textarea`
  • `` inside ``

Not covered (yet — table structure is more complex): `` outside `//`, `` outside ``, etc. Can add later if it comes up.

Cost

The check is a lowercase string compare + a Set lookup per element append. Negligible even on large trees. And it only runs where `appendChild` runs — the check itself doesn't matter in SSG output shipped to production.

Test plan

  • 11 new unit tests in `validateNesting.test.ts` covering positive cases (all invalid pairs), negative cases (valid pairs like `

    `), dedup (once-per-pair), and case normalization.

  • Full suite: 744 pass / 2 skipped (was 733 / 2)
  • `npx tsc --noEmit` clean

🤖 Generated with Claude Code

Certain parent/child pairs get silently normalized by the browser's HTML parser: <p> inside <p>, block-level content inside <p>, nested anchors, interactive content inside <button>, nested forms. The live DOM ends up different from what SSR emitted, and hydration reports a confusing 'Expected <X> but not found in <Y>' mismatch far from the source of the actual bug.

New shared helper (packages/dom/src/Render/validateNesting.ts) codifies the invalid pairs from the HTML spec's content-model rules. DOMRenderer and StringRenderer's appendChild call warnIfInvalidNesting on each pair — emits a targeted console.warn once per pair per process. Small runtime cost, catches the bug at its source.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying effex with  Cloudflare Pages  Cloudflare Pages

Latest commit: 8b07d3d
Status: ✅  Deploy successful!
Preview URL: https://bea72b9c.effex.pages.dev
Branch Preview URL: https://feat-nesting-validation-warn.effex.pages.dev

View logs

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying effex-api with  Cloudflare Pages  Cloudflare Pages

Latest commit: 8b07d3d
Status: ✅  Deploy successful!
Preview URL: https://c3b5913b.effex-api.pages.dev
Branch Preview URL: https://feat-nesting-validation-warn.effex-api.pages.dev

View logs

@jonlaing
jonlaing merged commit df82933 into main Jul 24, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant