@unoverse-platform/studio 0.1.69 → 0.1.71

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 (40) hide show
  1. package/bin/studio.mjs +1 -1
  2. package/local/catalog.ts +2 -2
  3. package/local/mcp.ts +3 -2
  4. package/local/plugin.ts +13 -11
  5. package/local/{sample-template → sample-app}/chat/manifest.yaml +3 -3
  6. package/local/sample-component/text-chat/components/suggestions.yaml +3 -3
  7. package/local/scaffold.mjs +11 -11
  8. package/package.json +2 -1
  9. package/screens/NativeAppHost.tsx +5 -5
  10. package/screens/TemplatesView.tsx +77 -77
  11. package/screens/chrome.ts +1 -1
  12. package/screens/states.ts +3 -3
  13. package/src/App.tsx +20 -9
  14. package/src/NewProject.tsx +1 -1
  15. package/src/host.ts +2 -2
  16. package/vendor/sdk/appEngine.tsx +18 -18
  17. package/vendor/sdk/lib/{template.tsx → app.tsx} +11 -11
  18. package/vendor/sdk/lib/connection.tsx +10 -10
  19. package/vendor/sdk/lib/core/actions.ts +3 -3
  20. package/vendor/sdk/lib/core/{templates.ts → apps.ts} +1 -1
  21. package/vendor/sdk/lib/core/connection.ts +17 -15
  22. package/vendor/sdk/lib/core/index.ts +2 -2
  23. package/vendor/sdk/lib/core/store.ts +22 -22
  24. package/vendor/sdk/lib/core/types.ts +2 -2
  25. package/vendor/sdk/lib/index.ts +3 -3
  26. package/vendor/sdk/lib/render.tsx +1 -1
  27. package/vendor/sdk/lib/voice.tsx +2 -2
  28. package/vendor/sdk/main.tsx +7 -7
  29. package/vendor/skills/unoverse-create/SKILL.md +9 -9
  30. package/vendor/skills/unoverse-create/references/{template.md → app.md} +34 -34
  31. package/vendor/skills/unoverse-create/references/brand.md +8 -8
  32. package/vendor/skills/unoverse-create/references/component.md +20 -20
  33. package/vendor/skills/unoverse-create/references/node.md +2 -2
  34. package/vendor/skills/unoverse-prompt-component/SKILL.md +2 -2
  35. /package/local/{sample-template → sample-app}/chat/components/chat-region.yaml +0 -0
  36. /package/local/{sample-template → sample-app}/chat/components/core-focus.yaml +0 -0
  37. /package/local/{sample-template → sample-app}/chat/components/core.yaml +0 -0
  38. /package/local/{sample-template → sample-app}/chat/components/focus-overlay.yaml +0 -0
  39. /package/local/{sample-template → sample-app}/chat/layouts/focused.yaml +0 -0
  40. /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 `templates` as orgs.
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
- listTemplatesForWorkbench: (org?: string) => unknown[];
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
- listTemplatesForWorkbench: defs.listTemplatesForWorkbench as Loaders["listTemplatesForWorkbench"],
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
- const ref = String(uri).replace(/^unoverse:\/\/(components|atoms|apps|templates)\//, "");
159
- const kind = /\/(apps|templates)\//.test(String(uri)) ? "template" : /\/atoms\//.test(String(uri)) ? "atom" : "component";
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/templates?org= GET /dev/atoms
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 a template's.
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. Templates are
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
- const ref = uri.replace(/^unoverse:\/\/(components|atoms|apps|templates)\//, "");
44
- if (/\/(apps|templates)\//.test(uri)) {
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), "templates", name.toLowerCase());
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/templates":
350
- return json(res, { templates: c.listTemplatesForWorkbench(org) });
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://templates/<org>/<id>); both load the template kind.
364
- const kind = /\/(apps|templates)\//.test(uri) ? "template" : /\/atoms\//.test(uri) ? "atom" : "component";
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|templates)\//, "").replace(/@[a-z]+$/, "");
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 template, shipped with Studio so templates/ is
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/templates.md
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: template
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: setTemplateValue
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: setTemplateValue
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: setTemplateValue
113
+ type: setAppValue
114
114
  values:
115
115
  - key: openPanel
116
116
  value: faq
@@ -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
- * templates. It is the unit a developer authors in and the unit that publishes: every row
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", "templates", "styles"]) mkdirSync(join(root, d), { recursive: true });
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 TEMPLATE (a generic chat: focus overlay + the chat region),
90
- // so templates/ is never an empty folder and the first app is a rename away. Same
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 sampleTemplate = join(dirname(fileURLToPath(import.meta.url)), "sample-template/chat");
93
- if (existsSync(sampleTemplate) && readdirSync(join(root, "templates")).length === 0) {
94
- cpSync(sampleTemplate, join(root, "templates/chat"), { recursive: true });
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 template draws via `Ref`, so the surface lives once
97
- // and every app of this project shares it. Copied with the template, never without
98
- // it — a template whose chat-region resolves nothing renders an empty column.
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.69",
3
+ "version": "0.1.71",
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
- templateId,
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
- templateId?: string;
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), [templateId]);
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, templateId, userId, conversationId, token },
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, templateId, userId, conversationId, server, html]);
119
+ useEffect(postConfig, [token, appId, userId, conversationId, server, html]);
120
120
 
121
121
  if (error)
122
122
  return (