live_interaction_contracts

Copy Markdown View Source

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 contract runner for an already-started LiveView app, backed by Playwright and atomic 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:

{:live_interaction_contracts, "~> 1.2", only: [:dev, :test], runtime: false}

If you also use the reference components at runtime:

{:live_interaction_contracts, "~> 1.2"}

The harness requires elixir, node, and Playwright browsers:

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:

import {hooks as licHooks} from "phoenix-colocated/live_interaction_contracts";

new LiveSocket("/live", Socket, {hooks: {...hooks, ...licHooks}});

Reference component example

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:

# 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:

mix live_interaction_contracts.check \
  --url http://127.0.0.1:4000 \
  --contract test/interaction_contracts/account_menu.json

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.
  • SPEC.md: v1 frozen scope and project claims.
  • KERNEL.md: the state ownership model.
  • CONFORMANCE.md: harness and CI behavior.
  • BUILDING_COMPONENTS.md: how to add the next contract-backed component.
  • PROJECT_MAP.md: where the detailed evidence lives in this repository.