# `for=` id-ref + bubbling-event interaction contract

Source of truth: [ADR-0079](../../../../../../docs/ops/adr/adr-0079-for-attribute-id-ref-and-events-only-interaction-contract.md)
(ratified 2026-08-20, executed by gh#1780). Read the ADR before adding a new
`for=`-bound pair or touching `table-toolbar-ui`/`chart-legend-ui`/
`tooltip-ui`/`context-menu-ui`: this file is the routing pointer + the
fix-now checklist, not a restatement of the ruling.

## The one-line rule

`for=` means id-ref, everywhere in AdiaUI, no component may give it a
different resolution contract (`context-menu-ui`'s prior CSS-selector
reading was the violation this ADR ends). The interaction contract over any
`for=` binding is bubbling `CustomEvent`s in **both** directions, never a
direct property write or method call across the binding.
`chart-legend-ui`↔`chart-ui` is the ratified model: the bound element
dispatches a bubbling event, the target listens on `document` filtered by
`[for]` matching its own id.

## Fix-now checklist for a `for=`-bound primitive

- `for=` resolves via `getElementById` (id-ref), never
  `querySelectorAll`/a CSS-selector reading. A component needing
  selector-flavored targeting names a DIFFERENT attribute (see
  `context-menu-ui`'s `target-selector` below), it never overloads `for`.
- Every write the bound pair needs to make on each other is a bubbling
  `CustomEvent`, dispatched at the resolved target (or document-delegated,
  whichever the existing read direction already established), never a
  direct property write (`target.prop = …`) or method call
  (`target.method()`) across the binding.
- `chart-legend-ui`, `chart-ui`, and `tooltip-ui[follows=pointer]` are
  already verified compliant, don't touch them to "align" with a new
  pattern; the ADR names them explicitly out of scope for its own sweep.
- `table-toolbar-ui`→`table-ui`'s inbound command surface is
  `toolbar-search` / `toolbar-filter-set` / `toolbar-filter-clear` /
  `toolbar-columns-set` / `toolbar-paginate`, dispatched by the toolbar at
  its resolved `#target`, applied by `table-ui` via the same public-API call
  the direct write used to make. This is additive to `table-ui`'s contract:
  any consumer may dispatch these events, not only `table-toolbar-ui`. The
  existing toolbar→consumer notification events (`search`, `filter-change`,
  `sort-change`, `columns-change`, `page-size-change`) are unchanged and
  never reused as the inbound command channel (naming collision, rejected
  alternative in the ADR).
- A component whose `for=` was CSS-selector-flavored (`context-menu-ui`'s
  prior shape) renames the selector-flavored attribute to `target-selector`;
  `for` survives only as a **deprecated back-compat alias**, still resolved
  as a selector (never dual-purposed into id-ref sniffing, rejected in the
  ADR as ambiguous), honored only when `target-selector` is unset, with a
  one-shot `console.warn` pointing at the replacement. Document the alias as
  deprecated in the yaml SoT, the class doc comment, and the examples.

## What NOT to do

- Don't invent a document-level delegated-listener shape for a pair that
  already resolves a concrete target reference for its read direction, a
  targeted dispatch at the resolved element is simpler and still satisfies
  "events, not direct writes" (rejected alternative in the ADR, re:
  `table-toolbar-ui`).
- Don't dual-purpose one attribute name for two resolution contracts
  (id-ref sometimes, selector sometimes): this is the exact same-name/
  different-contract collision ADR-0053's no-shadowing doctrine forbids for
  global attributes, and the defect this ADR exists to end.
