@unoverse-platform/studio 0.1.25 → 0.1.27

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.
Files changed (45) hide show
  1. package/package.json +1 -1
  2. package/screens/AtomsView.tsx +40 -2
  3. package/screens/ComponentsView.tsx +112 -21
  4. package/screens/NativeAppHost.tsx +8 -4
  5. package/screens/SourceView.tsx +1 -1
  6. package/screens/TemplatesView.tsx +52 -22
  7. package/screens/states.ts +27 -4
  8. package/src/PublishDialog.tsx +1 -1
  9. package/vendor/sdk/appEngine.tsx +44 -3
  10. package/vendor/sdk/lib/UnoverseComponent.tsx +1 -1
  11. package/vendor/sdk/lib/core/actions.ts +20 -6
  12. package/vendor/sdk/lib/core/analytics.ts +89 -0
  13. package/vendor/sdk/lib/core/client.ts +1 -1
  14. package/vendor/sdk/lib/core/index.ts +30 -0
  15. package/vendor/sdk/lib/core/store.ts +65 -38
  16. package/vendor/sdk/lib/core/templates.ts +252 -0
  17. package/vendor/sdk/lib/core/types.ts +40 -7
  18. package/vendor/sdk/lib/index.ts +9 -0
  19. package/vendor/sdk/lib/orb/ThinkingOrb.tsx +127 -0
  20. package/vendor/sdk/lib/orb/engine/braid.ts +50 -0
  21. package/vendor/sdk/lib/orb/engine/core.ts +157 -0
  22. package/vendor/sdk/lib/orb/engine/lattice.ts +205 -0
  23. package/vendor/sdk/lib/orb/engine/morph.ts +138 -0
  24. package/vendor/sdk/lib/orb/engine/orbits.ts +80 -0
  25. package/vendor/sdk/lib/orb/engine/profiles.ts +162 -0
  26. package/vendor/sdk/lib/orb/engine/registry.ts +24 -0
  27. package/vendor/sdk/lib/orb/engine/ribbon.ts +93 -0
  28. package/vendor/sdk/lib/orb/engine/types.ts +14 -0
  29. package/vendor/sdk/lib/orb/engine/web.ts +98 -0
  30. package/vendor/sdk/lib/orb/index.ts +20 -0
  31. package/vendor/sdk/lib/orb/presets.ts +104 -0
  32. package/vendor/sdk/lib/orb/theme.ts +83 -0
  33. package/vendor/sdk/lib/orb/types.ts +73 -0
  34. package/vendor/sdk/lib/primitives.tsx +114 -5
  35. package/vendor/sdk/lib/render.tsx +25 -5
  36. package/vendor/sdk/lib/style.ts +6 -2
  37. package/vendor/sdk/lib/template.tsx +77 -218
  38. package/vendor/sdk/main.tsx +7 -0
  39. package/vendor/skills/unoverse-create/SKILL.md +18 -18
  40. package/vendor/skills/unoverse-create/references/agent-skill.md +12 -12
  41. package/vendor/skills/unoverse-create/references/block.md +5 -5
  42. package/vendor/skills/unoverse-create/references/component.md +150 -75
  43. package/vendor/skills/unoverse-create/references/node.md +1 -1
  44. package/vendor/skills/unoverse-create/references/template.md +142 -82
  45. package/vendor/skills/unoverse-create/references/workflow.md +15 -15
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: unoverse-create
3
- description: Create or edit Unoverse platform artifacts — UI components (including briefed/AI-filled components), templates, atoms and styles (rx/), agent skills and prompt blocks (prompts/), custom workflow nodes (nodes/), and workflows built live on the Canvas via the builder MCP. Use when the user wants to create, add, build, or modify a component, template, theme, agent skill, prompt block, custom node, or workflow in an Unoverse starter repo.
3
+ description: Create or edit Unoverse platform artifacts: UI components (including briefed/AI-filled components), templates, atoms and styles (rx/), agent skills and prompt blocks (prompts/), custom workflow nodes (nodes/), and workflows built live on the Canvas via the builder MCP. Use when the user wants to create, add, build, or modify a component, template, theme, agent skill, prompt block, custom node, or workflow in an Unoverse starter repo.
4
4
  ---
5
5
 
6
6
  # Creating Unoverse Artifacts
7
7
 
8
- You are helping a developer build in the **Unoverse developer carve-out** — the three
8
+ You are helping a developer build in the **Unoverse developer carve-out**, the three
9
9
  editable folders mounted into the running platform:
10
10
 
11
11
  | Folder | What lives there |
@@ -14,14 +14,14 @@ editable folders mounted into the running platform:
14
14
  | `apps/unoverse/prompts/` | **Behavior**: agent skills (`skills/`) + prompt blocks (`blocks/`) |
15
15
  | `apps/unoverse/nodes/` | **Logic**: custom workflow nodes, authored as YAML |
16
16
 
17
- ## Step 1 — Identify the artifact, read its playbook
17
+ ## Step 1: Identify the artifact, read its playbook
18
18
 
19
19
  Decide what the user is creating, then **read the matching reference file before writing
