Current section
Files
Jump to
Current section
Files
live_interaction_contracts
CONTRACT_ISLAND.md
CONTRACT_ISLAND.md
# CONTRACT_ISLAND — client-built children need `phx-update="ignore"`
A **rendering island** is a hook that builds its own DOM subtree (via
`createElement`/`innerHTML`) into a server-rendered container the server template
leaves empty — e.g. a canvas builder, an SVG seat map, a chart, a scroll-spy list.
This is a legitimate, common pattern for heavy client-owned rendering.
## The contract (proven, all engines)
> **Client-built children are wiped by ANY LiveView patch — even a distant sibling
> patch — unless their container has `phx-update="ignore"`.**
The server template renders the container with no children; morphdom, whenever it
reconciles that container, sees "template has 0 children, DOM has N" and removes the
N client-built children. `phx-update="ignore"` tells morphdom to skip the subtree, so
they survive.
Evidence (`priv/island-survival-spike/`, self-asserting, 2 runs × 3 engines,
deterministic): an unprotected container's 5 client-built nodes drop to **0** after a
patch; an otherwise-identical `phx-update="ignore"` container keeps all **5**. The
wipe fires even on a patch to an unrelated sibling — it is not limited to patches of
the island's own subtree.
## Why it matters
- The hook usually builds **once** (on `mounted()`). After a wipe it does **not**
auto-rebuild, so the island goes blank until a remount / an explicit re-render.
- The risk is invisible in tests that only patch the island directly, and invisible to
`LiveViewTest` (string-level). It needs a real browser + a distant-patch case.
## The rule
- **Any hook that builds its own DOM subtree MUST put `phx-update="ignore"` on the
container it fills** (or an equivalent skip). No exceptions for "the server never
patches that region" — a distant patch is enough.
- This is the subtree-level sibling of the attribute-level `JS.ignore_attributes`
(`KERNEL` rule 1.3 / the popover `aria-expanded` case): both tell reconciliation to
keep its hands off client-owned DOM. Attribute writes on server DOM →
`ignore_attributes`; client-built children → `phx-update="ignore"`.
## Validated against real code
Surfaced by auditing a production app (Hanken): its `SeatMap` and `SettingsScrollSpy`
islands both correctly use `phx-update="ignore"`. This contract is the executable
proof of *why* that is required — it turns "they happened to do it" into "without it,
any patch wipes the subtree, on every engine."
*Evidence: `priv/island-survival-spike/island-survival-results.json`. Run:
`mix live_interaction_contracts.test --suite island`.*