asHook
asHook specification
What asHook Is
Section titled “What asHook Is”asHook is a special prototype form in Proto UI. Like a prototype, it has setup and can declare props, state, event, feedback, expose, rule, lifecycle, and other capabilities. Unlike a prototype, it has no independent interaction subject and only exists while attached to another prototype during that caller setup.
Intuitively, an asHook means “be this kind of interaction subject” or “inherit this behavioral protocol.” When a prototype calls asFocusable(), it is not composing a new child component. It is making the current prototype inherit focusable identity and related capabilities.
asHook is not an information channel and not prototype-level composition. It does not create an independent component instance, runtime instance, or template root. All effects it introduces belong to the caller prototype.
asHook And use-style Hooks
Section titled “asHook And use-style Hooks”asHook is inheritance-like: the caller declares that it is a kind of interaction subject or inherits a behavioral protocol. It usually has no parameters, and it applies once by default.
Compositional logic reuse is closer to a useXxx hook. If a helper is only an ordinary function wrapper, prototype authors can write the function themselves. A governed hook API becomes useful only when it needs trace, capture, setup guards, artifacts, disposers, or conflict diagnostics.
The naming and API boundary for defineHook / defineUseHook remains unresolved and is not part of the current core asHook contract.
Definition And Naming
Section titled “Definition And Naming”defineAsHook(spec) accepts at least the same baseline definition shape as definePrototype(spec):
import { defineAsHook } from '@proto.ui/core';
export const asPressed = defineAsHook({ name: 'as-pressed', setup(def) { const pressed = def.state.fromInteraction('pressed');
def.expose.state('pressed', pressed); },});The name field is the prototype spec name and follows prototype kebab-case naming rules. The exported caller binding should appear as asXxx, but runtime cannot reliably know which variable the returned caller was assigned to. Validating the asXxx caller binding therefore belongs to lint, CLI, generator, or TypeScript tooling.
asHook setup still follows the prototype setup contract: it may return only a render function or void. AsHookResult is not the direct setup return contract; it is the caller return value produced by the asHook runtime from captured setup effects, handles, artifacts, and an optional render return. An asHook render fragment does not commit automatically; the caller must compose it explicitly.
Invocation Period
Section titled “Invocation Period”An asHook caller can be invoked only during caller setup:
export const button = definePrototype({ name: 'button', setup(def) { asTrigger(); const focusable = asFocusable(); focusable.configure({ disabled: false }); const pressed = def.state.fromInteraction('pressed');
return () => ({ type: 'button', style: pressed.get() ? tw('translate-y-px') : undefined, children: 'Button', }); },});Render, lifecycle callbacks, event callbacks, props watchers, context watchers, and other runtime periods cannot add new asHook applications. Runtime behavior must be declared by the asHook during setup and later execute through existing callback, state, feedback, event, expose, or related contracts.
Default Repeat Policy
Section titled “Default Repeat Policy”The default asHook repeat policy is once. Within one caller prototype setup chain, the same asHook identity installs only once by default. Later calls with the same identity must be skipped and should not throw.
This prevents inheritance-like interaction identity from being installed repeatedly and producing duplicate side effects. Hooks that need parameter merging, latest-wins behavior, or multiple installations must define that through privileged asHook contracts or future explicit contracts. They are not default asHook semantics.
Effects And Ownership
Section titled “Effects And Ownership”Setup effects introduced by an asHook through def belong to the caller prototype:
asHook setup -> def.state / def.event / def.feedback / def.expose / def.rule / ... -> captured setup effects -> attached to caller prototype -> executed by each owning module contractThe asHook runtime only captures, attaches, and exposes results. Each module still owns its own dedupe, merge, conflict, diagnostic, and execution-order semantics. asHook does not create independent props/state/event/feedback/context/expose/rule runtime domains for itself.
An asHook may depend on any sub-API on the def handle. Using def.props, def.state, def.event, def.feedback, def.expose, def.context, def.rule, def.lifecycle, or another setup API is normal asHook design, not over-coupling. The same phase rules apply as in prototypes: def declarations happen during setup, and callback APIs declared by the asHook may later receive run in the same way prototype-authored callbacks do.
Current capture buckets are implementation details, not public semantics. Contracts describe which setup effects are captured, which artifacts/disposers/handles are exposed, and how those effects attach to the caller.
AsHookResult
Section titled “AsHookResult”AsHookResult is the return value of an asHook caller. It may expose:
| Result | Meaning |
|---|---|
| handles | Stable handles introduced by the asHook for caller consumption |
| artifacts | Analyzable products such as event keys, method descriptors, or style handles |
| render fragment | Renderable fragments that the caller must compose explicitly |
| disposers | Functions for undoing reversible setup effects during setup |
The disposer boundary matters: disposers let the caller undo setup effects introduced by the asHook during setup. They are not runtime cleanup, callback disposers, or lifecycle disposers.
Contributions that cannot or need not be removed may expose only handles or artifacts. For example, creating a state slot usually does not need “delete this state” semantics; not consuming it is enough to cancel its use.
State Projection
Section titled “State Projection”Controllable state created or introduced inside an asHook should be projected to the caller as a borrowed view, not an owned view.
The caller may read it, set defaults, set runtime values, and register watchers; however, the state source remains reusable logic introduced by the asHook rather than state directly declared and fully owned by the caller.
System-maintained facts introduced by privileged asHooks may instead be exposed as observed state-backed handles. They should still carry standard state metadata so rule, expose, and diagnostics can consume them as state-shaped values.
Interaction state follows the same boundary. If an asHook declares interaction state internally, that state must belong to the caller interaction subject rather than becoming asHook-private state.
The caller prototype must retain readonly trace metadata for applied asHooks. Trace supports diagnostics, anatomy requirement checks, tooling display, and implementation debugging.
Trace should include at least:
| Field | Purpose |
|---|---|
| identity | Identifies the applied asHook |
| order | Records application order |
| privileged | Marks whether the asHook is privileged |
| repeat policy | Exposes stable repeat-policy information |
Trace is not writable prototype-author state and is not a runtime behavior channel. Diagnostic systems may read it, but must not inject behavior through it.
Privileged asHooks
Section titled “Privileged asHooks”A privileged asHook is an official asHook form that exposes powerful, host-sensitive, or otherwise non-author-facing module-port capabilities to prototype authors through a restricted API.
Typical areas include focus, overlay, hit participation, interaction boundary, and APIs such as asTrigger that need to move event targets or coordinate multiple modules.
Privileged asHooks still share the normal asHook def syntax rights. They should express props, state, event, feedback, expose, context, rule, lifecycle, and other prototype-syntax behavior through def where possible. Non-public module ports, facades, or host capabilities should be reserved for the parts that ordinary prototype syntax cannot express.
Each privileged asHook is its own API and must define:
| Item | Meaning |
|---|---|
| parameters | Whether parameters are allowed, whether they affect identity, and how they are validated |
| return value | Which handles, artifacts, or restricted facades are exposed |
| repeat policy | Whether it remains once or uses custom merge rules |
| configuration merge | How repeated calls merge, override, or report conflicts |
| safety boundary | Which capabilities can be rolled back and which cannot for safety or host reasons |
Privileged asHooks must mark privileged: true in trace metadata.
Deferred Scope
Section titled “Deferred Scope”The following items are recorded as debt or governed design space, but are not stable v0 asHook core:
| Item | Status |
|---|---|
| caller binding name validation | spec.name and asXxx caller binding are separate; tooling should validate the binding later |
| ordinary configurable authored asHooks | Implementation support exists, but option identity, configuration merge, and conflict diagnostics remain governed |
defineHook / defineUseHook | Whether Proto UI needs a separate use-style hook API remains unresolved |
| capture bucket shape | Current runtime buckets are implementation details and are not public semantics |
| privileged asHook details | Defined by secondary scopes such as focus, overlay, and boundary |
Contract Preview
Section titled “Contract Preview”Relationship To Tests
Section titled “Relationship To Tests”asHook test entities are organized around core behavior, result capture, and privileged capabilities:
| Test entity | Main coverage |
|---|---|
T-AS-HOOK-0001 | subjectless attachment, setup-only caller, prototype-compatible definition, default once policy, readonly trace |
T-AS-HOOK-0002 | caller-owned captured effects, AsHookResult artifacts, setup-only disposers, borrowed state projection |
T-AS-HOOK-PRIVILEGED-0001 | privileged module-port capability, privileged trace, rollback boundary |
Current runtime contract tests cover core asHook behavior. Focus, boundary, overlay, and hit-participation tests carry the minimum privileged API boundary.
Relationship To Other Specifications
Section titled “Relationship To Other Specifications”Coredefines prototype setup, render returns, and setup/runtime phase boundaries; asHook reuses those foundations.Statedefines owned, borrowed, and observed views; state exposed by asHook projects to borrowed views.Event,Feedback,Expose,Context,Rule, and other modules own their effects’ dedupe, merge, conflicts, and execution order; asHook only attaches those effects to the caller.Anatomymay read asHook trace to check role requirements, but cannot inject behavior through trace.- Privileged asHooks may use module ports unavailable to ordinary prototype-author APIs; those capabilities require their own secondary contracts.