@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.
- package/local/plugin.ts +20 -12
- 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 +140 -0
- package/vendor/base/src/agent-mcp/lean.ts +111 -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 +172 -0
- package/vendor/base/src/agent-mcp/tools.ts +134 -0
- package/vendor/base/src/agent-mcp/types.ts +130 -0
- package/vendor/base/src/auth/auth.ts +278 -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 +128 -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 +643 -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 +260 -0
- package/vendor/base/src/items/baseVersion.ts +96 -0
- package/vendor/base/src/items/collect.ts +199 -0
- package/vendor/base/src/items/fingerprint.ts +34 -0
- package/vendor/base/src/items/publish.ts +198 -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.mjs +64 -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 +107 -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 +125 -0
- package/vendor/base/src/manifests/runtime/channels/workflow.ts +310 -0
- package/vendor/base/src/manifests/runtime/context.ts +150 -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 +542 -0
- package/vendor/base/src/manifests/runtime/usage.ts +87 -0
- package/vendor/base/src/manifests/source.ts +187 -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 +154 -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 +438 -0
- package/vendor/base/src/workflow/routing.ts +122 -0
- package/vendor/sdk/lib/core/store.ts +26 -6
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mtime-validated definition file cache.
|
|
3
|
+
*
|
|
4
|
+
* The definition + theme stores (definitions.ts, theme.ts) read the same small rx/ files
|
|
5
|
+
* on EVERY MCP resource read/list and every theme fetch — and a single
|
|
6
|
+
* `listDefinitions()` re-reads the same atoms once per composing component. Re-reading +
|
|
7
|
+
* re-parsing them per request is the server's hottest avoidable cost.
|
|
8
|
+
*
|
|
9
|
+
* This caches the PARSED value keyed by absolute path, re-reading ONLY when the file's
|
|
10
|
+
* mtime changes. So hot-reload still works (edit a definition, refresh → mtime moves →
|
|
11
|
+
* re-parse), but a steady-state request pays a single `stat()` instead of open+read+parse.
|
|
12
|
+
*
|
|
13
|
+
* Returned objects are SHARED, not cloned — callers that MUTATE the parsed value MUST
|
|
14
|
+
* clone first (the definition loader does, to expand atom `Ref`s in place); theme reads
|
|
15
|
+
* are read-only. Call only for files known to exist (callers already guard with existsSync);
|
|
16
|
+
* `statSync` throws on a missing path.
|
|
17
|
+
*
|
|
18
|
+
* ---
|
|
19
|
+
*
|
|
20
|
+
* FORMAT: rx definitions are authored as `.yaml` OR `.json` and the two are
|
|
21
|
+
* interchangeable everywhere. YAML is the authoring format (it carries comments and reads
|
|
22
|
+
* without punctuation noise); JSON stays the WIRE format, because definitions are served
|
|
23
|
+
* verbatim as MCP resources to clients that `JSON.parse` them. Nothing downstream of this
|
|
24
|
+
* module knows which extension a definition was authored in.
|
|
25
|
+
*
|
|
26
|
+
* YAML parses to the same data model, so the JSON Schema at `rx/_schema/` validates both
|
|
27
|
+
* with no change. `$schema` is stripped on read: an editor needs the pointer in the file,
|
|
28
|
+
* a client must never receive it in the payload.
|
|
29
|
+
*/
|
|
30
|
+
import { readFileSync, readdirSync, statSync, existsSync } from "fs";
|
|
31
|
+
import { join } from "path";
|
|
32
|
+
import { parse as parseYaml } from "yaml";
|
|
33
|
+
|
|
34
|
+
/** Authored definition extensions, in RESOLUTION ORDER. `.yaml` wins so a converted file
|
|
35
|
+
* takes precedence over a `.json` left behind mid-migration. `defPath` is the only place
|
|
36
|
+
* that order is applied — never re-implement it at a call site. */
|
|
37
|
+
export const DEF_EXTS = [".yaml", ".json"] as const;
|
|
38
|
+
|
|
39
|
+
const cache = new Map<string, { mtimeMs: number; data: unknown }>();
|
|
40
|
+
|
|
41
|
+
/** Parsed definition for `path`, cached until the file's mtime changes. Parser is chosen
|
|
42
|
+
* by extension; `$schema` (an editor affordance, never wire data) is dropped. */
|
|
43
|
+
export function readDefCached<T = any>(path: string): T {
|
|
44
|
+
const mtimeMs = statSync(path).mtimeMs;
|
|
45
|
+
const hit = cache.get(path);
|
|
46
|
+
if (hit && hit.mtimeMs === mtimeMs) return hit.data as T;
|
|
47
|
+
const text = readFileSync(path, "utf8");
|
|
48
|
+
const parsed = path.endsWith(".yaml") ? parseYaml(text) : JSON.parse(text);
|
|
49
|
+
if (parsed && typeof parsed === "object" && "$schema" in parsed) delete (parsed as Record<string, unknown>).$schema;
|
|
50
|
+
cache.set(path, { mtimeMs, data: parsed });
|
|
51
|
+
return parsed as T;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Resolve an EXTENSION-LESS definition path to the file that actually exists, or
|
|
56
|
+
* undefined. `defPath(dir, "card")` finds `card.yaml` then `card.json`.
|
|
57
|
+
*
|
|
58
|
+
* Every definition lookup goes through here so the migration stays invisible: a folder may
|
|
59
|
+
* hold YAML and JSON side by side while it is being converted. Authoring BOTH for one name
|
|
60
|
+
* is an error the linter reports — this resolver silently prefers `.yaml` rather than
|
|
61
|
+
* failing, because a half-converted tree must keep serving.
|
|
62
|
+
*/
|
|
63
|
+
export function defPath(dir: string, name: string): string | undefined {
|
|
64
|
+
for (const ext of DEF_EXTS) {
|
|
65
|
+
const p = join(dir, name + ext);
|
|
66
|
+
if (existsSync(p)) return p;
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** True when `file` is an authored definition (either format). Replaces the
|
|
72
|
+
* `endsWith(".json")` filters that used to gate directory scans. */
|
|
73
|
+
export const isDefFile = (file: string): boolean => DEF_EXTS.some((e) => file.endsWith(e));
|
|
74
|
+
|
|
75
|
+
/** A definition filename without its extension: `welcome.yaml` → `welcome`. */
|
|
76
|
+
export const defName = (file: string): string => file.replace(/\.(yaml|json)$/, "");
|
|
77
|
+
|
|
78
|
+
const textCache = new Map<string, { mtimeMs: number; text: string }>();
|
|
79
|
+
|
|
80
|
+
/** File text for `path`, cached until the file's mtime changes. Same contract as
|
|
81
|
+
* readDefCached — for large non-JSON reads (e.g. the single-file webSDK bundle)
|
|
82
|
+
* that would otherwise sync-read megabytes off disk per request. */
|
|
83
|
+
export function readTextCached(path: string): string {
|
|
84
|
+
const mtimeMs = statSync(path).mtimeMs;
|
|
85
|
+
const hit = textCache.get(path);
|
|
86
|
+
if (hit && hit.mtimeMs === mtimeMs) return hit.text;
|
|
87
|
+
const text = readFileSync(path, "utf8");
|
|
88
|
+
textCache.set(path, { mtimeMs, text });
|
|
89
|
+
return text;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Change signature over a set of directories: every definition file's name+mtime,
|
|
94
|
+
* recursing into subdirectories (definition folders carry `states/` etc. — all
|
|
95
|
+
* shallow). A stat sweep is tens of µs for the rx/ dirs — versus re-running full
|
|
96
|
+
* token/definition resolution per request. Missing dirs contribute their absence
|
|
97
|
+
* (so creating one invalidates).
|
|
98
|
+
*
|
|
99
|
+
* MUST cover both extensions: a signature that only saw `.json` would leave every YAML
|
|
100
|
+
* edit invisible to hot reload, and the staleness would look like a caching bug rather
|
|
101
|
+
* than a missing filter.
|
|
102
|
+
*/
|
|
103
|
+
export function dirSignature(dirs: string[]): string {
|
|
104
|
+
const parts: string[] = [];
|
|
105
|
+
const walk = (dir: string) => {
|
|
106
|
+
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
107
|
+
if (e.isDirectory()) walk(join(dir, e.name));
|
|
108
|
+
else if (isDefFile(e.name)) parts.push(`${dir}/${e.name}:${statSync(join(dir, e.name)).mtimeMs}`);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
for (const dir of dirs) {
|
|
112
|
+
if (!existsSync(dir)) parts.push(`${dir}:absent`);
|
|
113
|
+
else walk(dir);
|
|
114
|
+
}
|
|
115
|
+
return parts.join(";");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const computed = new Map<string, { sig: string; value: unknown }>();
|
|
119
|
+
|
|
120
|
+
/** Memoize `compute()` under `key`, re-running only when `sig` changes.
|
|
121
|
+
* Returned values are SHARED — callers must not mutate them. */
|
|
122
|
+
export function cachedBySignature<T>(key: string, sig: string, compute: () => T): T {
|
|
123
|
+
const hit = computed.get(key);
|
|
124
|
+
if (hit && hit.sig === sig) return hit.value as T;
|
|
125
|
+
const value = compute();
|
|
126
|
+
computed.set(key, { sig, value });
|
|
127
|
+
return value;
|
|
128
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rx DEFINITIONS: components, templates, atoms, themes.
|
|
3
|
+
*
|
|
4
|
+
* The other half of "read the YAML and make it mean something". `manifests/` turns node
|
|
5
|
+
* YAML into a runnable node; this turns rx YAML into the components, templates and tokens
|
|
6
|
+
* an interface is assembled from.
|
|
7
|
+
*
|
|
8
|
+
* IT LIVES HERE SO STUDIO CAN BE PUBLISHED. These four files sat in the platform server, so
|
|
9
|
+
* anything outside this monorepo that wanted to list or render rx content had to reach into
|
|
10
|
+
* `apps/unoverse/server/src` — which nothing installed from npm can do. Studio was blocked
|
|
11
|
+
* on exactly that.
|
|
12
|
+
*
|
|
13
|
+
* READING, not rendering. These read rx off disk; `@unoverse/sdk` renders it. The split
|
|
14
|
+
* matters: the SDK is a dumb renderer and must stay one.
|
|
15
|
+
*/
|
|
16
|
+
export { resolveStringTemplate } from "../template/StringTemplateResolver.js";
|
|
17
|
+
export * from "./definitions.js";
|
|
18
|
+
export * from "./theme.js";
|
|
19
|
+
export * from "./inputs.js";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE single derivation of "which definition props are workflow INPUTS".
|
|
3
|
+
*
|
|
4
|
+
* A node's `configSchema` is derived FROM the component's `props`, and that derivation has
|
|
5
|
+
* to live in ONE place so node synthesis, the workbench CONTROLS panel and the server never
|
|
6
|
+
* disagree. Consumers do not re-decide what an input is.
|
|
7
|
+
*
|
|
8
|
+
* IMPLEMENTED HERE, not re-exported. This file used to be a single line pulling it out of
|
|
9
|
+
* `@unoverse-platform/plugin-base`, which made the new runtime depend on the legacy node
|
|
10
|
+
* library it exists to replace. Backwards, and it would have travelled into every consumer
|
|
11
|
+
* of this package as a real npm dependency.
|
|
12
|
+
*
|
|
13
|
+
* `plugin-base` still carries its own copy, for the TypeScript nodes that import it. A
|
|
14
|
+
* knowing duplicate: eight lines of a rule that has not changed, and that copy dies with the
|
|
15
|
+
* last code node. THIS is the canonical one.
|
|
16
|
+
*
|
|
17
|
+
* The rule (mirroring the design system's `workflowInput`): a prop is an input when it
|
|
18
|
+
* declares `input: true`. A definition declaring NO `input` flags at all makes every prop an
|
|
19
|
+
* input, which is back-compat for the single-object components that legitimately expose all
|
|
20
|
+
* of them.
|
|
21
|
+
*/
|
|
22
|
+
export interface InputPropLike {
|
|
23
|
+
input?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function inputPropKeys(props: Record<string, InputPropLike> | undefined): string[] {
|
|
27
|
+
const entries = Object.entries(props ?? {});
|
|
28
|
+
const explicit = entries.some(([, p]) => p?.input !== undefined);
|
|
29
|
+
return entries.filter(([, p]) => (explicit ? p?.input === true : true)).map(([k]) => k);
|
|
30
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The COMPONENT-TREE compiler: `states:` → the served projections.
|
|
3
|
+
* Split from compile.ts into this folder 2026-09-02; the rules are unchanged.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { UnoverseDefinition } from "../../model.js";
|
|
7
|
+
|
|
8
|
+
/** STATE MODEL v2 compile for a COMPONENT def (both authored forms — folder and
|
|
9
|
+
* FLAT single-file, unified 2026-08-29): the tree (top-level `states:`; legacy
|
|
10
|
+
* `state.view`) becomes the served projections (publicStates / initialView /
|
|
11
|
+
* stateTree), the flat scalars the render scope reads, and the SYNTHESIZED root
|
|
12
|
+
* where none is authored. */
|
|
13
|
+
export function compileComponentTree(def: UnoverseDefinition): void {
|
|
14
|
+
// THE RULED FORM (2026-08-29): the tree lives at TOP-LEVEL `states:`
|
|
15
|
+
// like every kind; `state.view` is the legacy wrapper, still read.
|
|
16
|
+
const topStates = (def as unknown as { states?: unknown }).states;
|
|
17
|
+
const topForm = topStates && typeof topStates === "object" && !Array.isArray(topStates)
|
|
18
|
+
? { states: topStates as Record<string, never> }
|
|
19
|
+
: undefined;
|
|
20
|
+
const tree = (topForm ?? (def.state as Record<string, unknown> | undefined)?.view) as
|
|
21
|
+
| {
|
|
22
|
+
initial?: unknown;
|
|
23
|
+
states?: Record<string, { on?: unknown; initial?: unknown; states?: Record<string, unknown> } | null>;
|
|
24
|
+
}
|
|
25
|
+
| undefined;
|
|
26
|
+
if (!(tree && typeof tree === "object" && tree.states && typeof tree.states === "object")) return;
|
|
27
|
+
const names = Object.keys(tree.states);
|
|
28
|
+
const initial = typeof tree.initial === "string" ? tree.initial : names[0];
|
|
29
|
+
def.publicStates = names;
|
|
30
|
+
def.initialView = initial;
|
|
31
|
+
// The nested projection viewers render from (never scan layouts for it).
|
|
32
|
+
def.stateTree = names.map((n) => {
|
|
33
|
+
const s = (tree.states as Record<string, { layout?: unknown; on?: unknown; initial?: unknown; states?: Record<string, { layout?: unknown } | null> } | null>)[n];
|
|
34
|
+
const entry: NonNullable<UnoverseDefinition["stateTree"]>[number] = { name: n };
|
|
35
|
+
if (s && typeof s === "object") {
|
|
36
|
+
if (typeof s.layout === "string") entry.layout = s.layout;
|
|
37
|
+
if (typeof s.on === "string") entry.on = s.on;
|
|
38
|
+
if (typeof s.initial === "string") entry.initial = s.initial;
|
|
39
|
+
if (s.states && typeof s.states === "object")
|
|
40
|
+
entry.states = Object.entries(s.states).map(([k, v]) => ({
|
|
41
|
+
name: k,
|
|
42
|
+
...(v && typeof v === "object" && typeof v.layout === "string" ? { layout: v.layout } : {}),
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
return entry;
|
|
46
|
+
});
|
|
47
|
+
// ONE AXIS ON A MIGRATED SLICE. The manifest's `defaultState` is seeded above
|
|
48
|
+
// for legacy components; a component with a tree has already said where it
|
|
49
|
+
// arrives, so the alias is dropped here rather than shipped alongside `view`.
|
|
50
|
+
// Two spellings on one slice is how a stale reader keeps working by accident:
|
|
51
|
+
// it reads the alias, sees the ARRIVAL state forever, and never notices the
|
|
52
|
+
// component moved. (The manifest keeps the key: at app level it is the load
|
|
53
|
+
// mode the router branches on, a different axis with the same name.)
|
|
54
|
+
// STARTING VALUES (owner ruling 2026-08-29 late): the authored home is
|
|
55
|
+
// `values:` — the initial values of keys the component writes (the
|
|
56
|
+
// setValue vocabulary). `state:` scalars are the legacy spelling,
|
|
57
|
+
// still read; either way the SERVED shape is unchanged (def.state).
|
|
58
|
+
const rawValues = (def as unknown as { values?: unknown }).values;
|
|
59
|
+
const { defaultState: _legacyAlias, view: _legacyTree, ...legacyScalars } = (def.state ?? {}) as Record<string, unknown>;
|
|
60
|
+
const authored =
|
|
61
|
+
rawValues && typeof rawValues === "object" && !Array.isArray(rawValues)
|
|
62
|
+
? (rawValues as Record<string, unknown>)
|
|
63
|
+
: legacyScalars;
|
|
64
|
+
delete (def as unknown as Record<string, unknown>).values;
|
|
65
|
+
// ORDER IS THE PRIORITY, at BOTH levels (2026-08-15). The first state
|
|
66
|
+
// declared is the one the component arrives in, and the first substate
|
|
67
|
+
// declared is the step its `on` axis starts on. An `initial` key said the
|
|
68
|
+
// same thing a second time and was free to contradict the list, which is
|
|
69
|
+
// how a component could declare one arrival and ship another. It is still
|
|
70
|
+
// READ where a def has not been swept, and lint-forbidden in rx.
|
|
71
|
+
const flat: Record<string, unknown> = { ...authored, state: initial };
|
|
72
|
+
delete (def as unknown as Record<string, unknown>).states; // the tree compiled; the raw block never rides the served def
|
|
73
|
+
for (const s of Object.values(tree.states))
|
|
74
|
+
if (s && typeof s === "object" && typeof s.on === "string") {
|
|
75
|
+
const subs = s.states && typeof s.states === "object" ? Object.keys(s.states) : [];
|
|
76
|
+
const step = typeof s.initial === "string" ? s.initial : subs[0];
|
|
77
|
+
if (step) flat[s.on] = step;
|
|
78
|
+
}
|
|
79
|
+
def.state = flat;
|
|
80
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The COMPILERS, one file per job (split from compile.ts 2026-09-02, mirroring
|
|
3
|
+
* the expand/ split of 2026-08-30):
|
|
4
|
+
*
|
|
5
|
+
* component.ts a component's `states:` tree → served projections
|
|
6
|
+
* sections.ts a template layout's three words → wire claims + sections
|
|
7
|
+
* template.ts a template's states (folder) or root (block) → layouts/previews
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export { compileComponentTree } from "./component.js";
|
|
11
|
+
export { expandTemplateSections } from "./sections.js";
|
|
12
|
+
export { synthesizeTemplateRoot } from "./template.js";
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SECTION-WORD compiler: a template layout's three words → wire claims +
|
|
3
|
+
* section projections. Split from compile.ts into this folder 2026-09-02; the
|
|
4
|
+
* rules are unchanged.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { UnoverseDefinition } from "../../model.js";
|
|
8
|
+
|
|
9
|
+
/** Expand a template layout's THREE-WORD sections (§9b, 2026-08-29; one director word
|
|
10
|
+
* since 2026-09-02) into the wire primitives the renderer already speaks, collecting
|
|
11
|
+
* the section projections as it goes. `static:`/`copywriter:` link a component
|
|
12
|
+
* (face = its basename) and become a limit-1 claim on that face; `director:` with a
|
|
13
|
+
* `component` is a SEARCH place (same limit-1 claim; the grammar rides the section for
|
|
14
|
+
* the fill path), and without one it is the open DELIVERY claim — its face is the
|
|
15
|
+
* `state:` pin when authored (arrivals are switched in and held), else the owning
|
|
16
|
+
* state's name (the free name-match dance). The SDK stays dumb: authors never see
|
|
17
|
+
* ComponentSlot/select/where, and the wire never sees the three words. */
|
|
18
|
+
export function expandTemplateSections(
|
|
19
|
+
node: unknown,
|
|
20
|
+
owningState: string,
|
|
21
|
+
templateName: string,
|
|
22
|
+
out: NonNullable<UnoverseDefinition["sections"]>[string],
|
|
23
|
+
): unknown {
|
|
24
|
+
if (Array.isArray(node)) return node.map((c) => expandTemplateSections(c, owningState, templateName, out));
|
|
25
|
+
if (!node || typeof node !== "object") return node;
|
|
26
|
+
const n = node as Record<string, unknown>;
|
|
27
|
+
const linked = typeof n.static === "string" ? "static" : typeof n.copywriter === "string" ? "copywriter" : null;
|
|
28
|
+
if (linked) {
|
|
29
|
+
const path = String(n[linked]);
|
|
30
|
+
const face = path.split("/").pop() ?? path;
|
|
31
|
+
out.push({ kind: linked, face, component: path, ref: `${templateName}/${face}` });
|
|
32
|
+
const { static: _s, copywriter: _c, ...rest } = n;
|
|
33
|
+
return {
|
|
34
|
+
type: "ComponentSlot",
|
|
35
|
+
select: { from: "all", where: { field: "state", eq: face }, limit: 1 },
|
|
36
|
+
...rest,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
// RETIRED VOCABULARY IS A LOUD FAILURE, never a legacy-read (the no-shims law).
|
|
40
|
+
// `query:` merged into `director:` 2026-09-02 (§9e rule 10b REVERSED); `limit` and
|
|
41
|
+
// `ask` retired the same day (`pick` is the one cap word; the question is derived,
|
|
42
|
+
// rule 10d). A stale file fails at load with the fix in the message.
|
|
43
|
+
if (n.query !== undefined) {
|
|
44
|
+
throw new Error(`template "${templateName}": the query: word is retired (§9e rule 10b reversed 2026-09-02) — spell the place director: with the same component/items/type/include/pick`);
|
|
45
|
+
}
|
|
46
|
+
if (n.director !== undefined && (n.director === null || (typeof n.director === "object" && !Array.isArray(n.director)))) {
|
|
47
|
+
const d = (n.director ?? {}) as Record<string, unknown>;
|
|
48
|
+
if (d.limit !== undefined) throw new Error(`template "${templateName}": director limit is retired — the cap word is pick everywhere (§9e rule 9, renamed 2026-09-02)`);
|
|
49
|
+
if (d.ask !== undefined || d.query !== undefined) throw new Error(`template "${templateName}": a director place never states its question — it is derived (§9e rule 10d): the writing, the page's pool, the component's contract, or the guest's ask`);
|
|
50
|
+
/**
|
|
51
|
+
* A SEARCH PLACE (§9e, one word since 2026-09-02) — the director given a component.
|
|
52
|
+
*
|
|
53
|
+
* It LINKS the part exactly as `static:`/`copywriter:` do, so the slot is identical: a
|
|
54
|
+
* limit-1 claim on the part's own face. The content is a search the page runs itself,
|
|
55
|
+
* before any model has spoken; the QUESTION is never here (rule 10d — the writer's
|
|
56
|
+
* headings for an `items` place, the page's pool for a gallery, the component's own
|
|
57
|
+
* contract, or the guest's ask). The layout says only what KIND may answer (`include`)
|
|
58
|
+
* and how many survive (`pick`); none of it reaches the slot — it rides the section
|
|
59
|
+
* for the fill path.
|
|
60
|
+
*
|
|
61
|
+
* NOTHING DECLARES WHICH PROP TAKES THE ROWS. The schema already says: the prop whose
|
|
62
|
+
* items require only `ref`. So a searched part keeps BOTH lanes — its refs filled by
|
|
63
|
+
* search at shell open with no model, its headings and taglines written by the
|
|
64
|
+
* copywriter afterwards (§9e rule 10). Same part, both lanes, in that order.
|
|
65
|
+
*/
|
|
66
|
+
if (typeof d.component === "string" && d.component) {
|
|
67
|
+
const path = String(d.component);
|
|
68
|
+
const face = path.split("/").pop() ?? path;
|
|
69
|
+
// AN ITEMS PLACE (§9e rule 10c): the place holds a LIST the writer shapes — each
|
|
70
|
+
// item's heading arrives WITH its own search, so they can never disagree.
|
|
71
|
+
//
|
|
72
|
+
// THE SEARCH WORDS SIT ON THE THING THAT SEARCHES (owner ruling 2026-09-02): each
|
|
73
|
+
// ITEM searches, so type/include/pick nest INSIDE items: beside min/max, and pick
|
|
74
|
+
// reads unambiguously as per-item. The flat spelling on an items place is a LOUD
|
|
75
|
+
// failure — before this, flat `pick` meant two different scopes depending on
|
|
76
|
+
// whether items: happened to be present.
|
|
77
|
+
const items = d.items && typeof d.items === "object" && !Array.isArray(d.items) ? (d.items as Record<string, unknown>) : null;
|
|
78
|
+
if (items && (d.type !== undefined || d.include !== undefined || d.pick !== undefined))
|
|
79
|
+
throw new Error(
|
|
80
|
+
`template "${templateName}": an items place nests its search words INSIDE items: (each ITEM searches) — items: { min, max, type, include, pick }`,
|
|
81
|
+
);
|
|
82
|
+
const src = items ?? d;
|
|
83
|
+
out.push({
|
|
84
|
+
kind: "director",
|
|
85
|
+
face,
|
|
86
|
+
component: path,
|
|
87
|
+
ref: `${templateName}/${face}`,
|
|
88
|
+
...(src.type === "discovery" || src.type === "intent" ? { type: src.type } : {}),
|
|
89
|
+
...(Array.isArray(src.include) ? { include: src.include.map(String) } : {}),
|
|
90
|
+
...(typeof src.pick === "number" ? { pick: src.pick } : {}),
|
|
91
|
+
...(items ? { items: { min: Number(items.min) || 1, max: Number(items.max) || 1 } } : {}),
|
|
92
|
+
});
|
|
93
|
+
const { director: _d, ...rest } = n;
|
|
94
|
+
return {
|
|
95
|
+
type: "ComponentSlot",
|
|
96
|
+
select: { from: "all", where: { field: "state", eq: face }, limit: 1 },
|
|
97
|
+
...rest,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* THE COMPILER ADDRESSES THE PLACE (§9d, ruled 2026-08-31).
|
|
102
|
+
*
|
|
103
|
+
* A `director:` place is somewhere things may go — it carries no name, no prose and no
|
|
104
|
+
* state, because the ONE mind reading the page's brief already knows what the page is.
|
|
105
|
+
* But a slot still has to select SOMETHING, and two places on one layout may not claim
|
|
106
|
+
* the same face (the reaction contract's one-view-one-surface rule). So the address is
|
|
107
|
+
* handed out here, by position, and an author never types a face name.
|
|
108
|
+
*
|
|
109
|
+
* An AUTHORED `state:` still wins, for the flat-email case §9b rule 4 names — arrivals
|
|
110
|
+
* pinned into one declared state — and for any template written before this ruling.
|
|
111
|
+
*/
|
|
112
|
+
const nth = out.filter((sec) => sec.kind === "director" && !sec.component).length;
|
|
113
|
+
const face = typeof d.state === "string" ? d.state : nth === 0 ? owningState : `${owningState}-${nth + 1}`;
|
|
114
|
+
out.push({
|
|
115
|
+
kind: "director",
|
|
116
|
+
face,
|
|
117
|
+
...(typeof d.state === "string" ? { state: d.state } : {}),
|
|
118
|
+
...(typeof d.pick === "number" ? { pick: d.pick } : {}),
|
|
119
|
+
...(typeof d.rules === "string" ? { rules: d.rules } : {}),
|
|
120
|
+
...(Array.isArray(d.preview) ? { preview: d.preview.map(String) } : {}),
|
|
121
|
+
});
|
|
122
|
+
const { director: _d, ...rest } = n;
|
|
123
|
+
return {
|
|
124
|
+
type: "ComponentSlot",
|
|
125
|
+
select: { from: "all", where: { field: "state", eq: face }, ...(typeof d.pick === "number" ? { limit: d.pick } : {}) },
|
|
126
|
+
...rest,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const o: Record<string, unknown> = {};
|
|
130
|
+
for (const [k2, v] of Object.entries(n)) o[k2] = expandTemplateSections(v, owningState, templateName, out);
|
|
131
|
+
return o;
|
|
132
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TEMPLATE-ROOT compiler: a template's states (folder form) or root (a flat
|
|
3
|
+
* BLOCK) → the served layouts, sections, previews and root. Split from compile.ts
|
|
4
|
+
* into this folder 2026-09-02; the dead `parts:`/`shapes:` legacy tails (retired
|
|
5
|
+
* §9b vocabulary no definition authored any more) were deleted in the same move.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { UnoverseDefinition } from "../../model.js";
|
|
9
|
+
import { loadDefinition } from "../load.js";
|
|
10
|
+
import { composeIncludes } from "../expand/index.js";
|
|
11
|
+
import { expandTemplateSections } from "./sections.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* ONE PREVIEWED INSTANCE'S SEED — `preview ?? default` per prop, the same values the
|
|
15
|
+
* Components screen has always seeded. An instance seeded with nothing draws as a page of
|
|
16
|
+
* ghosts, which is how a finished arrangement comes to read as a broken one.
|
|
17
|
+
*
|
|
18
|
+
* Shared by both authored forms (a folder's states and a flat block's root): a mock that
|
|
19
|
+
* differed by which form a template happened to be written in would be a preview nobody
|
|
20
|
+
* could trust.
|
|
21
|
+
*/
|
|
22
|
+
function seedFor(ref: string, org?: string): Record<string, unknown> | undefined {
|
|
23
|
+
const part = loadDefinition(ref, "component", "expanded", org);
|
|
24
|
+
const seed = Object.fromEntries(
|
|
25
|
+
Object.entries((part?.props ?? {}) as Record<string, { preview?: unknown; default?: unknown; type?: string }>).map(
|
|
26
|
+
([k, v]) => [k, v?.preview ?? v?.default ?? (v?.type === "array" ? [] : v?.type === "object" ? {} : "")],
|
|
27
|
+
),
|
|
28
|
+
);
|
|
29
|
+
return Object.keys(seed).length ? seed : undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Stamp a section list's Studio stand-ins: which refs each face previews, seeded
|
|
33
|
+
* `preview ?? default` (keyed by REF, because a director band names several and may
|
|
34
|
+
* name one twice). A linked part previews ITSELF; a delivery band previews whatever
|
|
35
|
+
* its `preview:` names. */
|
|
36
|
+
function seedSections(
|
|
37
|
+
secs: NonNullable<UnoverseDefinition["sections"]>[string],
|
|
38
|
+
org: string | undefined,
|
|
39
|
+
previews: Record<string, string[]>,
|
|
40
|
+
): void {
|
|
41
|
+
for (const s of secs) {
|
|
42
|
+
// A LINKED part previews ITSELF — including a director SEARCH place, which carries a
|
|
43
|
+
// ref since the one-word merge (a kind check here hid every search place's mock the
|
|
44
|
+
// day the kinds merged). Only an unlinked DELIVERY band previews its `preview:` list.
|
|
45
|
+
const refs = s.ref ? [s.ref] : s.kind === "director" ? (s.preview ?? []) : [];
|
|
46
|
+
if (s.ref) previews[s.face] = [s.ref];
|
|
47
|
+
else if (s.preview?.length) previews[s.face] = s.preview;
|
|
48
|
+
const seeds: Record<string, Record<string, unknown>> = {};
|
|
49
|
+
for (const ref of new Set(refs)) {
|
|
50
|
+
const seed = seedFor(ref, org);
|
|
51
|
+
if (seed) seeds[ref] = seed;
|
|
52
|
+
}
|
|
53
|
+
if (Object.keys(seeds).length) s.seeds = seeds;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function synthesizeTemplateRoot(def: UnoverseDefinition, folderDir?: string): void {
|
|
58
|
+
if (def.root) {
|
|
59
|
+
/**
|
|
60
|
+
* A BLOCK — a flat template that declares a DRAWING instead of a tree (2026-08-31).
|
|
61
|
+
*
|
|
62
|
+
* Its three words still have to compile. Without this the `director:` line sat in the
|
|
63
|
+
* served root verbatim, unexpanded and inert: the renderer speaks ComponentSlot and
|
|
64
|
+
* knows no such word, so a block drew as an empty box wherever it was used (observed
|
|
65
|
+
* in Studio the moment the first one was authored). The folder form compiles its
|
|
66
|
+
* sections as it composes each state's layout; the flat form had no such pass.
|
|
67
|
+
*
|
|
68
|
+
* Compiled into the SAME projections a one-state template serves, so nothing
|
|
69
|
+
* downstream has to know which form it was authored in: `layouts`, `defaultLayout`,
|
|
70
|
+
* `sections`, `publicStates`. The author still writes only `root:`.
|
|
71
|
+
*/
|
|
72
|
+
if ((def as unknown as { states?: unknown }).states === undefined && def.layouts === undefined) {
|
|
73
|
+
const secs: NonNullable<UnoverseDefinition["sections"]>[string] = [];
|
|
74
|
+
const compiled = expandTemplateSections(def.root, def.name, def.name, secs);
|
|
75
|
+
if (secs.length) {
|
|
76
|
+
const previews: Record<string, string[]> = {};
|
|
77
|
+
seedSections(secs, def.org, previews);
|
|
78
|
+
if (Object.keys(previews).length) def.preview = { ...(def.preview ?? {}), ...previews };
|
|
79
|
+
}
|
|
80
|
+
// The projections are built WHETHER OR NOT the root carries section words
|
|
81
|
+
// (2026-09-02): a pure-arrangement block — `band`, `two-column`, style and an
|
|
82
|
+
// opening, no fill lane — is still a template, and without a layout Studio drew it
|
|
83
|
+
// as a blank canvas (observed the day blocks returned). Its own authored children
|
|
84
|
+
// are its preview.
|
|
85
|
+
def.root = compiled as UnoverseDefinition["root"];
|
|
86
|
+
def.layouts = { [def.name]: compiled as never };
|
|
87
|
+
def.defaultLayout = def.name;
|
|
88
|
+
if (secs.length) def.sections = { [def.name]: secs };
|
|
89
|
+
def.publicStates = [def.name];
|
|
90
|
+
def.initialView = def.name;
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
// STATES SERVE THE APP-RUNTIME CONTRACT (§9b RULED 2026-08-29): a template's states
|
|
95
|
+
// compose into def.layouts — sections expanded to wire ComponentSlots, no baked
|
|
96
|
+
// mocks — with the FIRST state as the base (def.root + defaultLayout) and the rest
|
|
97
|
+
// as the reaction ladder (def.stateOrder). The SDK then does everything the rules
|
|
98
|
+
// say: an instance's face change surfaces the name-matched layout, its claim catches
|
|
99
|
+
// the instance, steps stay contained in the instance's own slice. Studio seeds real
|
|
100
|
+
// instances from the compiled sections and the same runtime runs.
|
|
101
|
+
const rawStates = (def as unknown as { states?: unknown }).states;
|
|
102
|
+
const states: Record<string, { layout?: unknown } | null> =
|
|
103
|
+
rawStates && typeof rawStates === "object" && !Array.isArray(rawStates)
|
|
104
|
+
? (rawStates as Record<string, { layout?: unknown } | null>)
|
|
105
|
+
: {};
|
|
106
|
+
const shapeNames = Object.keys(states);
|
|
107
|
+
if (!folderDir || !shapeNames.length) return;
|
|
108
|
+
const layouts: Record<string, unknown> = {};
|
|
109
|
+
const sections: NonNullable<UnoverseDefinition["sections"]> = {};
|
|
110
|
+
const compiledPreview: Record<string, string[]> = {};
|
|
111
|
+
// THE LATCH IS A FACT ABOUT A STATE (MCP guide §The Component Latch). Collected on the
|
|
112
|
+
// same pass as the layouts and stamped as `def.latches`, because `states` is deleted
|
|
113
|
+
// below: a state that says nothing here can never be addressed, and an instance moving
|
|
114
|
+
// between states gains and loses the pill with no eviction and nothing to unregister.
|
|
115
|
+
const latches: NonNullable<UnoverseDefinition["latches"]> = {};
|
|
116
|
+
for (const name of shapeNames) {
|
|
117
|
+
const st = (states[name] ?? {}) as Record<string, unknown>;
|
|
118
|
+
const latch = st.latch as { title?: unknown; icon?: unknown } | undefined;
|
|
119
|
+
// TITLE OR NOTHING: the pill's whole content is the title, so a latch without one
|
|
120
|
+
// draws a nameless pill the guest cannot read. Declared but empty is an authoring
|
|
121
|
+
// slip, and taking it literally is what makes the slip visible.
|
|
122
|
+
if (latch && typeof latch === "object" && typeof latch.title === "string" && latch.title.trim())
|
|
123
|
+
latches[name] = { title: latch.title, ...(typeof latch.icon === "string" ? { icon: latch.icon } : {}) };
|
|
124
|
+
if (typeof st.layout !== "string") continue;
|
|
125
|
+
const secs: NonNullable<UnoverseDefinition["sections"]>[string] = [];
|
|
126
|
+
layouts[name] = expandTemplateSections(composeIncludes({ $include: st.layout }, folderDir), name, def.name, secs);
|
|
127
|
+
sections[name] = secs;
|
|
128
|
+
// MOCK VALUES TRAVEL WITH THE SECTION — a preview host seeds instances from the wire.
|
|
129
|
+
seedSections(secs, def.org, compiledPreview);
|
|
130
|
+
}
|
|
131
|
+
if (!Object.keys(layouts).length) return;
|
|
132
|
+
def.layouts = layouts;
|
|
133
|
+
def.defaultLayout = shapeNames[0];
|
|
134
|
+
def.stateOrder = shapeNames.slice(1);
|
|
135
|
+
// THE FACE IT ARRIVES WEARING (2026-08-30). A template's states compiled into
|
|
136
|
+
// layouts and stopped there, so it carried no `publicStates`/`initialView` — the
|
|
137
|
+
// two projections that say "this is my interface, and this is the face I arrive
|
|
138
|
+
// in". Every consumer of a face was therefore blind to it: a preview could not
|
|
139
|
+
// seed it, a state chip could not activate it, and — the one that matters — an
|
|
140
|
+
// app's ladder had nothing to react to when a template STREAMED in, so a
|
|
141
|
+
// discovered page landed inline in the conversation instead of taking its
|
|
142
|
+
// surface. Stamped exactly as the component tier stamps them, because it is the
|
|
143
|
+
// same contract: states are the interface, the first declared one is arrival.
|
|
144
|
+
def.publicStates = shapeNames;
|
|
145
|
+
def.initialView = shapeNames[0];
|
|
146
|
+
if (Object.keys(latches).length) def.latches = latches;
|
|
147
|
+
if (Object.keys(sections).length) def.sections = sections;
|
|
148
|
+
if (Object.keys(compiledPreview).length) def.preview = { ...(def.preview ?? {}), ...compiledPreview };
|
|
149
|
+
delete (def as unknown as Record<string, unknown>).states;
|
|
150
|
+
// The base arrangement, wrapped in a container so responsive columns can
|
|
151
|
+
// query their surface width in any host.
|
|
152
|
+
def.root = { type: "Box", style: { width: "full", container: "inline-size" }, children: [layouts[shapeNames[0]]] };
|
|
153
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `$include` COMPOSITION: a definition's siblings, inlined.
|
|
3
|
+
*
|
|
4
|
+
* The authoring-time half of composition — a layout pulls in `components/header`, a state
|
|
5
|
+
* pulls in a shared shape. Resolved against the definition's own folder, recursively.
|
|
6
|
+
*
|
|
7
|
+
* Split from expand.ts 2026-08-30; the rules are unchanged.
|
|
8
|
+
*/
|
|
9
|
+
import { readDefCached, defPath } from "../../fsCache.js";
|
|
10
|
+
import type { AnyNode } from "./refs.js";
|
|
11
|
+
|
|
12
|
+
export function composeIncludes(value: any, folderDir: string): any {
|
|
13
|
+
if (Array.isArray(value)) return value.map((v) => composeIncludes(v, folderDir));
|
|
14
|
+
if (value && typeof value === "object") {
|
|
15
|
+
if (typeof value.$include === "string") {
|
|
16
|
+
const incPath = defPath(folderDir, value.$include);
|
|
17
|
+
// Name the missing include: the raw ENOENT names a `.yaml` that was never meant to
|
|
18
|
+
// exist, which reads as a resolver bug rather than a broken reference.
|
|
19
|
+
if (!incPath) throw new Error(`$include "${value.$include}" resolves to no file in ${folderDir}`);
|
|
20
|
+
return composeIncludes(readDefCached(incPath), folderDir);
|
|
21
|
+
}
|
|
22
|
+
const out: AnyNode = {};
|
|
23
|
+
for (const [k, v] of Object.entries(value)) out[k] = composeIncludes(v, folderDir);
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* COMPOSITION — the surface. Split by JOB 2026-08-30, the way `markdown/` and
|
|
3
|
+
* `agent-mcp/` are: `expand.ts` had grown to five unrelated jobs in one file.
|
|
4
|
+
*
|
|
5
|
+
* refs.ts inlining a Ref: the field remaps and the `with` literals
|
|
6
|
+
* inherit.ts what an embedded piece carries up (its state, its prop contracts)
|
|
7
|
+
* walk.ts the expansion itself
|
|
8
|
+
* referenced.ts the reference-don't-copy form (each atom shipped once)
|
|
9
|
+
* includes.ts `$include` composition
|
|
10
|
+
*
|
|
11
|
+
* Every export keeps its name, so nothing that imports composition had to change.
|
|
12
|
+
*/
|
|
13
|
+
export { remapFields, applyLiterals, type AnyNode } from "./refs.js";
|
|
14
|
+
export { machineryKeys, inheritState, inheritProps, mergeInheritedProps } from "./inherit.js";
|
|
15
|
+
export { expandNode, expandRefs } from "./walk.js";
|
|
16
|
+
export { referencedDefinition } from "./referenced.js";
|
|
17
|
+
export { composeIncludes } from "./includes.js";
|