Routes, and how the traits on a route share the screen.
A page is a route and the traits that run on it. When the route opens, each trait receives INIT once; when it closes, the traits are released.
page "/notes" -> NoteList, NoteForm
page "/notes/:id" as NoteDetail -> NoteView
page "/" -> Home [initial]:id is a route parameter; it arrives in the page's INIT payload as ?id.as Name gives the page a name other orbitals and agents can refer to.[initial] marks the landing page.Every page must be reachable: a link, a navigation item or a navigate effect must lead to it, or orb validate reports it.
Two traits that both render into main would stack two bodies on one page, which the validator rejects. Compose them instead: a page trait renders a layout and embeds the others with @trait.Name:
1trait LibraryPage -> LibraryView [interaction, instance] {
2 initial: idle
3 state idle {
4 INIT -> idle
5 (render-ui main { type: stack, direction: vertical, gap: lg, children: [@trait.BookFacts, @trait.BookShelf] })
6 }
7}
8
9page "/library" -> LibraryPageEach embedded trait keeps its own state machine and its own updates; the page trait only decides where they go.
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
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}render-ui draws into a named slot. main is the page body. modal, drawer and dock are overlays: whatever opens them must clear them ((render-ui modal null)) on every path out of the state that opened them, and the validator checks that. toast shows notices; floating mounts on every page.
The closed circuit ties events, states and screens together.
The language where the rule is the program.