Skip to main content

Building a Full Multi-Orbital Application

Source: tests/schemas/09-full-app.orb

This tutorial walks through the complete full-app-test schema — a real application with three connected orbitals. It combines everything from the previous tutorials: entities, state machines, render-ui, guards, and cross-orbital events.

Entity(Matter)Page(Space)Trait(Energy)idleactivehas_traitrenderstransition
Orbital Unit = Entity + Traits + Pages

Application Overview

TaskManager orbital          ProjectManager orbital       UserManager orbital
entity: Task entity: Project entity: User
traits: traits: traits:
TaskLifecycle ProjectStats UserBrowser
TaskCRUD listens: pages:
pages: TASK_COMPLETED /users
/tasks TASK_CREATED
emits:
TASK_COMPLETED
TASK_CREATED

The data flow:

  1. User creates or completes a task in TaskManager
  2. TaskManager emits TASK_CREATED or TASK_COMPLETED
  3. ProjectManager listens and updates its project counters

Orbital 1: TaskManager

Entity

{
"name": "Task",
"persistence": "persistent",
"collection": "tasks",
"fields": [
{ "name": "id", "type": "string", "required": true },
{ "name": "title", "type": "string", "required": true },
{ "name": "description", "type": "string" },
{ "name": "priority", "type": "enum", "values": ["low", "medium", "high"], "default": "medium" },
{ "name": "dueDate", "type": "date" },
{ "name": "assigneeId", "type": "string" },
{ "name": "projectId", "type": "string" }
]
}

Trait 1: TaskLifecycle

Manages the task's workflow status. Emits TASK_COMPLETED when a task is approved or completed directly.

States: todo → inProgress → review → done

Key transitions:

{ "from": "review", "event": "APPROVE", "to": "done",
"effects": [["emit", "TASK_COMPLETED", { "taskId": "@entity.id", "projectId": "@entity.projectId" }]]
},
{ "from": "inProgress", "event": "COMPLETE", "to": "done",
"effects": [["emit", "TASK_COMPLETED", { "taskId": "@entity.id", "projectId": "@entity.projectId" }]]
}

Trait 2: TaskCRUD

Manages the list UI. Emits TASK_CREATED when a new task is saved.

States: listing → creating | editing

Key transitions:

{ "from": "creating", "event": "SAVE", "to": "listing",
"effects": [
["persist", "update", "Task", "@entity"],
["emit", "TASK_CREATED", { "taskId": "@entity.id", "projectId": "@entity.projectId" }],
["notify", "success", "Task created"]
]
},
{ "from": "listing", "event": "VIEW", "to": "listing",
"effects": [["navigate", "/tasks/@payload.id"]]
}

Pages

"pages": [
{
"name": "TaskListPage",
"path": "/tasks",
"traits": [{ "ref": "TaskCRUD", "linkedEntity": "Task" }]
}
]

Orbital-level emits

"emits": ["TASK_COMPLETED", "TASK_CREATED"]

Orbital 2: ProjectManager

Entity

Tracks aggregate stats per project, updated reactively when tasks change:

{
"name": "Project",
"persistence": "persistent",
"collection": "projects",
"fields": [
{ "name": "id", "type": "string", "required": true },
{ "name": "name", "type": "string", "required": true },
{ "name": "description", "type": "string" },
{ "name": "taskCount", "type": "number", "default": 0 },
{ "name": "completedCount", "type": "number", "default": 0 }
]
}

Trait: ProjectStats

Listens to both TASK_COMPLETED and TASK_CREATED and increments counters:

