unoverse 0.1.169 → 0.1.171
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/README.md
CHANGED
|
@@ -8,7 +8,7 @@ npm install -g unoverse
|
|
|
8
8
|
unoverse create # asks what you're building, then sets it up
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
- **Studio** — author components,
|
|
11
|
+
- **Studio** — author components, apps, nodes, and agent skills. Most
|
|
12
12
|
people start (and stay) here.
|
|
13
13
|
- **Universe** — run the platform yourself, on your own infrastructure. Needs a
|
|
14
14
|
registry access token from your Unoverse admin.
|
|
@@ -29,7 +29,7 @@ unoverse stop
|
|
|
29
29
|
|
|
30
30
|
Creating a universe registers the `canvas` MCP (`.mcp.json`): open the folder in
|
|
31
31
|
Claude Code and describe the workflow you want, built live on your Canvas. The
|
|
32
|
-
authoring skills for components,
|
|
32
|
+
authoring skills for components, apps, themes, and custom nodes come with Studio
|
|
33
33
|
projects (`unoverse studio`). `unoverse update` keeps the CLI, a universe's images,
|
|
34
34
|
and any installed skills current; to add the skills to your own agent by hand:
|
|
35
35
|
`npx skills add unoverse-platform/skills`.
|
package/package.json
CHANGED
|
@@ -112,7 +112,7 @@ export function collectProject(designRoot, project) {
|
|
|
112
112
|
//
|
|
113
113
|
// Both become the same shape, a map of relative path to contents, so nothing downstream
|
|
114
114
|
// has to care which it was. A single file is simply a map with one entry.
|
|
115
|
-
for (const [dir, kind] of [["components", "component"], ["
|
|
115
|
+
for (const [dir, kind] of [["components", "component"], ["apps", "template"], ["atoms", "atom"]]) {
|
|
116
116
|
const home = join(root, dir);
|
|
117
117
|
if (!existsSync(home))
|
|
118
118
|
continue;
|
|
@@ -18,7 +18,7 @@ import { RAW_VALUE, PARTIAL_DIRS } from "./vocabulary.mjs";
|
|
|
18
18
|
import { isDefFile, defName, defPath, readDef, parseDef } from "./defs.mjs";
|
|
19
19
|
|
|
20
20
|
export function makeLintFile(ctx) {
|
|
21
|
-
const { report, walkNode, isFixture, isHook, isManifest,
|
|
21
|
+
const { report, walkNode, isFixture, isHook, isManifest, isAppPath, defRoot, componentNamesForFile, DS, orgDirs, RX, readText } = ctx;
|
|
22
22
|
|
|
23
23
|
function checkStateOrder(order, rootFolder, file, includeLayouts = false) {
|
|
24
24
|
if (!Array.isArray(order)) return;
|
|
@@ -90,7 +90,7 @@ function lintFile(file) {
|
|
|
90
90
|
|
|
91
91
|
if (isManifest(file)) {
|
|
92
92
|
const root = dirname(file);
|
|
93
|
-
if (
|
|
93
|
+
if (isAppPath(file)) {
|
|
94
94
|
// TEMPLATE manifest = the envelope. Requires binding + a resolvable root.
|
|
95
95
|
for (const req of ["name", "whenToUse"])
|
|
96
96
|
if (!json[req]) report("warn", file, `template manifest missing "${req}": ${req === "whenToUse" ? "the AI selects the app by it" : "the display name"} (docs/design/05)`);
|
|
@@ -302,9 +302,9 @@ function lintFile(file) {
|
|
|
302
302
|
if (json[k] !== undefined)
|
|
303
303
|
report("error", file, `envelope duplicates manifest meta "${k}". The discovery manifest is the single home (docs/design/03a)`);
|
|
304
304
|
|
|
305
|
-
// deprecated bridge: a top-level `defaultState` triggers the component node
|
|
305
|
+
// deprecated bridge: a top-level `defaultState` triggers the component node APP_DATA emit
|
|
306
306
|
if (json.defaultState !== undefined)
|
|
307
|
-
report("warn", file, `top-level "defaultState" is the deprecated bridge (component-node
|
|
307
|
+
report("warn", file, `top-level "defaultState" is the deprecated bridge (component-node APP_DATA emit). The master state lives in the \`state\` block; templates react via ComponentSlot.select.where (STATE_MODEL §5b)`);
|
|
308
308
|
|
|
309
309
|
// only components that ADOPTED the structure are held to the full discipline
|
|
310
310
|
if (hasLayouts || stateFiles.length || hasStateBlock) {
|
|
@@ -503,7 +503,7 @@ function lintFile(file) {
|
|
|
503
503
|
// bare partial (layouts/ states/ components/ blocks/, or an atom). A template
|
|
504
504
|
// layout's TOP-LEVEL node is the app's layout root — the one non-slot home for
|
|
505
505
|
// `appWidth` (state-owned sizing, docs/design/05).
|
|
506
|
-
walkNode(json, file, root, null, /[\\/]layouts[\\/][^\\/]+\.(json|yaml)$/.test(file) &&
|
|
506
|
+
walkNode(json, file, root, null, /[\\/]layouts[\\/][^\\/]+\.(json|yaml)$/.test(file) && isAppPath(file));
|
|
507
507
|
}
|
|
508
508
|
}
|
|
509
509
|
|
|
@@ -67,7 +67,7 @@ export function lintDefinitions(designRoot, options = {}) {
|
|
|
67
67
|
try {
|
|
68
68
|
return (
|
|
69
69
|
statSync(d).isDirectory() &&
|
|
70
|
-
["components", "atoms", "styles", "
|
|
70
|
+
["components", "atoms", "styles", "apps"].some((s) => existsSync(join(d, s)))
|
|
71
71
|
);
|
|
72
72
|
} catch {
|
|
73
73
|
return false;
|
|
@@ -164,7 +164,7 @@ const isHook = (f) => {
|
|
|
164
164
|
return life.some((e) => typeof e === "object" && e && typeof e.handler === "string" && e.handler.toLowerCase() === stem);
|
|
165
165
|
};
|
|
166
166
|
const isManifest = (f) => /^manifest\.(json|yaml)$/.test(basename(f));
|
|
167
|
-
const
|
|
167
|
+
const isAppPath = (f) => f.includes(`${sep}apps${sep}`);
|
|
168
168
|
// The DEFINITION ROOT folder for a file: a bare partial lives one level under it
|
|
169
169
|
// (layouts/ states/ components/ blocks/); every `$include` resolves against it.
|
|
170
170
|
const defRoot = (file) => {
|
|
@@ -535,7 +535,7 @@ const keyHomes = [
|
|
|
535
535
|
[join(DS, "components"), true],
|
|
536
536
|
];
|
|
537
537
|
for (const orgDir of orgDirs) {
|
|
538
|
-
keyHomes.push([join(orgDir, "components"), true], [join(orgDir, "
|
|
538
|
+
keyHomes.push([join(orgDir, "components"), true], [join(orgDir, "apps"), true]);
|
|
539
539
|
}
|
|
540
540
|
for (const [dir, isDir] of keyHomes) {
|
|
541
541
|
if (!existsSync(dir)) continue;
|
|
@@ -568,7 +568,7 @@ for (const [dir, isDir] of keyHomes) {
|
|
|
568
568
|
// components (same law everywhere — an org component is a component, just org-private)
|
|
569
569
|
const homes = [{ dir: join(DS, "components") }, { dir: join(DS, "atoms") }];
|
|
570
570
|
for (const orgDir of orgDirs) {
|
|
571
|
-
for (const sub of ["
|
|
571
|
+
for (const sub of ["apps", "components"]) {
|
|
572
572
|
const d = join(orgDir, sub);
|
|
573
573
|
if (existsSync(d) && statSync(d).isDirectory()) homes.push({ dir: d });
|
|
574
574
|
}
|
|
@@ -610,7 +610,7 @@ for (const orgDir of orgDirs) {
|
|
|
610
610
|
// Exactly one app is an org's home; a host opens the app whose manifest sets `default: true`
|
|
611
611
|
// as the conversation's entry point. Two defaults = ambiguous front door → error.
|
|
612
612
|
for (const orgDir of orgDirs) {
|
|
613
|
-
const tdir = join(orgDir, "
|
|
613
|
+
const tdir = join(orgDir, "apps");
|
|
614
614
|
if (!(existsSync(tdir) && statSync(tdir).isDirectory())) continue;
|
|
615
615
|
const defaults = [];
|
|
616
616
|
for (const e of readdirSync(tdir)) {
|
|
@@ -627,7 +627,7 @@ for (const orgDir of orgDirs) {
|
|
|
627
627
|
report(
|
|
628
628
|
"error",
|
|
629
629
|
mf,
|
|
630
|
-
`org "${org}" has ${defaults.length} apps with "default": true. An org has exactly ONE default app (its /mcp/${org} front door). Keep it on one manifest, remove it from the others (docs/unoverse/
|
|
630
|
+
`org "${org}" has ${defaults.length} apps with "default": true. An org has exactly ONE default app (its /mcp/${org} front door). Keep it on one manifest, remove it from the others (docs/unoverse/UNOVERSE_MCP_APP_PROTOCOL.md §4b)`,
|
|
631
631
|
);
|
|
632
632
|
}
|
|
633
633
|
|
|
@@ -875,7 +875,7 @@ function componentNamesForFile(file) {
|
|
|
875
875
|
const ctx = {
|
|
876
876
|
RX, DS, orgDirs, report, spaceSteps, stepList, checkDimension, checkToken, checkCondition,
|
|
877
877
|
appSizesForFile, componentNamesForFile, refResolves, canonicalRef, declaredProps, atomsDirExists,
|
|
878
|
-
isFixture, isHook, isManifest,
|
|
878
|
+
isFixture, isHook, isManifest, isAppPath, defRoot, readText,
|
|
879
879
|
};
|
|
880
880
|
const walkNode = makeWalkNode(ctx);
|
|
881
881
|
const lintFile = makeLintFile({ ...ctx, walkNode });
|
|
@@ -18,7 +18,7 @@ import { PRIMITIVES, CONDITION_KEYS, STYLE_KEYS, RAW_VALUE, CHILD_NODE_KEYS, PAR
|
|
|
18
18
|
import { isDefFile, defName, defPath, readDef } from "./defs.mjs";
|
|
19
19
|
|
|
20
20
|
export function makeWalkNode(ctx) {
|
|
21
|
-
const { report, checkCondition, checkDimension, checkToken, appSizesForFile, componentNamesForFile, refResolves, canonicalRef, declaredProps, atomsDirExists, stepList, spaceSteps,
|
|
21
|
+
const { report, checkCondition, checkDimension, checkToken, appSizesForFile, componentNamesForFile, refResolves, canonicalRef, declaredProps, atomsDirExists, stepList, spaceSteps, isAppPath, defRoot } = ctx;
|
|
22
22
|
|
|
23
23
|
function walkNode(node, file, root, widthCap = null, isLayoutRoot = false) {
|
|
24
24
|
if (Array.isArray(node)) return node.forEach((n) => walkNode(n, file, root, widthCap));
|
|
@@ -197,11 +197,11 @@ function walkNode(node, file, root, widthCap = null, isLayoutRoot = false) {
|
|
|
197
197
|
|
|
198
198
|
// deprecated FOCUS BRIDGE (STATE_MODEL §5b): nothing writes a template `defaultState`
|
|
199
199
|
// key anymore — a component writes only its own slice; templates react via select.where.
|
|
200
|
-
// (Legit panel/draft
|
|
200
|
+
// (Legit panel/draft setAppValue writes a DIFFERENT key and is untouched.)
|
|
201
201
|
//
|
|
202
202
|
const scanAction = (a) => {
|
|
203
203
|
if (!a || typeof a !== "object") return;
|
|
204
|
-
if (a.type === "
|
|
204
|
+
if (a.type === "setAppValue") {
|
|
205
205
|
// WHAT IS DEPRECATED IS THE FOCUS BRIDGE, not the action (2026-08-23, owner
|
|
206
206
|
// ruling). Writing `defaultState` to move the template is the bridge: a component
|
|
207
207
|
// reaching up to change which surface is shown, which the tree reacts to by name
|
|
@@ -209,7 +209,7 @@ function walkNode(node, file, root, widthCap = null, isLayoutRoot = false) {
|
|
|
209
209
|
// anywhere — a `Ref`-embedded component has no slice of its own, so template state
|
|
210
210
|
// is the only state in scope, and the shared chat chrome depends on it.
|
|
211
211
|
if (Array.isArray(a.values) && a.values.some((v) => v && v.key === "defaultState"))
|
|
212
|
-
report(
|
|
212
|
+
report(isAppPath(file) ? "warn" : "error", file, `setAppValue writing "defaultState" is the deprecated focus bridge. Change the view with setValue and let the template react via ComponentSlot.select.where (STATE_MODEL §5b)`);
|
|
213
213
|
}
|
|
214
214
|
if (a.then) scanAction(a.then);
|
|
215
215
|
};
|