@unoverse-platform/studio 0.1.45 → 0.1.47
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/bin/studio.mjs +11 -9
- package/local/authoring.ts +4 -3
- package/local/catalog.ts +3 -2
- package/local/design-home.d.mts +2 -0
- package/local/design-home.mjs +31 -0
- package/local/mcp.ts +5 -1
- package/local/plugin.ts +29 -28
- package/local/scaffold.mjs +11 -7
- package/local/source.ts +4 -3
- package/package.json +1 -1
- package/screens/DesignSystemView.tsx +1 -1
- package/screens/TemplatesView.tsx +3 -3
- package/src/App.tsx +1 -1
- package/src/NewProject.tsx +2 -2
- package/src/PublishDialog.tsx +1 -1
- package/vendor/sdk/appEngine.tsx +1 -1
- package/vendor/sdk/lib/core/client.ts +1 -1
- package/vendor/sdk/lib/core/types.ts +9 -9
- package/vendor/sdk/lib/isolate.tsx +1 -1
- package/vendor/sdk/lib/primitives.tsx +7 -7
- package/vendor/sdk/lib/render.tsx +2 -2
- package/vendor/sdk/lib/style.ts +3 -3
- package/vendor/sdk/lib/template.tsx +2 -2
- package/vendor/sdk/lib/theme.ts +2 -2
- package/vendor/skills/unoverse-create/SKILL.md +62 -28
- package/vendor/skills/unoverse-create/references/agent-skill.md +8 -5
- package/vendor/skills/unoverse-create/references/block.md +3 -2
- package/vendor/skills/unoverse-create/references/brand.md +17 -16
- package/vendor/skills/unoverse-create/references/component.md +36 -25
- package/vendor/skills/unoverse-create/references/node.md +17 -11
- package/vendor/skills/unoverse-create/references/template.md +14 -9
- package/vendor/skills/unoverse-prompt-component/SKILL.md +24 -19
|
@@ -247,7 +247,7 @@ function useTextSelection(enabled: boolean) {
|
|
|
247
247
|
* (childList change) OR streaming text (a child resizing) — but only while the user is
|
|
248
248
|
* already near the bottom, so scrolling up to read history is never yanked back down.
|
|
249
249
|
* NOT chat-specific: any `overflow:auto` Box can opt in. The SDK owns behaviour, never
|
|
250
|
-
* styles — the scroll container itself is defined in `
|
|
250
|
+
* styles — the scroll container itself is defined in `design/` (the conversation state box).
|
|
251
251
|
*/
|
|
252
252
|
function useAutoScrollToEnd(enabled: boolean) {
|
|
253
253
|
const ref = useRef<HTMLDivElement | null>(null);
|
|
@@ -281,11 +281,11 @@ function useAutoScrollToEnd(enabled: boolean) {
|
|
|
281
281
|
/**
|
|
282
282
|
* Generic "sticky bar that reveals only once you've scrolled past the element before it"
|
|
283
283
|
* (opt-in via `node.revealOnStick`). Pins the bar at the top (its own `position: sticky`
|
|
284
|
-
* + `top` in `
|
|
284
|
+
* + `top` in `design/`) and, using an IntersectionObserver on the bar's PREVIOUS sibling as
|
|
285
285
|
* the sentinel (e.g. a hero image), keeps it hidden while that sibling is on screen and
|
|
286
286
|
* fades it in once the sibling scrolls away. A negative margin removes the bar's flow
|
|
287
287
|
* footprint so it OVERLAYS the content (no gap at the top). NOT card-specific: any sticky
|
|
288
|
-
* bar can opt in. The SDK owns behaviour, never styles — the bar's look lives in `
|
|
288
|
+
* bar can opt in. The SDK owns behaviour, never styles — the bar's look lives in `design/`.
|
|
289
289
|
*/
|
|
290
290
|
function useRevealWhenStuck(enabled: boolean) {
|
|
291
291
|
const ref = useRef<HTMLDivElement | null>(null);
|
|
@@ -415,7 +415,7 @@ export function IconView({ name, style, theme }: { name?: string; style?: Record
|
|
|
415
415
|
|
|
416
416
|
/**
|
|
417
417
|
* `Skeleton` — built-in loading placeholder. ALL dimensions are DATA, fetched
|
|
418
|
-
* from the theme (theme.skeleton ←
|
|
418
|
+
* from the theme (theme.skeleton ← design/styles/semantic/skeleton.json). The SDK
|
|
419
419
|
* authors nothing here but structure (flex/full). `bars` are 'width height' pairs.
|
|
420
420
|
*/
|
|
421
421
|
export function SkeletonView({ variant = "text", theme }: { variant?: string; theme: ResolvedTheme }): ReactNode {
|
|
@@ -680,9 +680,9 @@ export function InputView({ node, data, theme, onAction }: { node: UnoverseNode;
|
|
|
680
680
|
* markdown-to-jsx overrides for the `Markdown` primitive.
|
|
681
681
|
*
|
|
682
682
|
* ⛔ AUTHORS ZERO STYLE VALUES. Every element style is read verbatim from the
|
|
683
|
-
* SERVED prose recipe (`theme.prose` ←
|
|
683
|
+
* SERVED prose recipe (`theme.prose` ← design/styles/semantic/prose.json). The SDK
|
|
684
684
|
* only maps element → recipe key and supplies BEHAVIOUR (target=_blank, image
|
|
685
|
-
* error-fallback). No hex, no px/rem, no weights here — they live in
|
|
685
|
+
* error-fallback). No hex, no px/rem, no weights here — they live in design/.
|
|
686
686
|
*
|
|
687
687
|
* Built once per theme (stable component identities so streaming re-renders don't
|
|
688
688
|
* remount images and drop their broken-state).
|
|
@@ -734,7 +734,7 @@ function buildMarkdownOverrides(theme: ResolvedTheme, inheritLinkColor = false)
|
|
|
734
734
|
// INLINE MARKS and BLOCKS. Authored copy uses all of these (a bolded access code, a
|
|
735
735
|
// rule between sections, a quoted term), and an element absent from this map renders
|
|
736
736
|
// at the browser's default — the one place a definition's look escapes the token
|
|
737
|
-
// layer. Each maps to a served recipe key; the VALUES live in
|
|
737
|
+
// layer. Each maps to a served recipe key; the VALUES live in design/styles/semantic/prose.
|
|
738
738
|
strong: { props: { style: s("strong") } },
|
|
739
739
|
em: { props: { style: s("emphasis") } },
|
|
740
740
|
del: { props: { style: s("strikethrough") } },
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* `Button` (and its styling logic) kept leaking back into the dispatcher.
|
|
13
13
|
*
|
|
14
14
|
* THE FRAMEWORK IN ONE SENTENCE: this SDK is a FIXED, GENERIC interpreter. ALL
|
|
15
|
-
* UX is built in DATA (
|
|
15
|
+
* UX is built in DATA (design/ definitions + the served theme) — an author creates
|
|
16
16
|
* ANY interface WITHOUT editing this SDK. (See FRAMEWORK.md.)
|
|
17
17
|
*
|
|
18
18
|
* WHERE THINGS LIVE:
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
*
|
|
31
31
|
* Need something the vocab can't express? Prefer extending the generic interpreter
|
|
32
32
|
* ONCE (a new style-vocab key in ./style, a new model projection) over a primitive.
|
|
33
|
-
* You almost never need to touch this file — build it in
|
|
33
|
+
* You almost never need to touch this file — build it in design/ first.
|
|
34
34
|
* ════════════════════════════════════════════════════════════════════════════
|
|
35
35
|
*/
|
|
36
36
|
import type { ReactNode } from "react";
|
package/vendor/sdk/lib/style.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The style + animation INTERPRETER — neutral vocab → CSS, resolving token NAMES
|
|
3
3
|
* against a theme FETCHED from the server. ⛔ OWNS ZERO STYLE VALUES (no hex, no
|
|
4
|
-
* px/rem/em, no recipes); every value lives in
|
|
4
|
+
* px/rem/em, no recipes); every value lives in design/styles and is served. See
|
|
5
5
|
* FRAMEWORK.md and render.tsx's header. Enforced by test/golden-rule.test.mjs.
|
|
6
6
|
*/
|
|
7
7
|
import type { CSSProperties } from "react";
|
|
@@ -227,10 +227,10 @@ export function cssDecls(css: CSSProperties, important = false): string {
|
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
/**
|
|
230
|
-
* Serialize the SERVED keyframes (theme.keyframes ←
|
|
230
|
+
* Serialize the SERVED keyframes (theme.keyframes ← design/styles/semantic/keyframes.json)
|
|
231
231
|
* into a CSS string, injected ONCE per render root (<style>). This authors zero values —
|
|
232
232
|
* it only structures DATA into `@keyframes uno-<name>{ <stop>{ <prop>:<val> } }`. Adding a
|
|
233
|
-
* new animation = adding a keyframe to
|
|
233
|
+
* new animation = adding a keyframe to design/ + a `style.animation` ref in a definition. No
|
|
234
234
|
* SDK edit, no new primitive. Keyframe decls use real CSS prop names, KEBAB-CASE
|
|
235
235
|
* (`box-shadow`, never `boxShadow`) — that is the canonical authored form, lint-enforced.
|
|
236
236
|
* Property keys are still kebab-normalized here, exactly as Studio's gallery serializer
|
|
@@ -179,7 +179,7 @@ export function StreamedUnoverseTemplate({ client, store, uri, onAction, theme:
|
|
|
179
179
|
* THE LATCH, PUBLISHED FOR CHROME (docs/MCP_COMPLETE_GUIDE.md §The Component Latch).
|
|
180
180
|
*
|
|
181
181
|
* `resolveLatch` derives WHICH instance the conversation addresses; the composer's pill
|
|
182
|
-
* is ordinary
|
|
182
|
+
* is ordinary design chrome, so it needs those facts as template state to bind against. Same
|
|
183
183
|
* shape as the voice service (§4 "services feed the state"): a producer writes derived
|
|
184
184
|
* values into the normal bucket, and `core` learns no UI concept — nothing here knows a
|
|
185
185
|
* pill exists.
|
|
@@ -328,7 +328,7 @@ export function StreamedUnoverseTemplate({ client, store, uri, onAction, theme:
|
|
|
328
328
|
|
|
329
329
|
// Timeline — GENERIC iterator. Walks the timeline and renders the `user` / `assistant`
|
|
330
330
|
// DATA sub-tree per turn. Zero chat UX in the SDK: bubbles, alignment, structure all live
|
|
331
|
-
// in those sub-trees (
|
|
331
|
+
// in those sub-trees (design/templates/chatlayout/*.json). The assistant sub-tree's ComponentSlot
|
|
332
332
|
// is scoped to THAT turn's components.
|
|
333
333
|
const resolveTimeline = (node: UnoverseNode, key?: React.Key): ReactNode => {
|
|
334
334
|
return (
|
package/vendor/sdk/lib/theme.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* useUnoverseTheme — fetch the RESOLVED theme from the MCP server by name.
|
|
3
3
|
*
|
|
4
4
|
* The SDK owns ZERO token values. The server owns the visual (apps/unoverse/
|
|
5
|
-
*
|
|
6
|
-
* fetches it (cached in the client). A brand change in
|
|
5
|
+
* design/styles, resolved live) and serves it at `unoverse://theme/{name}`; this hook
|
|
6
|
+
* fetches it (cached in the client). A brand change in design/styles is refresh-only
|
|
7
7
|
* — there is no baked theme in this bundle to rebuild (UNOVERSE_SPEC §2d-1).
|
|
8
8
|
*/
|
|
9
9
|
import { useEffect, useState } from "react";
|
|
@@ -1,18 +1,35 @@
|
|
|
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 (
|
|
3
|
+
description: "Create or edit Unoverse platform artifacts: UI components (including briefed/AI-filled components), templates, atoms and styles (design/), brand and org packs (themes, tokens, rebranding, cloning a universe for a new client), agent skills and prompt blocks (prompts/), custom workflow nodes (nodes/), and workflows built live on the Canvas via the builder MCP. Use whenever the user wants to create, add, build, or modify a component, template, card, page, theme, brand, org pack, agent skill, prompt block, custom node, or workflow in an Unoverse project, even if they don't say 'Unoverse' (they'll describe UI like 'a card with a button and an image' or 'a page the AI fills in')."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Creating Unoverse Artifacts
|
|
7
7
|
|
|
8
|
-
You are helping a developer build in
|
|
9
|
-
|
|
8
|
+
You are helping a developer build in an Unoverse project: the three editable folders
|
|
9
|
+
the platform reads.
|
|
10
10
|
|
|
11
11
|
| Folder | What lives there |
|
|
12
12
|
|---|---|
|
|
13
|
-
| `
|
|
14
|
-
| `
|
|
15
|
-
| `
|
|
13
|
+
| `design/` | **Design** (data, not code): per-project components, templates + styles; each project is `design/<project>/` |
|
|
14
|
+
| `prompts/` | **Behavior**: agent skills (`skills/`) + prompt blocks (`blocks/`) |
|
|
15
|
+
| `nodes/` | **Logic**: custom workflow nodes, authored as YAML |
|
|
16
|
+
|
|
17
|
+
Paths in this skill are relative to the project root. In a Studio developer workspace
|
|
18
|
+
that is the workspace folder itself; in the platform monorepo it is `apps/unoverse/`.
|
|
19
|
+
|
|
20
|
+
## Step 0: Anchor in the project before writing
|
|
21
|
+
|
|
22
|
+
1. **Pick the right `design/<project>/`.** Every design artifact belongs to a project (an
|
|
23
|
+
"org"). If the workspace has more than one, or the only one looks like a placeholder,
|
|
24
|
+
ask which project the artifact belongs to. Never invent a new project folder to hold
|
|
25
|
+
one artifact.
|
|
26
|
+
2. **The design system is installed, not on disk.** A developer workspace holds only the
|
|
27
|
+
developer's own projects; the marketplace design system (shared atoms, components,
|
|
28
|
+
the token foundation) resolves from the installed platform. See it through Studio's
|
|
29
|
+
authoring MCP: `listDefinitions` (kind `atom` / `component`, org `marketplace`),
|
|
30
|
+
`readDefinition` for a full definition, `getVocabulary` for the closed node/style
|
|
31
|
+
vocabulary. Compose those atoms; never hand-roll a shape the design system already
|
|
32
|
+
ships. (In the platform monorepo the same material is on disk at `design/marketplace/`.)
|
|
16
33
|
|
|
17
34
|
## Step 1: Identify the artifact, read its playbook
|
|
18
35
|
|
|
@@ -31,40 +48,54 @@ anything**:
|
|
|
31
48
|
| A workflow (wire nodes into an agent/pipeline on the Canvas) | `references/workflow.md` |
|
|
32
49
|
|
|
33
50
|
Workflows are the one artifact that is NOT files: they are built live on the running
|
|
34
|
-
platform through the `canvas` MCP (registered by
|
|
51
|
+
platform through the `canvas` MCP (registered by the project's `.mcp.json`).
|
|
35
52
|
The playbook covers the bind/build/test contract.
|
|
36
53
|
|
|
37
54
|
If it's ambiguous (e.g. "add a card that shows weather"), it's usually a **component**
|
|
38
55
|
(the UI) plus possibly a **node** (the data source). Confirm the scope with the user.
|
|
39
56
|
|
|
40
|
-
**New to design artifacts?** The guided learning journey at
|
|
41
|
-
|
|
42
|
-
(
|
|
43
|
-
|
|
44
|
-
|
|
57
|
+
**New to design artifacts?** The guided learning journey at
|
|
58
|
+
[docs.unoverse.ai/design](https://docs.unoverse.ai/design/overview.md)
|
|
59
|
+
([quick-start](https://docs.unoverse.ai/design/quick-start.md) →
|
|
60
|
+
[components](https://docs.unoverse.ai/design/components.md) →
|
|
61
|
+
[state](https://docs.unoverse.ai/design/state.md) →
|
|
62
|
+
[templates](https://docs.unoverse.ai/design/templates.md) →
|
|
63
|
+
[styles-and-tokens](https://docs.unoverse.ai/design/styles-and-tokens.md) →
|
|
64
|
+
[studio](https://docs.unoverse.ai/design/studio.md) →
|
|
65
|
+
[validate-and-ship](https://docs.unoverse.ai/design/validate-and-ship.md) →
|
|
66
|
+
[troubleshooting](https://docs.unoverse.ai/design/troubleshooting.md)) teaches
|
|
67
|
+
components, templates, the state model (incl. locked voice/native state), tokens,
|
|
68
|
+
Studio, and validation in order;
|
|
69
|
+
[design/CLAUDE.md](https://docs.unoverse.ai/design/CLAUDE.md) is the condensed agent
|
|
70
|
+
rulebook. The playbooks here assume that material. Append `.md` to any docs URL to
|
|
71
|
+
fetch it as raw markdown.
|
|
45
72
|
|
|
46
73
|
## Step 2: Hard rules (apply to every artifact)
|
|
47
74
|
|
|
48
|
-
1. **Only edit the three
|
|
75
|
+
1. **Only edit the three project folders.** Never touch, vendor, or work around the
|
|
49
76
|
SDK, engine, or server. They are not yours to change. If a task seems to require an
|
|
50
77
|
SDK change, the answer is almost always "express it as data". Re-read the playbook.
|
|
51
78
|
2. **Docs are the source of truth.** The playbooks name the design journey and the deep
|
|
52
|
-
references ([UNOVERSE_AUTHORING](https://
|
|
53
|
-
[
|
|
54
|
-
(https://
|
|
55
|
-
|
|
79
|
+
references ([UNOVERSE_AUTHORING](https://docs.unoverse.ai/unoverse/UNOVERSE_AUTHORING.md),
|
|
80
|
+
[UNOVERSE_STATE_MODEL](https://docs.unoverse.ai/unoverse/UNOVERSE_STATE_MODEL.md),
|
|
81
|
+
[UNOVERSE_LAYERS](https://docs.unoverse.ai/unoverse/UNOVERSE_LAYERS.md),
|
|
82
|
+
[UNOVERSE_CONFORMANCE](https://docs.unoverse.ai/unoverse/UNOVERSE_CONFORMANCE.md)),
|
|
83
|
+
all served at docs.unoverse.ai. When the user's request goes beyond the playbook,
|
|
84
|
+
read the named section rather than improvising conventions.
|
|
56
85
|
3. **UI is data.** No pixels, hex colors, or CSS anywhere: token names only. No logic
|
|
57
86
|
in definitions: anything computed (totals, formatting, chosen colors) is computed in
|
|
58
87
|
the node and sent as a plain field.
|
|
59
88
|
4. **Audit before you're done.** Every component/template must pass the conformance
|
|
60
|
-
checklist ([UNOVERSE_AUTHORING](https://
|
|
89
|
+
checklist ([UNOVERSE_AUTHORING](https://docs.unoverse.ai/unoverse/UNOVERSE_AUTHORING.md) §9).
|
|
90
|
+
Walk it item by item.
|
|
61
91
|
5. **Discovery meta is ranked, not read.** Any spatially-discoverable artifact (a
|
|
62
92
|
component or template with a manifest, an agent skill, a node) is selected by
|
|
63
93
|
embedding `title`/`name` + `whenToUse`/`description` + `category` against the user's
|
|
64
94
|
own words (or the planner's task). The strict contract is
|
|
65
|
-
|
|
66
|
-
words dominate, disqualify by property
|
|
67
|
-
the summary; read
|
|
95
|
+
[node-discoverability](https://docs.unoverse.ai/nodes/node-discoverability.md):
|
|
96
|
+
outcome-first, utterance-shaped, opening words dominate, disqualify by property
|
|
97
|
+
(never name a sibling). Each playbook carries the summary; read that page before
|
|
98
|
+
writing any meta.
|
|
68
99
|
|
|
69
100
|
## Step 3: Deploy loop (after the artifact is written)
|
|
70
101
|
|
|
@@ -72,18 +103,21 @@ Components and templates have NO scaffold command. Author them from the referenc
|
|
|
72
103
|
(mirror the closest existing artifact). Validation lives in Studio: its publish step
|
|
73
104
|
lints every definition before anything leaves the machine (0 errors required, doc-cited
|
|
74
105
|
messages; justify any warning), and the conformance checklist is
|
|
75
|
-
[UNOVERSE_CONFORMANCE](https://
|
|
106
|
+
[UNOVERSE_CONFORMANCE](https://docs.unoverse.ai/unoverse/UNOVERSE_CONFORMANCE.md).
|
|
76
107
|
|
|
77
108
|
| Artifact | To see it live |
|
|
78
109
|
|---|---|
|
|
79
110
|
| Component / atom / template / style | Preview in Studio (`unoverse studio`), then publish from Studio: the publish lint gates it |
|
|
80
|
-
| Existing component restyle/edit only | takes effect live (the SDK reads `
|
|
111
|
+
| Existing component restyle/edit only | takes effect live (the SDK reads `design/` directly) |
|
|
81
112
|
| Agent skill / prompt block | publish from Studio, same gate |
|
|
82
113
|
| Node | Studio's Nodes screen: Load sample → Run. A node is YAML; running it is the only proof (no build) |
|
|
83
114
|
|
|
84
|
-
|
|
85
|
-
|
|
115
|
+
**Close the loop with the developer.** After creating something visual, tell them how
|
|
116
|
+
to see it: open Studio (`unoverse studio`, :4108), find the artifact, preview it. The
|
|
117
|
+
deliverable is the rendered thing, never the YAML alone.
|
|
86
118
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
>
|
|
119
|
+
Verify the platform with `unoverse check` (services, schema, environment).
|
|
120
|
+
|
|
121
|
+
> In the platform monorepo (not a developer workspace), the project root is
|
|
122
|
+
> `apps/unoverse/`, the docs source is `packages/docs/`, and the dev loop is
|
|
123
|
+
> `npm run dev`. Component nodes synthesize from design/ definitions at boot.
|
|
@@ -7,7 +7,7 @@ at runtime (e.g. how to handle complaints, how to walk a user through a process)
|
|
|
7
7
|
## Where files go
|
|
8
8
|
|
|
9
9
|
```
|
|
10
|
-
|
|
10
|
+
prompts/skills/<skill-name>/
|
|
11
11
|
SKILL.md # required: frontmatter + instructions
|
|
12
12
|
```
|
|
13
13
|
|
|
@@ -21,7 +21,7 @@ The runtime scans this folder and serves skills to agents; agents match on the
|
|
|
21
21
|
name: skill-name # required: lowercase, numbers, hyphens; max 64 chars
|
|
22
22
|
title: Skill Name # human display name, shown as the item's title in listings
|
|
23
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.
|
|
24
|
+
whenToUse: The SELECTION text: outcome-first, in the user's vocabulary; ranked against intent for spatial discovery. See https://docs.unoverse.ai/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)
|
|
@@ -53,7 +53,8 @@ is invalid.
|
|
|
53
53
|
discovery / findIntent). Write it **outcome-first in the user's vocabulary**
|
|
54
54
|
("Handle an unhappy customer: a complaint, frustration, or a request to
|
|
55
55
|
escalate…"), never mechanism-first. The full rules are
|
|
56
|
-
|
|
56
|
+
[node-discoverability](https://docs.unoverse.ai/nodes/node-discoverability.md).
|
|
57
|
+
They apply to skills verbatim.
|
|
57
58
|
2. **Study an existing skill** in `prompts/skills/` and match its voice and structure.
|
|
58
59
|
3. Keep instructions outcome-focused and scannable: short sections, bulleted do/don't
|
|
59
60
|
lists, explicit escalation/stop conditions.
|
|
@@ -61,5 +62,7 @@ is invalid.
|
|
|
61
62
|
|
|
62
63
|
## Ship
|
|
63
64
|
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
Publish from Studio (the publish lint gates it). Operators running the platform
|
|
66
|
+
directly can instead `docker compose restart unoverse`: the runtime rescans skills at
|
|
67
|
+
boot. Verify the skill appears via the platform's skill listing (`unoverse check` for
|
|
68
|
+
overall health).
|
|
@@ -8,7 +8,7 @@ is an ingredient.
|
|
|
8
8
|
## Where files go
|
|
9
9
|
|
|
10
10
|
```
|
|
11
|
-
|
|
11
|
+
prompts/blocks/<category>/<block-name>.md
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
The immediate subdirectory is the block's category (existing: `core/`, `formatting/`).
|
|
@@ -40,4 +40,5 @@ present tense, no meta-commentary about being a block.]
|
|
|
40
40
|
|
|
41
41
|
## Ship
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
Publish from Studio (the publish lint gates it). Operators running the platform
|
|
44
|
+
directly can instead `docker compose restart unoverse`: the runtime rescans blocks at boot.
|
|
@@ -13,11 +13,11 @@ client. They share one discipline and one failure mode.
|
|
|
13
13
|
|
|
14
14
|
## 1. How overriding actually works: name a token to override, omit it to inherit
|
|
15
15
|
|
|
16
|
-
`
|
|
16
|
+
`design/marketplace/styles` is the **foundation**: the shared token contract every org
|
|
17
17
|
inherits. Your org pack is read **on top** of it.
|
|
18
18
|
|
|
19
|
-
The server builds a theme in three passes (`
|
|
20
|
-
|
|
19
|
+
The server builds a theme in three passes (`buildTheme`, in the platform's theme
|
|
20
|
+
resolver):
|
|
21
21
|
|
|
22
22
|
1. the foundation's `base/` then `semantic/`, into a flat registry keyed by dotted path
|
|
23
23
|
2. **your org's `base/` then `semantic/`, over the top**
|
|
@@ -45,8 +45,8 @@ re-copying anything.
|
|
|
45
45
|
|
|
46
46
|
| Pack | `styles/base/` holds |
|
|
47
47
|
|---|---|
|
|
48
|
-
| Foundation (`
|
|
49
|
-
| A brand (`
|
|
48
|
+
| Foundation (`design/marketplace/`) | `border` `color` `motion` `radius` `shadow` `spacing` `typography` |
|
|
49
|
+
| A brand (`design/bpp/`) | `color` `typography` **only** |
|
|
50
50
|
|
|
51
51
|
BPP names two files and inherits the other five. That ratio is the health check: a pack
|
|
52
52
|
redefining `spacing` or `radius` is not branding, it is forking. The neutral core is
|
|
@@ -57,7 +57,7 @@ redefining `spacing` or `radius` is not branding, it is forking. The neutral cor
|
|
|
57
57
|
An org is a folder. There is no registry to edit and nothing to declare.
|
|
58
58
|
|
|
59
59
|
```
|
|
60
|
-
|
|
60
|
+
design/<neworg>/
|
|
61
61
|
styles/
|
|
62
62
|
base/color.yaml brand INGREDIENTS (color.brand.*)
|
|
63
63
|
base/typography.yaml font families only
|
|
@@ -85,10 +85,11 @@ you genuinely differ.
|
|
|
85
85
|
|
|
86
86
|
Its theme is then served at `unoverse://theme/<neworg>/light`, and a template points at it.
|
|
87
87
|
|
|
88
|
-
## 4. Anatomy of an org pack (`
|
|
88
|
+
## 4. Anatomy of an org pack (`design/<org>/styles/`)
|
|
89
89
|
|
|
90
|
-
Read
|
|
91
|
-
|
|
90
|
+
Read an installed org pack as the worked example (Studio MCP `listOrgs` →
|
|
91
|
+
`listDefinitions`; in the platform monorepo, `design/bpp/styles/`). Every file in a healthy
|
|
92
|
+
pack carries a `$description` explaining its own job, and those descriptions are the spec.
|
|
92
93
|
|
|
93
94
|
| File | Holds | Rule |
|
|
94
95
|
|---|---|---|
|
|
@@ -127,7 +128,7 @@ naming your situation is the intended answer.
|
|
|
127
128
|
Copying an org pack is mechanical, and every gotcha is a name that exists in more than one
|
|
128
129
|
place. Work through them in order.
|
|
129
130
|
|
|
130
|
-
1. **Copy the folder.** `
|
|
131
|
+
1. **Copy the folder.** `design/<source>/` → `design/<neworg>/`, keeping `styles/`, `components/`,
|
|
131
132
|
`templates/`.
|
|
132
133
|
2. **Rename the templates.** Folder AND the id inside the manifest. `bpp-chat-layout`
|
|
133
134
|
becomes `lyceum-chat-layout` in both places. Template ids are org-qualified and they
|
|
@@ -137,25 +138,25 @@ place. Work through them in order.
|
|
|
137
138
|
is addressed `unoverse://components/<org>/<name>`. The one forbidden case is a name
|
|
138
139
|
that shadows a design-system (marketplace) component; the publish lint catches it.
|
|
139
140
|
Update template manifest `name`s only where the id itself is org-branded.
|
|
140
|
-
4. **Register the client surface** if
|
|
141
|
-
|
|
141
|
+
4. **Register the client surface** if a demo client serves it (platform monorepo only:
|
|
142
|
+
`packages/demo/src/demo/clients.js`).
|
|
142
143
|
5. **Point the app at the new theme.** Templates reference `unoverse://theme/<org>/<name>`.
|
|
143
144
|
A copied template still names the old org until you change it.
|
|
144
145
|
|
|
145
146
|
## 7. Done means all four
|
|
146
147
|
|
|
147
|
-
- `grep -ri "<oldorg>"
|
|
148
|
+
- `grep -ri "<oldorg>" design/<neworg>/` returns **nothing**. This is the check that catches
|
|
148
149
|
the id someone missed at midnight, and it is the one people skip.
|
|
149
150
|
- The new org appears in **Studio's universe dropdown** and its theme renders.
|
|
150
151
|
- **Studio's publish lint: 0 errors.** Justify any warning out loud.
|
|
151
152
|
- The conformance checklist passes for every copied component
|
|
152
|
-
([UNOVERSE_CONFORMANCE](https://
|
|
153
|
+
([UNOVERSE_CONFORMANCE](https://docs.unoverse.ai/unoverse/UNOVERSE_CONFORMANCE.md)).
|
|
153
154
|
|
|
154
155
|
## Deeper references
|
|
155
156
|
|
|
156
157
|
Token structure and the resolve order:
|
|
157
|
-
[UNOVERSE_AUTHORING](https://
|
|
158
|
+
[UNOVERSE_AUTHORING](https://docs.unoverse.ai/unoverse/UNOVERSE_AUTHORING.md).
|
|
158
159
|
Which tier owns what:
|
|
159
|
-
[UNOVERSE_LAYERS](https://
|
|
160
|
+
[UNOVERSE_LAYERS](https://docs.unoverse.ai/unoverse/UNOVERSE_LAYERS.md).
|
|
160
161
|
Styles are W3C design tokens, two-tier base/semantic, resolved live by the server and
|
|
161
162
|
served at `unoverse://theme/{org}/{name}`. The SDK fetches them and bundles none.
|
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
# Playbook: Components & Atoms (
|
|
1
|
+
# Playbook: Components & Atoms (design data)
|
|
2
2
|
|
|
3
|
-
**Read first:**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
**Read first:** [design/components](https://docs.unoverse.ai/design/components.md) (the
|
|
4
|
+
microapp anatomy) + [design/state](https://docs.unoverse.ai/design/state.md) (the state
|
|
5
|
+
model). Deep law: [UNOVERSE_AUTHORING](https://docs.unoverse.ai/unoverse/UNOVERSE_AUTHORING.md) §3 (three homes),
|
|
6
|
+
[UNOVERSE_STATE_MODEL](https://docs.unoverse.ai/unoverse/UNOVERSE_STATE_MODEL.md) §5 (the six rules),
|
|
7
|
+
[UNOVERSE_LAYERS](https://docs.unoverse.ai/unoverse/UNOVERSE_LAYERS.md) (states own layouts).
|
|
8
|
+
**Study an exemplar before writing.** The canonical State Model v2 state tree is inlined
|
|
9
|
+
below; for a full worked component, read an installed one through Studio's MCP
|
|
10
|
+
(`listDefinitions` kind `component`, then `readDefinition`), and prefer a rich
|
|
11
|
+
multi-state one over a starter stub. (In the platform monorepo the canonical exemplar
|
|
12
|
+
is on disk: `design/sab/components/product-card`.)
|
|
8
13
|
|
|
9
14
|
## The anatomy
|
|
10
15
|
|
|
11
16
|
```
|
|
12
|
-
|
|
17
|
+
design/<project>/components/<name>/
|
|
13
18
|
<name>.yaml # ROOT: envelope (name/category/nodeSize/outputs/props) + the STATE TREE + root
|
|
14
19
|
manifest.yaml # RENDER CONTRACT + discovery meta (title / description ≤120 /
|
|
15
20
|
# whenToUse utterance-shaped). Presence = spatially discoverable;
|
|
@@ -25,7 +30,7 @@ manifest, no folders. Structure is EARNED; start flat.
|
|
|
25
30
|
|
|
26
31
|
A component is **data plus a state tree**; each state owns the layout(s) that draw it.
|
|
27
32
|
The root's `state:` block IS the design, readable top to bottom. The canonical shape
|
|
28
|
-
(
|
|
33
|
+
(a product-card component: a grid state and a detail page with nested steps):
|
|
29
34
|
|
|
30
35
|
```yaml
|
|
31
36
|
state:
|
|
@@ -101,9 +106,9 @@ The rules, in the order they bite:
|
|
|
101
106
|
BY NAME (as-is, no mapping); a name the source doesn't carry silently renders the
|
|
102
107
|
preview default (tell: title streams, image stays mock). Content-attached cards use
|
|
103
108
|
the writer vocabulary: `title` `tagline` `description` `bodyCopy` `introParagraph`
|
|
104
|
-
`primaryImage` `images` `link` `callToAction`.
|
|
105
|
-
`
|
|
106
|
-
|
|
109
|
+
`primaryImage` `images` `link` `callToAction`. Law:
|
|
110
|
+
`UNOVERSE_MCP_TEMPLATE_PROTOCOL.md` §Content-attached cards (platform spec; a
|
|
111
|
+
guard test in the platform server enforces it).
|
|
107
112
|
2. **Root = `Switch on view`**, cases per PUBLIC state, each `$include` of the state's
|
|
108
113
|
layout (the tree above). Keep an `inline` state for any component that lives in a
|
|
109
114
|
conversation flow: the flow is its placeholder there. One EXCEPTION is the
|
|
@@ -119,7 +124,8 @@ The rules, in the order they bite:
|
|
|
119
124
|
closes itself, or a NEW TEMPLATE loads (the template swap is the hard refresh boundary).
|
|
120
125
|
Durable slices also opt out of `cancelBelow` retraction when a higher template state
|
|
121
126
|
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;
|
|
127
|
+
user turn. Values are a closed set: `turn | conversation` (lint-checked;
|
|
128
|
+
[design/components](https://docs.unoverse.ai/design/components.md)).
|
|
123
129
|
3. **State is local; the `view` is the interface** (STATE_MODEL §5). Private axes
|
|
124
130
|
(`step`, `phase`, …) are the component's own business; the template never sees them.
|
|
125
131
|
The only thing that crosses is the public `view`. The component writes ONLY its own
|
|
@@ -135,14 +141,18 @@ The rules, in the order they bite:
|
|
|
135
141
|
of bound fields). Same shape, different values = ONE state, data-driven: seven wizard
|
|
136
142
|
questions sharing one shape are one `question` state whose data changes, never seven
|
|
137
143
|
files (LAYERS §4, §7).
|
|
138
|
-
5. **Atoms** (
|
|
144
|
+
5. **Atoms** (the installed design system's shared shapes, used via `Ref`; list them with
|
|
145
|
+
Studio's MCP `listDefinitions` kind `atom`, or on disk at `design/marketplace/atoms/` in the
|
|
146
|
+
platform monorepo) are for shapes shared across components, authoring-time
|
|
139
147
|
only (the server expands them before serving; no Studio view). `Ref props` remaps FIELDS;
|
|
140
148
|
**`Ref with` passes LITERALS**: `{ "type": "Ref", "ref": "button", "with": { "label":
|
|
141
149
|
"Learn more", "icon": "arrowRight" }, "action": { … } }`. A bind whose field is a `with`
|
|
142
150
|
key becomes a hardcoded attribute, a truthy `with` key drops a matching `visibleWhen`
|
|
143
151
|
guard (unprovided ⇒ that piece stays hidden), `{{key}}` style bindings take the value.
|
|
144
152
|
⚠ Icon: literal = `icon: "phone"`; bound = `bind: { name: field }`.
|
|
145
|
-
6. **Briefs: components an AI fills**
|
|
153
|
+
6. **Briefs: components an AI fills**
|
|
154
|
+
([design/components](https://docs.unoverse.ai/design/components.md) §Briefed components;
|
|
155
|
+
deep law: [UNOVERSE_AUTHORING](https://docs.unoverse.ai/unoverse/UNOVERSE_AUTHORING.md) §3b). A `brief`
|
|
146
156
|
sits ON the node that renders what it describes, next to its `bind`, never in the
|
|
147
157
|
manifest, never a separate file:
|
|
148
158
|
- bound element → `"brief": { "description": "…", "maxLength": 60 }`
|
|
@@ -157,15 +167,16 @@ The rules, in the order they bite:
|
|
|
157
167
|
for surgical refinement). **The schema IS the instruction channel.**
|
|
158
168
|
Write briefs, never side-channel prompts/skills for filling a component. Shape is
|
|
159
169
|
closed + linted. Pairs with the single-state permanent pattern (rule 2) for
|
|
160
|
-
continuously-enriched pages
|
|
170
|
+
continuously-enriched pages.
|
|
161
171
|
7. **Tokens only** (LAW 1) + closed style keys + real space-scale steps
|
|
162
|
-
(
|
|
172
|
+
([design/styles-and-tokens](https://docs.unoverse.ai/design/styles-and-tokens.md)):
|
|
173
|
+
an invented step is silently broken CSS.
|
|
163
174
|
8. **Lifecycle hooks: the component fetches its own data** (full doc:
|
|
164
|
-
|
|
165
|
-
[UNOVERSE_AUTHORING](https://
|
|
175
|
+
[design/lifecycle-hooks](https://docs.unoverse.ai/design/lifecycle-hooks.md); deep law:
|
|
176
|
+
[UNOVERSE_AUTHORING](https://docs.unoverse.ai/unoverse/UNOVERSE_AUTHORING.md) §3c). When a component needs data the
|
|
166
177
|
workflow did not send (not from an AI = brief, not from the workflow = `input` props),
|
|
167
178
|
it declares CALLS that the platform runs at a platform-owned fire point. **No code**:
|
|
168
|
-
a hook is data like everything else in `
|
|
179
|
+
a hook is data like everything else in `design/`. It's for DATA, not styling or logic.
|
|
169
180
|
- **Two phases**: `onStart` (the instance is created: data every copy needs) and
|
|
170
181
|
`onEnterView` (the instance entered a named STATE: data only that state needs). A
|
|
171
182
|
detail view's content is `onEnterView`, NEVER `onStart` — hydrating at creation
|
|
@@ -183,7 +194,7 @@ The rules, in the order they bite:
|
|
|
183
194
|
never travels through a model. Use it for "fill this card with its own record".
|
|
184
195
|
|
|
185
196
|
```yaml
|
|
186
|
-
# manifest.yaml — the shipped
|
|
197
|
+
# manifest.yaml — the shipped product-card exemplar
|
|
187
198
|
lifecycle:
|
|
188
199
|
- phase: onEnterView
|
|
189
200
|
layouts: [ detail ] # STATE names; the grid state never wakes it
|
|
@@ -199,7 +210,7 @@ The rules, in the order they bite:
|
|
|
199
210
|
- **`allowedHosts` in the manifest is NOT optional**: a hook that makes requests
|
|
200
211
|
without it is a lint ERROR (deny by default). Credentials are named by type in the
|
|
201
212
|
manifest and resolved server-side from encrypted storage; **a key never sits in
|
|
202
|
-
`
|
|
213
|
+
`design/`** (a shared design folder) and never reaches the client.
|
|
203
214
|
- A phase fires once per instance; enrichment streams in (no blocked render); errors
|
|
204
215
|
fail quietly, leaving whatever the card already had.
|
|
205
216
|
|
|
@@ -232,14 +243,13 @@ The rules, in the order they bite:
|
|
|
232
243
|
LEGACY form. It still runs for handlers that predate declared calls, but it is **never
|
|
233
244
|
the one to copy**: a script fetches any host with a key from the environment, and the
|
|
234
245
|
calls runtime settles both questions.
|
|
235
|
-
Exemplar: `rx/yasisland/components/restaurant-card/` (maps lookup, declared calls).
|
|
236
246
|
|
|
237
|
-
## Discovery meta: write it to RANK (docs/nodes/node-discoverability.md)
|
|
247
|
+
## Discovery meta: write it to RANK ([node-discoverability](https://docs.unoverse.ai/nodes/node-discoverability.md))
|
|
238
248
|
|
|
239
249
|
A component with a manifest is an **MCP app**: spatial embeds `` `title. whenToUse||description
|
|
240
250
|
[category]` `` and ranks it against the USER'S OWN WORDS (`findIntent`). The meta is the
|
|
241
251
|
selection text, not documentation. Weak meta makes a working component invisible. The
|
|
242
|
-
strict contract (
|
|
252
|
+
strict contract (that page, applied verbatim):
|
|
243
253
|
|
|
244
254
|
- **`title`** = the thing itself, human display name, no mechanism, no org prefix.
|
|
245
255
|
- **`description`** = what it IS, ONE line ≤120 chars, the listing subtitle; never
|
|
@@ -260,7 +270,8 @@ component. Its key nouns/verbs must appear in `whenToUse`'s first sentence.
|
|
|
260
270
|
|
|
261
271
|
## Workflow
|
|
262
272
|
|
|
263
|
-
1. Study
|
|
273
|
+
1. Study the closest installed exemplar (Studio MCP `listDefinitions` → `readDefinition`;
|
|
274
|
+
monorepo: `design/sab/components/product-card`); copy its folder shape.
|
|
264
275
|
2. Declare the state tree first: public states on top, steps nested, each state owning
|
|
265
276
|
its layout (same-name default, `layout:` only when the filename differs).
|
|
266
277
|
3. Write the envelope + manifest + layouts; root = `Switch on view`; every shown thing
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
# Playbook: custom workflow nodes
|
|
2
2
|
|
|
3
|
-
**Read first, in order
|
|
4
|
-
|
|
5
|
-
1.
|
|
6
|
-
non-negotiable rules, every file with an example, and
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
**Read first, in order** (all served at docs.unoverse.ai; append `.md` for raw markdown):
|
|
4
|
+
|
|
5
|
+
1. [nodes/CLAUDE.md](https://docs.unoverse.ai/nodes/CLAUDE.md): the authoritative agent
|
|
6
|
+
guide. The folder, the eight non-negotiable rules, every file with an example, and
|
|
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.
|
|
9
|
+
3. [nodes/node-discoverability](https://docs.unoverse.ai/nodes/node-discoverability.md): **read before writing `whenToUse`**.
|
|
10
|
+
4. As needed: [credentials](https://docs.unoverse.ai/nodes/credentials.md),
|
|
11
|
+
[who-can-run-it](https://docs.unoverse.ai/nodes/who-can-run-it.md),
|
|
12
|
+
[config-schema](https://docs.unoverse.ai/nodes/config-schema.md),
|
|
13
|
+
[service-connectors](https://docs.unoverse.ai/nodes/service-connectors.md),
|
|
14
|
+
[mcp-services](https://docs.unoverse.ai/nodes/mcp-services.md),
|
|
15
|
+
[signal-routing](https://docs.unoverse.ai/nodes/signal-routing.md),
|
|
16
|
+
[testing-nodes](https://docs.unoverse.ai/nodes/testing-nodes.md).
|
|
11
17
|
|
|
12
18
|
This playbook adds the workflow around those docs. It never overrides them.
|
|
13
19
|
|
|
@@ -20,7 +26,7 @@ is a folder of YAML files that the platform interprets.
|
|
|
20
26
|
missing a capability. Say so and stop, rather than reaching for code.
|
|
21
27
|
|
|
22
28
|
```
|
|
23
|
-
|
|
29
|
+
nodes/<package>/
|
|
24
30
|
package.yaml # name, category, allowedHosts, requires
|
|
25
31
|
credentials/
|
|
26
32
|
<name>Credential.yaml # the SHAPE of a credential, never a value
|
|
@@ -79,7 +85,7 @@ package only for a new integration.
|
|
|
79
85
|
```
|
|
80
86
|
|
|
81
87
|
`required: false` is right for almost every node: the trigger already decided who was let
|
|
82
|
-
in. See
|
|
88
|
+
in. See [who-can-run-it](https://docs.unoverse.ai/nodes/who-can-run-it.md).
|
|
83
89
|
3. **Write `interface.yaml`.** Name outputs for what they carry, because those names become
|
|
84
90
|
what someone types in a template.
|
|
85
91
|
4. **Write `config.yaml`.** Every value the workflow supplies needs `ui:field: template`. A
|
|
@@ -107,7 +113,7 @@ package only for a new integration.
|
|
|
107
113
|
5. **Write `api/run.yaml`.** A list, each entry named. Include `error` wherever the service
|
|
108
114
|
can answer 200 with a failure body. Where one call is really many, name the capability on
|
|
109
115
|
the call rather than trying to express a loop: `paginate`, `chunk`, `poll` or `state`.
|
|
110
|
-
See
|
|
116
|
+
See [calls-that-loop](https://docs.unoverse.ai/nodes/calls-that-loop.md), and declare each one in `requires`. A call
|
|
111
117
|
authenticates with `credential: { scheme, token }`, which is OUTBOUND: how the node proves
|
|
112
118
|
itself to the vendor. It is not `node.yaml`'s `auth`, which is who may run the node.
|
|
113
119
|
6. **Write `api/events.yaml`.** One row per output connector, in the order `interface.yaml`
|