20
20
  anything**:
21
21
 
22
22
  | The user wants… | Read |
23
23
  |---|---|
24
- | A UI component or shared atom — incl. a page/card the AI fills with content (briefed) | `references/component.md` (§Briefs for AI-filled) |
24
+ | A UI component or shared atom, incl. a page/card the AI fills with content (briefed) | `references/component.md` (§Briefs for AI-filled) |
25
25
  | A template / app layout (chat surface, wizard shell) | `references/template.md` |
26
26
  | A style / theme / token change | `references/component.md` §Tokens |
27
27
  | An agent skill (behavior guide the AI follows) | `references/agent-skill.md` |
@@ -30,11 +30,11 @@ anything**:
30
30
  | A workflow (wire nodes into an agent/pipeline on the Canvas) | `references/workflow.md` |
31
31
 
32
32
  Workflows are the one artifact that is NOT files: they are built live on the running
33
- platform through the `canvas` MCP (registered by this repo's `.mcp.json`) —
34
- the playbook covers the bind/build/test contract.
33
+ platform through the `canvas` MCP (registered by this repo's `.mcp.json`).
34
+ The playbook covers the bind/build/test contract.
35
35
 
36
36
  If it's ambiguous (e.g. "add a card that shows weather"), it's usually a **component**
37
- (the UI) plus possibly a **node** (the data source) — confirm the scope with the user.
37
+ (the UI) plus possibly a **node** (the data source). Confirm the scope with the user.
38
38
 
39
39
  **New to design artifacts?** The guided learning journey at `docs/design/` (README →
40
40
  quick-start → … → troubleshooting) teaches components, templates, the state model
@@ -42,32 +42,32 @@ quick-start → … → troubleshooting) teaches components, templates, the stat
42
42
  `docs/design/CLAUDE.md` is the condensed agent rulebook. The playbooks here assume that
43
43
  material.
44
44
 
45
- ## Step 2 — Hard rules (apply to every artifact)
45
+ ## Step 2: Hard rules (apply to every artifact)
46
46
 
47
47
  1. **Only edit the three carve-out folders.** Never touch, vendor, or work around the
48
- SDK, engine, or server — they are not yours to change. If a task seems to require an
49
- SDK change, the answer is almost always "express it as data" — re-read the playbook.
48
+ SDK, engine, or server. They are not yours to change. If a task seems to require an
49
+ SDK change, the answer is almost always "express it as data". Re-read the playbook.
50
50
  2. **Docs are the source of truth.** The playbooks name the design journey and the deep
51
51
  references ([UNOVERSE_AUTHORING](https://github.com/unoverse-platform/docs/blob/main/unoverse/UNOVERSE_AUTHORING.md), [UNOVERSE_STATE_MODEL](https://github.com/unoverse-platform/docs/blob/main/unoverse/UNOVERSE_STATE_MODEL.md), [UNOVERSE_LAYERS](https://github.com/unoverse-platform/docs/blob/main/unoverse/UNOVERSE_LAYERS.md),
52
52
  [UNOVERSE_CONFORMANCE](https://github.com/unoverse-platform/docs/blob/main/unoverse/UNOVERSE_CONFORMANCE.md)). They live in the docs repo
53
53
  (https://github.com/unoverse-platform/docs), not in your repo. When the user's request goes beyond the playbook, read the named section rather
54
54
  than improvising conventions.
55
- 3. **UI is data.** No pixels, hex colors, or CSS anywhere — token names only. No logic
56
- in definitions — anything computed (totals, formatting, chosen colors) is computed in
55
+ 3. **UI is data.** No pixels, hex colors, or CSS anywhere: token names only. No logic
56
+ in definitions: anything computed (totals, formatting, chosen colors) is computed in
57
57
  the node and sent as a plain field.
58
58
  4. **Audit before you're done.** Every component/template must pass the conformance
59
59
  checklist ([UNOVERSE_AUTHORING](https://github.com/unoverse-platform/docs/blob/main/unoverse/UNOVERSE_AUTHORING.md) §9). Walk it item by item.
60
- 5. **Discovery meta is ranked, not read.** Any spatially-discoverable artifact — a
61
- component or template with a manifest, an agent skill, a node — is selected by
60
+ 5. **Discovery meta is ranked, not read.** Any spatially-discoverable artifact (a
61
+ component or template with a manifest, an agent skill, a node) is selected by
62
62
  embedding `title`/`name` + `whenToUse`/`description` + `category` against the user's
63
63
  own words (or the planner's task). The strict contract is
64
64
  `docs/nodes/node-discoverability.md`: outcome-first, utterance-shaped, opening
65
65
  words dominate, disqualify by property (never name a sibling). Each playbook carries
66
66
  the summary; read doc 14 before writing any meta.
67
67
 
68
- ## Step 3 — Deploy loop (after the artifact is written)
68
+ ## Step 3: Deploy loop (after the artifact is written)
69
69
 
70
- Components and templates have NO scaffold command — author them from the references
70
+ Components and templates have NO scaffold command. Author them from the references
71
71
  (mirror the closest existing artifact). Validation lives in Studio: its publish step
72
72
  lints every definition before anything leaves the machine (0 errors required, doc-cited
73
73
  messages; justify any warning), and the conformance checklist is
@@ -75,7 +75,7 @@ messages; justify any warning), and the conformance checklist is
75
75
 
76
76
  | Artifact | To see it live |
77
77
  |---|---|
78
- | Component / atom / template / style | Preview in Studio (`unoverse studio`), then publish from Studio — the publish lint gates it |
78
+ | Component / atom / template / style | Preview in Studio (`unoverse studio`), then publish from Studio: the publish lint gates it |
79
79
  | Existing component restyle/edit only | takes effect live (the SDK reads `rx/` directly) |
80
80
  | Agent skill / prompt block | publish from Studio, same gate |
81
81
  | Node | Studio's Nodes screen: Load sample → Run. A node is YAML; running it is the only proof (no build) |
@@ -85,4 +85,4 @@ on :4108 (`unoverse studio` from the project folder).
85
85
 
86
86
  > In the platform monorepo (not the starter), the node docs live at
87
87
  > `packages/docs/nodes/`, the design journey at `packages/docs/design/`, and the dev loop
88
- > is just `npm run dev` — component nodes synthesize from rx/ definitions at boot.
88
+ > is just `npm run dev`. Component nodes synthesize from rx/ definitions at boot.
@@ -1,4 +1,4 @@
1
- # Playbook — Agent Skills (prompts/skills)
1
+ # Playbook: Agent Skills (prompts/skills)
2
2
 
3
3
  An agent skill is a markdown behavior guide the platform's AI agents discover and follow
4
4
  at runtime (e.g. how to handle complaints, how to walk a user through a process). It is
@@ -8,7 +8,7 @@ at runtime (e.g. how to handle complaints, how to walk a user through a process)
8
8
 
9
9
  ```
10
10
  apps/unoverse/prompts/skills/<skill-name>/
11
- SKILL.md # required — frontmatter + instructions
11
+ SKILL.md # required: frontmatter + instructions
12
12
  ```
13
13
 
14
14
  The runtime scans this folder and serves skills to agents; agents match on the
@@ -19,9 +19,9 @@ The runtime scans this folder and serves skills to agents; agents match on the
19
19
  ```markdown
20
20
  ---
21
21
  name: skill-name # required: lowercase, numbers, hyphens; max 64 chars
22
- title: Skill Name # human display name — shows as the item's title in listings
23
- description: What this skill IS — ONE short line (≤ 120 chars), the listing subtitle
24
- whenToUse: The SELECTION text — outcome-first, in the user's vocabulary; ranked against intent for spatial discovery. See docs/nodes/node-discoverability.md — the node rules apply verbatim.
22
+ title: Skill Name # human display name, shown as the item's title in listings
23
+ description: What this skill IS: ONE short line (≤ 120 chars), the listing subtitle
24
+ whenToUse: The SELECTION text: outcome-first, in the user's vocabulary; ranked against intent for spatial discovery. See docs/nodes/node-discoverability.md. The node rules apply verbatim.
25
25
  version: 1.0.0 # optional, semver
26
26
  category: ai # optional: ai | integration | workflow | utility
27
27
  triggers: # optional, max 20 keywords/phrases (2–50 chars each)
@@ -36,24 +36,24 @@ nodeTypes: [] # optional: workflow node types the skill relies
36
36
  [The situations that should activate this behavior]
37
37
 
38
38
  ## Approach / Instructions
39
- [Clear, step-by-step guidance. Concrete do/don't phrasing beats abstractions —
40
- include example lines the agent can use verbatim where tone matters.]
39
+ [Clear, step-by-step guidance. Concrete do/don't phrasing beats abstractions.
40
+ Include example lines the agent can use verbatim where tone matters.]
41
41
 
42
42
  ## Examples
43
43
  [Concrete before/after or dialogue examples]
44
44
  ```
45
45
 
46
- Stick to the keys above. The markdown body must exist — a skill with frontmatter only
46
+ Stick to the keys above. The markdown body must exist: a skill with frontmatter only
47
47
  is invalid.
48
48
 
49
49
  ## Writing rules
50
50
 
51
51
  1. **`whenToUse` does the routing; `description` says what it is.** Never blend them
52
52
  into one field. `whenToUse` is embedded and ranked against user intent (spatial
53
- discovery / findIntent) — write it **outcome-first in the user's vocabulary**
54
- ("Handle an unhappy customer — a complaint, frustration, or a request to
53
+ discovery / findIntent). Write it **outcome-first in the user's vocabulary**
54
+ ("Handle an unhappy customer: a complaint, frustration, or a request to
55
55
  escalate…"), never mechanism-first. The full rules are
56
- `docs/nodes/node-discoverability.md` — they apply to skills verbatim.
56
+ `docs/nodes/node-discoverability.md`. They apply to skills verbatim.
57
57
  2. **Study an existing skill** in `prompts/skills/` and match its voice and structure.
58
58
  3. Keep instructions outcome-focused and scannable: short sections, bulleted do/don't
59
59
  lists, explicit escalation/stop conditions.
@@ -61,5 +61,5 @@ is invalid.
61
61
 
62
62
  ## Ship
63
63
 
64
- `docker compose restart unoverse` — the runtime rescans skills at boot. Verify the
64
+ `docker compose restart unoverse`. The runtime rescans skills at boot. Verify the
65
65
  skill appears via the platform's skill listing (`unoverse check` for overall health).
@@ -1,6 +1,6 @@
1
- # Playbook — Prompt Blocks (prompts/blocks)
1
+ # Playbook: Prompt Blocks (prompts/blocks)
2
2
 
3
- A prompt block is a **reusable prompt fragment** — a named piece of agent prompt text
3
+ A prompt block is a **reusable prompt fragment**: a named piece of agent prompt text
4
4
  (formatting rules, a reasoning pattern, a policy) that workflows compose into agent
5
5
  system prompts. Smaller than a skill: a skill is a discoverable behavior guide; a block
6
6
  is an ingredient.
@@ -23,7 +23,7 @@ description: What this block contributes to a prompt
23
23
  tags: [topic, topic2]
24
24
  ---
25
25
 
26
- [The prompt text itself — written as direct instructions to the agent,
26
+ [The prompt text itself, written as direct instructions to the agent,
27
27
  present tense, no meta-commentary about being a block.]
28
28
  ```
29
29
 
@@ -31,7 +31,7 @@ present tense, no meta-commentary about being a block.]
31
31
 
32
32
  1. **Study an existing block** (e.g. `blocks/formatting/markdown-guidelines.md`) and
33
33
  match its shape: tight sections, imperative bullets.
34
- 2. A block must stand alone — no references to other blocks, no assumptions about what
34
+ 2. A block must stand alone: no references to other blocks, no assumptions about what
35
35
  else is in the prompt.
36
36
  3. Keep it generic and reusable. Anything workflow-specific belongs in that workflow's
37
37
  own prompt, not in a shared block.
@@ -40,4 +40,4 @@ present tense, no meta-commentary about being a block.]
40
40
 
41
41
  ## Ship
42
42
 
43
- `docker compose restart unoverse` — the runtime rescans blocks at boot.
43
+ `docker compose restart unoverse`. The runtime rescans blocks at boot.
@@ -1,116 +1,186 @@
1
- # Playbook — Components & Atoms (rx data)
1
+ # Playbook: Components & Atoms (rx data)
2
2
 
3
3
  **Read first:** `docs/design/components.md` (the microapp anatomy) + `docs/design/state.md`
4
- (the reaction contract). Deep law: [UNOVERSE_AUTHORING](https://github.com/unoverse-platform/docs/blob/main/unoverse/UNOVERSE_AUTHORING.md) §3 (three homes),
5
- `UNOVERSE_STATE_MODEL.md` §5b. **Study the exemplars before writing**: `rx/components/journeyfinder`
6
- and cardfinder — mirror them exactly.
4
+ (the state model). Deep law: [UNOVERSE_AUTHORING](https://github.com/unoverse-platform/docs/blob/main/unoverse/UNOVERSE_AUTHORING.md) §3 (three homes),
5
+ `UNOVERSE_STATE_MODEL.md` §5 (the six rules), `UNOVERSE_LAYERS.md` (states own layouts).
6
+ **Study the exemplar before writing**: `rx/sab/components/product-card` is the canonical
7
+ State Model v2 component; mirror it exactly.
7
8
 
8
9
  ## The anatomy
9
10
 
10
11
  ```
11
- apps/unoverse/rx/components/<name>/ # DESIGN SYSTEM tier (generic, universal)
12
- apps/unoverse/rx/orgs/<org>/components/<name>/ # ORG tier (the client's own microapp, org-private)
13
- <name>.json # envelope: name/category/nodeSize/outputs/props/state/stateOrder/root
14
- manifest.json # RENDER CONTRACT: arrival defaultState (open name, default "inline")
15
- # + discovery meta (title / description ≤120 / whenToUse utterance-shaped).
16
- # Presence = spatially discoverable; envelope NEVER duplicates the meta.
17
- layouts/ # the FACES — filename = state name: inline.json, focused.json, (custom).json
18
- states/ # the component's PRIVATE steps (wizard questions) — listed in stateOrder
19
- components/ # component-local shared partials (earned: 2+ states share a shape)
12
+ apps/unoverse/rx/<project>/components/<name>/
13
+ <name>.yaml # ROOT: envelope (name/category/nodeSize/outputs/props) + the STATE TREE + root
14
+ manifest.yaml # RENDER CONTRACT + discovery meta (title / description ≤120 /
15
+ # whenToUse utterance-shaped). Presence = spatially discoverable;
16
+ # the envelope NEVER duplicates the meta.
17
+ layouts/ # the DRAWINGS: one file per state that owns an arrangement
18
+ components/ # component-local shared partials (earned: 2+ layouts share a shape)
20
19
  ```
21
20
 
22
- A **flat component** (simple card/chart) is just `<name>.json` + `root` — one face, no manifest,
23
- no folders. Structure is EARNED; start flat.
21
+ A **flat component** (simple card/chart) is just `<name>.yaml` + `root`: one drawing, no
22
+ manifest, no folders. Structure is EARNED; start flat.
24
23
 
25
- ## The rules (Studio's publish lint enforces all of these — 0 errors required)
24
+ ## The state model (v2): data + a state tree
25
+
26
+ A component is **data plus a state tree**; each state owns the layout(s) that draw it.
27
+ The root's `state:` block IS the design, readable top to bottom. The canonical shape
28
+ (`rx/sab/components/product-card/product-card.yaml`):
29
+
30
+ ```yaml
31
+ state:
32
+ view: # the PUBLIC axis
33
+ initial: products
34
+ states:
35
+ products: {} # no layout key: same-name default, {} is complete
36
+ detail: # the full page
37
+ layout: product # declared ONLY because the filename differs
38
+ on: step # a PRIVATE axis: the steps inside detail
39
+ initial: detail
40
+ states:
41
+ detail: { layout: product-detail }
42
+ apply: { layout: product-apply }
43
+ root:
44
+ type: Box
45
+ children:
46
+ - type: Switch
47
+ on: view # the root switches on `view`, ONE case active
48
+ cases:
49
+ products: { $include: layouts/products }
50
+ detail: { $include: layouts/product }
51
+ ```
52
+
53
+ The rules, in the order they bite:
54
+
55
+ - **The axis is `view`.** The component's public state lives in its slice under `view`.
56
+ The root is a `Switch on view`; buttons transition with
57
+ `setValue { values: [{ key: view, value: detail }] }`. That write is the ENTIRE
58
+ transition machinery: no guards, no events, no per-component machines.
59
+ - **Public states are the top level of the `view` axis** and they are the template's
60
+ WHOLE vocabulary: everything templates can react to, nothing more. Steps nest
61
+ privately (`on: <key>` + `initial` + `states`), invisible outside the component.
62
+ The placement rule: **if the template must rearrange to show it, make it public;
63
+ if the template wouldn't move, nest it privately.** Privacy is the default;
64
+ promotion is deliberate.
65
+ - **Each state owns its layout(s).** `layout:` is OPTIONAL: a state with no declaration
66
+ draws the layout of its own name, and `{}` is a complete state. Write `layout:` only
67
+ when the filename differs. A layout is never addressable on its own; only its owning
68
+ state selects it. Name states for what they MEAN (`detail`), never for their drawing.
69
+ - **Shell vs steps.** A state's own layout is its SHELL, always on while the state is
70
+ active. Its nested substates are the STEPS, the only choices inside it, addressed by
71
+ state names (layout filenames are plumbing).
72
+ - **Variants**: one meaning, several arrangements:
73
+ `focused: { layouts: { horizontal: focused-h, vertical: focused-v } }`. Variants bind
74
+ the SAME fields; anything that changes meaning or bound data is a second state
75
+ (lint-enforced). The component picks its variant by the space it finds itself in
76
+ (container queries); templates influence it only by controlling the container.
77
+ - **Arrival: nobody sends a state.** The host template places the spawned actor: it
78
+ honors the component's declared `initial` when it has a matching state; otherwise it
79
+ scans its own states in declared order and takes the first name on the component's
80
+ public menu; no overlap and the component wakes in its own initial chain and renders
81
+ inline. The server compiles the tree to the public menu + initials it serves
82
+ (`publicStates`, `initialView`). Post-spawn, the template NEVER writes a component's
83
+ state.
84
+ - **Unmatched view = inline. Always.** An ignored public state does not stay put, it
85
+ FALLS INLINE in the flow. A step that must never move the page belongs in the nest,
86
+ not on the public menu.
87
+ - Legacy: v1's `defaultState` field and layout-name-sync are superseded; the SDK reads
88
+ `defaultState` only as a migration alias for `view`. New authoring writes `view`.
89
+
90
+ ## The rules (Studio's publish lint enforces all of these; 0 errors required)
26
91
 
27
92
  1. **Three homes for everything it shows** (AUTHORING §3):
28
93
  - static content → **hardcoded literals** in the layout (`value`, literal `items: []` on
29
- Each, `src`) — never props, never `state`;
94
+ Each, `src`), never props, never `state`;
30
95
  - internal view-state → the **`state` block, SCALARS ONLY** (`step`, `phase`,
31
- `progressPct`) — an array/object/URL in `state` is slop (workflow data → props;
96
+ `progressPct`): an array/object/URL in `state` is slop (workflow data → props;
32
97
  static → hardcode);
33
98
  - workflow-fed data → **`props` with `input: true`** (the `default` is the preview mock).
34
99
  Usually empty.
35
- - **Prop names = the data contract — never invent them.** Source data hydrates props
100
+ - **Prop names = the data contract. Never invent them.** Source data hydrates props
36
101
  BY NAME (as-is, no mapping); a name the source doesn't carry silently renders the
37
102
  preview default (tell: title streams, image stays mock). Content-attached cards use
38
103
  the writer vocabulary: `title` `tagline` `description` `bodyCopy` `introParagraph`
39
104
  `primaryImage` `images` `link` `callToAction`. Guard:
40
105
  `server/src/runtime/content-card-hydration.test.ts`; law:
41
106
  `UNOVERSE_MCP_TEMPLATE_PROTOCOL.md` §Content-attached cards.
42
- 2. **Root = `Switch on defaultState`** — the component is a switch of **views**, ONE active.
43
- Cases `$include layouts/<state>` (filename = view name); `default` → the inline layout.
44
- Arrival view = **manifest.defaultState**. Keep an `inline` case for any component that
45
- lives in a conversation flow — it is its placeholder there. EXCEPTION — the
46
- **single-face (permanent) component**: exactly ONE named view + `default` pointing at
47
- the SAME face, NO inline. It arrives in its surface, cannot leave it (no ✕, no other
48
- view), and is enriched in place via data merges to one stable instance — e.g. a
49
- continuously-updated page living in a template's focus surface. Permanence is by
50
- construction, never by a pinning rule.
51
- **Lifetime (OPTIONAL manifest flag)** — `"lifetime": "conversation"` marks a durable,
107
+ 2. **Root = `Switch on view`**, cases per PUBLIC state, each `$include` of the state's
108
+ layout (the tree above). Keep an `inline` state for any component that lives in a
109
+ conversation flow: the flow is its placeholder there. One EXCEPTION is the
110
+ **single-state (permanent) component**: exactly ONE public state, NO inline. It
111
+ arrives in its surface, cannot leave it (no ✕, no other state), and is enriched in
112
+ place via data merges to one stable instance, e.g. a continuously-updated page
113
+ living in a template's focus state. Permanence is by construction, never by a
114
+ pinning rule.
115
+ **Lifetime (OPTIONAL manifest flag).** `"lifetime": "conversation"` marks a durable,
52
116
  conversation-scoped surface (a cart, an itinerary, a composed page): the platform keys
53
- its instance by the CONVERSATION (every re-call hydrates the SAME slice — merge, not
117
+ its instance by the CONVERSATION (every re-call hydrates the SAME slice: merge, not
54
118
  re-place) and the new-turn reset skips it, so it stays on screen until it is replaced,
55
119
  closes itself, or a NEW TEMPLATE loads (the template swap is the hard refresh boundary).
56
- Default `"turn"`: the instance returns to inline / retires on the next user turn.
57
- Values are a closed set — `turn | conversation` (lint-checked; docs/design/04).
58
- 3. **State is local; the VIEW is the interface** (STATE_MODEL §5b). Internal `state`
59
- (`step`, `phase`, …) is the component's own business — the template never sees it. The
60
- only thing that crosses to the template is the **active view** (`defaultState`). The
61
- component writes ONLY its own slice (`setValue`): expand = `setValue { defaultState:
62
- "course" }`; its expanded face carries its own ✕ back to `"inline"`. When a template has
63
- a surface for that view, the instance **lifts out of the flow into the surface** — the
64
- SDK renders each instance in exactly ONE placeholder, so you never hide a flow copy
65
- yourself (no `hideBelow`, no overlay hack). ❌ NEVER `setTemplateValue` to open a surface
66
- (the deprecated bridge — linted).
67
- 4. **`stateOrder`** names exactly the `states/*.json` files, in order.
68
- 5. **Atoms** (`rx/atoms/`, via `Ref`) are for shapes shared across components — authoring-time
120
+ Durable slices also opt out of `cancelBelow` retraction when a higher template state
121
+ takes over. Default `"turn"`: the instance returns to inline / retires on the next
122
+ user turn. Values are a closed set: `turn | conversation` (lint-checked; docs/design/04).
123
+ 3. **State is local; the `view` is the interface** (STATE_MODEL §5). Private axes
124
+ (`step`, `phase`, …) are the component's own business; the template never sees them.
125
+ The only thing that crosses is the public `view`. The component writes ONLY its own
126
+ slice (`setValue`): expand = `setValue { view: "detail" }`; its expanded layout
127
+ carries its own ✕ back down. When a hosting template has a state of that name, the
128
+ instance **lifts out of the flow into that state's slot**. The SDK renders each
129
+ instance in exactly ONE placeholder, so you never hide a flow copy yourself (no
130
+ `hideBelow`, no overlay hack). ❌ NEVER `setTemplateValue` to open a surface
131
+ (the deprecated bridge, linted). ❌ Never guard a layout against its own state:
132
+ the tree owns "which state is active"; a layout re-checking its discriminant is
133
+ the double-guard smell the model removes.
134
+ 4. **A new layout file is earned only by a different arrangement** (or a different set
135
+ of bound fields). Same shape, different values = ONE state, data-driven: seven wizard
136
+ questions sharing one shape are one `question` state whose data changes, never seven
137
+ files (LAYERS §4, §7).
138
+ 5. **Atoms** (`rx/atoms/`, via `Ref`) are for shapes shared across components, authoring-time
69
139
  only (the server expands them before serving; no Studio view). `Ref props` remaps FIELDS;
70
140
  **`Ref with` passes LITERALS**: `{ "type": "Ref", "ref": "button", "with": { "label":
71
- "Learn more", "icon": "arrowRight" }, "action": { … } }` — a bind whose field is a `with`
141
+ "Learn more", "icon": "arrowRight" }, "action": { … } }`. A bind whose field is a `with`
72
142
  key becomes a hardcoded attribute, a truthy `with` key drops a matching `visibleWhen`
73
143
  guard (unprovided ⇒ that piece stays hidden), `{{key}}` style bindings take the value.
74
144
  ⚠ Icon: literal = `icon: "phone"`; bound = `bind: { name: field }`.
75
- 6. **Briefs — components an AI fills** (docs/design/03 §Briefed components; deep law: [UNOVERSE_AUTHORING](https://github.com/unoverse-platform/docs/blob/main/unoverse/UNOVERSE_AUTHORING.md) §3b). A `brief`
76
- sits ON the node that renders what it describes, next to its `bind` — never in the
145
+ 6. **Briefs: components an AI fills** (docs/design/03 §Briefed components; deep law: [UNOVERSE_AUTHORING](https://github.com/unoverse-platform/docs/blob/main/unoverse/UNOVERSE_AUTHORING.md) §3b). A `brief`
146
+ sits ON the node that renders what it describes, next to its `bind`, never in the
77
147
  manifest, never a separate file:
78
148
  - bound element → `"brief": { "description": "…", "maxLength": 60 }`
79
149
  - Each → `"brief": { "description": "…", "minItems": 3, "maxItems": 5 }` (its template's
80
- binds define the item shape — brief those fields too)
81
- - face/partial ROOT (no bind) → a plain-string brief = composition context
150
+ binds define the item shape; brief those fields too)
151
+ - layout/partial ROOT (no bind) → a plain-string brief = composition context
82
152
  (ordering, refinement rules like "update, never discard").
83
153
  The platform compiles briefs into the component's MCP tool schema (keys pass through
84
- verbatim — the brief uses JSON Schema's OWN vocabulary) and injects the grounding law (fill ONLY from
85
- spatial search results — never invent). The server REFEREES (invalid/empty args →
154
+ verbatim; the brief uses JSON Schema's OWN vocabulary) and injects the grounding law (fill ONLY from
155
+ spatial search results, never invent). The server REFEREES (invalid/empty args →
86
156
  instructive result, no render) and MIRRORS (success result returns the rendered page
87
- for surgical refinement). **The schema IS the instruction channel** —
88
- write briefs, never side-channel prompts/skills for filling a component. Shape is
89
- closed + linted. Pairs with the single-face permanent pattern (rule 2) for
157
+ for surgical refinement). **The schema IS the instruction channel.**
158
+ Write briefs, never side-channel prompts/skills for filling a component. Shape is
159
+ closed + linted. Pairs with the single-state permanent pattern (rule 2) for
90
160
  continuously-enriched pages; exemplar: `rx/components/perfectday`.
91
161
  7. **Tokens only** (LAW 1) + closed style keys + real space-scale steps
92
- (`docs/design/06`) — an invented step is silently broken CSS.
93
- 8. **Lifecycle handlers — a component's own server-side code** (deep law:
162
+ (`docs/design/06`): an invented step is silently broken CSS.
163
+ 8. **Lifecycle handlers: a component's own server-side code** (deep law:
94
164
  [UNOVERSE_AUTHORING](https://github.com/unoverse-platform/docs/blob/main/unoverse/UNOVERSE_AUTHORING.md) §3c). When a component needs live data from an
95
165
  API or a computation (not from an AI = brief, not from the workflow = `input` props), it
96
166
  may bring a small server-side script that runs when the component renders. It's for DATA,
97
167
  not styling or logic-in-the-def.
98
168
  - **Opt in via the manifest** (authorizes execution; auditable): `"lifecycle":
99
- ["onStart"]` in `manifest.json`. A handler file with no opt-in is INERT.
100
- - **The code is a sibling file named for the lifecycle** — `onStart` → `onstart.js`,
101
- beside `<name>.json`. Its **default export** is the handler.
169
+ ["onStart"]` in the manifest. A handler file with no opt-in is INERT.
170
+ - **The code is a sibling file named for the lifecycle**: `onStart` → `onstart.js`,
171
+ beside the root file. Its **default export** is the handler.
102
172
  - **`onStart`** is the only lifecycle today; it runs SERVER-SIDE when the instance is
103
173
  created (the MCP render path), and whatever it returns is **merged into the instance
104
- by prop name** (unknown keys inert — same contract as workflow-fed props). Return only
174
+ by prop name** (unknown keys inert, same contract as workflow-fed props). Return only
105
175
  the fields you're filling; **never overwrite a curated prop** you didn't intend to.
106
176
  - **`ctx` is a bounded sandbox**: `ctx.props` (the instance's current data),
107
177
  `ctx.instanceId`, `ctx.env(name)` (server config). The **API key comes from
108
- `ctx.env`, NEVER hardcoded** — `rx/` is a shared design folder; a secret in it leaks.
178
+ `ctx.env`, NEVER hardcoded**: `rx/` is a shared design folder; a secret in it leaks.
109
179
  - Self-contained (the external call lives in the handler); errors never break the render.
110
- Exemplar: `rx/orgs/yasisland/components/restaurantcard/onstart.js` (Google Maps details).
180
+ Exemplar: `rx/yasisland/components/restaurantcard/onstart.js` (Google Maps details).
111
181
 
112
182
  ```js
113
- // onstart.js — manifest opts in with "lifecycle": ["onStart"]
183
+ // onstart.js: manifest opts in with "lifecycle": ["onStart"]
114
184
  export default async function onStart(ctx) {
115
185
  const key = ctx.env("SEARCHAPI_KEY"); // secret from server config
116
186
  const res = await fetch(`https://…&q=${encodeURIComponent(ctx.props.title)}&api_key=${key}`);
@@ -119,34 +189,39 @@ no folders. Structure is EARNED; start flat.
119
189
  }
120
190
  ```
121
191
 
122
- ## Discovery meta — write it to RANK (docs/nodes/node-discoverability.md)
192
+ ## Discovery meta: write it to RANK (docs/nodes/node-discoverability.md)
123
193
 
124
194
  A component with a manifest is an **MCP app**: spatial embeds `` `title. whenToUse||description
125
195
  [category]` `` and ranks it against the USER'S OWN WORDS (`findIntent`). The meta is the
126
- selection text, not documentation — weak meta makes a working component invisible. The
196
+ selection text, not documentation. Weak meta makes a working component invisible. The
127
197
  strict contract (doc 14, applies verbatim):
128
198
 
129
- - **`title`** = the thing itself, human display name — no mechanism, no org prefix.
130
- - **`description`** = what it IS, ONE line ≤120 chars — the listing subtitle; never
199
+ - **`title`** = the thing itself, human display name, no mechanism, no org prefix.
200
+ - **`description`** = what it IS, ONE line ≤120 chars, the listing subtitle; never
131
201
  "use when…" inside it.
132
202
  - **`whenToUse`** = the selection text, **utterance-shaped**: the words a user would
133
- actually say, outcome first — the OPENING words dominate the embedding. Never
203
+ actually say, outcome first; the OPENING words dominate the embedding. Never
134
204
  selector-shaped dev framing ("Pick when the user asks…"), never layout/mechanism first
135
- ("A single-face page that…" ranks near rendering concepts and loses every intent query).
205
+ ("A single-state page that…" ranks near rendering concepts and loses every intent query).
136
206
  - **Disqualify by PROPERTY, never by naming a sibling** ("Not for answering a single
137
- question, not for one item on its own") — naming rivals dates and tangles; the ranking
207
+ question, not for one item on its own"). Naming rivals dates and tangles; the ranking
138
208
  surfaces the alternative. A fallback/home surface never enumerates siblings' jobs
139
209
  (the generalist trap).
140
210
  - **`category`** = the domain of the JOB (Travel, Payments…), not the implementation.
141
211
  - Meta embeds **AS-IS** (no LLM rewrite); editing `whenToUse` re-embeds on the next train.
142
212
 
143
213
  Self-test before shipping: write the sentence the user would actually say to want this
144
- component — its key nouns/verbs must appear in `whenToUse`'s first sentence.
214
+ component. Its key nouns/verbs must appear in `whenToUse`'s first sentence.
145
215
 
146
216
  ## Workflow
147
217
 
148
- 1. Study the closest exemplar; copy its folder shape.
149
- 2. Write the envelope + manifest + layouts; put every shown thing in its ONE home.
150
- 3. Publish from Studio — its lint must report 0 errors (it cites the doc for every rule).
151
- 4. Restart unoverse → Studio: mock (prop defaults + state picker + Inline/Focused
152
- toggle), then live. Debug: stream log → state inspector → definition; never guess.
218
+ 1. Study `rx/sab/components/product-card`; copy its folder shape.
219
+ 2. Declare the state tree first: public states on top, steps nested, each state owning
220
+ its layout (same-name default, `layout:` only when the filename differs).
221
+ 3. Write the envelope + manifest + layouts; root = `Switch on view`; every shown thing
222
+ in its ONE home; transitions are `setValue` writes to `view` (or a private axis).
223
+ 4. Publish from Studio; its lint must report 0 errors (the rules are v2-aware and cite
224
+ the doc for every rule).
225
+ 5. Restart unoverse → Studio: mock (prop defaults + the state switcher reads the tree:
226
+ public states on top, steps beneath by state name), then live. Debug: stream log →
227
+ state inspector → definition; never guess.
@@ -161,7 +161,7 @@ the manifest describes the real service rather than something merely coherent.
161
161
  returns: "return response.items.map(helpers.row)"
162
162
  ```
163
163
 
164
- A helper sees ONLY its arguments and its sibling helpers — never `config`, `credentials`
164
+ A helper sees ONLY its arguments and its sibling helpers, never `config`, `credentials`
165
165
  or the caller's scope. Same sandbox, no extra authority.
166
166
 
167
167
  ## Do not