{
"name": "ProjectStats",
"linkedEntity": "Project",
"category": "interaction",
"listens": [
{ "event": "TASK_COMPLETED", "scope": "external" },
{ "event": "TASK_CREATED", "scope": "external" }
],
"stateMachine": {
"states": [{ "name": "idle", "isInitial": true }],
"events": [
{ "key": "INIT", "name": "Initialize" },
{ "key": "TASK_COMPLETED", "name": "Task Completed" },
{ "key": "TASK_CREATED", "name": "Task Created" }
],
"transitions": [
{
"from": "idle", "event": "INIT", "to": "idle",
"effects": [
["fetch", "Project"],
["render-ui", "main", {
"type": "stats",
"items": [
{ "label": "Total Tasks", "value": "@entity.taskCount" },
{ "label": "Completed", "value": "@entity.completedCount" }
]
}]
]
},
{
"from": "idle", "event": "TASK_CREATED", "to": "idle",
"effects": [["increment", "@entity.taskCount", 1]]
},
{
"from": "idle", "event": "TASK_COMPLETED", "to": "idle",
"effects": [["increment", "@entity.completedCount", 1]]
}
]
}
}

The TASK_CREATED and TASK_COMPLETED events are received from TaskManager. They trigger self-loop transitions that fire increment effects — updating the project stats in real time.

Pages & orbital-level listens

"pages": [
{
"name": "ProjectListPage",
"path": "/projects",
"traits": [{ "ref": "ProjectStats", "linkedEntity": "Project" }]
}
],
"listens": [
{ "event": "TASK_COMPLETED", "from": "TaskManager" },
{ "event": "TASK_CREATED", "from": "TaskManager" }
]

Orbital 3: UserManager

The simplest orbital — a read-only browser for users with a navigate-to-detail action.

Entity

{
"name": "User",
"persistence": "persistent",
"collection": "users",
"fields": [
{ "name": "id", "type": "string", "required": true },
{ "name": "name", "type": "string", "required": true },
{ "name": "email", "type": "string", "required": true },
{ "name": "role", "type": "enum", "values": ["admin", "member", "guest"], "default": "member" }
]
}

Trait: UserBrowser

{
"name": "UserBrowser",
"linkedEntity": "User",
"category": "interaction",
"stateMachine": {
"states": [{ "name": "browsing", "isInitial": true }],
"events": [
{ "key": "INIT", "name": "Initialize" },
{ "key": "VIEW", "name": "View User", "payload": [
{ "name": "id", "type": "string", "required": true }
]}
],
"transitions": [
{
"from": "browsing", "event": "INIT", "to": "browsing",
"effects": [
["fetch", "User"],
["render-ui", "main", {
"type": "entity-table",
"entity": "User",
"columns": ["name", "email", "role"],
"itemActions": [{ "event": "VIEW", "label": "View" }]
}]
]
},
{
"from": "browsing", "event": "VIEW", "to": "browsing",
"effects": [["navigate", "/users/@payload.id"]]
}
]
}
}

Pages

"pages": [
{
"name": "UserListPage",
"path": "/users",
"traits": [{ "ref": "UserBrowser", "linkedEntity": "User" }]
}
]

Application Routes Summary

PathOrbitalTraitDescription
/tasksTaskManagerTaskCRUDBrowse, create, edit, delete tasks
/tasks/:idTaskManagerTaskCRUDNavigate to task detail (via navigate effect)
/projectsProjectManagerProjectStatsView project stats updated by task events
/usersUserManagerUserBrowserBrowse users, click to view detail

Patterns in This App

ConceptWhere it appears
Multiple traits per orbitalTaskManager has TaskLifecycle + TaskCRUD
Terminal statesdone in TaskLifecycle (isTerminal: true)
Cross-orbital emitTaskLifecycle emits TASK_COMPLETED, TaskCRUD emits TASK_CREATED
Cross-orbital listenProjectStats listens to both events and increments counters
Self-loop transitionsAll INIT transitions; ProjectStats event handlers
Payload in eventsVIEW carries id; TASK_COMPLETED carries taskId + projectId
navigate effectTaskCRUD's VIEW transition navigates to /tasks/@payload.id
increment effectProjectStats uses ["increment", "@entity.taskCount", 1]

Next Steps