Skip to content

kit-boundary

<kit-boundary surface="campaign-page" feature="donations" entity-type="campaign" entity-id="abc">
<!-- interactions inside inherit this context -->
</kit-boundary>

<kit-boundary> does two jobs:

  1. Provides context (surface, feature, entity, plus inherited values from ancestor boundaries) to events emitted within.
  2. Delegates metadata — clicks, composed meta:event, form submit, and opt-in field changes are normalized through the runtime.

@atheory-ai/kitsune-app

import '@atheory-ai/kitsune-app'
import { KitBoundaryElement } from '@atheory-ai/kitsune-app'
AttributeTypeNotes
surfacestringThe current visible region. Stacks into surfaces when nested. Reflects.
featurestringThe product area. Inherited from ancestors if omitted. Reflects.
entity-typestringType of the current entity (e.g. note). Reflects.
entity-idstringID of the current entity. Reflects.
PropertyTypeNotes
contextKitContextThe computed context, including inherited values. Read-only getter.

When asked for context:

  1. Walks up to the nearest ancestor <kit-boundary> or <kit-route> and reads its context.
  2. Pushes its own surface onto the parent’s surfaces (if present).
  3. Sets its own feature (or inherits if omitted).
  4. Sets its own entity from entity-type/entity-id (or inherits if omitted).
  5. Returns the combined object.
<kit-boundary surface="app" feature="notes">
<kit-boundary surface="note-list">
<kit-boundary surface="note-card" entity-type="note" entity-id="42">
<kit-button meta-event="note.opened">Open</kit-button>
</kit-boundary>
</kit-boundary>
</kit-boundary>

The button’s emitted event:

{
type: 'note.opened',
context: {
surfaces: ['app', 'note-list', 'note-card'],
surface: 'note-card',
feature: 'notes',
entity: { type: 'note', id: '42' }
},
payload: {},
source: { tagName: 'kit-button' }
}

The boundary attaches a click listener on connectedCallback. On click:

  1. It walks event.composedPath() looking for the first element with meta-event, data-meta-event, meta-command, or data-meta-command.
  2. If found, it calls parseMetadata(element, this.context) and:
    • Calls shell.runtime.emit(...) if there’s an event.
    • Calls shell.runtime.command(...) if there’s a command.

If the click doesn’t match any metadata, nothing happens — other handlers continue to run normally.

The boundary also listens for:

  • meta:event composed custom events and emits the detail.type runtime event with boundary context.
  • Native submit on forms with metadata, adding payload.values.
  • Native change on controls with meta-event-on-change / data-meta-event-on-change, adding name and value when available.

The boundary stops walking at the first element with metadata. If a child element wraps a deeper element with metadata, only the inner element’s metadata is parsed.

This is rarely a problem in practice — metadata typically lives on the click target itself. If you have nested clickable elements with metadata, consider whether they should be separate interactions.

Add one at:

  • The application root (the <kit-shell> is usually wrapped or the root boundary is the shell’s first child).
  • Each major surface (sidebar, main, dialog, modal, drawer).
  • Each form, list, or widget that handles its own entity.
  • Each list item that represents an entity.

Don’t add boundaries for layout-only wrappers — they add noise to context and aren’t useful.

For cases outside the DOM (server-side normalization, framework adapters), use runtime.createBoundary:

const boundary = runtime.createBoundary({
context: {
surface: 'workspace',
feature: 'notes',
entity: { type: 'note', id: '42' },
},
})
// later
boundary.destroy()

This is a runtime-level boundary that fires boundary.created / boundary.destroyed diagnostics but doesn’t attach DOM listeners.