Skip to content

fix(element): hydrate renderer-owned light DOM in place across SSR upgrade #1148

Description

@SisyphusZheng

Category: Framework / Element runtime
Priority: P2
Target: v0.43.3
Release gate: BLOCKING

Maintainer classification

Treat this as a compatibility correction to the existing renderMode = 'light' SSR/CSR promise, not as a new render mode.

The correction must not add a public option, change the Shadow/DSD default, or introduce a general VDOM reconciler. If implementation requires a new public lifecycle hook, ownership option, runtime default, or other additive semantic, stop and reclassify that part through ADR-0140 as a future-minor change.

Context

ADR-0092 correctly chose an explicit render-root policy:

  • shadow renders into Shadow DOM and emits DSD on the server;
  • light renders into the host and does not attach a shadow root.

However, it specified SSR and CSR independently and did not define the transition from server-rendered light DOM to an upgraded interactive element. It also did not define who owns host children during that transition.

The current client runtime therefore routes every light-mode connection through renderIntoLightDom(), which calls clearChildren(host) and mounts fresh DOM. pre-hydration-click.ts explicitly skips light-mode hosts because the recorded target is detached by that replacement.

Relevant evidence:

  • docs/adr/ADR-0092-dsdelement-render-mode.md
  • packages/element/src/open-element-runtime.ts
  • packages/element/src/open-element-render.ts
  • packages/element/src/internal/core/pre-hydration-click.ts
  • packages/element/__tests__/pre-hydration-click.test.ts

Required contract amendment

Amend ADR-0092, or add a successor ADR, with this narrow contract:

  1. renderMode = 'light' means the component owns the host's rendered child subtree.
  2. SSR output for that subtree is the authoritative initial DOM.
  3. On client upgrade, matching SSR DOM is activated in place by binding existing event, signal, attribute, and branch markers.
  4. Matching activation preserves node identity, focus, selection, live form values, nested custom-element instances, and pre-upgrade interaction targets.
  5. A structural or marker mismatch emits the existing structured hydration diagnostic and may degrade to a full client render.
  6. Light mode does not simultaneously promise arbitrary consumer-owned child projection. Use Shadow DOM plus slots for projection, or a separate enhancement/controller pattern for decorating arbitrary existing DOM.

Implementation scope

  • Generalize the internal binding/hydration target only as far as needed to accept the renderer-owned light host subtree.
  • Route populated light-mode SSR output through in-place activation.
  • Keep empty-host CSR behavior unchanged.
  • Enable pre-hydration click capture/replay for light-mode hosts when the target survives activation.
  • Preserve current Shadow/DSD behavior and public package surface.
  • Add one nested light custom-element fixture that proves parent activation does not replace the child instance.

Acceptance

  • SSR -> delayed upgrade keeps exact node identity for an input, button, and nested custom element.
  • Focus, selection, and a value typed before upgrade survive matching activation.
  • A pre-upgrade click is replayed exactly once after light activation.
  • Event, signal, attribute, Show, and keyed/unkeyed For bindings activate against existing light DOM.
  • Marker/branch mismatch produces a structured diagnostic and safely degrades.
  • Empty-host CSR light rendering and explicit update() remain compatible.
  • Shadow/DSD hydration tests remain unchanged and green.
  • Chromium, Firefox, and WebKit exercise the real SSR -> delayed-upgrade path.
  • A nextCrm authoring-fitness slice supplies external application evidence before v0.43.3 publication.
  • ADR, current version plan, status, roadmap, release notes, and package tests agree on the final compatibility classification.

Non-goals

  • No change to the default Shadow/DSD mode.
  • No generic DOM diff/reconciliation engine.
  • No arbitrary light-child projection contract.
  • No Zag/Open Props integration in this issue.
  • No new framework package or public adapter.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions