Metadata Protocol
The metadata protocol is the bridge from DOM to runtime. The boundary listens at its host element, parses metadata from the click target, and emits events or dispatches commands accordingly.
Attribute table
Section titled “Attribute table”| Attribute | On a custom element | On plain HTML | Maps to |
|---|---|---|---|
| Event type | meta-event | data-meta-event | event.type |
| Command type | meta-command | data-meta-command | command.type |
| Payload value | meta-prop-<key> | data-meta-prop-<key> | payload[<camelCaseKey>] |
| Payload object | meta-payload | data-meta-payload | JSON object merged into payload |
| Field change event | meta-event-on-change | data-meta-event-on-change | event emitted on native change |
Custom elements may use the cleaner non-prefixed form. Plain HTML elements should use data- to comply with HTML’s reserved attribute namespace.
Examples
Section titled “Examples”Single event
Section titled “Single event”<kit-button meta-event="checkout.started">Pay</kit-button>Click produces:
runtime.emit({ type: 'checkout.started', context: <from boundary>, payload: {}, source: { tagName: 'kit-button' },})Single command
Section titled “Single command”<kit-button meta-command="dialog.open" meta-prop-target="confirm"> Open confirm</kit-button>Click produces:
runtime.command({ type: 'dialog.open', context: <from boundary>, payload: { target: 'confirm' }, source: { tagName: 'kit-button' },})Both at once
Section titled “Both at once”<kit-button meta-event="settings.saved" meta-command="dialog.close" meta-prop-target="settings-dialog"> Save</kit-button>Click produces both: the event first, then the command. Both share the same payload.
Plain HTML
Section titled “Plain HTML”<button data-meta-event="comment.posted" data-meta-prop-thread-id="t_42" data-meta-prop-character-count="120"> Post</button>Same shape, with the data- prefix.
Payload conventions
Section titled “Payload conventions”-
Keys are kebab-case in the attribute, camelCase in the payload.
<button data-meta-prop-cart-id="c_42" data-meta-prop-line-items="3">...</button>Becomes:
payload: { cartId: 'c_42', lineItems: '3' } -
meta-prop-*values are always strings. Attributes can’t carry numbers, booleans, or objects natively. If you need typed payload values, usemeta-payload:<button data-meta-event="cart.update" data-meta-payload='{"itemCount":3,"isGift":true}'>...</button> -
meta-prop-*overridesmeta-payload.data-meta-payload='{"cartId":"a"}' data-meta-prop-cart-id="b"producespayload: { cartId: 'b' }.
What gets parsed
Section titled “What gets parsed”When a click reaches a <kit-boundary>:
- The boundary walks
event.composedPath()looking for the first element withmeta-event,data-meta-event,meta-command, ordata-meta-command. - It reads all
meta-event,meta-command, andmeta-prop-*(and thedata-variants) from that element. - It reads its own context (surface, feature, entity) plus any inherited ancestor context.
- It calls
parseMetadata(element, context)and dispatches.
If no metadata is found in the path, nothing happens.
Non-click dispatch
Section titled “Non-click dispatch”The boundary handles more than clicks:
- Composed
meta:event— any element can dispatchnew CustomEvent('meta:event', { bubbles: true, composed: true, detail: { type, payload } }). - Form submit —
<form data-meta-event="thing.submitted">emits on native submit and includespayload.values. - Field change — fields with
data-meta-event-on-change="field.changed"emit on native change and includename/valuewhen available.
What does not get parsed
Section titled “What does not get parsed”- Attributes on parent elements other than payload props.
- Stale metadata — attributes are read at dispatch time, not at render time.
Reading metadata programmatically
Section titled “Reading metadata programmatically”If you need to parse metadata outside the boundary:
import { parseMetadata } from '@atheory-ai/kitsune-app'
const element = document.querySelector('button[data-meta-event]')!const metadata = parseMetadata(element, { surface: 'page' })
// metadata.event and metadata.command are now KitEventInput / KitCommandInput shapesOr for the raw read without normalization:
import { readDomMetadata } from '@atheory-ai/kitsune-app'
const raw = readDomMetadata(element, { surface: 'page' })// { eventType, commandType, context, payload, source }Source object
Section titled “Source object”Every dispatched event/command includes a source object that the metadata layer fills in:
source: { tagName: 'kit-button' }This is useful for diagnostics and for filtering in modules. Future versions may add more fields (e.g., the element’s id, computed accessible name).
Additive evolution
Section titled “Additive evolution”The protocol is additive. Existing markup keeps working as new metadata channels are introduced.