@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.
- package/package.json +1 -1
- package/screens/ComponentsView.tsx +4 -3
- package/screens/TemplatesLibraryView.tsx +197 -82
- package/screens/TemplatesView.tsx +49 -8
- package/screens/states.ts +25 -7
- package/src/host.ts +1 -1
- package/vendor/sdk/lib/UnoverseComponent.tsx +49 -3
- package/vendor/sdk/lib/app.tsx +121 -31
- package/vendor/sdk/lib/connection.tsx +34 -33
- package/vendor/sdk/lib/core/actions.ts +2 -2
- package/vendor/sdk/lib/core/apps.ts +31 -18
- package/vendor/sdk/lib/core/conditions.ts +15 -3
- package/vendor/sdk/lib/core/connection.ts +17 -1
- package/vendor/sdk/lib/core/store.ts +104 -21
- package/vendor/sdk/lib/core/types.ts +8 -0
- package/vendor/sdk/lib/transition/MountArrangement.tsx +71 -0
- package/vendor/sdk/lib/transition/decide.ts +53 -0
- package/vendor/sdk/lib/transition/guestHold.ts +34 -0
- package/vendor/sdk/lib/transition/index.ts +9 -0
- package/vendor/sdk/lib/transition/viewTransition.ts +29 -0
- package/vendor/skills/unoverse-create/SKILL.md +11 -5
- package/vendor/skills/unoverse-create/references/app.md +35 -21
- package/vendor/skills/unoverse-create/references/component.md +55 -54
- package/vendor/skills/unoverse-create/references/node.md +3 -1
- package/vendor/skills/unoverse-create/references/template.md +133 -0
|
@@ -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 #
|
|
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).
|
|
22
|
-
#
|
|
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
|
|
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
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
75
|
-
The root is a `Switch on
|
|
76
|
-
`setValue { values: [{ key:
|
|
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
|
|
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
|
|
88
|
-
- **Each state owns its layout(s)
|
|
89
|
-
|
|
90
|
-
|
|
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
|
|
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`
|
|
113
|
-
|
|
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
|
-
-
|
|
121
|
-
`progressPct`): an array/object/URL
|
|
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 (`
|
|
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
|
|
147
|
-
|
|
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. **
|
|
151
|
-
|
|
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. **
|
|
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 `
|
|
170
|
-
slice (`setValue`): expand = `setValue {
|
|
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 `
|
|
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
|
|
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
|
|
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
|
|
315
|
-
|
|
316
|
-
|
|
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
|
|
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
|