@unoverse-platform/studio 0.1.56 → 0.1.58

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/local/plugin.ts CHANGED
@@ -222,7 +222,7 @@ export function localStudioHost(): Plugin {
222
222
  // 200. That has now cost three separate bugs (the /api allowlist, the dev proxy for
223
223
  // /.well-known and /publish), each of which looked like a missing route rather than
224
224
  // a filtered one.
225
- if (!/^\/(dev|local|mcp|skills|prompt-blocks|registry|nodes|keys|execute|publish)\b/.test(path)) return next();
225
+ if (!/^\/(dev|local|mcp|skills|prompt-blocks|registry|nodes|keys|execute)\b/.test(path)) return next();
226
226
 
227
227
  /** Read a JSON request body. */
228
228
  const body = async (): Promise<Record<string, unknown>> => {
@@ -248,71 +248,10 @@ export function localStudioHost(): Plugin {
248
248
  envFiles: [resolve(projectRoot, "..", ".env"), resolve(projectRoot, ".env")],
249
249
  });
250
250
 
251
- /**
252
- * PUBLISH, in two calls: plan then send.
253
- *
254
- * It runs HERE rather than in the browser because publishing reads the
255
- * developer's disk: their definitions, and the design system they built on.
256
- * A browser cannot, and a server that could would be a server this tool
257
- * deliberately does not have.
258
- *
259
- * The token is passed per request and never stored. Studio holds it in the
260
- * OIDC session for the tab (UniverseSession) and hands it over for the one
261
- * call; local Studio writes no secret to disk (local/keys.ts), and connecting
262
- * to a universe must not be the thing that breaks that.
263
- *
264
- * Same functions the CLI uses (`unoverse publish assets`), so the two cannot
265
- * disagree about what a publish is.
266
- */
267
- case path === "/publish/plan" || path === "/publish/send": {
268
- const b = await body();
269
- const project = String(b.project ?? "");
270
- const universe = String(b.universe ?? "");
271
- const token = String(b.token ?? "");
272
- if (!project || !universe || !token)
273
- return json(res, { error: "project, universe and token are required" }, 400);
274
-
275
- // Loaded through Vite's SSR runner, like the catalog and node loaders above: a bare
276
- // dynamic import of a .js path does not resolve to the .ts source. The type below
277
- // mirrors the module's real exports — tsc can't follow an ssrLoadModule path.
278
- type PublishItem = { kind: string; name: string; why?: string; mode?: string };
279
- type PublishModule = {
280
- lintForPublish(designRoot: string, project: string): Promise<{ problems: unknown[]; errors: unknown[] }>;
281
- collectProject(designRoot: string, project: string): unknown;
282
- planPublish(collected: unknown, universe: string, token: string): Promise<Record<string, PublishItem[]>>;
283
- sendPublish(plan: unknown, universe: string, token: string): Promise<{ sent: PublishItem[]; failed?: PublishItem[] }>;
284
- };
285
- const items = (await ssr(`${baseSrc}/items/publish.ts`)) as PublishModule;
286
- const designRoot = designHome(projectRoot);
287
-
288
- // LINT FIRST, ALWAYS. Nothing leaves the machine until the rules pass: a
289
- // developer should learn about a raw hex value here, not from an HTTP error.
290
- const { problems, errors } = await items.lintForPublish(designRoot, project);
291
- if (errors.length) return json(res, { blocked: true, problems, errors });
292
-
293
- const collected = items.collectProject(designRoot, project);
294
- const plan = await items.planPublish(collected, universe, token);
295
-
296
- // The screen renders kinds and names. The plan carries every file's full
297
- // contents, so sending it whole would push the entire project through the
298
- // browser for a list, and again on send.
299
- type Lean = { kind: string; name: string; why?: string; mode?: string };
300
- const lean = (list: Lean[] = []) =>
301
- list.map(({ kind, name, why, mode }) => ({ kind, name, why, mode }));
302
- const leanPlan = Object.fromEntries(
303
- Object.entries(plan as Record<string, Lean[]>).map(([k, v]) => [k, lean(v)]),
304
- );
305
-
306
- if (path === "/publish/plan") return json(res, { problems, plan: leanPlan });
307
-
308
- const result = await items.sendPublish(plan, universe, token);
309
- return json(res, {
310
- problems,
311
- plan: leanPlan,
312
- sent: lean(result.sent),
313
- failed: lean(result.failed),
314
- });
315
- }
251
+ // NO PUBLISH ROUTES. Studio had /publish/plan and /publish/send; they moved
252
+ // to the terminal whole (LOCAL_STUDIO.md §Publish is terminal-only): the CLI
253
+ // runs the same base items/publish functions directly, and no token ever
254
+ // nears the browser because the browser no longer takes part.
316
255
 
317
256
  case path === "/dev/orgs":
318
257
  return json(res, { orgs: c.themeOrgs() });
@@ -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