This is not a component library. It is a set of executable interaction contracts for Phoenix LiveView and browser-native state machines, plus a conformance harness that continuously verifies them against real browsers.
Its primary artifacts are a conformance harness and a delegation ledger — not components. Components (a popover, a combobox) are derivations of the contracts, not the point.
What it answers
For any browser-native interaction machine (popover, dialog, focus, scroll, a combobox's listbox), it answers, with executable evidence across Chromium, Firefox, and WebKit:
- Can this machine's state be delegated to the browser under LiveView patching?
- Where does its state live (top layer, a server-reconciled attribute, node-bound editing state)?
- Will a LiveView patch touch it?
- What can the server observe, and what breaks if it treats that as authoritative?
- What is the destructive boundary?
Why it exists
In a LiveView app, interaction state and server-rendered content share one DOM subtree with two writers separated by network latency. The hard case — a combobox: server-rendered results + client-owned active descendant + versioned async query + browser-owned listbox shell — is exactly where the naive approaches fail. This project establishes, by test, which delegations are safe and which are not, and freezes that as a contract a conforming implementation must uphold.
v1 frozen status
Frozen for v1: the kernel invariants, the delegation-ledger format, the conformance
harness, CI-as-fuse, the popover / tooltip / menu / select / combobox / cursor /
channel contracts, the shipped LiveInteractionContracts.Channel guard, anchored
placement presets, and the amber dialog-adapter contract (CONTRACT_DIALOG.md,
7/7 conformance-gated). Still not frozen: a polished public <.dialog> component
and any real-device IME claim. See SPEC.md §"v1 verdict".
Repository map
| Path | Role |
|---|---|
SPEC.md | Durable project statements + v1 frozen/not-frozen verdict |
KERNEL.md | The five kernel members, precise frozen definitions |
DELEGATION_LEDGER.md | Ledger format + classification rules (durable) |
delegation-ledger.md / .json | The current ledger — generated by the harness, not hand-edited |
CONFORMANCE.md | The harness, the two-tier CI gate, how the ledger is regenerated |
CURSOR_CONTRACT.md | Client-owned coordination contract |
CHANNEL_CONTRACT.md | Versioned async query/result contract |
REFERENCE_POPOVER_CONTRACT.md | First green reference machine |
CONTRACT_DIALOG.md | Amber reference adapter (repairs red native <dialog>) |
CONTRACT_MENU.md | Menu reference (green + cursor): popover + aria-activedescendant |
CONTRACT_ISLAND.md | Client-built children need phx-update=ignore (proven) |
CONTRACT_TOOLTIP.md | Tooltip reference (green, pure delegation): hover/focus popover |
CONTRACT_SELECT.md | Select reference (green): popover + cursor + server-owned value |
CONTRACT_COMBOBOX.md | Combobox reference (green w/ construction requirement): popover × cursor × Channel |
RED_FIXTURES.md | dialog + details: why they are excluded from public API |
BACKLOG.md | Amber/unknown items and deferred work |
TASKS.md | Roadmap to v1.0 — milestones with acceptance gates |
REPRODUCE.md | Exact commands to reproduce every spike |
BUILDING_COMPONENTS.md | Playbook for adding the next conformance-backed component |
mix.exs, lib/ | The Mix package + conformance tasks |
lib/live_interaction_contracts/components/popover.ex | Reference <.popover> component (HEEx anatomy) |
lib/live_interaction_contracts/components/menu.ex | Reference <.menu> component (popover + cursor) |
lib/live_interaction_contracts/components/tooltip.ex | Reference <.tooltip> component (hover/focus popover) |
lib/live_interaction_contracts/components/select.ex | Reference <.select> component (popover + cursor + server-owned value) |
lib/live_interaction_contracts/components/combobox.ex | Reference <.combobox> component (popover × cursor × Channel) |
lib/live_interaction_contracts/components.ex | Single use LiveInteractionContracts.Components import entrypoint |
lib/live_interaction_contracts/channel.ex | Channel — the server-side monotonic guard as library code |
| (colocated hooks) | Client JS ships inside each component as a colocated hook (LiveView 1.1+) |
priv/harness/ | Multi-version conformance harness (target app + driver + ledger gen) |
priv/cursor-spike/ | Client-owned tier evidence (replace-with-same-id) |
priv/combobox-spike/ | Composition evidence (Delegation × Cursor × Channel) |
priv/dialog-adapter-spike/ | Amber dialog-adapter evidence (7/7 conformance) |
priv/island-survival-spike/ | Island survival: client-built children vs patches |
priv/popover-reference/ | Reference-component conformance (7/7, dogfoods <.popover>) |
priv/menu-reference/ | Menu conformance (8/8, dogfoods <.menu>) |
priv/tooltip-reference/ | Tooltip conformance (8/8, dogfoods <.tooltip>) |
priv/select-reference/ | Select conformance (9/9, dogfoods <.select>) |
priv/combobox-reference/ | Combobox conformance (9/9, dogfoods <.combobox> + Channel) |
reports/ | Dated evidence reports (version-specific; not durable spec; repo-only, not packaged) |
.github/workflows/conformance.yml | The fuse |
docs_site/ | Minimal docs page (components + live ledger); elixir docs_site/build.exs regenerates |
Install
# harness / ledger / fuse only (dev/test):
{:live_interaction_contracts, "~> 0.1", only: [:dev, :test], runtime: false}
# also using the reference components (e.g. <.popover>) at runtime:
{:live_interaction_contracts, "~> 0.1"}The conformance tasks require elixir, node, and Playwright browsers
(npx playwright install). The reference components require phoenix_live_view
1.1+ (already in your app). Their client JS ships as colocated hooks — no
manual registration, no vendored JS file. mix compile extracts them; then in
your app.js:
import {hooks as licHooks} from "phoenix-colocated/live_interaction_contracts";
new LiveSocket("/live", Socket, {hooks: {...hooks, ...licHooks}});Minimal consumer example (your own mix.exs must include the peer deps):
# mix.exs deps — phoenix_live_view is YOUR dependency, not transitively provided
{:phoenix, "~> 1.8"},
{:phoenix_live_view, "~> 1.1"},
{:live_interaction_contracts, "~> 0.1"}defmodule MyAppWeb.Demo do
use Phoenix.Component
use LiveInteractionContracts.Components
def demo(assigns) do
~H"""
<.popover id="demo" placement="bottom">
<:trigger>Open</:trigger>
<:content>Patch-safe by contract.</:content>
</.popover>
"""
end
endThe conformance tasks below run from your project — they boot the packaged
fixtures against your LiveView version (Playwright must be installed:
cd deps/live_interaction_contracts && npm i).
Quickstart
# runs the harness in real browsers against your project's LiveView version,
# regenerates the ledger, and asserts the fuse (non-zero exit on regression)
mix live_interaction_contracts.test # all three browsers
mix live_interaction_contracts.test --browser chromium
mix live_interaction_contracts.test --suite combobox
mix live_interaction_contracts.test --lv "github:phoenixframework/phoenix_live_view#main"
mix live_interaction_contracts.ledger # regenerate ledger only
# audit a target app for patch-safety / dual-write risk (advisory, KERNEL rules)
mix live_interaction_contracts.audit --path /path/to/app
# version-upgrade fuse: diff two harness result sets, non-zero on drift
mix live_interaction_contracts.compare --a before.json --b after.jsonBy default the harness detects and tests your project's LiveView version. See
REPRODUCE.md for the raw (non-mix) invocation used to develop the spikes.
Reading order
To add a component, follow BUILDING_COMPONENTS.md — a mechanical, copy-the-
popover-reference playbook that encodes every boot/Playwright/morphdom gotcha so the
work is delegatable.
SPEC.md → KERNEL.md → the individual contracts → CONFORMANCE.md. The
reports/*_REPORT.md files are the evidence behind the contracts; read them when you doubt a
claim. They are version-specific observations, deliberately kept out of the
durable spec.