Create and link surfaces
Import Surface from @culvii/kit, create it in a .culvii.ts file, and assign it through a
step's ui property.
Create an output surface
import { Step, Surface, Workflow } from '@culvii/kit';
const responseSummary = new Surface({
id: 'http-response-summary',
name: 'HTTP response summary',
description: 'Status and body returned by the HTTP request.',
slot: 'output',
definition: {
schema: 'kit.ui/v1',
root: 'layout',
components: [
{
id: 'layout',
type: 'Stack',
children: ['status', 'body'],
},
{
id: 'status',
type: 'StatusPill',
props: {
label: {
path: '$output/Get Second Resource/main/0/0/statusMessage',
default: 'Unknown',
},
value: {
path: '$output/Get Second Resource/main/0/0/statusCode',
},
},
},
{
id: 'body',
type: 'JsonInspector',
props: {
value: {
path: '$output/Get Second Resource/main/0/0/body',
},
},
},
],
},
});
definition.root is the ID of the top-level component. Components reference their children by ID,
so they can be declared in any order.
Attach the local surface directly
Assign the Surface instance to the matching slot:
const getSecondResource = new Step({
id: 'get-second-resource',
name: 'Get Second Resource',
type: 'core.http',
params: {
method: 'GET',
url: 'https://api.example.com/resource',
},
ui: {
output: responseSummary,
},
});
The CLI discovers both resources when it evaluates the file. Surfaces are synced or deployed before workflows, so the workflow reference can be validated.
Reuse a surface
A single tenant-scoped surface can be assigned to multiple steps and workflows. Reference it by its
id without redefining it:
const anotherRequest = new Step({
id: 'another-request',
name: 'Another Request',
type: 'core.http',
params: {
method: 'GET',
url: 'https://api.example.com/another-resource',
},
ui: {
output: {
kind: 'custom',
surfaceKey: 'http-response-summary',
fallbackSurfaceId: 'default.json',
},
},
});
For a highly reusable surface, bind a JsonInspector to $output or bind to a shared upstream
step through $input. A path that hardcodes Get Second Resource only resolves when that step is
present in the surface's data model.
Pin a published version
Omit version to follow the active session draft in development and the current published version
after deployment:
ui: {
output: {
kind: 'custom',
surfaceKey: 'http-response-summary',
fallbackSurfaceId: 'default.json',
},
}
Set version to keep using one published version:
ui: {
output: {
kind: 'custom',
surfaceKey: 'http-response-summary',
version: 3,
fallbackSurfaceId: 'default.json',
},
}
fallbackSurfaceId identifies the first default candidate to try when the custom surface cannot be
rendered. Other compatible defaults and the generic default are also returned to the UI.
Select a built-in default
No Surface resource is needed when a built-in is sufficient:
const saveResult = new Step({
id: 'save-result',
name: 'Save Result',
type: 'core.set',
params: {
values: { saved: true },
},
ui: {
output: {
kind: 'default',
surfaceId: 'default.json',
},
},
});
The current generic defaults are:
| Slot | Surface ID |
|---|---|
output | default.json |
wait | default.resume-form |
Node packages can register additional defaults for a node type.
Create a wait surface
Wait surfaces can collect form values and resume or stop the execution:
const approvalSurface = new Surface({
id: 'expense-approval',
name: 'Expense approval',
slot: 'wait',
definition: {
schema: 'kit.ui/v1',
root: 'form',
components: [
{
id: 'form',
type: 'Form',
children: ['amount', 'comment', 'decision'],
},
{
id: 'amount',
type: 'KeyValue',
props: {
label: 'Requested amount',
value: {
path: '$input/Collect Request/main/0/0/amount',
},
},
},
{
id: 'comment',
type: 'TextArea',
props: {
label: 'Reviewer comment',
bind: '/$form/comment',
},
},
{
id: 'decision',
type: 'ApproveRejectBar',
actions: {
approve: {
type: 'node.resume',
payload: {
comment: { path: '$form/comment', default: '' },
},
},
reject: {
type: 'node.stop',
payload: {
reason: { path: '$form/comment', default: 'Rejected by reviewer' },
},
},
},
},
],
form: {
fields: [
{
name: 'comment',
label: 'Reviewer comment',
type: 'textarea',
},
],
},
},
});
const approval = new Step({
id: 'manager-approval',
name: 'Manager Approval',
type: 'core.set',
params: {
values: { reviewer: 'manager' },
},
ui: {
wait: approvalSurface,
},
});
The Kit adds pauseAfterExecution: true during serialization. Explicitly setting it to false
while assigning a wait surface is rejected.
Sync and deploy
Save the surface and its workflows in normal .culvii.ts entrypoints:
# Create session drafts and update them on save
culvii dev
# Publish changed surfaces, workflows, and agents together
culvii deploy --env sandbox --workspace my-workspace
Changing a published surface creates a new immutable surface version. The deploy plan includes the surface even when no workflow code changed, provided the surface is constructed by a discovered entrypoint.