rx/. You declare the calls. The platform runs them.
When you need one
Two situations, and both are data the workflow did not send. The data belongs to one view. A card in a grid shows a title and a line of description. Its detail view needs the whole record: body copy, sections, call to action. Loading that for the grid would fetch twelve documents to draw twelve thumbnails. The data lives somewhere else. A live rating, a stock level, a venue lookup. It changes on its own schedule, so the component asks for it when it renders.The two moments
A view is a state, and the manifest key says
layouts. Both are true, and they are the
same word. A componentβs root is a Switch on defaultState, and each case includes the
face file named after it. State course renders layouts/course. So layouts: [ course ]
scopes the hook to the state named course, and the file it renders happens to carry that
name too.
The distinction matters more than it looks. A card and its detail view are one
instance: a course in a grid and the same course expanded differ only by which layout is
active. So if a detail viewβs content were fetched at onStart, opening a grid of twelve
cards would fetch twelve full documents to render twelve thumbnails.
onEnterView fetches only what somebody actually opened.
Declaring one
The manifest opts in. Nothing runs that the manifest did not name.manifest.yaml
phase: which moment,onStartoronEnterView.layouts: which views wake it.onEnterViewonly. Omit it and every layout fires.handler: what runs. Either a platform-ready handler, or your own, in which case<handler>.yamlsits beside the manifest.
fetchPlaceDetails says the
job; onEnterView already said the moment. It also means one component can want two
different jobs at the same moment without a naming collision.
Nothing to wire up. A component changes its own view by writing defaultState (the
close button on a focused card writes list, the card in the list writes focus). That
write is the event: the platform reports it, and a hook scoped to the entered layout
runs. No signal to author, no extra action to chain, nothing to remember. If your card
already changes its own view, it already emits the event.
Platform-ready handlers
Some handlers would be identical in every project, so the platform ships them and a component just points at one. No file, no code, no copy-paste.getDetail
Fill this card with its own full record.
A search result is a summary. getDetail fetches the long-form copy behind it: the body
of a product or service page, and the editorial fields around it.
It reads the instanceβs universal_id, fetches that item from the dictionary, and merges
these fields into the card:
Prop names match by name, exactly like every other hydration path. A field the record does
not carry is left alone, so the card keeps whatever the search row already gave it. Fields
outside this list stay behind: crawler bookkeeping such as timestamps, paths and structured
markup never reaches a card.
The card carries the id and the platform dereferences it, so authored copy never travels
through a model to reach a card.
- It can only fetch its own record. The id comes from the platformβs copy of the instance, so a card cannot ask for somebody elseβs content.
- It fails quietly. No id, an unknown id, a slow engine: the card keeps whatever the search row already gave it. A thinner detail view, never a broken one.
Beyond the built-ins: declare the calls
When no platform handler fits, a hook makes its own requests. It declares them the way a node does. Name your own handler, and the platform runs the calls you put in<handler>.yaml.
Here is a whole component that does it. RestaurantCard streams into the conversation
knowing only what the search gave it. Open it, and it fills its own live details from a
maps API. Three parts, no code:
manifest.yaml
manifest.yaml
handler: fetchPlaceDetails is not a platform handler, so the platform runs
fetchplacedetails.yaml from this folder. layouts: [ focus ] is why the card in the
list never calls anything: only entering the focus face wakes the hook.
allowedHosts is not optional. A hook that makes requests without it is a lint error,
not a warning. Deny by default, and the refusal names the component.
Credentials are named here and fetched server-side by type. The key never sits in the
folder. It never sits in the definition either.
fetchplacedetails.yaml
The file is named after the handler, lowercased.
fetchplacedetails.yaml
calls is the same list a nodeβs api/run.yaml holds, and it runs through
the same function. A hook inherits the host allowlist, the credentials, the retries, the
timeouts and the transports. returns projects the results into partial props. They
merge into the instance by name, like any other hook.
The real component takes a second call for photos, and guards it with
when: "return !!(...).place_id" so it only runs when the first call found a place. Calls
run in order, and each one can read the ones before it through calls.<name>.
The filename is the handler, lowercased.
handler: fetchPlaceDetails looks for
fetchplacedetails.yaml. macOS ignores the case difference and Linux does not, so a
camel-cased file works on a laptop and fetches nothing in a container.rx/ stays free of code.
A raw
<phase>.js file beside the manifest is the older form, and the one case still
named for the phase. It still runs, for handlers
that predate declared calls. It is not the pattern to copy. A script does its own fetch
to any host, with a key read from the environment, and the calls runtime settles both of
those questions once.What the platform guarantees
A hook runs on the server, so the platform is strict about it. Fire points are platform-owned. You add handlers, never moments. The phase set is closed, so nothing fires that the platform did not define. The hosts are locked. A hook reaches only the hosts its own manifest lists, and a component that declares none reaches nothing. A definition cannot execute code, but it could name any URL, so the allowlist is the boundary that makes a data hook safe. A call carrying a credential must also be HTTPS, so a key never travels in clear text. The credential stays in the platform. The manifest names it by type. The platform fetches it server-side from encrypted storage and attaches it to the call. It is never in the folder, never in the definition, and never on the client. The client asks for nothing. When a view opens, the client says only βthis instance entered this layoutβ. The platform answers every other question itself: which component that is, whose session owns it, what props it holds. Inputs are re-derived server-side and never taken from the message. An instance the session does not own runs nothing at all. A phase fires once per instance. Open a detail view, close it, open it again: one fetch. Body copy does not change mid-conversation. Enrichment streams in. The view opens immediately with what the card already has, and the fetched fields fill in when they arrive. No blocked render, no spinner.Lint catches the mismatches
The two halves, what the manifest declares and what actually runs, have to agree. Each of these is an error, not a silent no-op:Which one do I need?
Next: 07, Studio, see it, test it, on every channel.

