@unoverse-platform/studio 0.1.85 → 0.1.87

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,10 +16,11 @@ folder shape. (In the platform monorepo the richest multi-state exemplar is on d
16
16
 
17
17
  ```
18
18
  design/<project>/components/<name>/
19
- <name>.yaml # ROOT: envelope (name/category/nodeSize/outputs/props) + the STATE TREE + root
19
+ <name>.yaml # the envelope: name/category/nodeSize/outputs/props + `states:` + `values:` (root only as a real shell)
20
20
  manifest.yaml # RENDER CONTRACT + discovery meta (title / description ≤120 /
21
- # whenToUse utterance-shaped). Presence = spatially discoverable;
22
- # the envelope NEVER duplicates the meta.
21
+ # whenToUse utterance-shaped). REQUIRED on every artifact EXCEPT
22
+ # a flat file nothing discovers (2026-08-30); the envelope NEVER
23
+ # duplicates the meta.
23
24
  layouts/ # the DRAWINGS: one file per state that owns an arrangement
24
25
  components/ # component-local shared partials (earned: 2+ layouts share a shape)
25
26
  ```
@@ -32,14 +33,14 @@ manifest, no folders. Structure is EARNED; start flat.
32
33
  A component is **data plus a state tree**; each state owns the layout(s) that draw it.
33
34
  The root's `state:` block IS the design, readable top to bottom.
34
35
 
35
- > **THE GATE — answer this before adding ANY state to `view`:**
36
+ > **THE GATE, answer this before adding ANY state to the tree:**
36
37
  > *"Would a hosting app rearrange the page to show this?"*
37
38
  > **No → it's a private step**: nest it under `on:` inside an existing public state.
38
39
  > **Yes → public.**
39
40
  > ⚠ "The user asked for N states" never means N public states. In-place swaps
40
41
  > (confirmation, success, empty, error, loading, a form's next step) are almost always
41
- > PRIVATE. Privacy is the default; promotion to `view` is a deliberate act with a
42
- > app consequence.
42
+ > PRIVATE. Privacy is the default; promotion to a PUBLIC state is a deliberate act
43
+ > with an app consequence.
43
44
 
44
45
  The canonical shape (a product-card component). Note where each state landed and why:
45
46
  `products` and `detail` are PUBLIC because an app genuinely rearranges for each (a
@@ -47,35 +48,30 @@ grid rail vs a full page); `detail`'s inner `detail`/`apply` are PRIVATE steps b
47
48
  applying swaps content in place — the page does not move:
48
49
 
49
50
  ```yaml
50
- state:
51
- view: # the PUBLIC axis
52
- initial: products
51
+ states: # TOP LEVEL (2026-08-29), like every kind; the word is
52
+ products: # `state` everywhere (setValue/Switch on/select field)
53
+ layout: layouts/products # REQUIRED path: nothing assumed
54
+ detail: # the full page
55
+ layout: layouts/product
56
+ on: step # a PRIVATE axis: the steps inside detail
53
57
  states:
54
- products: {} # no layout key: same-name default, {} is complete
55
- detail: # the full page
56
- layout: product # declared ONLY because the filename differs
57
- on: step # a PRIVATE axis: the steps inside detail
58
- initial: detail
59
- states:
60
- detail: { layout: product-detail }
61
- apply: { layout: product-apply }
62
- root:
63
- type: Box
64
- children:
65
- - type: Switch
66
- on: view # the root switches on `view`, ONE case active
67
- cases:
68
- products: { $include: layouts/products }
69
- detail: { $include: layouts/product }
58
+ detail: { layout: layouts/product-detail }
59
+ apply: { layout: layouts/product-apply }
60
+ values: # STARTING VALUES of keys the component writes (scalars)
61
+ progressPct: 10%
70
62
  ```
71
63
 
64
+ **No `root`**: the compiler synthesizes the `Switch on state` from the tree. Author
65
+ a root ONLY as a real shell (chrome beyond the plain Switch); a restated Switch is
66
+ linted as redundant.
67
+
72
68
  The rules, in the order they bite:
73
69
 
74
- - **The axis is `view`.** The component's public state lives in its slice under `view`.
75
- The root is a `Switch on view`; buttons transition with
76
- `setValue { values: [{ key: view, value: detail }] }`. That write is the ENTIRE
70
+ - **The word is `state`** (ruled 2026-08-29). The component's public state lives in
71
+ its slice under `state`. The root is a `Switch on state`; buttons transition with
72
+ `setValue { values: [{ key: state, value: detail }] }`. That write is the ENTIRE
77
73
  transition machinery: no guards, no events, no per-component machines.
78
- - **Public states are the top level of the `view` axis** and they are the app's
74
+ - **Public states are the top level of `states:`** and they are the app's
79
75
  WHOLE vocabulary: everything apps can react to, nothing more. Steps nest
80
76
  privately (`on: <key>` + `initial` + `states`), invisible outside the component.
81
77
  The placement rule is THE GATE above: **if the app must rearrange to show it,
@@ -84,11 +80,11 @@ The rules, in the order they bite:
84
80
  **The tell in Studio:** non-initial public states preview FULL-BLEED (no stage
85
81
  padding), precisely because they are app-liftable surfaces. If a state you
86
82
  meant as an in-place swap suddenly renders edge-to-edge, you promoted a private
87
- step to `view` — move it into the nest.
88
- - **Each state owns its layout(s).** `layout:` is OPTIONAL: a state with no declaration
89
- draws the layout of its own name, and `{}` is a complete state. Write `layout:` only
90
- when the filename differs. A layout is never addressable on its own; only its owning
91
- state selects it. Name states for what they MEAN (`detail`), never for their drawing. **Substate drawings are axis-prefixed**: a step's file carries its axis key
83
+ step to the public menu — move it into the nest.
84
+ - **Each state owns its layout(s), DECLARED as a path** (`layout: layouts/<file>`,
85
+ REQUIRED — 2026-08-29; no same-name default, `{}` is not complete; the one
86
+ exception is a FLAT component with no layouts/ folder). A layout is never
87
+ addressable on its own; only its owning state selects it. Name states for what they MEAN (`detail`), never for their drawing. **Substate drawings are axis-prefixed**: a step's file carries its axis key
92
88
  (`step-register`, `callState-idle`) and is declared via `layout:`; unprefixed
93
89
  files are arrangements (exemplars: `course-application`, `voice-chat`).
94
90
  - **Shell vs steps.** A state's own layout is its SHELL, always on while the state is
@@ -106,20 +102,20 @@ The rules, in the order they bite:
106
102
  inline. The server compiles the tree to the public menu + initials it serves
107
103
  (`publicStates`, `initialView`). Post-spawn, the app NEVER writes a component's
108
104
  state.
109
- - **Unmatched view = inline. Always.** An ignored public state does not stay put, it
105
+ - **Unmatched state = inline. Always.** An ignored public state does not stay put, it
110
106
  FALLS INLINE in the flow. A step that must never move the page belongs in the nest,
111
107
  not on the public menu.
112
- - Legacy: v1's `defaultState` field and layout-name-sync are superseded; the SDK reads
113
- `defaultState` only as a migration alias for `view`. New authoring writes `view`.
108
+ - Legacy: `view` (the v2 intermediate) and v1's `defaultState` are read-only
109
+ aliases; layout-name-sync is superseded. New authoring writes `state`.
114
110
 
115
111
  ## The rules (Studio's publish lint enforces all of these; 0 errors required)
116
112
 
117
113
  1. **Three homes for everything it shows** (AUTHORING §3):
118
114
  - static content → **hardcoded literals** in the layout (`value`, literal `items: []` on
119
115
  Each, `src`), never props, never `state`;
120
- - internal view-state → the **`state` block, SCALARS ONLY** (`step`, `phase`,
121
- `progressPct`): an array/object/URL in `state` is slop (workflow data → props;
122
- static → hardcode);
116
+ - starting values of keys it writes → the **`values:` block, SCALARS ONLY**
117
+ (`step`, `phase`, `progressPct`; `state:` scalars = legacy): an array/object/URL
118
+ there is slop (workflow data → props; static → hardcode);
123
119
  - workflow-fed data → **`props` with `input: true`** (the `default` is the preview mock).
124
120
  Usually empty.
125
121
  - **Projected-field preview rule:** any LOCKED/projected field a
@@ -127,7 +123,7 @@ The rules, in the order they bite:
127
123
  a prop with a preview default: Studio's bare component preview supplies ONLY prop
128
124
  defaults, so an undeclared projected read renders NOTHING there; live, the projected
129
125
  value wins and the default never applies. A Ref-embedded component's root Switch
130
- likewise needs a `default:` case (`view` is absent without a spawned slice).
126
+ likewise needs a `default:` case (`state` is absent without a spawned slice).
131
127
  - **Who owns a mood: the DRIVER decides.** Ask what WRITES the discriminant:
132
128
  (1) the component's own buttons (`setValue`) → component substates on a private axis,
133
129
  names from the design (`course-application`); (2) a service projecting a NAMED value
@@ -143,12 +139,15 @@ The rules, in the order they bite:
143
139
  - **Prop names = the data contract. Never invent them.** Source data hydrates props
144
140
  BY NAME (as-is, no mapping); a name the source doesn't carry silently renders the
145
141
  preview default (tell: title streams, image stays mock). Content-attached cards use
146
- the writer vocabulary: `title` `tagline` `description` `bodyCopy` `introParagraph`
147
- `primaryImage` `images` `link` `callToAction`. Law:
142
+ the writer vocabulary, listed layer by layer in
143
+ [design/interface-data](https://docs.unoverse.ai/design/interface-data.md)
144
+ (`title` `tagline` `description` `bodyCopy` `primaryImage` `images` …). Law:
148
145
  `UNOVERSE_MCP_APP_PROTOCOL.md` §Content-attached cards (platform spec; a
149
146
  guard test in the platform server enforces it).
150
- 2. **Root = `Switch on view`**, cases per PUBLIC state, each `$include` of the state's
151
- layout (the tree above). Keep an `inline` state for any component that lives in a
147
+ 2. **NO root: the compiler synthesizes the `Switch on state`** from `states:` (the tree
148
+ above), so a hand-written root that merely restates the tree is lint-flagged as
149
+ redundant. Author a `root:` ONLY for real always-on chrome beyond that Switch.
150
+ Keep an `inline` state for any component that lives in a
152
151
  conversation flow: the flow is its placeholder there. One EXCEPTION is the
153
152
  **single-state (permanent) component**: exactly ONE public state, NO inline. It
154
153
  arrives in its surface, cannot leave it (no ✕, no other state), and is enriched in
@@ -164,14 +163,14 @@ The rules, in the order they bite:
164
163
  takes over. Default `"turn"`: the instance returns to inline / retires on the next
165
164
  user turn. Values are a closed set: `turn | conversation` (lint-checked;
166
165
  [design/components](https://docs.unoverse.ai/design/components.md)).
167
- 3. **State is local; the `view` is the interface** (STATE_MODEL §5). Private axes
166
+ 3. **Private state is local; the public `state` is the interface** (STATE_MODEL §5). Private axes
168
167
  (`step`, `phase`, …) are the component's own business; the app never sees them.
169
- The only thing that crosses is the public `view`. The component writes ONLY its own
170
- slice (`setValue`): expand = `setValue { view: "detail" }`; its expanded layout
168
+ The only thing that crosses is the public `state`. The component writes ONLY its own
169
+ slice (`setValue`): expand = `setValue { state: "page" }`; its expanded layout
171
170
  carries its own ✕ back down. When a hosting app has a state of that name, the
172
171
  instance **lifts out of the flow into that state's slot**. The SDK renders each
173
172
  instance in exactly ONE placeholder, so you never hide a flow copy yourself (no
174
- `hideBelow`, no overlay hack). ❌ NEVER `setTemplateValue` to open a surface
173
+ `hideBelow`, no overlay hack). ❌ NEVER `setAppValue` to open a surface
175
174
  (the deprecated bridge, linted). ❌ Never guard a layout against its own state:
176
175
  the tree owns "which state is active"; a layout re-checking its discriminant is
177
176
  the double-guard smell the model removes.
@@ -182,7 +181,7 @@ The rules, in the order they bite:
182
181
  5. **Atoms** (the base design system's shared shapes, used via `Ref`; browse them at
183
182
  [marketplace/definitions/atoms](https://github.com/unoverse-platform/marketplace/tree/main/definitions/atoms), or on disk at `design/marketplace/atoms/` in the
184
183
  platform monorepo) are for shapes shared across components, authoring-time
185
- only (the server expands them before serving; no Studio view). `Ref props` remaps FIELDS;
184
+ only (the server expands them before serving; no Studio entry). `Ref props` remaps FIELDS;
186
185
  **`Ref with` passes LITERALS**: `{ "type": "Ref", "ref": "button", "with": { "label":
187
186
  "Learn more", "icon": "arrowRight" }, "action": { … } }`. A bind whose field is a `with`
188
187
  key becomes a hardcoded attribute, a truthy `with` key drops a matching `visibleWhen`
@@ -225,7 +224,7 @@ The rules, in the order they bite:
225
224
  `onEnterView` only. Omit it and every state fires.
226
225
  - **Name the handler for what it DOES, never for the phase** (`fetchPlaceDetails`, not
227
226
  `onEnterView`): the phase already said the moment.
228
- - **Nothing to wire up.** A component changing its own view writes `view`, and that
227
+ - **Nothing to wire up.** A component changing its own face writes `state`, and that
229
228
  write IS the event the hook reacts to. No signal to author, no action to chain.
230
229
  - **Prefer a platform-ready handler.** `getDetail` (zero code) reads the instance's
231
230
  `universal_id` and merges the stored record's fields in by name, so authored copy
@@ -311,9 +310,11 @@ component. Its key nouns/verbs must appear in `whenToUse`'s first sentence.
311
310
  1. Study the closest published exemplar ([marketplace/definitions/components](https://github.com/unoverse-platform/marketplace/tree/main/definitions/components);
312
311
  monorepo: `design/sab/components/product-card`); copy its folder shape.
313
312
  2. Declare the state tree first: public states on top, steps nested, each state owning
314
- its layout (same-name default, `layout:` only when the filename differs).
315
- 3. Write the envelope + manifest + layouts; root = `Switch on view`; every shown thing
316
- in its ONE home; transitions are `setValue` writes to `view` (or a private axis).
313
+ its layout as a root-relative path (`layout: layouts/<file>`, required; a FLAT
314
+ component with no layouts/ folder declares face-only states with no layout).
315
+ 3. Write the envelope + manifest + layouts; the root is synthesized from `states:`; every
316
+ shown thing in its ONE home; transitions are `setValue` writes to `state` (or a private
317
+ axis).
317
318
  4. Publish from Studio; its lint must report 0 errors (the rules are v2-aware and cite
318
319
  the doc for every rule).
319
320
  5. Restart unoverse → Studio: mock (prop defaults + the state switcher reads the tree:
@@ -5,7 +5,9 @@
5
5
  1. [nodes/CLAUDE.md](https://docs.unoverse.ai/nodes/CLAUDE.md): the authoritative agent
6
6
  guide. The folder, the eight non-negotiable rules, every file with an example, and
7
7
  the lint-error-to-fix table.
8
- 2. [nodes/manifest-nodes](https://docs.unoverse.ai/nodes/manifest-nodes.md): the format in full, including the run context.
8
+ 2. [nodes/manifest-nodes](https://docs.unoverse.ai/nodes/manifest-nodes.md): the format in full.
9
+ [nodes/expressions](https://docs.unoverse.ai/nodes/expressions.md) is the template and
10
+ expression grammar, the sandbox, and every root the run context carries.
9
11
  3. [nodes/node-discoverability](https://docs.unoverse.ai/nodes/node-discoverability.md): **read before writing `whenToUse`**.
10
12
  4. As needed: [credentials](https://docs.unoverse.ai/nodes/credentials.md),
11
13
  [who-can-run-it](https://docs.unoverse.ai/nodes/who-can-run-it.md),
@@ -0,0 +1,133 @@
1
+ # Playbook: Templates
2
+
3
+ **Read first:** [design/templates](https://docs.unoverse.ai/design/templates.md)
4
+ (the three words, the director, placing one in an app). Deep law:
5
+ [UNOVERSE_TEMPLATE_MODEL](https://docs.unoverse.ai/unoverse/UNOVERSE_TEMPLATE_MODEL.md)
6
+ — §9b is the grammar (RULED 2026-08-29), §5b the fill lanes, §6c the director's
7
+ twelve rules, §12 how a template is placed. Everything in the component playbook
8
+ (closed vocabulary, tokens only, no logic in definitions) applies here too.
9
+ **Study an exemplar before writing**: `design/marketplace/templates/grid-page`
10
+ (two states, the free browse↔detail dance) and
11
+ `design/marketplace/templates/email-digest` (one flat state, all three words,
12
+ template-local components).
13
+
14
+ ## What a template IS
15
+
16
+ | Kind | Authors say | Decides what appears |
17
+ |---|---|---|
18
+ | **component** | `type: component` | itself — one interface with its own states |
19
+ | **template** | `type: template` | an ARRANGEMENT whose sections deliveries fill |
20
+ | **app** | `type: app` (folder `apps/`) | the reader — the surface a template is placed on |
21
+
22
+ ## The file tree (one folder grammar)
23
+
24
+ ```
25
+ design/<org|marketplace>/templates/<name>/
26
+ <name>.yaml # the ENVELOPE: type + name + states (each → its layout path)
27
+ manifest.yaml # discovery meta ONLY: description, whenToUse, category, version
28
+ layouts/<state>.yaml # one arrangement per state — the three-word sections
29
+ components/ # the template's OWN parts (§9b.7): flat files, no
30
+ # manifests, unprefixed (the folder is the namespace)
31
+ ```
32
+
33
+ ## The envelope: states, like every kind
34
+
35
+ ```yaml
36
+ unoverse: "1.0"
37
+ type: template
38
+ name: grid-page
39
+ states:
40
+ grid: # FIRST = the base
41
+ layout: layouts/grid
42
+ page: # the rest = the reaction ladder
43
+ layout: layouts/page
44
+ ```
45
+
46
+ Use the standard names **`grid`** (compact collection) and **`page`** (full detail):
47
+ every card in every org declares them, so any card matches any template with zero
48
+ mapping. Browse and detail are ONE template — a card taps itself into `page`, the
49
+ name-matched state draws, its ✕ writes it back. RETIRED spellings (legacy-read, never
50
+ author): `parts`, `shapes`, `slots`, `accepts`, `prompt: {component}`, root `director:`.
51
+
52
+ ## The layout: three words, each says HOW a section fills
53
+
54
+ ```yaml
55
+ type: Box
56
+ children:
57
+ - static: components/masthead # placed as designed — nothing writes into it
58
+ - copywriter: components/subject # written into through its briefs (must carry briefs)
59
+ - copywriter: components/body
60
+ - director: # NOT linked: decided from what streams in
61
+ state: grid # arrivals SWITCHED into this state and HELD (flat email);
62
+ limit: 3 # omit to let them keep their own (grid↔page dance)
63
+ rules: >- # ONE home: this section. Marketplace = universal wording;
64
+ The items most relevant to the reader, strongest matches first.
65
+ preview: [ card, card, card ] # Studio's stand-ins until a real run
66
+ - copywriter: components/signoff
67
+ - static: components/footer
68
+ ```
69
+
70
+ - **Link what you know; leave open what the delivery decides.** Never name a
71
+ component in the director section — the delivery decides what arrives (any org's
72
+ cards), and a universal template may carry no client words.
73
+ - **ONE `director:` per template.** Two directed regions = two templates, composed
74
+ by the app (the one-mind boundary, law 9.4).
75
+ - **Never author `ComponentSlot`/`select`/`where` in a template** — the three words
76
+ compile to the wire primitives at serve time; the SDK stays dumb.
77
+ - Linked parts preview THEMSELVES (their mock content lives in the component, once);
78
+ `preview:` exists only on the director section.
79
+ - `limit` is the hard cap, machine-enforced; `rules` are soft, brief-style English.
80
+ - **One home for the director's brain: its section.** Marketplace templates ship
81
+ UNIVERSAL rules (the reader's relevance — never client words); an org wanting
82
+ bespoke judgment ships its own template. No placement grant.
83
+
84
+ ## Template-local components
85
+
86
+ Parts that exist only for this template live in its `components/` folder as FLAT
87
+ FILES (`components/masthead.yaml`), unprefixed, with NO manifest — a manifest is
88
+ discovery, and a local part is never discovered — and NO state tree: the face IS
89
+ the filename (`body.yaml` → face `body`), so a one-face part is just props + root. Each is a REAL component (own face
90
+ named for itself; briefed ones compile into the copywriter's MCP tool schema).
91
+ Addressed `<template>/<part>` (`email-digest/masthead`) when a global ref is needed.
92
+ Genuinely universal pieces (`card`) stay in the marketplace.
93
+
94
+ ## `manifest.yaml`
95
+
96
+ ```yaml
97
+ description: "What it is, for a human."
98
+ whenToUse: "Ranked against user intent — outcome-first, in the reader's words."
99
+ category: Communication
100
+ version: 1.0.0
101
+ ```
102
+
103
+ Nothing else. `whenToUse` is embedded and ranked, not read — follow
104
+ [node-discoverability](https://docs.unoverse.ai/nodes/node-discoverability.md).
105
+ Quote any string containing `: ` (a bare colon is a YAML parse error, and per-entry
106
+ isolation makes the template silently vanish from the catalogue).
107
+
108
+ ## Placement: how a template gets used (§12)
109
+
110
+ The template does not name its app; the app places it. In the app state's layout:
111
+
112
+ ```yaml
113
+ - type: Template
114
+ name: courses # the app state this place belongs to
115
+ template: grid-page # WHAT it holds
116
+ appWidth: flex # a place declares its width ONCE
117
+ ```
118
+
119
+ The placement JUST PLACES — the director's rules live in the template (one home).
120
+ An org wanting bespoke judgment ships its own template. (The placement `director:`
121
+ grant is RETIRED; the live BPP mount's grant migrates.)
122
+
123
+ Empty collapses: a place holding nothing draws nothing, frame included.
124
+
125
+ ## Before you're done
126
+
127
+ - [ ] `type: template`, kebab-case name matching the folder; `states:` with explicit layout paths
128
+ - [ ] Every layout section is one of the three words; exactly ONE `director:` in the template
129
+ - [ ] No client words, no component names in the director section, no retired spellings
130
+ - [ ] Copywriter sections link components that actually carry briefs
131
+ - [ ] Template-only parts live in `components/`, unprefixed
132
+ - [ ] `manifest.yaml`: description, whenToUse, category, version — nothing else
133
+ - [ ] Tokens only; previewed in Studio's **Templates** screen at multiple widths