@unoverse-platform/studio 0.1.69 → 0.1.70
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 +1 -1
- package/local/catalog.ts +2 -2
- package/local/mcp.ts +3 -2
- package/local/plugin.ts +13 -11
- package/local/{sample-template → sample-app}/chat/manifest.yaml +3 -3
- package/local/sample-component/text-chat/components/suggestions.yaml +3 -3
- package/local/scaffold.mjs +11 -11
- package/package.json +2 -1
- package/screens/NativeAppHost.tsx +5 -5
- package/screens/TemplatesView.tsx +77 -77
- package/screens/chrome.ts +1 -1
- package/screens/states.ts +3 -3
- package/src/App.tsx +20 -9
- package/src/NewProject.tsx +1 -1
- package/src/host.ts +2 -2
- package/vendor/sdk/appEngine.tsx +18 -18
- package/vendor/sdk/lib/{template.tsx → app.tsx} +11 -11
- package/vendor/sdk/lib/connection.tsx +10 -10
- package/vendor/sdk/lib/core/actions.ts +3 -3
- package/vendor/sdk/lib/core/{templates.ts → apps.ts} +1 -1
- package/vendor/sdk/lib/core/connection.ts +17 -15
- package/vendor/sdk/lib/core/index.ts +2 -2
- package/vendor/sdk/lib/core/store.ts +22 -22
- package/vendor/sdk/lib/core/types.ts +2 -2
- package/vendor/sdk/lib/index.ts +3 -3
- package/vendor/sdk/lib/render.tsx +1 -1
- package/vendor/sdk/lib/voice.tsx +2 -2
- package/vendor/sdk/main.tsx +7 -7
- package/vendor/skills/unoverse-create/SKILL.md +9 -9
- package/vendor/skills/unoverse-create/references/{template.md → app.md} +34 -34
- package/vendor/skills/unoverse-create/references/brand.md +8 -8
- package/vendor/skills/unoverse-create/references/component.md +20 -20
- package/vendor/skills/unoverse-create/references/node.md +2 -2
- package/vendor/skills/unoverse-prompt-component/SKILL.md +2 -2
- /package/local/{sample-template → sample-app}/chat/components/chat-region.yaml +0 -0
- /package/local/{sample-template → sample-app}/chat/components/core-focus.yaml +0 -0
- /package/local/{sample-template → sample-app}/chat/components/core.yaml +0 -0
- /package/local/{sample-template → sample-app}/chat/components/focus-overlay.yaml +0 -0
- /package/local/{sample-template → sample-app}/chat/layouts/focused.yaml +0 -0
- /package/local/{sample-template → sample-app}/chat/layouts/standard.yaml +0 -0
package/bin/studio.mjs
CHANGED
|
@@ -141,7 +141,7 @@ async function scaffoldIfEmpty() {
|
|
|
141
141
|
// identical (local/scaffold.mjs). rx/<name>/ stays nested: every subfolder of rx/ IS a
|
|
142
142
|
// project (base collect.ts listProjects) and its folder name is the `org` that
|
|
143
143
|
// publishes, so a flat rx/ could hold exactly one project and would read `components`
|
|
144
|
-
// and `
|
|
144
|
+
// and `apps` as orgs.
|
|
145
145
|
createFirstProject(home, name);
|
|
146
146
|
console.log(`\n Created design/${name}/, prompts/ and nodes/ in ${cwd}\n`);
|
|
147
147
|
}
|
package/local/catalog.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { existsSync } from "fs";
|
|
|
18
18
|
type Loaders = {
|
|
19
19
|
listDefinitions: (kind: string, org?: string) => unknown[];
|
|
20
20
|
listStates: (kind: string, name: string) => unknown[];
|
|
21
|
-
|
|
21
|
+
listAppsForWorkbench: (org?: string) => unknown[];
|
|
22
22
|
loadDefinition: (ref: string, kind: string) => unknown;
|
|
23
23
|
listOrgs: () => string[];
|
|
24
24
|
inputPropKeys: (props: unknown) => string[];
|
|
@@ -98,7 +98,7 @@ export async function loadCatalog(
|
|
|
98
98
|
return {
|
|
99
99
|
listDefinitions: defs.listDefinitions as Loaders["listDefinitions"],
|
|
100
100
|
listStates: defs.listStates as Loaders["listStates"],
|
|
101
|
-
|
|
101
|
+
listAppsForWorkbench: defs.listAppsForWorkbench as Loaders["listAppsForWorkbench"],
|
|
102
102
|
loadDefinition: defs.loadDefinition as Loaders["loadDefinition"],
|
|
103
103
|
listOrgs: defs.listOrgs as Loaders["listOrgs"],
|
|
104
104
|
inputPropKeys: inputs.inputPropKeys as Loaders["inputPropKeys"],
|
package/local/mcp.ts
CHANGED
|
@@ -155,8 +155,9 @@ export function buildTools(deps: McpDeps, appRoot: string): Record<string, Tool>
|
|
|
155
155
|
run: async ({ uri }) => {
|
|
156
156
|
needsBinding();
|
|
157
157
|
const c = await deps.catalog();
|
|
158
|
-
|
|
159
|
-
const
|
|
158
|
+
// `.../definition` is a suffix on the app's URI, not part of its ref.
|
|
159
|
+
const ref = String(uri).replace(/^unoverse:\/\/(components|atoms|apps)\//, "").replace(/\/definition$/, "");
|
|
160
|
+
const kind = /\/apps\//.test(String(uri)) ? "template" : /\/atoms\//.test(String(uri)) ? "atom" : "component";
|
|
160
161
|
return { definition: c.loadDefinition(ref, kind), files: deps.sourceFiles(String(uri)) };
|
|
161
162
|
},
|
|
162
163
|
},
|
package/local/plugin.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Routes mirror the server's shapes exactly, because the screens parse those shapes:
|
|
10
10
|
* GET /dev/orgs GET /dev/themes?org= GET /dev/components?org=
|
|
11
|
-
* GET /dev/
|
|
11
|
+
* GET /dev/apps?org= GET /dev/atoms
|
|
12
12
|
* GET /local/theme?ref= GET /local/definition?uri=
|
|
13
13
|
* GET /skills /skills/:name GET /prompt-blocks
|
|
14
14
|
* GET|POST /registry/state (no database locally, see below)
|
|
@@ -29,10 +29,10 @@ import { atomNames, vocabulary } from "./authoring.js";
|
|
|
29
29
|
import { buildTools, handleMcp, mintPairingCode } from "./mcp.js";
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* A definition URI's home on disk: a component's folder, or
|
|
32
|
+
* A definition URI's home on disk: a component's folder, or an app's.
|
|
33
33
|
*
|
|
34
34
|
* Resolution is the loaders', not this file's: `componentFolder` already knows a
|
|
35
|
-
* component lives in either tier and that names are unique across them.
|
|
35
|
+
* component lives in either tier and that names are unique across them. Apps are
|
|
36
36
|
* manifest-only under their own project, which `projectDir` locates.
|
|
37
37
|
*/
|
|
38
38
|
function definitionHome(
|
|
@@ -40,13 +40,15 @@ function definitionHome(
|
|
|
40
40
|
c: { componentFolder: (ref: string) => string | null; projectDir: (n: string) => string; listOrgs: () => string[] },
|
|
41
41
|
projectRoot: string,
|
|
42
42
|
): string | null {
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
// An app definition is addressed as `unoverse://apps/<org>/<name>/definition`, so the
|
|
44
|
+
// suffix has to come off before the rest is read as `<org>/<name>`.
|
|
45
|
+
const ref = uri.replace(/^unoverse:\/\/(components|atoms|apps)\//, "").replace(/\/definition$/, "");
|
|
46
|
+
if (/\/apps\//.test(uri)) {
|
|
45
47
|
const i = ref.indexOf("/");
|
|
46
48
|
const [org, name] = i === -1 ? [null, ref] : [ref.slice(0, i), ref.slice(i + 1)];
|
|
47
49
|
const orgs = org ? [org] : c.listOrgs();
|
|
48
50
|
for (const o of orgs) {
|
|
49
|
-
const dir = resolve(c.projectDir(o), "
|
|
51
|
+
const dir = resolve(c.projectDir(o), "apps", name.toLowerCase());
|
|
50
52
|
if (existsSync(dir)) return dir;
|
|
51
53
|
}
|
|
52
54
|
return null;
|
|
@@ -346,8 +348,8 @@ export function localStudioHost(): Plugin {
|
|
|
346
348
|
}),
|
|
347
349
|
});
|
|
348
350
|
|
|
349
|
-
case path === "/dev/
|
|
350
|
-
return json(res, {
|
|
351
|
+
case path === "/dev/apps":
|
|
352
|
+
return json(res, { apps: c.listAppsForWorkbench(org) });
|
|
351
353
|
|
|
352
354
|
case path === "/dev/atoms":
|
|
353
355
|
return json(res, { atoms: c.listDefinitions("atom") });
|
|
@@ -360,14 +362,14 @@ export function localStudioHost(): Plugin {
|
|
|
360
362
|
case path === "/local/definition": {
|
|
361
363
|
const uri = url.searchParams.get("uri") ?? "";
|
|
362
364
|
// Apps are addressed both ways (unoverse://apps/<org>/<id> and
|
|
363
|
-
// unoverse://
|
|
364
|
-
const kind = /\/
|
|
365
|
+
// unoverse://apps/<org>/<id>/definition); both load the app kind.
|
|
366
|
+
const kind = /\/apps\//.test(uri) ? "template" : /\/atoms\//.test(uri) ? "atom" : "component";
|
|
365
367
|
// A trailing `@form` is a request for a VARIANT of the definition, not part of
|
|
366
368
|
// its name. This door did not know that convention and looked up the whole
|
|
367
369
|
// string, so every component read as `<name>@refs` answered "definition not
|
|
368
370
|
// found". Strip any variant: this route serves the one form it has, which is
|
|
369
371
|
// also the only form the server serves.
|
|
370
|
-
const ref = uri.replace(/^unoverse:\/\/(components|atoms|apps
|
|
372
|
+
const ref = uri.replace(/^unoverse:\/\/(components|atoms|apps)\//, "").replace(/\/definition$/, "").replace(/@[a-z]+$/, "");
|
|
371
373
|
const d = c.loadDefinition(ref, kind);
|
|
372
374
|
return d ? json(res, d) : json(res, { error: `definition not found: ${uri}` }, 404);
|
|
373
375
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
# A GENERIC CHAT — a worked example
|
|
1
|
+
# A GENERIC CHAT — a worked example app, shipped with Studio so apps/ is
|
|
2
2
|
# never an empty folder. It is yours: rename it, restyle it (it uses only foundation
|
|
3
3
|
# tokens, so your project's theme restyles it whole), or delete it.
|
|
4
|
-
# Guide: https://docs.unoverse.ai/design/
|
|
4
|
+
# Guide: https://docs.unoverse.ai/design/apps.md
|
|
5
5
|
name: chat
|
|
6
6
|
description: A conversational assistant surface with a welcome hero, suggestions, and a focus overlay.
|
|
7
7
|
whenToUse: Talk to the assistant — ask a question, get help, or start any task in conversation. The conversational home and fallback surface.
|
|
8
8
|
category: Assistant
|
|
9
9
|
version: 1.0.0
|
|
10
|
-
defaultState:
|
|
10
|
+
defaultState: app
|
|
11
11
|
inputSchema:
|
|
12
12
|
type: object
|
|
13
13
|
properties:
|
|
@@ -32,7 +32,7 @@ children:
|
|
|
32
32
|
children:
|
|
33
33
|
- type: Button
|
|
34
34
|
action:
|
|
35
|
-
type:
|
|
35
|
+
type: setAppValue
|
|
36
36
|
values:
|
|
37
37
|
- key: openPanel
|
|
38
38
|
value: null
|
|
@@ -70,7 +70,7 @@ children:
|
|
|
70
70
|
action:
|
|
71
71
|
type: sendMessage
|
|
72
72
|
then:
|
|
73
|
-
type:
|
|
73
|
+
type: setAppValue
|
|
74
74
|
values:
|
|
75
75
|
- key: openPanel
|
|
76
76
|
value: null
|
|
@@ -110,7 +110,7 @@ children:
|
|
|
110
110
|
children:
|
|
111
111
|
- type: Button
|
|
112
112
|
action:
|
|
113
|
-
type:
|
|
113
|
+
type: setAppValue
|
|
114
114
|
values:
|
|
115
115
|
- key: openPanel
|
|
116
116
|
value: faq
|
package/local/scaffold.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Creating a project, from either end.
|
|
3
3
|
*
|
|
4
|
-
* A project (an "org") is `design/<name>/` holding components, styles and
|
|
5
|
-
*
|
|
4
|
+
* A project (an "org") is `design/<name>/` holding components, styles and apps.
|
|
5
|
+
* It is the unit a developer authors in and the unit that publishes: every row
|
|
6
6
|
* that lands in a universe carries its name in the `org` column.
|
|
7
7
|
*
|
|
8
8
|
* ONE IMPLEMENTATION, TWO CALLERS. `bin/studio.mjs` creates the FIRST project at launch,
|
|
@@ -73,7 +73,7 @@ export function createProject(home, name) {
|
|
|
73
73
|
const root = join(designHome(home), name);
|
|
74
74
|
if (existsSync(root)) throw new Error(`${name} already exists in this workspace`);
|
|
75
75
|
|
|
76
|
-
for (const d of ["components/welcome", "
|
|
76
|
+
for (const d of ["components/welcome", "apps", "styles"]) mkdirSync(join(root, d), { recursive: true });
|
|
77
77
|
writeFileSync(join(root, "components/welcome/welcome.yaml"), WELCOME);
|
|
78
78
|
|
|
79
79
|
// Worked example styles (sample-styles: brand ingredients + the override rules), so
|
|
@@ -86,16 +86,16 @@ export function createProject(home, name) {
|
|
|
86
86
|
cpSync(sampleStyles, join(root, "styles"), { recursive: true });
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
// A worked example
|
|
90
|
-
// so
|
|
89
|
+
// A worked example APP (a generic chat: focus overlay + the chat region),
|
|
90
|
+
// so apps/ is never an empty folder and the first app is a rename away. Same
|
|
91
91
|
// rule as the styles: copied only into an empty folder, then the developer's.
|
|
92
|
-
const
|
|
93
|
-
if (existsSync(
|
|
94
|
-
cpSync(
|
|
92
|
+
const sampleApp = join(dirname(fileURLToPath(import.meta.url)), "sample-app/chat");
|
|
93
|
+
if (existsSync(sampleApp) && readdirSync(join(root, "apps")).length === 0) {
|
|
94
|
+
cpSync(sampleApp, join(root, "apps/chat"), { recursive: true });
|
|
95
95
|
// Its other half: the chat CX itself (welcome hero, timeline, suggestions,
|
|
96
|
-
// composer) is a COMPONENT the
|
|
97
|
-
// and every app of this project shares it. Copied with the
|
|
98
|
-
// it
|
|
96
|
+
// composer) is a COMPONENT the app draws via `Ref`, so the surface lives once
|
|
97
|
+
// and every app of this project shares it. Copied with the app, never without
|
|
98
|
+
// it: an app whose chat-region resolves nothing renders an empty column.
|
|
99
99
|
const sampleComponent = join(dirname(fileURLToPath(import.meta.url)), "sample-component/text-chat");
|
|
100
100
|
if (existsSync(sampleComponent)) cpSync(sampleComponent, join(root, "components/text-chat"), { recursive: true });
|
|
101
101
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unoverse-platform/studio",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.70",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Unoverse Studio: the local authoring app. Reads your own rx/ folder, no backend.",
|
|
6
6
|
"scripts": {
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"@unoverse-platform/base": "^0.3.37",
|
|
30
30
|
"@unoverse-platform/marketplace": "*",
|
|
31
31
|
"@vitejs/plugin-react": "^4.3.4",
|
|
32
|
+
"lucide-react": "^0.546.0",
|
|
32
33
|
"markdown-to-jsx": "^7.7.17",
|
|
33
34
|
"oidc-client-ts": "^3.4.1",
|
|
34
35
|
"react": "^18.2.0",
|
|
@@ -18,7 +18,7 @@ const APP_UI_URI = "ui://unoverse/app.html";
|
|
|
18
18
|
export function NativeAppHost({
|
|
19
19
|
server,
|
|
20
20
|
token,
|
|
21
|
-
|
|
21
|
+
appId,
|
|
22
22
|
userId,
|
|
23
23
|
conversationId,
|
|
24
24
|
onStatus,
|
|
@@ -26,7 +26,7 @@ export function NativeAppHost({
|
|
|
26
26
|
/** Universe origin, e.g. http://localhost:4105 — the /mcp and /stream host. */
|
|
27
27
|
server: string;
|
|
28
28
|
token?: string;
|
|
29
|
-
|
|
29
|
+
appId?: string;
|
|
30
30
|
userId: string;
|
|
31
31
|
conversationId: string;
|
|
32
32
|
/** The iframe app owns the ONLY live connection — it posts `unoverse:status`
|
|
@@ -53,7 +53,7 @@ export function NativeAppHost({
|
|
|
53
53
|
return () => window.removeEventListener("message", onSize);
|
|
54
54
|
}, [onStatus]);
|
|
55
55
|
// A different app starts back at the host default until it reports.
|
|
56
|
-
useEffect(() => setAppWidth(null), [
|
|
56
|
+
useEffect(() => setAppWidth(null), [appId]);
|
|
57
57
|
|
|
58
58
|
// Pull the app shell from the universe — one resources/read, then the client closes.
|
|
59
59
|
useEffect(() => {
|
|
@@ -110,13 +110,13 @@ then come back — Studio picks the session up by itself.`
|
|
|
110
110
|
frame.current?.contentWindow?.postMessage(
|
|
111
111
|
{
|
|
112
112
|
type: "unoverse:config",
|
|
113
|
-
config: { serverUrl: server, apiUrl: server,
|
|
113
|
+
config: { serverUrl: server, apiUrl: server, appId, userId, conversationId, token },
|
|
114
114
|
},
|
|
115
115
|
"*",
|
|
116
116
|
);
|
|
117
117
|
};
|
|
118
118
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
119
|
-
useEffect(postConfig, [token,
|
|
119
|
+
useEffect(postConfig, [token, appId, userId, conversationId, server, html]);
|
|
120
120
|
|
|
121
121
|
if (error)
|
|
122
122
|
return (
|