@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.
Files changed (132) hide show
  1. package/local/plugin.ts +21 -65
  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 +153 -0
  7. package/vendor/base/src/agent-mcp/lean.ts +114 -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 +234 -0
  11. package/vendor/base/src/agent-mcp/tools.ts +134 -0
  12. package/vendor/base/src/agent-mcp/types.ts +138 -0
  13. package/vendor/base/src/auth/auth.ts +307 -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 +155 -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 +659 -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 +397 -0
  38. package/vendor/base/src/items/baseVersion.ts +96 -0
  39. package/vendor/base/src/items/collect.ts +246 -0
  40. package/vendor/base/src/items/fingerprint.ts +34 -0
  41. package/vendor/base/src/items/publish.ts +212 -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.d.mts +28 -0
  53. package/vendor/base/src/lint/nodes/index.mjs +82 -0
  54. package/vendor/base/src/lint/nodes/load.mjs +126 -0
  55. package/vendor/base/src/lint/nodes/node.mjs +764 -0
  56. package/vendor/base/src/lint/nodes/package.mjs +112 -0
  57. package/vendor/base/src/lint/nodes/schema.mjs +75 -0
  58. package/vendor/base/src/logger.ts +33 -0
  59. package/vendor/base/src/manifests/compose.ts +539 -0
  60. package/vendor/base/src/manifests/executor/callback.ts +107 -0
  61. package/vendor/base/src/manifests/executor/context.ts +110 -0
  62. package/vendor/base/src/manifests/executor/index.ts +47 -0
  63. package/vendor/base/src/manifests/executor/lookup.ts +21 -0
  64. package/vendor/base/src/manifests/executor/memory.ts +62 -0
  65. package/vendor/base/src/manifests/executor/promise.ts +71 -0
  66. package/vendor/base/src/manifests/executor/refire.ts +34 -0
  67. package/vendor/base/src/manifests/executor/send.ts +65 -0
  68. package/vendor/base/src/manifests/executor/session.ts +46 -0
  69. package/vendor/base/src/manifests/executor/toolBridge.ts +266 -0
  70. package/vendor/base/src/manifests/index.ts +280 -0
  71. package/vendor/base/src/manifests/integrity.ts +93 -0
  72. package/vendor/base/src/manifests/rows.ts +33 -0
  73. package/vendor/base/src/manifests/runtime/auth/aws.ts +240 -0
  74. package/vendor/base/src/manifests/runtime/auth/oauth.ts +76 -0
  75. package/vendor/base/src/manifests/runtime/authorize.ts +159 -0
  76. package/vendor/base/src/manifests/runtime/channels/final.ts +171 -0
  77. package/vendor/base/src/manifests/runtime/channels/service.ts +138 -0
  78. package/vendor/base/src/manifests/runtime/channels/workflow.ts +310 -0
  79. package/vendor/base/src/manifests/runtime/context.ts +156 -0
  80. package/vendor/base/src/manifests/runtime/docstore/handlers.ts +547 -0
  81. package/vendor/base/src/manifests/runtime/docstore/index.ts +135 -0
  82. package/vendor/base/src/manifests/runtime/docstore/sectionizer.ts +184 -0
  83. package/vendor/base/src/manifests/runtime/docstore/store.ts +172 -0
  84. package/vendor/base/src/manifests/runtime/docstore/types.ts +60 -0
  85. package/vendor/base/src/manifests/runtime/duplex/audioLane.ts +165 -0
  86. package/vendor/base/src/manifests/runtime/duplex/session.ts +511 -0
  87. package/vendor/base/src/manifests/runtime/events.ts +223 -0
  88. package/vendor/base/src/manifests/runtime/helpers.ts +53 -0
  89. package/vendor/base/src/manifests/runtime/http/allowedHosts.ts +118 -0
  90. package/vendor/base/src/manifests/runtime/http/request.ts +637 -0
  91. package/vendor/base/src/manifests/runtime/http/response.ts +162 -0
  92. package/vendor/base/src/manifests/runtime/index.ts +57 -0
  93. package/vendor/base/src/manifests/runtime/loops/chunk.ts +98 -0
  94. package/vendor/base/src/manifests/runtime/loops/loop.ts +191 -0
  95. package/vendor/base/src/manifests/runtime/loops/paginate.ts +143 -0
  96. package/vendor/base/src/manifests/runtime/loops/poll.ts +169 -0
  97. package/vendor/base/src/manifests/runtime/narrate.ts +53 -0
  98. package/vendor/base/src/manifests/runtime/state.ts +171 -0
  99. package/vendor/base/src/manifests/runtime/templating.ts +110 -0
  100. package/vendor/base/src/manifests/runtime/tools/regionSkills.ts +125 -0
  101. package/vendor/base/src/manifests/runtime/tools/toolloop.ts +550 -0
  102. package/vendor/base/src/manifests/runtime/usage.ts +87 -0
  103. package/vendor/base/src/manifests/source.ts +196 -0
  104. package/vendor/base/src/manifests/watch.ts +67 -0
  105. package/vendor/base/src/markdown/document.ts +141 -0
  106. package/vendor/base/src/paths.ts +180 -0
  107. package/vendor/base/src/platform/audioManager.ts +100 -0
  108. package/vendor/base/src/platform/clientTransport.ts +65 -0
  109. package/vendor/base/src/platform/executionContext.ts +157 -0
  110. package/vendor/base/src/platform/index.ts +10 -0
  111. package/vendor/base/src/platform/pluginAPI.ts +146 -0
  112. package/vendor/base/src/platform/redis.ts +54 -0
  113. package/vendor/base/src/platform/serviceCalls.ts +222 -0
  114. package/vendor/base/src/pluginBase.ts +248 -0
  115. package/vendor/base/src/plugins/README.md +57 -0
  116. package/vendor/base/src/plugins/discovery.ts +87 -0
  117. package/vendor/base/src/plugins/index.ts +16 -0
  118. package/vendor/base/src/plugins/loader.ts +179 -0
  119. package/vendor/base/src/plugins/redis.ts +36 -0
  120. package/vendor/base/src/plugins/state.ts +53 -0
  121. package/vendor/base/src/registry.ts +95 -0
  122. package/vendor/base/src/security/permissions.ts +93 -0
  123. package/vendor/base/src/security/rules.ts +73 -0
  124. package/vendor/base/src/template/SafeExpression.ts +556 -0
  125. package/vendor/base/src/template/StringTemplateResolver.ts +75 -0
  126. package/vendor/base/src/template/index.ts +22 -0
  127. package/vendor/base/src/types.ts +50 -0
  128. package/vendor/base/src/workflow/GetMemoryService.ts +158 -0
  129. package/vendor/base/src/workflow/PlatformMemoryCore.ts +449 -0
  130. package/vendor/base/src/workflow/routing.ts +122 -0
  131. package/vendor/sdk/lib/core/store.ts +26 -6
  132. package/local/mcp.ts +0 -265
