Import a behavior, bind it to your data and change what the call site may change. The state machine stays the behavior's.
Most of an Orb program is composed, not written. A behavior from the standard library (or your own blocks) is imported with uses, then referenced one trait at a time or as a whole orbital. You bind it to your entity and set its knobs; its states and transitions stay exactly as the behavior defines them.
1uses Browse from "std/behaviors/std-browse"
2
3trait BookShelf = Browse.traits.BrowseItemBrowse -> Book {
4 config {
5 browseLook: feed
6 fields: [{ name: "name", label: "Title", variant: h4 }]
7 }
8}Alias.traits.Name -> Entity takes the trait and rebinds it to your entity. The body may only touch the call-site surface:
| Override | Effect |
|---|---|
config { key: value } | Set knobs the trait declares |
events { OLD: NEW } | Rename event keys everywhere in the machine |
fields { old: new } | Rename the entity fields the trait reads and writes |
listens { … } | Replace the trait's listens entirely |
emitsScope internal | Keep every emit inside the orbital |
There is no way to add a state, remove a transition or change a transition's effects at a call site. To react differently, add a sibling trait that listens to the behavior's events. That rule is what makes a composed program as checkable as a hand-written one.
Here two standard behaviors are bound to a local Book entity, with no state machine written by hand:
LibraryPage
on LibraryView · states: 1 · transitions: 0BookShelf
on Book · states: 3 · transitions: 9BookFacts
on LibraryView · states: 1 · transitions: 0MasterListView
on Book · states: 1 · transitions: 1books
on loan
Abelson and Sussman
Hoare
Knuth
The program above as state machines, one per trait: each box is a state, each arrow an event that moves it, and the icons on an arrow are the effects that event runs. A ◇ on an arrow is a guard; hover it to read the check. How to read AVL
1app std-orb-docex-library "1.0.0"
2"std-orb-docex-library — composition: two standard behaviors bound to a local entity and configured at the call site, with no state machine written by hand."
3orbital LibraryOrbital {
4 uses Browse from "std/behaviors/std-browse"
5 uses StatBand from "std/behaviors/std-stat-band"
6
7 entity Book [persistent: docex_books, local] {
8 id : string!
9 name : string!
10 author : string = ""
11 status : "active" | "pending" | "inactive" = active
12 instances [
13 { id: "B-1", name: "Structure and Interpretation", author: "Abelson and Sussman", status: active },
14 { id: "B-2", name: "Communicating Sequential Processes", author: "Hoare", status: pending },
15 { id: "B-3", name: "The Art of Computer Programming", author: "Knuth", status: active }
16 ]
17 }
18
19 entity LibraryView [runtime] {
20 id : string!
21 }
22
23 trait LibraryPage -> LibraryView [interaction, instance] {
24 initial: idle
25
26 state idle {
27 INIT -> idle
28 (render-ui main { type: stack, direction: vertical, gap: lg, children: [@trait.BookFacts, @trait.BookShelf] })
29 }
30 }
31
32 trait BookShelf = Browse.traits.BrowseItemBrowse -> Book {
33 config {
34 browseLook: feed
35 bodySearch: false
36 fields: [{ name: "name", label: "Title", variant: h4 }, { name: "author", label: "Author", variant: caption }, { name: "status", label: "Status", variant: badge, labels: { active: "On the shelf", pending: "On loan", inactive: "Lost" } }]
37 }
38 }
39
40 trait BookFacts = StatBand.traits.StatBandRender -> LibraryView {
41 config {
42 heading: "The shelf at a glance"
43 items: [{ value: "3", label: "books" }, { value: "1", label: "on loan" }]
44 }
45 }
46
47 page "/library" -> LibraryPage
48}1uses Board from "std/behaviors/std-board"
2
3orbital Hiring = Board.orbitals.BoardOrbital {
4 entity Candidate
5 fields { title: name }
6 extend { referredBy : string = "" }
7 pages { "/board": "/hiring" }
8}An orbital reference brings the entity, every trait and every page. Its body can rebind the entity, rename fields, add new fields with extend, remap routes, trim traits with omit or only, and set the orbital's declared knobs with config. Imported names are prefixed with your orbital's name, so two imports of the same behavior never collide.
Knobs live on a trait (trait.config), on an orbital (the knobs it publishes across its traits) and on the app. A trait publishes an orbital or app knob to itself by defaulting its own knob to @config.<knob>, and resolution walks from the call site to the orbital to the app. A declared knob no trait forwards is an error, and so is overriding a knob that was never declared.
1uses lazy Guide from "./guide"
2
3orbital GuideIntro = Guide.orbitals.GuidePage {
4 pages { "/guide": "/guide/intro" }
5}A uses lazy behavior is compiled to its own file and loaded only when one of its pages opens, so a large site does not ship every section up front. A lazy alias can only be used as a whole orbital with pages. These documentation pages are lazy behaviors.
Browse what is available in the standard library and its reference.
The language where the rule is the program.