@unoverse-platform/studio 0.1.90 → 0.1.91

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 (130) hide show
  1. package/local/plugin.ts +20 -12
  2. package/package.json +13 -3
  3. package/vendor/base/src/agent-mcp/discovery.ts +203 -0
  4. package/vendor/base/src/agent-mcp/handoff.ts +56 -0
  5. package/vendor/base/src/agent-mcp/index.ts +44 -0
  6. package/vendor/base/src/agent-mcp/invoke.ts +140 -0
  7. package/vendor/base/src/agent-mcp/lean.ts +111 -0
  8. package/vendor/base/src/agent-mcp/memoryContext.ts +104 -0
  9. package/vendor/base/src/agent-mcp/memoryIngest.ts +88 -0
  10. package/vendor/base/src/agent-mcp/rowComponents.ts +172 -0
  11. package/vendor/base/src/agent-mcp/tools.ts +134 -0
  12. package/vendor/base/src/agent-mcp/types.ts +130 -0
  13. package/vendor/base/src/auth/auth.ts +278 -0
  14. package/vendor/base/src/auth/authConfig.ts +73 -0
  15. package/vendor/base/src/boot.ts +245 -0
  16. package/vendor/base/src/definitions/definitions.ts +18 -0
  17. package/vendor/base/src/definitions/dsPackage.ts +45 -0
  18. package/vendor/base/src/definitions/fsCache.ts +128 -0
  19. package/vendor/base/src/definitions/index.ts +19 -0
  20. package/vendor/base/src/definitions/inputs.ts +30 -0
  21. package/vendor/base/src/definitions/load/compile/component.ts +80 -0
  22. package/vendor/base/src/definitions/load/compile/index.ts +12 -0
  23. package/vendor/base/src/definitions/load/compile/sections.ts +132 -0
  24. package/vendor/base/src/definitions/load/compile/template.ts +153 -0
  25. package/vendor/base/src/definitions/load/expand/includes.ts +27 -0
  26. package/vendor/base/src/definitions/load/expand/index.ts +17 -0
  27. package/vendor/base/src/definitions/load/expand/inherit.ts +169 -0
  28. package/vendor/base/src/definitions/load/expand/referenced.ts +164 -0
  29. package/vendor/base/src/definitions/load/expand/refs.ts +106 -0
  30. package/vendor/base/src/definitions/load/expand/walk.ts +91 -0
  31. package/vendor/base/src/definitions/load/load.ts +487 -0
  32. package/vendor/base/src/definitions/model.ts +238 -0
  33. package/vendor/base/src/definitions/registry/catalog.ts +643 -0
  34. package/vendor/base/src/definitions/registry/homes.ts +252 -0
  35. package/vendor/base/src/definitions/theme.ts +383 -0
  36. package/vendor/base/src/executor.ts +284 -0
  37. package/vendor/base/src/http/uwsFetchAdapter.ts +260 -0
  38. package/vendor/base/src/items/baseVersion.ts +96 -0
  39. package/vendor/base/src/items/collect.ts +199 -0
  40. package/vendor/base/src/items/fingerprint.ts +34 -0
  41. package/vendor/base/src/items/publish.ts +198 -0
  42. package/vendor/base/src/lint/context.mjs +56 -0
  43. package/vendor/base/src/lint/design/defs.mjs +46 -0
  44. package/vendor/base/src/lint/design/file.mjs +737 -0
  45. package/vendor/base/src/lint/design/index.d.mts +30 -0
  46. package/vendor/base/src/lint/design/index.mjs +996 -0
  47. package/vendor/base/src/lint/design/tokens.mjs +128 -0
  48. package/vendor/base/src/lint/design/vocabulary.mjs +181 -0
  49. package/vendor/base/src/lint/design/walk.mjs +444 -0
  50. package/vendor/base/src/lint/index.mjs +28 -0
  51. package/vendor/base/src/lint/nodes/blocks.mjs +63 -0
  52. package/vendor/base/src/lint/nodes/index.mjs +64 -0
  53. package/vendor/base/src/lint/nodes/load.mjs +126 -0
  54. package/vendor/base/src/lint/nodes/node.mjs +764 -0
  55. package/vendor/base/src/lint/nodes/package.mjs +112 -0
  56. package/vendor/base/src/lint/nodes/schema.mjs +75 -0
  57. package/vendor/base/src/logger.ts +33 -0
  58. package/vendor/base/src/manifests/compose.ts +539 -0
  59. package/vendor/base/src/manifests/executor/callback.ts +107 -0
  60. package/vendor/base/src/manifests/executor/context.ts +107 -0
  61. package/vendor/base/src/manifests/executor/index.ts +47 -0
  62. package/vendor/base/src/manifests/executor/lookup.ts +21 -0
  63. package/vendor/base/src/manifests/executor/memory.ts +62 -0
  64. package/vendor/base/src/manifests/executor/promise.ts +71 -0
  65. package/vendor/base/src/manifests/executor/refire.ts +34 -0
  66. package/vendor/base/src/manifests/executor/send.ts +65 -0
  67. package/vendor/base/src/manifests/executor/session.ts +46 -0
  68. package/vendor/base/src/manifests/executor/toolBridge.ts +266 -0
  69. package/vendor/base/src/manifests/index.ts +280 -0
  70. package/vendor/base/src/manifests/integrity.ts +93 -0
  71. package/vendor/base/src/manifests/rows.ts +33 -0
  72. package/vendor/base/src/manifests/runtime/auth/aws.ts +240 -0
  73. package/vendor/base/src/manifests/runtime/auth/oauth.ts +76 -0
  74. package/vendor/base/src/manifests/runtime/authorize.ts +159 -0
  75. package/vendor/base/src/manifests/runtime/channels/final.ts +171 -0
  76. package/vendor/base/src/manifests/runtime/channels/service.ts +125 -0
  77. package/vendor/base/src/manifests/runtime/channels/workflow.ts +310 -0
  78. package/vendor/base/src/manifests/runtime/context.ts +150 -0
  79. package/vendor/base/src/manifests/runtime/docstore/handlers.ts +547 -0
  80. package/vendor/base/src/manifests/runtime/docstore/index.ts +135 -0
  81. package/vendor/base/src/manifests/runtime/docstore/sectionizer.ts +184 -0
  82. package/vendor/base/src/manifests/runtime/docstore/store.ts +172 -0
  83. package/vendor/base/src/manifests/runtime/docstore/types.ts +60 -0
  84. package/vendor/base/src/manifests/runtime/duplex/audioLane.ts +165 -0
  85. package/vendor/base/src/manifests/runtime/duplex/session.ts +511 -0
  86. package/vendor/base/src/manifests/runtime/events.ts +223 -0
  87. package/vendor/base/src/manifests/runtime/helpers.ts +53 -0
  88. package/vendor/base/src/manifests/runtime/http/allowedHosts.ts +118 -0
  89. package/vendor/base/src/manifests/runtime/http/request.ts +637 -0
  90. package/vendor/base/src/manifests/runtime/http/response.ts +162 -0
  91. package/vendor/base/src/manifests/runtime/index.ts +57 -0
  92. package/vendor/base/src/manifests/runtime/loops/chunk.ts +98 -0
  93. package/vendor/base/src/manifests/runtime/loops/loop.ts +191 -0
  94. package/vendor/base/src/manifests/runtime/loops/paginate.ts +143 -0
  95. package/vendor/base/src/manifests/runtime/loops/poll.ts +169 -0
  96. package/vendor/base/src/manifests/runtime/narrate.ts +53 -0
  97. package/vendor/base/src/manifests/runtime/state.ts +171 -0
  98. package/vendor/base/src/manifests/runtime/templating.ts +110 -0
  99. package/vendor/base/src/manifests/runtime/tools/regionSkills.ts +125 -0
  100. package/vendor/base/src/manifests/runtime/tools/toolloop.ts +542 -0
  101. package/vendor/base/src/manifests/runtime/usage.ts +87 -0
  102. package/vendor/base/src/manifests/source.ts +187 -0
  103. package/vendor/base/src/manifests/watch.ts +67 -0
  104. package/vendor/base/src/markdown/document.ts +141 -0
  105. package/vendor/base/src/paths.ts +180 -0
  106. package/vendor/base/src/platform/audioManager.ts +100 -0
  107. package/vendor/base/src/platform/clientTransport.ts +65 -0
  108. package/vendor/base/src/platform/executionContext.ts +154 -0
  109. package/vendor/base/src/platform/index.ts +10 -0
  110. package/vendor/base/src/platform/pluginAPI.ts +146 -0
  111. package/vendor/base/src/platform/redis.ts +54 -0
  112. package/vendor/base/src/platform/serviceCalls.ts +222 -0
  113. package/vendor/base/src/pluginBase.ts +248 -0
  114. package/vendor/base/src/plugins/README.md +57 -0
  115. package/vendor/base/src/plugins/discovery.ts +87 -0
  116. package/vendor/base/src/plugins/index.ts +16 -0
  117. package/vendor/base/src/plugins/loader.ts +179 -0
  118. package/vendor/base/src/plugins/redis.ts +36 -0
  119. package/vendor/base/src/plugins/state.ts +53 -0
  120. package/vendor/base/src/registry.ts +95 -0
  121. package/vendor/base/src/security/permissions.ts +93 -0
  122. package/vendor/base/src/security/rules.ts +73 -0
  123. package/vendor/base/src/template/SafeExpression.ts +556 -0
  124. package/vendor/base/src/template/StringTemplateResolver.ts +75 -0
  125. package/vendor/base/src/template/index.ts +22 -0
  126. package/vendor/base/src/types.ts +50 -0
  127. package/vendor/base/src/workflow/GetMemoryService.ts +158 -0
  128. package/vendor/base/src/workflow/PlatformMemoryCore.ts +438 -0
  129. package/vendor/base/src/workflow/routing.ts +122 -0
  130. package/vendor/sdk/lib/core/store.ts +26 -6
@@ -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
+