@@ -0,0 +1,164 @@
1
+ /**
2
+ * REFERENCE, DON'T COPY — the served form that ships each atom once.
3
+ *
4
+ * `expandRefs` specialises every Ref into an inline copy, so a shared button travels
5
+ * inside every definition that uses it, on every page load. This is the other form: the
6
+ * definition with its `Ref` nodes LEFT ALONE, plus one copy of each atom they name.
7
+ *
8
+ * Split from expand.ts 2026-08-30; the rules are unchanged.
9
+ */
10
+ import type { UnoverseDefinition } from "../../model.js";
11
+ import { loadDefinition } from "../load.js";
12
+ import { inheritState } from "./inherit.js";
13
+ import type { AnyNode } from "./refs.js";
14
+
15
+ export function referencedDefinition(def: UnoverseDefinition, contextOrg?: string): UnoverseDefinition {
16
+ const defs: Record<string, AnyNode> = {};
17
+ const inherit: Record<string, unknown> = {};
18
+ // Depth-first over the tree, collecting each named atom ONCE. An atom may itself Ref
19
+ // another, so collected atoms are walked too — otherwise the client resolves an atom
20
+ // and finds a reference it was never sent.
21
+ const collect = (node: AnyNode | undefined): void => {
22
+ if (!node || typeof node !== "object") return;
23
+ if (Array.isArray(node)) return node.forEach(collect);
24
+ if (node.type === "Ref" && typeof node.ref === "string") {
25
+ const name = node.ref;
26
+ if (!(name in defs)) {
27
+ // The referenced piece in its OWN referenced form, with its references merged up
28
+ // into the same flat map. The client threads `defs` through every recursion, so a
29
+ // Ref inside a referenced piece resolves against the same map. Storing these
30
+ // EXPANDED instead was the difference between product-card costing 17 KB + 173 KB
31
+ // and 17 KB + a handful: it references `document`, and an expanded document drags
32
+ // in every copy this exists to remove.
33
+ const piece =
34
+ loadDefinition(name, "atom", "referenced") ?? loadDefinition(name, "component", "referenced", contextOrg ?? def.org);
35
+ if (piece?.root) {
36
+ defs[name] = piece.root as AnyNode;
37
+ // Same inheritance as the expanded form. The client resolves the Ref itself here,
38
+ // but `defs` carries roots only — state never travels node-side, so it has to be
39
+ // merged onto the definition server-side or a referenced-mode host keeps the bug
40
+ // the expanded form no longer has.
41
+ inheritState(inherit, piece);
42
+ for (const [k, v] of Object.entries((piece.defs ?? {}) as Record<string, AnyNode>)) if (!(k in defs)) defs[k] = v;
43
+ collect(piece.root as AnyNode);
44
+ }
45
+ }
46
+ }
47
+ if (Array.isArray(node.children)) node.children.forEach(collect);
48
+ if (node.item) collect(node.item as AnyNode);
49
+ if (node.cases && typeof node.cases === "object") for (const k of Object.keys(node.cases)) collect(node.cases[k] as AnyNode);
50
+ if (node.frame) collect(node.frame as AnyNode);
51
+ if (node.fallback) collect(node.fallback as AnyNode);
52
+ };
53
+ // EVERY TREE THE DEFINITION SHIPS, not just `root`. A template's arrangements live in
54
+ // `layouts`, and they are the whole payload — a chat template's `root` is a stub. Walking
55
+ // `root` alone deduplicated nothing at all for templates: measured on bpp-chat,
56
+ // whose four layouts include the SAME 7 KB chrome file, the referenced form came back 11%
57
+ // smaller instead of the ~70% every component saw.
58
+ const trees = () => [def.root, ...Object.values(def.layouts ?? {})] as AnyNode[];
59
+ trees().forEach(collect);
60
+ if (Object.keys(inherit).length) def = { ...def, state: { ...inherit, ...((def.state as Record<string, unknown>) ?? {}) } };
61
+
62
+ // REPEATS ARE REFERENCES TOO, wherever they came from.
63
+ //
64
+ // Hoisting named atoms only fixes duplication that went through a `Ref`. A template
65
+ // composes its arrangements with `$include`, which is copied just as literally: a chat
66
+ // layout shipping four arrangements carried four copies of the same chrome, 35 KB of a
67
+ // 44 KB payload, and not one byte of it was an atom.
68
+ //
69
+ // So this is structural: any subtree appearing more than once becomes ONE entry plus
70
+ // references to it. Identical trees render identically, and the client resolves a
71
+ // parameterless reference by cloning, so nothing about meaning changes. Counted ACROSS
72
+ // the trees, because the repetition that matters most is one arrangement against another.
73
+ // SCOPED TO THIS DEFINITION, because `defs` is not this definition's map alone: every
74
+ // referenced piece merges ITS names up into the same flat namespace above. Generated
75
+ // names used to be a counter restarting at 0 for every definition, so a template's
76
+ // `shared:0` and a component's `shared:0` were the same key. The component's landed
77
+ // first, the template's store was skipped as "already there", and the template's chunk
78
+ // was handed a pointer to the component's — silently, since a collision looks exactly
79
+ // like a chunk already stored. Observed live 2026-08-23: bpp-chat's chrome resolved to
80
+ // bpp/text-chat's, so the `appWidth: chat` it carried never reached the client and the
81
+ // app sized wrong. Nothing errored; a chunk simply became a different chunk.
82
+ const hoisted = hoistRepeats(trees(), defs, `${def.org ? `${def.org}/` : ""}${def.name}`);
83
+ if (hoisted.length) {
84
+ const [root, ...layouts] = hoisted;
85
+ def = { ...def, root };
86
+ if (def.layouts) def.layouts = Object.fromEntries(Object.keys(def.layouts).map((k, i) => [k, layouts[i]]));
87
+ }
88
+
89
+ return Object.keys(defs).length ? ({ ...def, defs } as UnoverseDefinition) : def;
90
+ }
91
+
92
+ /**
93
+ * Replace every repeated subtree, across ALL of a definition's trees, with a reference to
94
+ * one copy. Returns the rewritten trees in the order given.
95
+ *
96
+ * Counting spans the whole set rather than each tree alone, because the duplication that
97
+ * dominates a template is one ARRANGEMENT against another: four layouts that each include
98
+ * the same chrome share nothing within themselves and everything between themselves.
99
+ *
100
+ * Top-down, so the LARGEST repeated tree is hoisted and its children ride inside it rather
101
+ * than being hoisted separately: hoisting a child first would leave the parent's copies
102
+ * differing only in that child's reference, and they would stop matching each other.
103
+ *
104
+ * `MIN` exists because a reference costs about 40 bytes; hoisting a 60-byte node makes the
105
+ * payload bigger and the tree harder to read for nothing.
106
+ */
107
+ function hoistRepeats(roots: AnyNode[], defs: Record<string, AnyNode>, scope: string): AnyNode[] {
108
+ const MIN = 700; // bytes: comfortably larger than the reference that replaces it
109
+ const counts = new Map<string, number>();
110
+ const count = (node: unknown): void => {
111
+ if (Array.isArray(node)) return node.forEach(count);
112
+ if (!node || typeof node !== "object") return;
113
+ const n = node as AnyNode;
114
+ if (n.type && n.type !== "Ref") {
115
+ const j = JSON.stringify(n);
116
+ if (j.length >= MIN) counts.set(j, (counts.get(j) ?? 0) + 1);
117
+ }
118
+ for (const v of Object.values(n)) if (v && typeof v === "object") count(v);
119
+ };
120
+ roots.forEach(count);
121
+
122
+ const repeated = new Map<string, string>(); // serialized tree → its reference name
123
+ let i = 0;
124
+ // `shared:<definition>:<n>` — the definition segment is what makes a generated name mean
125
+ // ONE chunk in the flat map every referenced piece also writes into.
126
+ for (const [j, n] of counts) if (n > 1) repeated.set(j, `shared:${scope}:${(i++).toString(36)}`);
127
+ if (!repeated.size) return [];
128
+
129
+ const swap = (node: unknown): unknown => {
130
+ if (Array.isArray(node)) return node.map(swap);
131
+ if (!node || typeof node !== "object") return node;
132
+ const n = node as AnyNode;
133
+ if (n.type && n.type !== "Ref") {
134
+ const name = repeated.get(JSON.stringify(n));
135
+ if (name) {
136
+ // The first sighting becomes the stored copy, with ITS OWN repeats hoisted, so a
137
+ // shape repeated inside a repeated shape collapses too.
138
+ if (!(name in defs)) {
139
+ const stored = JSON.parse(JSON.stringify(n)) as AnyNode;
140
+ defs[name] = stored;
141
+ for (const [k, v] of Object.entries(stored)) if (v && typeof v === "object") stored[k] = swap(v) as AnyNode;
142
+ }
143
+ return { type: "Ref", ref: name };
144
+ }
145
+ }
146
+ for (const [k, v] of Object.entries(n)) if (v && typeof v === "object") n[k] = swap(v) as AnyNode;
147
+ return n;
148
+ };
149
+ // A whole tree can itself be a repeat (two arrangements that are the same), so the
150
+ // returned value is used rather than relying on in-place rewriting.
151
+ return roots.map((r) => swap(r) as AnyNode);
152
+ }
153
+
154
+
155
+ // ---- Folder composition: inline `{ "$include": "name" }` from sibling files ----
156
+ //
157
+ // A template can be a FOLDER (apps/chatlayout/) of multiple data files, so
158
+ // UX pieces live in their own files (chatlayout + user-turn + …). Any
159
+ // `{ "$include": "user-turn" }` node is replaced by the parsed content of
160
+ // `<folder>/user-turn.{yaml,json}` (recursively). All UX stays as served data — the SDK
161
+ // receives one fully-assembled tree.
162
+ //
163
+ // `$include` values are EXTENSION-LESS by design, so a partial can be converted from JSON
164
+ // to YAML without touching any file that references it.
@@ -0,0 +1,106 @@
1
+ /**
2
+ * INLINING A REF: the remaps and the literals.
3
+ *
4
+ * A component composes an atom with `{ type: "Ref", ref: "button", props: {…}, with: {…} }`.
5
+ * `props` REMAPS the atom's field names onto the host's; `with` resolves them to LITERALS.
6
+ * Everything here is that one job — nothing in this file knows what a definition is.
7
+ *
8
+ * Split from expand.ts 2026-08-30 (it had grown five jobs); the rules are unchanged.
9
+ */
10
+
11
+ export type AnyNode = Record<string, any>;
12
+
13
+ // Remap `{{field}}` data-bindings embedded in STYLE values (e.g. a radial gauge's
14
+ // `radial.at: "{{value}}"`, a progress bar's `width: "{{pct}}"`) — the style-side twin
15
+ // of the bind/visibleWhen remap, so an atom whose look is data-driven is parameterizable.
16
+ function remapStyleBindings(style: AnyNode, propMap: Record<string, string>): void {
17
+ for (const k of Object.keys(style)) {
18
+ const v = style[k];
19
+ if (typeof v === "string" && v.includes("{{")) {
20
+ style[k] = v.replace(/\{\{(\w[\w.]*)\}\}/g, (m, f) => (propMap[f] ? `{{${propMap[f]}}}` : m));
21
+ } else if (v && typeof v === "object") {
22
+ remapStyleBindings(v as AnyNode, propMap); // nested: radial.at, hover, etc.
23
+ }
24
+ }
25
+ }
26
+
27
+ export function remapFields(node: AnyNode, propMap: Record<string, string>): void {
28
+ // A NESTED `Ref` IS REMAPPED THROUGH ITS OWN `props`. The values of an inner reference's
29
+ // `props` are field names in THIS tree's scope, so the host's rename has to reach them:
30
+ // an atom that composes another atom (toggle-row and form-toggle both draw toggle-switch)
31
+ // otherwise hands the inner one the OUTER atom's prop name, which the host has just
32
+ // renamed away. The switch then tests an absent key and never draws its ON state — the
33
+ // control looks right, moves nothing, and says nothing. Same silence as the visibleWhen
34
+ // object form below, one level deeper.
35
+ if (node.type === "Ref" && node.props && typeof node.props === "object") {
36
+ for (const k of Object.keys(node.props as AnyNode)) {
37
+ const v = (node.props as AnyNode)[k];
38
+ if (typeof v === "string" && propMap[v]) (node.props as AnyNode)[k] = propMap[v];
39
+ }
40
+ }
41
+ if (node.bind) for (const k of Object.keys(node.bind)) if (propMap[node.bind[k]]) node.bind[k] = propMap[node.bind[k]];
42
+ if (typeof node.visibleWhen === "string" && propMap[node.visibleWhen]) node.visibleWhen = propMap[node.visibleWhen];
43
+ // THE OBJECT FORM REMAPS TOO. A guard is a guard whichever way it is written, and only
44
+ // the string form was carried across: an atom guarding on `{ field: "value", ne: "" }`
45
+ // kept testing its OWN prop name after the host remapped `value` to its field, so the
46
+ // guard read an absent key and the branch never showed. Silent, and it bit form-select
47
+ // the day it was written (2026-08-09).
48
+ if (node.visibleWhen && typeof node.visibleWhen === "object" && !Array.isArray(node.visibleWhen)) {
49
+ const g = node.visibleWhen as { field?: string };
50
+ if (typeof g.field === "string" && propMap[g.field]) g.field = propMap[g.field];
51
+ }
52
+ // `when` style variants are the same predicate wearing a different hat: an atom whose
53
+ // LOOK is data-driven (a switch track filling when `on` is true) must follow its host's
54
+ // field for the same reason.
55
+ const when = (node.style as AnyNode | undefined)?.when;
56
+ if (Array.isArray(when)) {
57
+ for (const v of when as { field?: string }[]) {
58
+ if (v && typeof v.field === "string" && propMap[v.field]) v.field = propMap[v.field];
59
+ }
60
+ }
61
+ if (node.style && typeof node.style === "object") remapStyleBindings(node.style as AnyNode, propMap);
62
+ if (Array.isArray(node.children)) node.children.forEach((c: AnyNode) => remapFields(c, propMap));
63
+ if (node.item) remapFields(node.item as AnyNode, propMap); // Each item subtree
64
+ if (node.cases && typeof node.cases === "object") for (const k of Object.keys(node.cases)) remapFields(node.cases[k] as AnyNode, propMap); // Switch branches
65
+ }
66
+
67
+ // Resolve an atom's bindings to LITERAL content (the Ref's `with` map). A bind entry
68
+ // whose field is a `with` key becomes a hardcoded attribute; a visibleWhen guard on a
69
+ // provided (truthy) key is satisfied statically and dropped; `{{key}}` style bindings
70
+ // take the value. Keys the atom doesn't bind are ignored (harmless).
71
+ /** Substitute `{{name}}` anywhere inside a style object, at any depth. */
72
+ function applyStyleLiterals(style: unknown, lits: Record<string, unknown>): void {
73
+ if (Array.isArray(style)) return style.forEach((v) => applyStyleLiterals(v, lits));
74
+ if (!style || typeof style !== "object") return;
75
+ const s = style as AnyNode;
76
+ for (const k of Object.keys(s)) {
77
+ const v = s[k];
78
+ if (typeof v === "string" && v.includes("{{"))
79
+ s[k] = v.replace(/\{\{(\w[\w.]*)\}\}/g, (m, f) => (f in lits ? String(lits[f]) : m));
80
+ else if (v && typeof v === "object") applyStyleLiterals(v, lits);
81
+ }
82
+ }
83
+
84
+ export function applyLiterals(node: AnyNode, lits: Record<string, unknown>): void {
85
+ if (node.bind) {
86
+ for (const attr of Object.keys(node.bind)) {
87
+ const field = node.bind[attr];
88
+ if (typeof field === "string" && field in lits) {
89
+ node[attr] = lits[field];
90
+ delete node.bind[attr];
91
+ }
92
+ }
93
+ if (Object.keys(node.bind).length === 0) delete node.bind;
94
+ }
95
+ if (typeof node.visibleWhen === "string" && node.visibleWhen in lits) {
96
+ if (lits[node.visibleWhen]) delete node.visibleWhen; // statically satisfied
97
+ // falsy literal: keep the (now never-true) guard — the node stays hidden
98
+ }
99
+ // Style values may NEST (a radial's `at`, a `when` clause's `apply`), so this walks the
100
+ // whole style object rather than its top level — a `{{value}}` one level down survived
101
+ // and reached the renderer verbatim.
102
+ if (node.style && typeof node.style === "object") applyStyleLiterals(node.style as AnyNode, lits);
103
+ if (Array.isArray(node.children)) node.children.forEach((c: AnyNode) => applyLiterals(c, lits));
104
+ if (node.item) applyLiterals(node.item as AnyNode, lits);
105
+ if (node.cases && typeof node.cases === "object") for (const k of Object.keys(node.cases)) applyLiterals(node.cases[k] as AnyNode, lits);
106
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * THE EXPANSION WALK: inline every `Ref`, gathering what the pieces carry up.
3
+ *
4
+ * One pass over a definition's tree. It calls into `refs.ts` to inline and remap, and into
5
+ * `inherit.ts` for the state and prop contracts an embedded piece hands its host.
6
+ *
7
+ * Split from expand.ts 2026-08-30; the rules are unchanged.
8
+ */
9
+ import type { UnoverseDefinition } from "../../model.js";
10
+ import { loadDefinition } from "../load.js";
11
+ import { remapFields, applyLiterals, type AnyNode } from "./refs.js";
12
+ import { inheritState, inheritProps, mergeInheritedProps } from "./inherit.js";
13
+
14
+ export function expandNode(node: AnyNode, contextOrg?: string, inherit?: Record<string, unknown>, props?: Record<string, Record<string, unknown>>, scope: string[] = []): AnyNode {
15
+ if (node?.type === "Ref") {
16
+ // A Ref inlines an ATOM (the usual case, rx/atoms). It also resolves a COMPONENT
17
+ // as a fallback, so a template can embed a shared flat component (e.g. the
18
+ // ComposerBar chrome) the same way it embeds an atom — the one mechanism for fixed,
19
+ // always-present chrome. (A component's root is inlined as-is; flat components only.)
20
+ // The parent definition's org rides along so a bare ref inside an org's own tree
21
+ // resolves to that org's component when two orgs share the name.
22
+ //
23
+ // AND A BLOCK (2026-08-31) — a flat TEMPLATE with no manifest: `pair`, `feature`,
24
+ // `single`, `rail`, `stack`. §2 always said "a rich template may compose basic ones
25
+ // inside itself", and this is that sentence, by the mechanism already here rather than
26
+ // a new one. Inlining is exactly right for it: the block's `director:` places arrive in
27
+ // the composing page's tree BEFORE its sections compile, so they become real slots and
28
+ // the page addresses them by position along with its own. Nothing is mounted, nothing
29
+ // is nested at runtime — the page ends up with the tree it would have had if someone
30
+ // had drawn the block by hand, which is the point.
31
+ //
32
+ // Last in the chain deliberately: atoms and components are the common case, and a
33
+ // template sharing a name with either keeps the old resolution.
34
+ const atom =
35
+ loadDefinition(String(node.ref), "atom") ??
36
+ loadDefinition(String(node.ref), "component", "expanded", contextOrg) ??
37
+ loadDefinition(String(node.ref), "template", "expanded", contextOrg);
38
+ if (!atom?.root) return node; // unknown ref — leave the Ref (renders as a no-op)
39
+ inheritState(inherit ?? {}, atom);
40
+ if (props) inheritProps(props, atom, (node.props ?? {}) as Record<string, string>, (node.with ?? {}) as Record<string, unknown>, scope);
41
+ const root = JSON.parse(JSON.stringify(atom.root)) as AnyNode;
42
+ remapFields(root, (node.props ?? {}) as Record<string, string>);
43
+ if (node.with && typeof node.with === "object") applyLiterals(root, node.with as Record<string, unknown>);
44
+ if (node.visibleWhen) root.visibleWhen = node.visibleWhen;
45
+ // A Ref may override the atom's `action` — the per-host behaviour the atom can't carry
46
+ // (e.g. each wizard step's option sets different fields). Parallel to style/visibleWhen.
47
+ if (node.action) root.action = node.action;
48
+ if (node.style) root.style = { ...(root.style ?? {}), ...node.style };
49
+ // THE BLOCK'S OPENING (2026-09-02, blocks returned): a Ref carrying `children:` fills
50
+ // the inlined root with the CALLER's children — a `band` or `two-column` block styles
51
+ // the frame, the composing layout says what sits inside. Same override family as
52
+ // style/action above; a Ref without children keeps the block's own (a complete block).
53
+ if (Array.isArray(node.children) && node.children.length) root.children = node.children;
54
+ return expandNode(root, contextOrg, inherit, props, scope); // expand nested refs too
55
+ }
56
+ if (Array.isArray(node?.children)) node.children = node.children.map((c: AnyNode) => expandNode(c, contextOrg, inherit, props, scope));
57
+ // `Each` carries a per-item subtree in `item` (not `children`) — expand it too,
58
+ // so an atom `Ref` inside a repeated item is inlined like anywhere else.
59
+ // An `Each`'s `item` draws ONE ITEM, so everything inside it belongs to that
60
+ // array's item shape (see `inheritProps`).
61
+ const itemsField = node?.type === "Each" ? (node.bind as AnyNode | undefined)?.items : undefined;
62
+ if (node?.item) node.item = expandNode(node.item, contextOrg, inherit, props, typeof itemsField === "string" ? [...scope, itemsField] : scope);
63
+ // `Switch` carries its branches in `cases` (a value→subtree map) — expand each, so an
64
+ // atom `Ref` inside a Switch branch is inlined like anywhere else (else the branch
65
+ // renders blank). Parallel to children/item.
66
+ if (node?.cases && typeof node.cases === "object") {
67
+ for (const k of Object.keys(node.cases)) node.cases[k] = expandNode(node.cases[k] as AnyNode, contextOrg, inherit, props, scope);
68
+ }
69
+ // `ComponentSlot` carries chrome in `frame` (born when the slot matches) and `fallback`
70
+ // (shown when it doesn't) — both are real child slots, so an atom `Ref` inside them (e.g. a
71
+ // ✕ CloseButton on a rail/panel frame) must inline like anywhere else, else the Ref survives.
72
+ if (node?.frame) node.frame = expandNode(node.frame as AnyNode, contextOrg, inherit, props, scope);
73
+ if (node?.fallback) node.fallback = expandNode(node.fallback as AnyNode, contextOrg, inherit, props, scope);
74
+ return node;
75
+ }
76
+
77
+
78
+ export function expandRefs(def: UnoverseDefinition, contextOrg?: string): UnoverseDefinition {
79
+ // Whatever the tree embeds declares its own starting values; this is where they land on
80
+ // the slice those inlined nodes actually read (see `inheritState`). Merged AFTER the
81
+ // whole tree is walked, and beneath the def's own state, so nothing an author wrote is
82
+ // touched — including the v2 `state.view` compile, which runs before this.
83
+ const inherit: Record<string, unknown> = {};
84
+ // The atoms' own field contracts land here (see `inheritProps`), beneath whatever the
85
+ // host declared — merged after the walk, so authored props are never touched.
86
+ const fromAtoms: Record<string, Record<string, unknown>> = {};
87
+ if (def?.root) def.root = expandNode(def.root as AnyNode, contextOrg ?? def.org, inherit, fromAtoms);
88
+ if (Object.keys(inherit).length) def.state = { ...inherit, ...((def.state as Record<string, unknown>) ?? {}) };
89
+ mergeInheritedProps(def, fromAtoms);
90
+ return def;
91
+ }