Context
Context specification
What Context Is
Section titled “What Context Is”Context is Proto UI’s Component ↔ Component information channel. It lets component instances inside the same communication scope share a JSON object value identified by a ContextKey.
Context is the official path for inter-component communication. Props, Event, Feedback, and Expose each have their own direction and cannot be stretched into general component-to-component communication mechanisms. Anatomy + Expose can work as a fallback capability composition in specific cases, but it is not the primary official channel.
The model is closer to a scoped channel shared by component instances than a simple provider-to-consumer data flow. A provider establishes scope and initial value, but it does not monopolize update authority.
ContextKey
Section titled “ContextKey”A ContextKey identifies the context channel. Its identity is reference-based, not based on string names, debugName, or object structure.
import { createContextKey } from '@proto.ui/core';
type TabsContextValue = { value: string; activeValue: string;};
export const TABS_CONTEXT = createContextKey<TabsContextValue>('base-tabs');debugName is only for diagnostics, errors, and debugging display. Two keys with the same debugName but different references are different context channels.
Provide And Scope
Section titled “Provide And Scope”Providers establish context scope during setup:
def.context.provide(TABS_CONTEXT, { value: '', activeValue: '',});
def.context.subscribe(TABS_CONTEXT);def.context.provide(key, defaultValue) is setup-only. It registers the current component instance as the scope owner for that key and installs the initial context value.
The same component instance cannot provide the same key twice. Different component instances may provide the same key at the same time; consumers bind to the nearest provider in the current scope ancestry. Common hosts express ancestry as a component tree or logical tree. Non-tree-shaped hosts may still support Context if the adapter supplies equivalent scope-resolution logic.
provide does not return a provider-side update function. When the provider itself needs to update context, it still declares subscription intent during setup, then uses run.context.update or run.context.tryUpdate from a runtime callback:
def.lifecycle.onMounted((run) => { run.context.update(TABS_CONTEXT, (prev) => ({ ...prev, activeValue: prev.value }));});Subscribe And Try Subscribe
Section titled “Subscribe And Try Subscribe”Context subscription APIs declare during setup how a component participates in a context scope.
def.context.subscribe(TABS_CONTEXT, (run, next, prev) => { // required dependency: provider must exist});
def.context.trySubscribe(TABS_CONTEXT, (run, next, prev) => { // optional cooperation: provider may be absent});subscribe(key, cb?) means this context is required for the component to operate. If no provider resolves during setup, the component should fail immediately because its basic behavior cannot be established.
trySubscribe(key, cb?) means the component can cooperate with a context if present, but can also operate autonomously if it is absent. Missing optional context is not an error; the prototype author is responsible for the absent case.
Keeping these APIs separate is intentional. It forces the author to state whether the context is mandatory or an optional cooperation environment.
Runtime Read And Update
Section titled “Runtime Read And Update”Context runtime surfaces follow the subscription intent declared during setup:
| API | Required setup intent | When provider is missing |
|---|---|---|
run.context.read(key) | subscribe(key) | throw |
run.context.tryRead(key) | trySubscribe(key) | null |
run.context.update(key, next) | subscribe(key) or trySubscribe(key) | throw |
run.context.tryUpdate(key, next) | trySubscribe(key) | false |
Runtime updates explicitly include the ContextKey and either a next value or updater:
run.context.update(TABS_CONTEXT, (prev) => ({ ...prev, value: nextValue,}));The API does not depend on an implicit “current context.” One component may subscribe to several contexts, so explicit keys avoid ambiguous update targets.
Context Value
Section titled “Context Value”The top-level context value must be a JSON-compatible plain object.
def.context.provide(TABS_CONTEXT, { value: '', activeValue: '',});Top-level null is not allowed as an actual context value because tryRead uses null to represent optional context absence. Nested values must stay JSON-compatible: strings, numbers, booleans, null, arrays, and plain objects are allowed; functions, symbols, class instances, Dates, DOM objects, and circular references are not portable context values.
Rebinding And Cleanup
Section titled “Rebinding And Cleanup”Context resolution happens at read/update time against current scope ancestry. If the component tree or equivalent scope relation changes, a consumer may rebind to another provider or become disconnected.
v0 does not provide connected/disconnected notification APIs. Required read/update fails when disconnected; optional tryRead/tryUpdate represents current unavailability with null or false.
Context participation is bound to component instance lifecycle. When an instance is disposed, its provided context entries, subscriptions, and pending callbacks must be cleaned up. After cleanup, old callbacks must not respond to later context updates.
Contract Previews
Section titled “Contract Previews”Test Mapping
Section titled “Test Mapping”Context coverage is mapped through these test entities:
| Test entity | Main coverage |
|---|---|
T-CONTEXT-0001 | ContextKey identity, provider scope, scope resolution, value boundary |
T-CONTEXT-0002 | required/optional subscription, read/update, callback delivery, render read, rebind/cleanup |
These tests turn Context from “shared data somewhere in the component tree” into verifiable boundaries: who establishes scope, who may subscribe, when reads and updates are valid, how values remain portable, and how scope changes and lifecycle cleanup affect observable behavior.
Related Specs
Section titled “Related Specs”Coredefines information channels, setup/runtime, JSON value boundaries, andcreateContextKey.Propsis App Maker → Component configuration input; Context is Component ↔ Component scoped cooperation.Exposeis Component → App Maker; using anatomy to reach another component’s expose can be a fallback capability, but it is not the official inter-component information channel.Eventis User → Component; context can be read or updated inside event callbacks, but event does not handle component-to-component communication.Stateexpresses component-internal continuity; a context value may contain state snapshots, but Context itself is not a state slot.Lifecycledefines availability boundaries for provide, subscribe, runtime callbacks, read/update, and cleanup.