@unoverse-platform/studio 0.1.90 → 0.1.92
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 +21 -65
- package/package.json +13 -3
- package/vendor/base/src/agent-mcp/discovery.ts +203 -0
- package/vendor/base/src/agent-mcp/handoff.ts +56 -0
- package/vendor/base/src/agent-mcp/index.ts +44 -0
- package/vendor/base/src/agent-mcp/invoke.ts +153 -0
- package/vendor/base/src/agent-mcp/lean.ts +114 -0
- package/vendor/base/src/agent-mcp/memoryContext.ts +104 -0
- package/vendor/base/src/agent-mcp/memoryIngest.ts +88 -0
- package/vendor/base/src/agent-mcp/rowComponents.ts +234 -0
- package/vendor/base/src/agent-mcp/tools.ts +134 -0
- package/vendor/base/src/agent-mcp/types.ts +138 -0
- package/vendor/base/src/auth/auth.ts +307 -0
- package/vendor/base/src/auth/authConfig.ts +73 -0
- package/vendor/base/src/boot.ts +245 -0
- package/vendor/base/src/definitions/definitions.ts +18 -0
- package/vendor/base/src/definitions/dsPackage.ts +45 -0
- package/vendor/base/src/definitions/fsCache.ts +155 -0
- package/vendor/base/src/definitions/index.ts +19 -0
- package/vendor/base/src/definitions/inputs.ts +30 -0
- package/vendor/base/src/definitions/load/compile/component.ts +80 -0
- package/vendor/base/src/definitions/load/compile/index.ts +12 -0
- package/vendor/base/src/definitions/load/compile/sections.ts +132 -0
- package/vendor/base/src/definitions/load/compile/template.ts +153 -0
- package/vendor/base/src/definitions/load/expand/includes.ts +27 -0
- package/vendor/base/src/definitions/load/expand/index.ts +17 -0
- package/vendor/base/src/definitions/load/expand/inherit.ts +169 -0
- package/vendor/base/src/definitions/load/expand/referenced.ts +164 -0
- package/vendor/base/src/definitions/load/expand/refs.ts +106 -0
- package/vendor/base/src/definitions/load/expand/walk.ts +91 -0
- package/vendor/base/src/definitions/load/load.ts +487 -0
- package/vendor/base/src/definitions/model.ts +238 -0
- package/vendor/base/src/definitions/registry/catalog.ts +659 -0
- package/vendor/base/src/definitions/registry/homes.ts +252 -0
- package/vendor/base/src/definitions/theme.ts +383 -0
- package/vendor/base/src/executor.ts +284 -0
- package/vendor/base/src/http/uwsFetchAdapter.ts +397 -0
- package/vendor/base/src/items/baseVersion.ts +96 -0
- package/vendor/base/src/items/collect.ts +246 -0
- package/vendor/base/src/items/fingerprint.ts +34 -0
- package/vendor/base/src/items/publish.ts +212 -0
- package/vendor/base/src/lint/context.mjs +56 -0
- package/vendor/base/src/lint/design/defs.mjs +46 -0
- package/vendor/base/src/lint/design/file.mjs +737 -0
- package/vendor/base/src/lint/design/index.d.mts +30 -0
- package/vendor/base/src/lint/design/index.mjs +996 -0
- package/vendor/base/src/lint/design/tokens.mjs +128 -0
- package/vendor/base/src/lint/design/vocabulary.mjs +181 -0
- package/vendor/base/src/lint/design/walk.mjs +444 -0
- package/vendor/base/src/lint/index.mjs +28 -0
- package/vendor/base/src/lint/nodes/blocks.mjs +63 -0
- package/vendor/base/src/lint/nodes/index.d.mts +28 -0
- package/vendor/base/src/lint/nodes/index.mjs +82 -0
- package/vendor/base/src/lint/nodes/load.mjs +126 -0
- package/vendor/base/src/lint/nodes/node.mjs +764 -0
- package/vendor/base/src/lint/nodes/package.mjs +112 -0
- package/vendor/base/src/lint/nodes/schema.mjs +75 -0
- package/vendor/base/src/logger.ts +33 -0
- package/vendor/base/src/manifests/compose.ts +539 -0
- package/vendor/base/src/manifests/executor/callback.ts +107 -0
- package/vendor/base/src/manifests/executor/context.ts +110 -0
- package/vendor/base/src/manifests/executor/index.ts +47 -0
- package/vendor/base/src/manifests/executor/lookup.ts +21 -0
- package/vendor/base/src/manifests/executor/memory.ts +62 -0
- package/vendor/base/src/manifests/executor/promise.ts +71 -0
- package/vendor/base/src/manifests/executor/refire.ts +34 -0
- package/vendor/base/src/manifests/executor/send.ts +65 -0
- package/vendor/base/src/manifests/executor/session.ts +46 -0
- package/vendor/base/src/manifests/executor/toolBridge.ts +266 -0
- package/vendor/base/src/manifests/index.ts +280 -0
- package/vendor/base/src/manifests/integrity.ts +93 -0
- package/vendor/base/src/manifests/rows.ts +33 -0
- package/vendor/base/src/manifests/runtime/auth/aws.ts +240 -0
- package/vendor/base/src/manifests/runtime/auth/oauth.ts +76 -0
- package/vendor/base/src/manifests/runtime/authorize.ts +159 -0
- package/vendor/base/src/manifests/runtime/channels/final.ts +171 -0
- package/vendor/base/src/manifests/runtime/channels/service.ts +138 -0
- package/vendor/base/src/manifests/runtime/channels/workflow.ts +310 -0
- package/vendor/base/src/manifests/runtime/context.ts +156 -0
- package/vendor/base/src/manifests/runtime/docstore/handlers.ts +547 -0
- package/vendor/base/src/manifests/runtime/docstore/index.ts +135 -0
- package/vendor/base/src/manifests/runtime/docstore/sectionizer.ts +184 -0
- package/vendor/base/src/manifests/runtime/docstore/store.ts +172 -0
- package/vendor/base/src/manifests/runtime/docstore/types.ts +60 -0
- package/vendor/base/src/manifests/runtime/duplex/audioLane.ts +165 -0
- package/vendor/base/src/manifests/runtime/duplex/session.ts +511 -0
- package/vendor/base/src/manifests/runtime/events.ts +223 -0
- package/vendor/base/src/manifests/runtime/helpers.ts +53 -0
- package/vendor/base/src/manifests/runtime/http/allowedHosts.ts +118 -0
- package/vendor/base/src/manifests/runtime/http/request.ts +637 -0
- package/vendor/base/src/manifests/runtime/http/response.ts +162 -0
- package/vendor/base/src/manifests/runtime/index.ts +57 -0
- package/vendor/base/src/manifests/runtime/loops/chunk.ts +98 -0
- package/vendor/base/src/manifests/runtime/loops/loop.ts +191 -0
- package/vendor/base/src/manifests/runtime/loops/paginate.ts +143 -0
- package/vendor/base/src/manifests/runtime/loops/poll.ts +169 -0
- package/vendor/base/src/manifests/runtime/narrate.ts +53 -0
- package/vendor/base/src/manifests/runtime/state.ts +171 -0
- package/vendor/base/src/manifests/runtime/templating.ts +110 -0
- package/vendor/base/src/manifests/runtime/tools/regionSkills.ts +125 -0
- package/vendor/base/src/manifests/runtime/tools/toolloop.ts +550 -0
- package/vendor/base/src/manifests/runtime/usage.ts +87 -0
- package/vendor/base/src/manifests/source.ts +196 -0
- package/vendor/base/src/manifests/watch.ts +67 -0
- package/vendor/base/src/markdown/document.ts +141 -0
- package/vendor/base/src/paths.ts +180 -0
- package/vendor/base/src/platform/audioManager.ts +100 -0
- package/vendor/base/src/platform/clientTransport.ts +65 -0
- package/vendor/base/src/platform/executionContext.ts +157 -0
- package/vendor/base/src/platform/index.ts +10 -0
- package/vendor/base/src/platform/pluginAPI.ts +146 -0
- package/vendor/base/src/platform/redis.ts +54 -0
- package/vendor/base/src/platform/serviceCalls.ts +222 -0
- package/vendor/base/src/pluginBase.ts +248 -0
- package/vendor/base/src/plugins/README.md +57 -0
- package/vendor/base/src/plugins/discovery.ts +87 -0
- package/vendor/base/src/plugins/index.ts +16 -0
- package/vendor/base/src/plugins/loader.ts +179 -0
- package/vendor/base/src/plugins/redis.ts +36 -0
- package/vendor/base/src/plugins/state.ts +53 -0
- package/vendor/base/src/registry.ts +95 -0
- package/vendor/base/src/security/permissions.ts +93 -0
- package/vendor/base/src/security/rules.ts +73 -0
- package/vendor/base/src/template/SafeExpression.ts +556 -0
- package/vendor/base/src/template/StringTemplateResolver.ts +75 -0
- package/vendor/base/src/template/index.ts +22 -0
- package/vendor/base/src/types.ts +50 -0
- package/vendor/base/src/workflow/GetMemoryService.ts +158 -0
- package/vendor/base/src/workflow/PlatformMemoryCore.ts +449 -0
- package/vendor/base/src/workflow/routing.ts +122 -0
- package/vendor/sdk/lib/core/store.ts +26 -6
- package/local/mcp.ts +0 -265
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unoverse definition store (v0: flat JSON files under apps/unoverse/design/{components,templates}).
|
|
3
|
+
*
|
|
4
|
+
* These are the NEUTRAL primitive+style definitions — NOT the legacy
|
|
5
|
+
* React UMD bundles in packages/design-system/components. Later this
|
|
6
|
+
* becomes a DB/Redis store for hot-reload + per-tenant (resources/updated).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readdirSync, existsSync, statSync } from "fs";
|
|
10
|
+
import { join, dirname } from "path";
|
|
11
|
+
import { fileURLToPath } from "url";
|
|
12
|
+
import { packagedDesignSystem } from "../dsPackage.js";
|
|
13
|
+
import { readDefCached, defPath, isDefFile, defName, dirSignature, cachedBySignature } from "../fsCache.js";
|
|
14
|
+
import { NODES_HOME, PLUGINS_DIR, DESIGN_HOME, INSTALLED_HOME, databaseOnly, designDir } from "../../paths.js";
|
|
15
|
+
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
// server/src -> apps/unoverse
|
|
18
|
+
|
|
19
|
+
// Split 2026-08-29 (owner: files stay ~500-800 lines): definitions.ts is the
|
|
20
|
+
// load/expand CORE and the public façade; homes.ts = where things live; model.ts =
|
|
21
|
+
// the served shapes + kind words; catalog.ts = listing, manifests, state order.
|
|
22
|
+
|
|
23
|
+
import { SHARED_DIR, projectDir, listOrgs, appDirs, templateDirs, componentDirs, resolveComponentDirs, componentHit, parseRef } from "../registry/homes.js";
|
|
24
|
+
import type { UnoverseDefinition, ComponentManifest } from "../model.js";
|
|
25
|
+
import type { AppManifest } from "../registry/catalog.js";
|
|
26
|
+
import { normalizeDeclaredKind, defaultLayoutOf } from "../model.js";
|
|
27
|
+
|
|
28
|
+
import { composeIncludes, expandNode, expandRefs, referencedDefinition, inheritState, mergeInheritedProps } from "./expand/index.js";
|
|
29
|
+
import { compileComponentTree, synthesizeTemplateRoot } from "./compile/index.js";
|
|
30
|
+
type AnyNode = Record<string, any>;
|
|
31
|
+
|
|
32
|
+
/** Resolve a definition by name (case-insensitive), searching the relevant folder(s).
|
|
33
|
+
*
|
|
34
|
+
* The EXPANDED result (clone + $include composition + Ref inlining — the expensive
|
|
35
|
+
* part, run on every MCP read and workbench poll) is memoized on the mtimes of its
|
|
36
|
+
* inputs: the def's own file/folder tree plus the atoms dir (any Ref may point at any
|
|
37
|
+
* atom). Edit-and-refresh still works — an mtime moves, the signature changes, the
|
|
38
|
+
* def re-expands. Returned objects are SHARED: callers must treat them as read-only
|
|
39
|
+
* (all current consumers stringify or shallow-spread). */
|
|
40
|
+
export function loadDefinition(
|
|
41
|
+
ref: string,
|
|
42
|
+
kind?: "component" | "app" | "atom" | "template",
|
|
43
|
+
/** "referenced" keeps `Ref` nodes and ships each named atom once in `defs`, for clients
|
|
44
|
+
* that resolve references themselves. Default inlines every Ref, as it always has. */
|
|
45
|
+
mode: "expanded" | "referenced" = "expanded",
|
|
46
|
+
/** The org of the definition/app doing the asking. A bare component ref resolves in
|
|
47
|
+
* this org first (a cloned pack's own refs mean its own components) — without it a
|
|
48
|
+
* name two orgs share is an error rather than a guess. */
|
|
49
|
+
contextOrg?: string,
|
|
50
|
+
): UnoverseDefinition | null {
|
|
51
|
+
// The two forms are memoized separately: same inputs, different output. Expansion
|
|
52
|
+
// reads the def's own org (stamped before form runs) so nested bare refs resolve
|
|
53
|
+
// within the def's org first.
|
|
54
|
+
const form = (d: UnoverseDefinition) => (mode === "referenced" ? referencedDefinition(d, d.org) : expandRefs(d));
|
|
55
|
+
const suffix = mode === "referenced" ? ":ref" : "";
|
|
56
|
+
// Default lookup is for SERVED kinds only (component/app). Atoms are
|
|
57
|
+
// internal — load them explicitly with kind: "atom" from the composition resolver.
|
|
58
|
+
const kinds: ("component" | "app" | "atom" | "template")[] = kind ? [kind] : ["component", "app"];
|
|
59
|
+
const { org: refOrg, name } = parseRef(ref);
|
|
60
|
+
const lower = name.toLowerCase();
|
|
61
|
+
for (const k of kinds) {
|
|
62
|
+
// Atoms live in ONE shared home. Components live in two tiers (marketplace +
|
|
63
|
+
// org packs). Apps live per org — search the ref's org (or all, bare).
|
|
64
|
+
// Templates (the Template Model kind) live in two tiers like components.
|
|
65
|
+
const dirs =
|
|
66
|
+
k === "app" ? appDirs(refOrg)
|
|
67
|
+
: k === "component" ? resolveComponentDirs(componentDirs(refOrg), refOrg, lower, contextOrg)
|
|
68
|
+
: k === "template" ? templateDirs(refOrg)
|
|
69
|
+
: [{ org: undefined as string | undefined, dir: SHARED_DIR[k] }];
|
|
70
|
+
for (const { org, dir } of dirs) {
|
|
71
|
+
// Flat form: <name>.{yaml,json}. The cached parse is SHARED — clone before expanding
|
|
72
|
+
// (expandRefs/composeIncludes mutate `root` in place); atoms are returned as-is
|
|
73
|
+
// (their consumers only read + clone the subtree).
|
|
74
|
+
const path = defPath(dir, lower);
|
|
75
|
+
if (path) {
|
|
76
|
+
const raw = readDefCached<UnoverseDefinition>(path);
|
|
77
|
+
// AN ATOM MAY COMPOSE ANOTHER ATOM, so it expands like everything else. Returning
|
|
78
|
+
// it raw was safe only while no atom carried a `Ref`: the composition resolver
|
|
79
|
+
// recurses, so a COMPONENT still drew the whole tree, but the atom served on its
|
|
80
|
+
// own (Studio's `unoverse://atoms/{name}`) kept a bare `Ref` that renders as
|
|
81
|
+
// NOTHING — observed live 2026-08-09, a form-toggle with no switch. Cloned before
|
|
82
|
+
// expanding, because the parsed object is shared and expansion mutates in place.
|
|
83
|
+
if (k === "atom") {
|
|
84
|
+
const sig = `${path}:${statSync(path).mtimeMs};${dirSignature([SHARED_DIR.atom])}`;
|
|
85
|
+
return cachedBySignature(`def:atom:${lower}${suffix}`, sig, () => form(structuredClone(raw)));
|
|
86
|
+
}
|
|
87
|
+
const sig = `${path}:${statSync(path).mtimeMs};${dirSignature([SHARED_DIR.atom, SHARED_DIR.component])}`;
|
|
88
|
+
// The org is stamped BEFORE form so expansion knows whose tree it is walking
|
|
89
|
+
// (bare nested refs resolve in the def's own org first).
|
|
90
|
+
return cachedBySignature(`def:${k}:${org ?? ""}:${lower}${suffix}`, sig, () => {
|
|
91
|
+
const d = Object.assign(structuredClone(raw), org ? { org } : {}) as UnoverseDefinition;
|
|
92
|
+
normalizeDeclaredKind(d);
|
|
93
|
+
if (k === "template") synthesizeTemplateRoot(d);
|
|
94
|
+
// The FLAT single-file form compiles its state tree exactly like the
|
|
95
|
+
// folder form (unified 2026-08-29 — a template-local part is flat).
|
|
96
|
+
if (k === "component") compileComponentTree(d);
|
|
97
|
+
return form(d);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
// Folder form: <name>/<name>.{yaml,json} (+ $include sibling files, states/, …).
|
|
101
|
+
// A TEMPLATE folder needs no envelope at all: the manifest IS the metadata and
|
|
102
|
+
// the default layout is the root (standard anatomy — manifest + layouts/ +
|
|
103
|
+
// components/ + states/). An envelope, when present, remains an override.
|
|
104
|
+
const folderDir = join(dir, lower);
|
|
105
|
+
const entry = defPath(folderDir, lower);
|
|
106
|
+
const manifestPath = defPath(folderDir, "manifest");
|
|
107
|
+
const manifestOnly = k === "app" && !entry && !!manifestPath;
|
|
108
|
+
if (entry || manifestOnly) {
|
|
109
|
+
// The signature must cover EVERYTHING the composed def embeds. An org definition
|
|
110
|
+
// may `Ref` an org SIBLING (deal-list embeds deal's card at expansion), so the
|
|
111
|
+
// org's whole components dir rides the signature — without it, editing the
|
|
112
|
+
// referenced component leaves every referrer serving a stale embedded copy
|
|
113
|
+
// (observed live 2026-08-25: the deal rail kept the old tile after deal changed).
|
|
114
|
+
const sig = `${dirSignature([folderDir])};${dirSignature([SHARED_DIR.atom, SHARED_DIR.component])};${org ? dirSignature([join(projectDir(org), "components")]) : ""}`;
|
|
115
|
+
return cachedBySignature(`def:${k}:${org ?? ""}:${lower}${suffix}`, sig, () => {
|
|
116
|
+
const def: UnoverseDefinition = manifestOnly
|
|
117
|
+
? (() => {
|
|
118
|
+
// manifestOnly is only true when manifestPath resolved.
|
|
119
|
+
const m = readDefCached<AppManifest>(manifestPath!);
|
|
120
|
+
return {
|
|
121
|
+
unoverse: "1.0",
|
|
122
|
+
kind: "app",
|
|
123
|
+
name: lower,
|
|
124
|
+
...(m.description ? { description: m.description } : {}),
|
|
125
|
+
...(m.whenToUse ? { whenToUse: m.whenToUse } : {}),
|
|
126
|
+
...(m.category ? { category: m.category } : {}),
|
|
127
|
+
...(m.service ? { service: m.service } : {}),
|
|
128
|
+
} as UnoverseDefinition;
|
|
129
|
+
})()
|
|
130
|
+
// ...otherwise `entry` resolved (the `if` admits only these two cases).
|
|
131
|
+
: structuredClone(readDefCached<UnoverseDefinition>(entry!));
|
|
132
|
+
normalizeDeclaredKind(def);
|
|
133
|
+
if (org) def.org = org;
|
|
134
|
+
// APP manifest merge (ONE FOLDER GRAMMAR, owner ruling 2026-08-29): an app
|
|
135
|
+
// with an ENVELOPE (`<name>.yaml` = the states tree, what the app IS) still
|
|
136
|
+
// takes its discovery meta and MCP face from the manifest, exactly like a
|
|
137
|
+
// component. The manifestOnly branch above already injected these for the
|
|
138
|
+
// legacy envelope-less form.
|
|
139
|
+
// COMPOSITION CONTEXT SURVIVES THE MERGE (UNOVERSE_AUTHORING §3b, 2026-08-30).
|
|
140
|
+
// The manifest's `description` is the DISCOVERY subtitle; a definition that an
|
|
141
|
+
// Agent fills also has something to say about filling the WHOLE of it (a form's
|
|
142
|
+
// "a value you propose is a suggestion until the applicant confirms it", a
|
|
143
|
+
// page's "on refinement turns, MERGE"). That used to ride an unbound `brief` on
|
|
144
|
+
// the layout root; with briefs retired it is the envelope's own `description`,
|
|
145
|
+
// and this keeps it when the manifest's overwrites it. Authors still write one
|
|
146
|
+
// word in each file; only the compiler sees the split.
|
|
147
|
+
const keepComposition = (def: UnoverseDefinition, incoming?: string): void => {
|
|
148
|
+
if (incoming && def.description && def.description !== incoming)
|
|
149
|
+
(def as unknown as Record<string, unknown>).brief = def.description;
|
|
150
|
+
};
|
|
151
|
+
if (k === "app" && manifestPath && !manifestOnly) {
|
|
152
|
+
const m = readDefCached<AppManifest & { title?: string; version?: string }>(manifestPath);
|
|
153
|
+
keepComposition(def, m.description);
|
|
154
|
+
if (m.description) def.description = m.description;
|
|
155
|
+
if (m.whenToUse) def.whenToUse = m.whenToUse;
|
|
156
|
+
if (m.category) def.category = m.category;
|
|
157
|
+
if (m.title) def.title = m.title;
|
|
158
|
+
if (m.version) def.version = m.version;
|
|
159
|
+
if (m.service) (def as unknown as Record<string, unknown>).service = m.service;
|
|
160
|
+
}
|
|
161
|
+
// TEMPLATE manifest (STANDARD, like components): the home for discovery
|
|
162
|
+
// meta and the Studio preview mocks. Manifest wins over the envelope so the
|
|
163
|
+
// meta has one authoritative home.
|
|
164
|
+
if (k === "template" && manifestPath) {
|
|
165
|
+
const m = readDefCached<Record<string, unknown>>(manifestPath);
|
|
166
|
+
// THE BRIEF IS AUTHORED (§9d): `brief:` on the envelope is the word, and it wins.
|
|
167
|
+
// `keepComposition` below is the LEGACY rescue for a template written before the
|
|
168
|
+
// word existed, which said its brief in the envelope's `description` and relied
|
|
169
|
+
// on the manifest's discovery copy displacing it.
|
|
170
|
+
if (!(def as unknown as Record<string, unknown>).brief)
|
|
171
|
+
keepComposition(def, typeof m.description === "string" ? m.description : undefined);
|
|
172
|
+
for (const key of ["title", "description", "whenToUse", "category", "version"] as const) {
|
|
173
|
+
if (m[key] != null) (def as unknown as Record<string, unknown>)[key] = m[key];
|
|
174
|
+
}
|
|
175
|
+
if (m.preview && typeof m.preview === "object") def.preview = m.preview as Record<string, string[]>;
|
|
176
|
+
// SPATIAL-CAPABLE, exactly as a component is (§9c, ruled 2026-08-30): a
|
|
177
|
+
// template is found by what a guest says, through the same projection, the
|
|
178
|
+
// same registry switch and the same embedded text. The difference is only
|
|
179
|
+
// where eligibility comes from — a component's manifest is OPTIONAL, so its
|
|
180
|
+
// presence is the signal; a template's is MANDATORY, so presence says
|
|
181
|
+
// nothing and the registry row is the whole opt-in (default OFF).
|
|
182
|
+
def.spatial = true;
|
|
183
|
+
}
|
|
184
|
+
// COMPONENT manifest (OPTIONAL — its PRESENCE means "spatial-capable"; a basic
|
|
185
|
+
// component has none and is never discovered). It is the single home for the
|
|
186
|
+
// discovery meta (description/whenToUse/title): merged over the def so every
|
|
187
|
+
// consumer (nodegen, workbench, catalog) reads one merged shape. `spatial: true`
|
|
188
|
+
// is the projected capability flag — derived from presence, never authored.
|
|
189
|
+
if (k === "component" && manifestPath) {
|
|
190
|
+
const m = readDefCached<ComponentManifest>(manifestPath);
|
|
191
|
+
keepComposition(def, m.description);
|
|
192
|
+
if (m.description) def.description = m.description;
|
|
193
|
+
if (m.whenToUse) def.whenToUse = m.whenToUse;
|
|
194
|
+
if (m.category) def.category = m.category;
|
|
195
|
+
if (m.title) def.title = m.title;
|
|
196
|
+
if (m.icon) def.icon = m.icon;
|
|
197
|
+
if (m.version) def.version = m.version;
|
|
198
|
+
// The render contract: the manifest's arrival `defaultState` seeds the composed
|
|
199
|
+
// `state` block, so the SDK (which merges def.state beneath live data) renders
|
|
200
|
+
// that face on arrival. Falls back to any authored state, else "inline".
|
|
201
|
+
// (Legacy path — a v2 `state.view` tree below supersedes it: viewOf reads
|
|
202
|
+
// `view` before `defaultState`, so the compiled scalar wins.)
|
|
203
|
+
if (m.defaultState)
|
|
204
|
+
def.state = { ...((def.state as Record<string, unknown>) ?? {}), defaultState: m.defaultState };
|
|
205
|
+
// A hook's credentials, carried so the canvas offers the same picker a node
|
|
206
|
+
// gets. A component that fetches declares WHICH credential it needs; the value
|
|
207
|
+
// is resolved server-side at run time and never reaches this folder.
|
|
208
|
+
if (Array.isArray((m as { credentials?: unknown }).credentials))
|
|
209
|
+
def.credentials = (m as { credentials?: unknown[] }).credentials!.map(String);
|
|
210
|
+
def.spatial = true;
|
|
211
|
+
}
|
|
212
|
+
// No authored root → the DEFAULT LAYOUT is the root. The manifest MAY name
|
|
213
|
+
// it (`layout:`), but the standing law makes the line unnecessary: states
|
|
214
|
+
// own layouts and DECLARATION ORDER ranks them, so the base defaults to the
|
|
215
|
+
// FIRST DECLARED STATE. "main" survives only for apps with no states block.
|
|
216
|
+
const layoutsDir = join(folderDir, "layouts");
|
|
217
|
+
// THE APP TREE'S HOME is the ENVELOPE (`<name>.yaml` — one folder grammar,
|
|
218
|
+
// owner ruling 2026-08-29): the states tree is what the app IS. The
|
|
219
|
+
// manifest's `states:` block is the LEGACY home, still read for unswept
|
|
220
|
+
// folders, exactly like `kind:`/`defaultState`.
|
|
221
|
+
type AppStatesRecord = Record<
|
|
222
|
+
string,
|
|
223
|
+
{ layout?: unknown; preview?: unknown; latch?: unknown; states?: Record<string, unknown> } | null
|
|
224
|
+
>;
|
|
225
|
+
const appStates: AppStatesRecord | undefined =
|
|
226
|
+
k === "app"
|
|
227
|
+
? (() => {
|
|
228
|
+
const fromEnvelope = entry ? (readDefCached<{ states?: AppStatesRecord }>(entry).states) : undefined;
|
|
229
|
+
const fromManifest = manifestPath ? (readDefCached<{ states?: AppStatesRecord }>(manifestPath).states) : undefined;
|
|
230
|
+
const s = fromEnvelope ?? fromManifest;
|
|
231
|
+
return s && typeof s === "object" && !Array.isArray(s) ? s : undefined;
|
|
232
|
+
})()
|
|
233
|
+
: undefined;
|
|
234
|
+
const defaultLayout = k === "app"
|
|
235
|
+
? defaultLayoutOf({ layout: manifestPath ? readDefCached<{ layout?: string }>(manifestPath).layout : undefined, states: appStates })
|
|
236
|
+
: manifestPath ? defaultLayoutOf(readDefCached<{ layout?: string; states?: unknown }>(manifestPath)) : "main";
|
|
237
|
+
// THE CONTAINERS (UNOVERSE_INTERFACE_MODEL §4), apps only: pure passthrough of
|
|
238
|
+
// the manifest's `containers:` block. The SDK resolves a layout's
|
|
239
|
+
// `type: Container` node against it; Studio seeds each container's own `mock`.
|
|
240
|
+
// Sources (`workflow`/`query`/`autoTrigger`) ride along as data until
|
|
241
|
+
// per-container routing lands.
|
|
242
|
+
if (k === "app" && manifestPath) {
|
|
243
|
+
const man = readDefCached<{ templates?: unknown; states?: Record<string, unknown> }>(manifestPath);
|
|
244
|
+
const merged: Record<string, unknown> = {};
|
|
245
|
+
// The legacy container form (deal-workspace): a top-level `templates:` block.
|
|
246
|
+
if (man.templates && typeof man.templates === "object" && !Array.isArray(man.templates))
|
|
247
|
+
Object.assign(merged, man.templates as Record<string, unknown>);
|
|
248
|
+
// A STATE ENTRY CARRIES ONLY WHAT SEEDS IT (owner ruling 2026-08-28
|
|
249
|
+
// evening): its `preview`. What a place holds and the judgment granted to
|
|
250
|
+
// it live on the PLACED NODE in the state's layout — projected below,
|
|
251
|
+
// after the layouts compose.
|
|
252
|
+
for (const [sname, sentry] of Object.entries(appStates ?? {})) {
|
|
253
|
+
if (!sentry || typeof sentry !== "object" || Array.isArray(sentry)) continue;
|
|
254
|
+
if (Array.isArray((sentry as { preview?: unknown }).preview)) {
|
|
255
|
+
merged[sname] = { ...(sentry as Record<string, unknown>) };
|
|
256
|
+
// The per-state spelling ALSO feeds the served preview map, so
|
|
257
|
+
// every consumer (Studio's walk, the mock synthesizer) sees the
|
|
258
|
+
// same mocks whichever home authored them. The legacy top-level
|
|
259
|
+
// map wins per state where both exist.
|
|
260
|
+
def.preview = { [sname]: (sentry as { preview: string[] }).preview, ...(def.preview ?? {}) };
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (Object.keys(merged).length) def.templates = merged as NonNullable<typeof def.templates>;
|
|
264
|
+
}
|
|
265
|
+
// THE DECLARED PATH WINS (owner ruling 2026-08-29): a state names its
|
|
266
|
+
// layout as a path relative to the definition folder (`layout:
|
|
267
|
+
// layouts/standard`) — nothing assumed, subfolders legal. The same-name
|
|
268
|
+
// resolution below it survives ONLY for the unswept installed tier,
|
|
269
|
+
// exactly like `kind:`/`defaultState`: legacy, still read, lint-forbidden.
|
|
270
|
+
const declaredLayouts: Record<string, string> = {};
|
|
271
|
+
for (const [sname, sentry] of Object.entries(appStates ?? {}))
|
|
272
|
+
if (sentry && typeof sentry === "object" && typeof sentry.layout === "string" && sentry.layout)
|
|
273
|
+
declaredLayouts[sname] = sentry.layout;
|
|
274
|
+
// THE TREE IS THE MACHINE (owner ruling 2026-08-29 late): a component's
|
|
275
|
+
// root Switch restated the tree, so the compiler SYNTHESIZES it — states
|
|
276
|
+
// declare layouts, the Switch falls out. An AUTHORED root wins untouched
|
|
277
|
+
// (a shell beyond the plain Switch is real design: text-chat's moods).
|
|
278
|
+
if (k === "component" && !def.root) {
|
|
279
|
+
const cTree = (def as unknown as { states?: Record<string, { layout?: unknown } | null> }).states;
|
|
280
|
+
if (cTree && typeof cTree === "object" && !Array.isArray(cTree)) {
|
|
281
|
+
const cases: Record<string, unknown> = {};
|
|
282
|
+
for (const [sname, sentry] of Object.entries(cTree))
|
|
283
|
+
if (sentry && typeof sentry === "object" && typeof sentry.layout === "string")
|
|
284
|
+
cases[sname] = { $include: sentry.layout };
|
|
285
|
+
const first = Object.keys(cases)[0];
|
|
286
|
+
if (first) {
|
|
287
|
+
if (!("default" in cases)) cases.default = cases[first];
|
|
288
|
+
def.root = {
|
|
289
|
+
type: "Box",
|
|
290
|
+
style: { width: "full", container: "inline-size" },
|
|
291
|
+
children: [{ type: "Switch", on: "state", cases }],
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (!def.root) {
|
|
297
|
+
if (declaredLayouts[defaultLayout]) def.root = { $include: declaredLayouts[defaultLayout] };
|
|
298
|
+
else if (defPath(layoutsDir, defaultLayout)) def.root = { $include: `layouts/${defaultLayout}` };
|
|
299
|
+
}
|
|
300
|
+
if (def.root) def.root = composeIncludes(def.root, folderDir);
|
|
301
|
+
// PROPS EXTRACT (UNOVERSE_MCP_FORMS §Per-field purpose): a long props block may
|
|
302
|
+
// live in a sibling file — `props: { $include: props }` — with the ordinary
|
|
303
|
+
// include every layout already uses. Composed here so every reader (schema
|
|
304
|
+
// compile, synthesize, workbench) sees one flat props map, never the include.
|
|
305
|
+
if (def.props) def.props = composeIncludes(def.props, folderDir);
|
|
306
|
+
// TEMPLATE LAYOUTS (name-sync, docs/design/05): a template with MULTIPLE
|
|
307
|
+
// layouts/ files is a set of full arrangements — the SDK presents the one
|
|
308
|
+
// whose NAME matches the latest surfaced component view, else the default.
|
|
309
|
+
// Each is composed+expanded exactly like the root and served as def.layouts.
|
|
310
|
+
if (k === "app" && existsSync(layoutsDir)) {
|
|
311
|
+
// ANATOMY UNIFIED (owner ruling 2026-08-22): layouts/ is the template's ONE
|
|
312
|
+
// drawings folder, so it holds ARRANGEMENTS and the drawings its chrome
|
|
313
|
+
// includes (substates, shell moods — the retired states/ folder's files).
|
|
314
|
+
// Only arrangements are served as def.layouts: a drawing some other file
|
|
315
|
+
// `$include`s is a part, not a presentation the SDK may switch to.
|
|
316
|
+
const included = new Set<string>();
|
|
317
|
+
const scanDirs = ["layouts", "components", "states"].map((d) => join(folderDir, d)).filter(existsSync);
|
|
318
|
+
for (const d of scanDirs)
|
|
319
|
+
for (const f of readdirSync(d).filter(isDefFile)) {
|
|
320
|
+
const collect = (n: unknown): void => {
|
|
321
|
+
if (Array.isArray(n)) return n.forEach(collect);
|
|
322
|
+
if (!n || typeof n !== "object") return;
|
|
323
|
+
const inc = (n as { $include?: unknown }).$include;
|
|
324
|
+
if (typeof inc === "string" && inc.startsWith("layouts/")) included.add(inc.slice("layouts/".length));
|
|
325
|
+
for (const v of Object.values(n)) collect(v);
|
|
326
|
+
};
|
|
327
|
+
try {
|
|
328
|
+
collect(readDefCached(join(d, f)));
|
|
329
|
+
} catch {
|
|
330
|
+
/* that file lints separately */
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const names = readdirSync(layoutsDir)
|
|
334
|
+
.filter(isDefFile)
|
|
335
|
+
.map(defName)
|
|
336
|
+
.filter((n) => n === defaultLayout || !included.has(n))
|
|
337
|
+
// A state with a DECLARED path is composed from the declaration below;
|
|
338
|
+
// its same-name file (when both exist) must not double-serve.
|
|
339
|
+
.filter((n) => !(n in declaredLayouts));
|
|
340
|
+
// THE PROPS AN ATOM DRAWN INSIDE A LAYOUT DECLARES (expand.ts,
|
|
341
|
+
// `inheritProps`): layouts expand HERE, before `expandRefs` sees the root,
|
|
342
|
+
// so the accumulator has to ride along or every atom-drawn field is missing
|
|
343
|
+
// from the compiled schema.
|
|
344
|
+
const fromAtoms: Record<string, Record<string, unknown>> = {};
|
|
345
|
+
const arrangements: Record<string, unknown> = Object.fromEntries(
|
|
346
|
+
names.map((n) => [n, expandNode(composeIncludes({ $include: `layouts/${n}` }, folderDir) as AnyNode, undefined, undefined, fromAtoms)]),
|
|
347
|
+
);
|
|
348
|
+
// DECLARED PATHS (2026-08-29 ruling): each state's arrangement composes
|
|
349
|
+
// from its own `layout:` path, keyed by the STATE name — the SDK's
|
|
350
|
+
// view→layout match is unchanged because keys were always state names.
|
|
351
|
+
for (const [sname, lpath] of Object.entries(declaredLayouts))
|
|
352
|
+
arrangements[sname] = expandNode(composeIncludes({ $include: lpath }, folderDir) as AnyNode, undefined, undefined, fromAtoms);
|
|
353
|
+
mergeInheritedProps(def, fromAtoms);
|
|
354
|
+
if (Object.keys(arrangements).length > 1) {
|
|
355
|
+
def.defaultLayout = defaultLayout;
|
|
356
|
+
def.layouts = arrangements;
|
|
357
|
+
}
|
|
358
|
+
// THE PLACED NODE OWNS THE MOUNT (owner ruling 2026-08-28 evening): a
|
|
359
|
+
// layout's `type: Template` node carries `template:` (what it holds) and
|
|
360
|
+
// `director:` (the judgment granted to this placement — any placed
|
|
361
|
+
// template may carry one). Projected onto def.templates keyed by the
|
|
362
|
+
// node's name, merged over the state entry's own keys (preview), and
|
|
363
|
+
// stamped with everything the mounted template CLAIMS, so the reaction
|
|
364
|
+
// ladder can enter the holding state on arrival.
|
|
365
|
+
//
|
|
366
|
+
// WHAT IT CLAIMS IS NOT ONLY ITS STATE NAMES (widened 2026-08-30). A
|
|
367
|
+
// grid-page claims `grid`/`page` because its own states carry its content,
|
|
368
|
+
// and reading the state names was enough. A rich template's content wears
|
|
369
|
+
// its SECTIONS' faces instead — Perfect Day's states are just `page`, while
|
|
370
|
+
// what actually arrives is `hero`, `day-early`, `early`, `late`, `gallery` —
|
|
371
|
+
// so a state-names-only claim set could never be matched and the holding
|
|
372
|
+
// state could never activate. The claim set is the union: the template has
|
|
373
|
+
// something to show when ANY face it draws is on screen.
|
|
374
|
+
for (const [lname, tree] of Object.entries(def.layouts ?? {})) {
|
|
375
|
+
const walkMounts = (n: unknown): void => {
|
|
376
|
+
if (Array.isArray(n)) return n.forEach(walkMounts);
|
|
377
|
+
if (!n || typeof n !== "object") return;
|
|
378
|
+
const un = n as { type?: unknown; name?: unknown; template?: unknown; director?: unknown; children?: unknown; frame?: unknown; cases?: Record<string, unknown> };
|
|
379
|
+
if (un.type === "Template" && typeof un.template === "string" && un.template) {
|
|
380
|
+
const pname = typeof un.name === "string" && un.name ? un.name : lname;
|
|
381
|
+
const tpl = loadDefinition(un.template, "template");
|
|
382
|
+
const base = (def.templates?.[pname] ?? {}) as Record<string, unknown>;
|
|
383
|
+
def.templates = {
|
|
384
|
+
...(def.templates ?? {}),
|
|
385
|
+
[pname]: { ...base, template: un.template, ...(un.director ? { director: un.director as Record<string, unknown> } : {}), claims: [...new Set([...Object.keys(tpl?.layouts ?? {}), ...Object.values(tpl?.sections ?? {}).flat().map((sec) => sec.face).filter(Boolean)])] },
|
|
386
|
+
} as NonNullable<typeof def.templates>;
|
|
387
|
+
}
|
|
388
|
+
walkMounts(un.children);
|
|
389
|
+
walkMounts(un.frame);
|
|
390
|
+
if (un.cases) Object.values(un.cases).forEach(walkMounts);
|
|
391
|
+
};
|
|
392
|
+
walkMounts(tree);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
// STATE MODEL v2 (UNOVERSE_STATE_MODEL §5 rule 5): a template's manifest
|
|
396
|
+
// `stateOrder` is served on the def — the SDK's priority ladder and
|
|
397
|
+
// arrival-scan order. Absent → the SDK stays in legacy recency mode.
|
|
398
|
+
if (k === "app" && manifestPath) {
|
|
399
|
+
const so = readDefCached<{ stateOrder?: unknown }>(manifestPath).stateOrder;
|
|
400
|
+
if (Array.isArray(so)) def.stateOrder = so.filter((n): n is string => typeof n === "string");
|
|
401
|
+
}
|
|
402
|
+
// THE TEMPLATE TREE (STATE_MODEL v2, template tier — sab checkpoint,
|
|
403
|
+
// 2026-08-08): the manifest's `states:` block is the template's state
|
|
404
|
+
// machine, compiled exactly like a component's `state.view` tree:
|
|
405
|
+
// - def.stateTree = the nested projection (viewers render it)
|
|
406
|
+
// - def.stateOrder = the REACTION ladder — top-level names minus the
|
|
407
|
+
// BASE (the state named for the default layout), in declared order.
|
|
408
|
+
// Supersedes an authored `stateOrder`.
|
|
409
|
+
// - CONTAINMENT is structural: a base substate (welcome) exists ONLY in
|
|
410
|
+
// the base arrangement. Its composed subtree is STRIPPED from every
|
|
411
|
+
// other layout — no hand-written guard, the declaration is enforced
|
|
412
|
+
// by compilation. (Identified by structural equality: the composed
|
|
413
|
+
// states/<name> file IS the inlined node.)
|
|
414
|
+
if (k === "app" && appStates) {
|
|
415
|
+
// THE LATCH IS A FACT ABOUT A STATE (MCP guide §The Component Latch, moved off
|
|
416
|
+
// the manifest 2026-08-31). Projected here for the same reason `preview` is:
|
|
417
|
+
// the raw states block never rides the served def, so anything a state declares
|
|
418
|
+
// has to become a projection or it is lost. `def.latches` is what the SDK's
|
|
419
|
+
// `resolveLatch` looks the wearer's face up in — a state that says nothing here
|
|
420
|
+
// can never be addressed, and an instance retracting out of an addressable
|
|
421
|
+
// state drops the pill with nothing to unregister.
|
|
422
|
+
const latches: NonNullable<UnoverseDefinition["latches"]> = {};
|
|
423
|
+
for (const [sname, sentry] of Object.entries(appStates)) {
|
|
424
|
+
const l = (sentry as { latch?: { title?: unknown; icon?: unknown } } | null)?.latch;
|
|
425
|
+
// TITLE OR NOTHING: the pill's whole content is its title, so a latch without
|
|
426
|
+
// one draws a nameless pill the guest cannot read. Taking a declared-but-empty
|
|
427
|
+
// block literally is what makes the authoring slip visible.
|
|
428
|
+
if (l && typeof l === "object" && typeof l.title === "string" && l.title.trim())
|
|
429
|
+
latches[sname] = { title: l.title, ...(typeof l.icon === "string" ? { icon: l.icon } : {}) };
|
|
430
|
+
}
|
|
431
|
+
if (Object.keys(latches).length) def.latches = latches;
|
|
432
|
+
// The tree COMPILES into projections; the raw block never rides the
|
|
433
|
+
// served def (stateTree is the viewers' shape).
|
|
434
|
+
delete (def as unknown as Record<string, unknown>).states;
|
|
435
|
+
const mStates = appStates;
|
|
436
|
+
{
|
|
437
|
+
const names = Object.keys(mStates);
|
|
438
|
+
const base = names.includes(defaultLayout) ? defaultLayout : names[0];
|
|
439
|
+
def.stateTree = names.map((n) => {
|
|
440
|
+
const s = mStates[n];
|
|
441
|
+
const entry: NonNullable<UnoverseDefinition["stateTree"]>[number] = { name: n };
|
|
442
|
+
if (s && typeof s === "object" && s.states && typeof s.states === "object")
|
|
443
|
+
entry.states = Object.keys(s.states).map((sub) => ({ name: sub }));
|
|
444
|
+
return entry;
|
|
445
|
+
});
|
|
446
|
+
def.stateOrder = names.filter((n) => n !== base);
|
|
447
|
+
const baseSubs = Object.keys((mStates[base] as { states?: Record<string, unknown> } | null)?.states ?? {});
|
|
448
|
+
if (def.layouts && baseSubs.length) {
|
|
449
|
+
// ANATOMY UNIFIED (owner ruling 2026-08-22): templates carry ONE drawings
|
|
450
|
+
// folder, like components — a substate's drawing lives in layouts/. The
|
|
451
|
+
// states/ path remains a LEGACY fallback so an unmigrated installed
|
|
452
|
+
// template keeps rendering; the lint retires it.
|
|
453
|
+
const marks = baseSubs
|
|
454
|
+
.map((s) => defPath(join(folderDir, "layouts"), `${base}-${s}`) ?? defPath(join(folderDir, "layouts"), s) ?? defPath(join(folderDir, "states"), s))
|
|
455
|
+
.filter((p): p is string => Boolean(p))
|
|
456
|
+
.map((p) => JSON.stringify(expandNode(composeIncludes(readDefCached(p), folderDir) as AnyNode)));
|
|
457
|
+
const strip = (node: unknown): unknown => {
|
|
458
|
+
if (Array.isArray(node)) return node.filter((c) => !marks.includes(JSON.stringify(c))).map(strip);
|
|
459
|
+
if (node && typeof node === "object") {
|
|
460
|
+
const out: AnyNode = {};
|
|
461
|
+
for (const [k2, v2] of Object.entries(node)) out[k2] = strip(v2);
|
|
462
|
+
return out;
|
|
463
|
+
}
|
|
464
|
+
return node;
|
|
465
|
+
};
|
|
466
|
+
for (const n of Object.keys(def.layouts)) if (n !== base) def.layouts[n] = strip(def.layouts[n]);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
// STATE MODEL v2 (UNOVERSE_STATE_MODEL §5 rules 1+2): an authored
|
|
471
|
+
// `state.view` TREE is the component's state machine — public states owning
|
|
472
|
+
// layouts, private substates nested beneath. It COMPILES here; the tree
|
|
473
|
+
// itself never reaches the render scope:
|
|
474
|
+
// - def.publicStates = the public menu (top-level names, tree order)
|
|
475
|
+
// - def.initialView = the declared initial (the retract/fallback base)
|
|
476
|
+
// - def.state.view → the scalar initial (the render scope reads a value)
|
|
477
|
+
// - a nested `on` + `initial` pair → its scalar initial (the substep key)
|
|
478
|
+
if (k === "component") compileComponentTree(def);
|
|
479
|
+
if (k === "template") synthesizeTemplateRoot(def, folderDir);
|
|
480
|
+
return k === "atom" ? def : form(def);
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return null;
|
|
486
|
+
}
|
|
487
|
+
|