Skip to main content
Using Effect? See Blocks and references in the Effect guide.

State

Each block has its own state, stored by Reintersect and validated against the block’s state schema on every write. Your app keeps no database for blocks.
State is stored as JSON, so the schema must accept and return the same shape. A schema that transforms values, such as one that parses a string into a Date, is a compile error: Block state is stored as JSON and Standard Schema has no encode step: the state schema must not transform values. Store dates as ISO strings. On the Effect entry, Effect Schema has an encode step, so transforming schemas are allowed there. initialState is checked against the schema. A typo such as { status: "pending" } doesn’t compile. Stored state is at most 64 KB as JSON.

Actions

An action is a named function that runs when a member clicks a button, submits a form, picks a choice with onChange, searches a Select, or approves an agent’s tool call. Define actions on their own and attach them to blocks:
src/actions/launch.ts

Options

The name is unique within a block: 1 to 64 characters from a-z, 0-9, _, . and -.

Handler context

Results

Return nothing, or an object with any of these fields: After the handler returns, the SDK merges the state, renders the block with it, and sends the new state and tree back in the same response. Members see the result as soon as Reintersect stores it. If the handler throws, takes longer than 5 seconds, or returns a state that fails validation, Reintersect keeps the previous state and tree and shows an error inline to the member who acted. If the block changed since the member loaded it, the click fails as stale and the block shows its latest version.

Binding actions in the view

The render context has bound versions of the block’s actions. Pass them to components:
Because ship is destructive, its button needs confirm and can’t be primary. Abort is the primary button here, and Ship is outlined in the danger colour. In the confirm dialog, the confirm button repeats the label Ship, and Cancel gets focus first. The types enforce how each action is used:

What the browser sees

When your view renders, the SDK replaces each bound action with an opaque reference such as a0 and sends Reintersect a table of what each reference means: the action name and its bound args. Reintersect keeps that table on the server. The browser only ever sends back the reference, the block revision and any form input, so a member can’t change the args you bound. Reintersect checks that the member can act on the block (circle access, audience, install active, block not final) before it calls your app. Agents follow the same rule. An agent can only run an action that is rendered in the block right now, with the args you bound, so it can do exactly what a member could click. See Agents.

Sharing actions across blocks

An action declares only the state it needs. Attaching it to a block checks that slice against the block’s state:
The checks report each kind of mismatch in plain English: a state key the block lacks, a value the block doesn’t allow, a block state that is wider than the action accepts, and tallies of an input the block doesn’t declare.

Long-running work

Reintersect waits up to 5 seconds for an action. For work that takes longer, return quickly and finish in the background with waitUntil, then push the result:
Inside an action, calls about that same block authenticate with the action’s interaction token, which lasts 15 minutes. Calls about other blocks use the install’s token. On Reintersect hosting, waitUntil keeps the Worker alive until the work finishes; on Node it runs the work detached.

Pushing state from your systems

Update blocks from events, routes, cron jobs or your own backend with the client. The block definition is the first argument, so state is typed and the app must include the block:
insert, update, replace and updateWhere need blocks:write. Every block has a version that increases with each change. Pass expectedVersion to make an update conditional; a stale version fails with ReintersectAppBlockVersionConflictError and nothing changes. Updating a final block fails with ReintersectAppBlockFinalError. Your app can only change blocks its own install created. Each update is validated against the state schema, rendered, and synced to everyone viewing the block. See Client for authentication and errors, and the API reference for the HTTP equivalents.