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:
- Provides context (surface, feature, entity, plus inherited values from ancestor boundaries) to events emitted within.
- Delegates metadata — clicks, composed
meta:event, form submit, and opt-in field changes are normalized through the runtime.
Package
Section titled “Package”@atheory-ai/kitsune-app
import '@atheory-ai/kitsune-app'import { KitBoundaryElement } from '@atheory-ai/kitsune-app'Attributes
Section titled “Attributes”| Attribute | Type | Notes |
|---|---|---|
surface | string | The current visible region. Stacks into surfaces when nested. Reflects. |
feature | string | The product area. Inherited from ancestors if omitted. Reflects. |
entity-type | string | Type of the current entity (e.g. note). Reflects. |
entity-id | string | ID of the current entity. Reflects. |
Properties
Section titled “Properties”| Property | Type | Notes |
|---|---|---|
context | KitContext | The computed context, including inherited values. Read-only getter. |
Context computation
Section titled “Context computation”When asked for context:
- Walks up to the nearest ancestor
<kit-boundary>or<kit-route>and reads its context. - Pushes its own
surfaceonto the parent’ssurfaces(if present). - Sets its own
feature(or inherits if omitted). - Sets its own
entityfromentity-type/entity-id(or inherits if omitted). - 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' }}Click delegation
Section titled “Click delegation”The boundary attaches a click listener on connectedCallback. On click:
- It walks
event.composedPath()looking for the first element withmeta-event,data-meta-event,meta-command, ordata-meta-command. - 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.
- Calls
If the click doesn’t match any metadata, nothing happens — other handlers continue to run normally.
Non-click delegation
Section titled “Non-click delegation”The boundary also listens for:
meta:eventcomposed custom events and emits thedetail.typeruntime event with boundary context.- Native
submiton forms with metadata, addingpayload.values. - Native
changeon controls withmeta-event-on-change/data-meta-event-on-change, addingnameandvaluewhen available.
Innermost-wins for metadata
Section titled “Innermost-wins for metadata”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.
When to add a boundary
Section titled “When to add a boundary”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.
Programmatic boundary
Section titled “Programmatic boundary”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' }, },})
// laterboundary.destroy()This is a runtime-level boundary that fires boundary.created / boundary.destroyed diagnostics but doesn’t attach DOM listeners.