@unoverse-platform/studio 0.1.56 → 0.1.57

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.
@@ -0,0 +1,40 @@
1
+ type: Box
2
+ visibleWhen: active
3
+ style:
4
+ direction: row
5
+ gap: "2"
6
+ align: start
7
+ children:
8
+ - type: Box
9
+ style:
10
+ direction: column
11
+ flex: 1
12
+ gap: "2"
13
+ minWidth: 0
14
+ children:
15
+ - type: ComponentSlot
16
+ select: {}
17
+ - type: Box
18
+ visibleWhen: streaming
19
+ children:
20
+ - type: Box
21
+ visibleWhen: empty
22
+ style:
23
+ direction: row
24
+ gap: "2"
25
+ align: center
26
+ children:
27
+ - $include: components/thinking-dots
28
+ - type: Text
29
+ bind:
30
+ value: thinkingText
31
+ style:
32
+ font: body.sm
33
+ color: text.secondary
34
+ - type: Text
35
+ visibleWhen: time
36
+ bind:
37
+ value: time
38
+ style:
39
+ font: body.sm
40
+ color: text.secondary
@@ -0,0 +1,46 @@
1
+ # THE CHAT COLUMN WITH A FOCUSED COMPONENT OVER IT — core, plus the focus surface
2
+ # laid over the conversation region. A core VARIANT, not a copy: header, states and
3
+ # composer are the same shared files core.yaml includes; the only difference is the
4
+ # overlay slot.
5
+ type: Box
6
+ appWidth: chat
7
+ style:
8
+ direction: column
9
+ minHeight: "0"
10
+ children:
11
+ - type: Box
12
+ style:
13
+ direction: column
14
+ flex: 1
15
+ minHeight: "0"
16
+ position: relative
17
+ children:
18
+ - $include: components/header
19
+ - type: Box
20
+ style:
21
+ direction: column
22
+ flex: 1
23
+ minHeight: "0"
24
+ position: relative
25
+ when:
26
+ - field: hasMessages
27
+ eq: true
28
+ apply:
29
+ background: surface.base
30
+ radiusTopLeft: 2xl
31
+ overflow: hidden
32
+ children:
33
+ - $include: states/welcome
34
+ - $include: states/conversation
35
+ - type: Box
36
+ style:
37
+ position: absolute
38
+ bottom: "0"
39
+ left: "0"
40
+ right: "0"
41
+ direction: column
42
+ children:
43
+ - $include: components/suggestions
44
+ - $include: components/focus-overlay
45
+ - type: Ref
46
+ ref: composer-bar
@@ -0,0 +1,41 @@
1
+ type: Box
2
+ appWidth: chat
3
+ style:
4
+ direction: column
5
+ minHeight: "0"
6
+ children:
7
+ - type: Box
8
+ style:
9
+ direction: column
10
+ flex: 1
11
+ minHeight: "0"
12
+ position: relative
13
+ children:
14
+ - $include: components/header
15
+ - type: Box
16
+ style:
17
+ direction: column
18
+ flex: 1
19
+ minHeight: "0"
20
+ position: relative
21
+ when:
22
+ - field: hasMessages
23
+ eq: true
24
+ apply:
25
+ background: surface.base
26
+ radiusTopLeft: 2xl
27
+ overflow: hidden
28
+ children:
29
+ - $include: states/welcome
30
+ - $include: states/conversation
31
+ - type: Box
32
+ style:
33
+ position: absolute
34
+ bottom: "0"
35
+ left: "0"
36
+ right: "0"
37
+ direction: column
38
+ children:
39
+ - $include: components/suggestions
40
+ - type: Ref
41
+ ref: composer-bar
@@ -0,0 +1,20 @@
1
+ type: ComponentSlot
2
+ select:
3
+ from: all
4
+ where:
5
+ field: view
6
+ eq: focused
7
+ limit: 1
8
+ frame:
9
+ type: Box
10
+ style:
11
+ position: absolute
12
+ inset: "0"
13
+ width: full
14
+ height: full
15
+ background: surface.base
16
+ overflow: hidden
17
+ direction: column
18
+ align: stretch
19
+ children:
20
+ - type: ComponentSlot
@@ -0,0 +1,53 @@
1
+ type: Box
2
+ style:
3
+ direction: row
4
+ align: center
5
+ justify: space-between
6
+ height: "20"
7
+ padding: [ "0", "4" ]
8
+ background: transparent
9
+ children:
10
+ - type: Box
11
+ style:
12
+ direction: row
13
+ align: center
14
+ gap: "2"
15
+ children:
16
+ - type: Icon
17
+ icon: sparkles
18
+ style:
19
+ width: "6"
20
+ height: "6"
21
+ color: action.primary
22
+ - type: Text
23
+ style:
24
+ font: title
25
+ color: text.primary
26
+ value: Assistant
27
+ - type: Button
28
+ action: close
29
+ style:
30
+ flex: 0 0 auto
31
+ cursor: pointer
32
+ width: "8"
33
+ height: "8"
34
+ radius: full
35
+ background: surface.base
36
+ border: subtle
37
+ shadow: sm
38
+ direction: row
39
+ align: center
40
+ justify: center
41
+ transition: all 150ms ease
42
+ hover:
43
+ background: surface.sunken
44
+ children:
45
+ - type: Icon
46
+ icon: close
47
+ style:
48
+ width: "4"
49
+ height: "4"
50
+ color: text.secondary
51
+ visibleWhen:
52
+ field: surfacedView
53
+ in: [ "" ]
@@ -0,0 +1,142 @@
1
+ type: Box
2
+ visibleWhen: hasMessages
3
+ style:
4
+ width: full
5
+ position: relative
6
+ direction: column
7
+ background: transparent
8
+ children:
9
+ - type: Box
10
+ visibleWhen:
11
+ field: openPanel
12
+ eq: faq
13
+ style:
14
+ width: full
15
+ direction: column
16
+ gap: "2"
17
+ background: surface.base
18
+ radius: lg
19
+ padding: "4"
20
+ shadow: lg
21
+ margin: [ "0", "0", "2", "0" ]
22
+ animation:
23
+ name: slideUp
24
+ duration: 0.3s
25
+ easing: ease-out
26
+ iteration: "1"
27
+ children:
28
+ - type: Box
29
+ style:
30
+ direction: row
31
+ justify: end
32
+ children:
33
+ - type: Button
34
+ action:
35
+ type: setTemplateValue
36
+ values:
37
+ - key: openPanel
38
+ value: null
39
+ style:
40
+ flex: 0 0 auto
41
+ cursor: pointer
42
+ width: "8"
43
+ height: "8"
44
+ radius: full
45
+ background: surface.base
46
+ border: subtle
47
+ shadow: sm
48
+ direction: row
49
+ align: center
50
+ justify: center
51
+ transition: all 150ms ease
52
+ hover:
53
+ background: surface.sunken
54
+ children:
55
+ - type: Icon
56
+ icon: close
57
+ style:
58
+ width: "4"
59
+ height: "4"
60
+ color: text.secondary
61
+ - type: Each
62
+ style:
63
+ direction: row
64
+ wrap: wrap
65
+ align: start
66
+ gap: "2"
67
+ width: full
68
+ template:
69
+ type: Button
70
+ action:
71
+ type: sendMessage
72
+ then:
73
+ type: setTemplateValue
74
+ values:
75
+ - key: openPanel
76
+ value: null
77
+ style:
78
+ direction: row
79
+ align: center
80
+ flex: 0 0 auto
81
+ maxWidth: full
82
+ padding: [ "2", "4" ]
83
+ background: surface.base
84
+ border: subtle
85
+ outline: none
86
+ radius: full
87
+ shadow: sm
88
+ cursor: pointer
89
+ transition: all 150ms ease
90
+ hover:
91
+ background: surface.sunken
92
+ children:
93
+ - type: Text
94
+ bind:
95
+ value: text
96
+ style:
97
+ font: body.sm
98
+ color: text.secondary
99
+ lineHeight: tight
100
+ items:
101
+ - text: What can you help me with?
102
+ - text: How does this work?
103
+ - text: Show me an example
104
+ - type: Box
105
+ style:
106
+ width: full
107
+ direction: row
108
+ justify: center
109
+ padding: [ "1", "0" ]
110
+ children:
111
+ - type: Button
112
+ action:
113
+ type: setTemplateValue
114
+ values:
115
+ - key: openPanel
116
+ value: faq
117
+ style:
118
+ direction: row
119
+ align: center
120
+ gap: "1"
121
+ padding: [ "1", "3" ]
122
+ background: surface.base
123
+ border: subtle
124
+ outline: none
125
+ radius: full
126
+ cursor: pointer
127
+ transition: all 150ms ease
128
+ hover:
129
+ background: surface.sunken
130
+ children:
131
+ - type: Icon
132
+ icon: chat
133
+ style:
134
+ width: "4"
135
+ height: "4"
136
+ color: text.secondary
137
+ - type: Text
138
+ style:
139
+ font: body.sm
140
+ color: text.secondary
141
+ weight: medium
142
+ value: Suggestions
@@ -0,0 +1,38 @@
1
+ type: Box
2
+ style:
3
+ direction: row
4
+ gap: "1"
5
+ align: center
6
+ children:
7
+ - type: Box
8
+ style:
9
+ width: "1.5"
10
+ height: "1.5"
11
+ background: action.primary
12
+ radius: full
13
+ animation:
14
+ name: wave
15
+ duration: 1.6s
16
+ easing: ease-in-out
17
+ - type: Box
18
+ style:
19
+ width: "1.5"
20
+ height: "1.5"
21
+ background: action.primary
22
+ radius: full
23
+ animation:
24
+ name: wave
25
+ duration: 1.6s
26
+ easing: ease-in-out
27
+ animationDelay: 0.16s
28
+ - type: Box
29
+ style:
30
+ width: "1.5"
31
+ height: "1.5"
32
+ background: action.primary
33
+ radius: full
34
+ animation:
35
+ name: wave
36
+ duration: 1.6s
37
+ easing: ease-in-out
38
+ animationDelay: 0.32s
@@ -0,0 +1,22 @@
1
+ type: Box
2
+ style:
3
+ direction: row
4
+ justify: end
5
+ children:
6
+ - type: Box
7
+ style:
8
+ direction: row
9
+ align: center
10
+ background: action.primary
11
+ radius: 2xl
12
+ radiusTopRight: sm
13
+ padding: [ "2", "4" ]
14
+ maxWidth: compact
15
+ children:
16
+ - type: Text
17
+ bind:
18
+ value: text
19
+ style:
20
+ font: body.md
21
+ color: action.primary-text
22
+ lineHeight: tight
@@ -0,0 +1,12 @@
1
+ # FOCUSED — a component holding the conversation region, composer live beneath.
2
+ # A LAYOUT owned by the `focused` state: a hosted component writes `view: focused`,
3
+ # the ladder makes this arrangement active, this file draws it.
4
+ type: Box
5
+ style:
6
+ direction: row
7
+ width: full
8
+ height: full
9
+ overflow: hidden
10
+ background: surface.sunken
11
+ children:
12
+ - $include: components/core-focus
@@ -0,0 +1,9 @@
1
+ type: Box
2
+ style:
3
+ direction: row
4
+ width: full
5
+ height: full
6
+ overflow: hidden
7
+ background: surface.sunken
8
+ children:
9
+ - $include: components/core
@@ -0,0 +1,41 @@
1
+ # A GENERIC CHAT — a worked example template, shipped with Studio so templates/ is
2
+ # never an empty folder. It is yours: rename it, restyle it (it uses only foundation
3
+ # tokens, so your project's theme restyles it whole), or delete it.
4
+ # Guide: https://docs.unoverse.ai/design/templates.md
5
+ name: chat
6
+ description: A conversational assistant surface with a welcome hero, suggestions, and a focus overlay.
7
+ whenToUse: Talk to the assistant — ask a question, get help, or start any task in conversation. The conversational home and fallback surface.
8
+ category: Assistant
9
+ version: 1.0.0
10
+ defaultState: template
11
+ inputSchema:
12
+ type: object
13
+ properties:
14
+ message:
15
+ type: string
16
+ description: The user's request
17
+
18
+ # THE TEMPLATE TREE (State Model v2): one declaration, everything follows.
19
+ # Top-level order IS the priority ladder (base first, then reaction states). NESTING
20
+ # is containment: welcome exists only inside standard — the compiler strips it from
21
+ # every other arrangement — and it is the landing default. No stateOrder, no
22
+ # hand-written guards.
23
+ # NOTE: the conversation timeline is NOT declared as a substate — it is standard's own
24
+ # shell content (states/conversation), included by the shared chrome (components/core)
25
+ # and legitimately drawn under the focus overlay too. Declaring it would contain it to
26
+ # standard and empty the chat column in every reaction state.
27
+ states:
28
+ standard:
29
+ states:
30
+ welcome: {}
31
+ focused: {}
32
+
33
+ preview:
34
+ conversation: [ markdown-renderer ]
35
+ focused: [ card ]
36
+
37
+ autoTrigger: false
38
+ # BIND YOUR WORKFLOW to bring this to life: build one on the Canvas, then set
39
+ # binding: { workflow: <workflow-id>, trigger: <trigger-node-id> }
40
+ # The app owns its workflow; until it is bound, this template previews but cannot run.
41
+ layout: standard
@@ -0,0 +1,21 @@
1
+ type: Box
2
+ visibleWhen: hasMessages
3
+ autoScroll: true
4
+ style:
5
+ flex: 1
6
+ minHeight: "0"
7
+ direction: column
8
+ overflow: auto
9
+ padding: [ "6", "8" ]
10
+ children:
11
+ - type: Timeline
12
+ style:
13
+ direction: column
14
+ gap: "4"
15
+ margin: [ auto, "0", "0", "0" ]
16
+ assistantData:
17
+ thinkingText: Thinking…
18
+ user:
19
+ $include: components/user-turn
20
+ assistant:
21
+ $include: components/assistant-turn
@@ -0,0 +1,119 @@
1
+ type: Box
2
+ visibleWhen: isEmpty
3
+ style:
4
+ direction: column
5
+ flex: 1
6
+ minHeight: "0"
7
+ overflow: hidden
8
+ justify: end
9
+ gap: "6"
10
+ padding: [ "8", "8", "4", "8" ]
11
+ children:
12
+ - type: Box
13
+ style:
14
+ direction: column
15
+ align: start
16
+ gap: "2"
17
+ maxWidth: reading
18
+ padding: [ "0", "8", "0", "0" ]
19
+ children:
20
+ - type: Text
21
+ style:
22
+ font: display
23
+ color: text.primary
24
+ lineHeight: none
25
+ align: start
26
+ value: How can I help?
27
+ - type: Text
28
+ style:
29
+ font: body.lg
30
+ color: text.secondary
31
+ align: start
32
+ value: Ask a question, or pick a place to start.
33
+ - type: Each
34
+ style:
35
+ direction: row
36
+ align: start
37
+ gap: "4"
38
+ width: full
39
+ padding: [ "2", "0", "2", "0" ]
40
+ template:
41
+ type: Button
42
+ action: sendMessage
43
+ style:
44
+ position: relative
45
+ flex: 1 1 0
46
+ minWidth: 0
47
+ height: "90"
48
+ padding: "0"
49
+ radius: 2xl
50
+ border: none
51
+ overflow: hidden
52
+ cursor: pointer
53
+ transition: all 200ms ease
54
+ shadow: lg
55
+ hover:
56
+ shadow: xl
57
+ transform: scale(1.02)
58
+ children:
59
+ - type: Image
60
+ bind:
61
+ src: image
62
+ alt: title
63
+ style:
64
+ position: absolute
65
+ inset: "0"
66
+ width: full
67
+ height: full
68
+ fit: cover
69
+ - type: Box
70
+ style:
71
+ position: absolute
72
+ inset: "0"
73
+ direction: column
74
+ align: start
75
+ justify: end
76
+ gap: "2"
77
+ padding: "4"
78
+ background: overlay.scrim
79
+ textAlign: start
80
+ children:
81
+ - type: Text
82
+ bind:
83
+ value: tag
84
+ style:
85
+ font: caption
86
+ color: white
87
+ lineHeight: tight
88
+ - type: Text
89
+ bind:
90
+ value: title
91
+ style:
92
+ font: title
93
+ color: white
94
+ lineHeight: tight
95
+ align: start
96
+ - type: Text
97
+ bind:
98
+ value: subtitle
99
+ style:
100
+ font: body.sm
101
+ color: white
102
+ lineHeight: snug
103
+ align: start
104
+ items:
105
+ - image: https://picsum.photos/seed/explore/840/1160
106
+ tag: Explore
107
+ title: What can you do?
108
+ subtitle: See what this assistant can help with.
109
+ text: What can you help me with?
110
+ - image: https://picsum.photos/seed/learn/840/1160
111
+ tag: Learn
112
+ title: Show me around
113
+ subtitle: A quick tour of how this works.
114
+ text: Give me a quick tour of what I can do here
115
+ - image: https://picsum.photos/seed/start/840/1160
116
+ tag: Start
117
+ title: Jump right in
118
+ subtitle: Describe what you need in your own words.
119
+ text: I would like some help getting started
@@ -43,7 +43,11 @@ export function checkProjectName(name) {
43
43
  const WELCOME = [
44
44
  "unoverse: '1.0'",
45
45
  "kind: component",
46
- "name: Welcome",
46
+ // name === filename (the item-key rule the publish lint enforces), and the closed
47
+ // style vocabulary has `font`, not `fontSize` — both caught by linting the scaffold
48
+ // output itself, which is the guard: the first component a developer ever sees must
49
+ // lint clean, or every rule the platform states is undermined by its own starter.
50
+ "name: welcome",
47
51
  "category: Content",
48
52
  "description: A first component. Edit this file and Studio updates as you save.",
49
53
  "root:",
@@ -52,7 +56,7 @@ const WELCOME = [
52
56
  " children:",
53
57
  " - type: Text",
54
58
  " value: Hello from your first component",
55
- " style: { fontSize: 'lg', color: 'text.primary' }",
59
+ " style: { font: 'body.lg', color: 'text.primary' }",
56
60
  "",
57
61
  ].join("\n");
58
62
 
@@ -81,6 +85,15 @@ export function createProject(home, name) {
81
85
  if (existsSync(sampleStyles) && readdirSync(join(root, "styles")).length === 0) {
82
86
  cpSync(sampleStyles, join(root, "styles"), { recursive: true });
83
87
  }
88
+
89
+ // A worked example TEMPLATE (a generic chat: welcome hero, suggestions, focus
90
+ // overlay — the bpp-chat-layout shape, neutralized to foundation tokens), so
91
+ // templates/ is never an empty folder and the first app is a rename away. Same
92
+ // rule as the styles: copied only into an empty folder, then the developer's.
93
+ const sampleTemplate = join(dirname(fileURLToPath(import.meta.url)), "sample-template/chat");
94
+ if (existsSync(sampleTemplate) && readdirSync(join(root, "templates")).length === 0) {
95
+ cpSync(sampleTemplate, join(root, "templates/chat"), { recursive: true });
96
+ }
84
97
  return root;
85
98
  }
86
99
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unoverse-platform/studio",
3
- "version": "0.1.56",
3
+ "version": "0.1.57",
4
4
  "type": "module",
5
5
  "description": "Unoverse Studio: the local authoring app. Reads your own rx/ folder, no backend.",
6
6
  "scripts": {
@@ -519,6 +519,11 @@ export function TemplatesView({
519
519
  nodeId: `${c.name}-${i}`,
520
520
  component: {
521
521
  type: c.name,
522
+ // The ADDRESS, exactly as a real server arrival carries it: the type alone
523
+ // cannot rebuild it (names are unique only within a tier — store.ts), so a
524
+ // mock seed without it resolved bare `unoverse://components/<name>` and the
525
+ // resolver rightly refused to guess between two orgs' clones.
526
+ componentUrl: `unoverse://components/${c.org ? `${c.org}/` : ""}${c.name}`,
522
527
  props: { ...defaultsOf(c), ...restState, defaultState: "inline" },
523
528
  },
524
529
  });
@@ -545,9 +550,12 @@ export function TemplatesView({
545
550
  Promise.all([
546
551
  // The header's org scope filters the list ("all" → every org's apps, each
547
552
  // tagged); the selected app's org still picks its own preview theme.
548
- // Components are universal — one catalog.
553
+ // Components are SCOPED like the templates: an org sees "universal + its own".
554
+ // Fetched unscoped, a cloned org's same-named component (design/lyceum cloned
555
+ // from design/bpp → two bare `journey-finder`s) sat beside the original and
556
+ // Mock seeding picked whichever listed first.
549
557
  authedFetch(`/dev/templates${org !== "all" ? `?org=${encodeURIComponent(org)}` : ""}`).then((r) => (r.ok ? r.json() : { templates: [] })),
550
- authedFetch("/dev/components").then((r) => (r.ok ? r.json() : { components: [] })),
558
+ authedFetch(`/dev/components${org !== "all" ? `?org=${encodeURIComponent(org)}` : ""}`).then((r) => (r.ok ? r.json() : { components: [] })),
551
559
  ])
552
560
  .then(([t, c]) => {
553
561
  const tl: Def[] = t.templates ?? [];
@@ -599,7 +607,14 @@ export function TemplatesView({
599
607
  const wantComponent = Boolean(layoutSel) || Boolean((activeState?.where as { field?: string } | undefined)?.field);
600
608
  let seededSet: Def[] = [];
601
609
  if (!wantEmpty) {
602
- const byName = (name: string) => components.find((c) => c.name.toLowerCase() === name.toLowerCase());
610
+ // ORG-AWARE: names are unique only within a tier, so a bare preview ref must
611
+ // resolve to the SELECTED APP's own org first, then the universal tier — never
612
+ // to another org's same-named clone (which the "all" scope still lists).
613
+ const appOrg = (selectedDef?.org ?? "").toLowerCase();
614
+ const byName = (name: string) => {
615
+ const hits = components.filter((c) => c.name.toLowerCase() === name.toLowerCase());
616
+ return hits.find((c) => (c.org ?? "").toLowerCase() === appOrg) ?? hits.find((c) => !c.org) ?? hits[0];
617
+ };
603
618
  const set = entry
604
619
  ? entry.map(byName).filter((c): c is Def => Boolean(c))
605
620
  : wantComponent