Current section
Files
Jump to
Current section
Files
live_interaction_contracts
README.md
README.md
# live_interaction_contracts
Real-browser verification for interactions in your own Phoenix LiveView application.
Point the external-app contract runner at an already-started application to check
whether browser-native interaction state survives LiveView patches and to produce
machine-readable evidence. The package also ships a calibration harness, delegation
ledger, and unstyled reference components as secondary implementation references.
Use it when you need to know whether a popover, menu, tooltip, select, combobox,
dialog adapter, focus handoff, or async query shell is safe under LiveView DOM
patching.
## Why this exists
LiveView gives the server authority over rendered HTML, but many interaction
machines keep their state in the browser: top-layer popovers, active descendants,
focus, selection, pending input, and listbox shells.
The hard failures happen when both sides think they own the same state. A patch
replaces a node, a client hook rebinds, the server renders stale results, or the
browser keeps state that the server cannot observe.
`live_interaction_contracts` turns those cases into executable contracts. Each
green claim is backed by browser tests across Chromium, Firefox, and WebKit.
Amber and unknown cases stay explicit.
The shortest proof is the harness's **one patch, three outcomes** trial: the same
acknowledged diff leaves the same DOM node alive in a popover, a raw modal dialog,
and an `open`-protected modal dialog. The popover stays coherent, the raw dialog is
torn into `open=false` while `:modal` still matches, and the protected dialog stays
coherent while its server-rendered child still updates. The controlled comparison is
raw versus protected dialog; popover is the native-safe contrast.
## What ships
- `mix live_interaction_contracts.check`, a strict single-contract and deterministic
directory-suite runner for an already-started LiveView app, backed by Playwright
and atomic per-contract JSON evidence.
- A Mix task that runs the conformance harness against real browsers.
- A generated delegation ledger that classifies each state machine as green,
amber, red, or unknown.
- Reference components for `<.popover>`, `<.tooltip>`, `<.menu>`, `<.select>`,
and `<.combobox>`.
- A `LiveInteractionContracts.Channel` helper for monotonic async query results.
- Contract documents that define the state ownership rules.
The components are intentionally unstyled. They are reference primitives, not a
finished UI kit. The future UI kit direction is Temper UI; this package is the
proof layer underneath it.
## Install
For harness, ledger, and CI use:
```elixir
{:live_interaction_contracts, "~> 1.3", only: [:dev, :test], runtime: false}
```
If you also use the reference components at runtime:
```elixir
{:live_interaction_contracts, "~> 1.3"}
```
The harness requires `elixir`, `node`, and Playwright browsers:
```sh
npm install --save-dev playwright@1.61.1
npx playwright install chromium firefox webkit
```
The reference components require Phoenix LiveView 1.1 or later. Their client JS
ships as Phoenix colocated hooks. After `mix compile`, register the extracted
hooks in your `app.js`:
```javascript
import {hooks as licHooks} from "phoenix-colocated/live_interaction_contracts";
new LiveSocket("/live", Socket, {hooks: {...hooks, ...licHooks}});
```
## Reference component example
```elixir
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
end
```
## Run the harness
Run these commands from your Phoenix project:
```sh
# all stable browser engines
mix live_interaction_contracts.test
# one browser
mix live_interaction_contracts.test --browser chromium
# one suite
mix live_interaction_contracts.test --suite combobox
# early warning against LiveView main
mix live_interaction_contracts.test --lv "github:phoenixframework/phoenix_live_view#main"
# regenerate the delegation ledger
mix live_interaction_contracts.ledger
# advisory app audit for patch-safety and dual-write risk
mix live_interaction_contracts.audit --path /path/to/app
# compare two harness result sets
mix live_interaction_contracts.compare --a before.json --b after.json
```
By default, the harness tests against your project's LiveView version.
## Check your own LiveView
Skills and code review can predict ownership risks; this command verifies the real
result. Start your application, then point a strict v1 JSON contract at it:
```sh
mix live_interaction_contracts.check \
--url http://127.0.0.1:4000 \
--contract test/interaction_contracts/account_menu.json
```
Run every independent first-level `*.json` contract in a directory, after one
complete preflight and in lexical filename order:
```sh
mix live_interaction_contracts.check \
--url http://127.0.0.1:4000 \
--contracts test/interaction_contracts \
--out-dir tmp/interaction-contract-results
```
Suite mode writes one schema-v1 `*.result.json` per contract and runs every valid
contract even after a violation. Its aggregate exit reduction is error (3), then
inconclusive (2), then fail (1), otherwise all pass (0). The suite is only batch
orchestration: it gives every contract a fresh browser context and provides no
dependency or inheritance mechanism. Contracts still target the same running
application, so application/database fixture isolation and reset remain the caller's
responsibility. See `CUSTOM_APP_CONTRACTS.md` for ownership guidance and worked
application patterns.
The contract describes click, hover, or deterministic scroll-into-view activation;
a click or a restricted `Enter`/`Space` key press that causes a LiveView patch; a
subject-local attribute whose change proves the patch arrived; and before/after
expectations for DOM object identity, an exact attribute value (including `null` for
absence), popover, dialog, or focus state. A press trigger can patch while the pointer
remains over a hover-owned interaction: pressing does not move the pointer, and the
declared post-state proves whether the hover-owned state actually survived the patch.
Node identity, popover, and dialog probes may use optional unique selectors; omitted
selectors target the contract subject. Focus and attribute probes require unique
selectors. Only the acknowledgment selector is required to be subject-local; probe
selectors are not restricted to descendants. The runner writes
`live-interaction-contracts-result.json` and exits non-zero for failed,
inconclusive, or invalid runs. See `BLUEPRINT.md` and
`priv/harness/consumer-popover-contract.json` for the v1 format.
Targets are restricted to loopback by default because contract clicks may cause real
application side effects. `--allow-remote` exists only for an explicitly safe test
environment; do not point the runner at production.
### 1.x compatibility
Because this package is already published as 1.x, existing Mix tasks, component APIs,
v1 contract fields, result fields, and exit-code meanings are compatibility
constraints. New probes and optional evidence may be added within 1.x; removing or
reinterpreting existing fields requires a new contract/result schema and a package
major release. M1 hardening is additive and does not weaken existing contracts.
## Current v1 coverage
Green reference machines:
- Popover
- Tooltip
- Menu
- Select
- Combobox
- Cursor coordination
- Versioned async query channel
Amber:
- Dialog adapter, with documented restrictions.
Unknown:
- Native select popup open-state.
- Real-device IME behavior.
## Read next
- `SPEC.md`: v1 frozen scope and project claims.
- `KERNEL.md`: the state ownership model.
- `CONFORMANCE.md`: harness and CI behavior.
- `CUSTOM_APP_CONTRACTS.md`: how to verify your own hooks and third-party widgets.
- `BUILDING_COMPONENTS.md`: how to add the next contract-backed component.
- `PROJECT_MAP.md`: where the detailed evidence lives in this repository.