@unoverse-platform/studio 0.1.102 → 0.1.104
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/package.json +4 -2
- package/screens/AtomsView.tsx +2 -1
- package/screens/ComponentsView.tsx +2 -1
- package/screens/NativeAppHost.tsx +1 -2
- package/screens/TemplatesLibraryView.tsx +14 -1
- package/screens/TemplatesView.tsx +2 -1
- package/screens/index.ts +4 -0
- package/screens/react/unoverse.tsx +281 -0
- package/vendor/base/src/agent-mcp/index.ts +1 -1
- package/vendor/base/src/agent-mcp/invoke.ts +6 -2
- package/vendor/base/src/agent-mcp/tools.ts +14 -5
- package/vendor/base/src/agent-mcp/types.ts +6 -0
- package/vendor/base/src/auth/auth.ts +90 -23
- package/vendor/base/src/definitions/load/compile/sections.ts +79 -0
- package/vendor/base/src/definitions/load/compile/template.ts +23 -2
- package/vendor/base/src/definitions/model.ts +9 -0
- package/vendor/base/src/definitions/registry/catalog.ts +6 -7
- package/vendor/base/src/executor.ts +25 -1
- package/vendor/base/src/manifests/executor/toolBridge.ts +25 -19
- package/vendor/base/src/manifests/runtime/channels/final.ts +4 -1
- package/vendor/base/src/manifests/runtime/duplex/session.ts +3 -9
- package/vendor/base/src/manifests/runtime/tools/toolloop.ts +28 -25
- package/vendor/base/src/platform/serviceCalls.ts +13 -0
- package/vendor/sdk/appEngine.ts +363 -0
- package/vendor/sdk/lib/actions.ts +36 -0
- package/vendor/sdk/lib/citations.ts +48 -0
- package/vendor/sdk/lib/core/apps.ts +18 -0
- package/vendor/sdk/lib/core/client.ts +3 -3
- package/vendor/sdk/lib/core/store.ts +6 -0
- package/vendor/sdk/lib/dom/app.ts +546 -0
- package/vendor/sdk/lib/dom/chrome.ts +125 -0
- package/vendor/sdk/lib/dom/component.ts +333 -0
- package/vendor/sdk/lib/dom/connection.ts +309 -0
- package/vendor/sdk/lib/dom/element.ts +69 -0
- package/vendor/sdk/lib/dom/isolate.ts +37 -0
- package/vendor/sdk/lib/dom/markdown.ts +388 -0
- package/vendor/sdk/lib/dom/markdownParse.ts +440 -0
- package/vendor/sdk/lib/dom/mounted.ts +29 -0
- package/vendor/sdk/lib/dom/primitives.ts +689 -0
- package/vendor/sdk/lib/dom/region.ts +195 -0
- package/vendor/sdk/lib/dom/render.ts +177 -0
- package/vendor/sdk/lib/dom/theme.ts +38 -0
- package/vendor/sdk/lib/dom/types.ts +36 -0
- package/vendor/sdk/lib/index.ts +26 -17
- package/vendor/sdk/lib/orb/canvas.ts +139 -0
- package/vendor/sdk/lib/orb/index.ts +8 -18
- package/vendor/sdk/lib/orb/presets.ts +1 -1
- package/vendor/sdk/lib/orb/{types.ts → states.ts} +0 -24
- package/vendor/sdk/lib/realtime/capture.ts +152 -0
- package/vendor/sdk/lib/realtime/playback.ts +129 -0
- package/vendor/sdk/lib/realtime/socket.ts +117 -0
- package/vendor/sdk/lib/style.ts +45 -37
- package/vendor/sdk/lib/voiceService.ts +344 -0
- package/vendor/sdk/main.ts +107 -0
- package/vendor/sdk/mcpAppBridge.ts +125 -102
- package/vendor/sdk/pageInterfaces.ts +452 -0
- package/vendor/sdk/responseView.ts +193 -0
- package/vite.config.ts +3 -3
- package/vendor/sdk/appEngine.tsx +0 -393
- package/vendor/sdk/lib/UnoverseComponent.tsx +0 -325
- package/vendor/sdk/lib/app.tsx +0 -730
- package/vendor/sdk/lib/connection.tsx +0 -433
- package/vendor/sdk/lib/isolate.tsx +0 -70
- package/vendor/sdk/lib/orb/ThinkingOrb.tsx +0 -131
- package/vendor/sdk/lib/orb/theme.ts +0 -83
- package/vendor/sdk/lib/primitives.tsx +0 -943
- package/vendor/sdk/lib/realtime/useAudioCapture.ts +0 -237
- package/vendor/sdk/lib/realtime/useAudioPlayback.ts +0 -157
- package/vendor/sdk/lib/realtime/useRealtimeWebSocket.ts +0 -164
- package/vendor/sdk/lib/render.tsx +0 -244
- package/vendor/sdk/lib/streamed.tsx +0 -30
- package/vendor/sdk/lib/theme.ts +0 -55
- package/vendor/sdk/lib/transition/MountArrangement.tsx +0 -71
- package/vendor/sdk/lib/transition/index.ts +0 -9
- package/vendor/sdk/lib/transition/viewTransition.ts +0 -29
- package/vendor/sdk/lib/voice.tsx +0 -422
- package/vendor/sdk/main.tsx +0 -125
- package/vendor/sdk/pageInterfaces.tsx +0 -383
- package/vendor/sdk/responseView.tsx +0 -82
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ════════════════════════════════════════════════════════════════════════════
|
|
3
|
-
* THE DISPATCHER: UnoverseNode → React. ⛔ READ BEFORE EDITING ⛔
|
|
4
|
-
* ════════════════════════════════════════════════════════════════════════════
|
|
5
|
-
*
|
|
6
|
-
* THIS FILE RENDERS NOTHING. It only WALKS the tree and DISPATCHES `node.type`
|
|
7
|
-
* to a primitive component in ./primitives. There is NOT A SINGLE raw element
|
|
8
|
-
* here — no `<div>`, `<span>`, `<button>`, `<img>`, `<input>`, `<svg>`. If you
|
|
9
|
-
* are about to write one, STOP: it belongs in ./primitives. This is enforced by
|
|
10
|
-
* test/dispatcher-only.test.mjs (the build fails if a raw element appears here),
|
|
11
|
-
* so the engine/primitive split can't silently rot — which is exactly how
|
|
12
|
-
* `Button` (and its styling logic) kept leaking back into the dispatcher.
|
|
13
|
-
*
|
|
14
|
-
* THE FRAMEWORK IN ONE SENTENCE: this SDK is a FIXED, GENERIC interpreter. ALL
|
|
15
|
-
* UX is built in DATA (design/ definitions + the served theme) — an author creates
|
|
16
|
-
* ANY interface WITHOUT editing this SDK. (See FRAMEWORK.md.)
|
|
17
|
-
*
|
|
18
|
-
* WHERE THINGS LIVE:
|
|
19
|
-
* • ./render — this dispatcher (control flow: visibleWhen, Each, slots) ONLY.
|
|
20
|
-
* • ./primitives — EVERY primitive (Box/Text/Image/Button/Icon/Skeleton/Input/
|
|
21
|
-
* Markdown/Unknown) + the shared per-element chrome.
|
|
22
|
-
* • ./style — the style + animation interpreter (styleToCss/keyframesCss).
|
|
23
|
-
*
|
|
24
|
-
* Two laws (govern all three files + the whole package):
|
|
25
|
-
* LAW 1 — OWN ZERO STYLE VALUES. No hex/px/rem/em/recipes; resolve token NAMES
|
|
26
|
-
* against the SERVED theme. Enforced by test/golden-rule.test.mjs.
|
|
27
|
-
* LAW 2 — OWN ZERO UX SHAPE. A primitive renders ONE generic element, nothing
|
|
28
|
-
* about a specific UX. A composer/card/loader is a LAYOUT — compose it
|
|
29
|
-
* in a DEFINITION. Never a per-UX primitive or per-UX config.
|
|
30
|
-
*
|
|
31
|
-
* Need something the vocab can't express? Prefer extending the generic interpreter
|
|
32
|
-
* ONCE (a new style-vocab key in ./style, a new model projection) over a primitive.
|
|
33
|
-
* You almost never need to touch this file — build it in design/ first.
|
|
34
|
-
* ════════════════════════════════════════════════════════════════════════════
|
|
35
|
-
*/
|
|
36
|
-
import type { ReactNode } from "react";
|
|
37
|
-
import type { ActionSpec, ResolvedTheme, UnoverseNode } from "./core";
|
|
38
|
-
import { visible, selectCase, cssWidth } from "./core";
|
|
39
|
-
import { resolveRef } from "./core/refs";
|
|
40
|
-
import { BoxView, TextView, ImageView, ButtonView, UnknownView, SkeletonView, InputView, SelectView, MarkdownView, IconView, OrbView } from "./primitives";
|
|
41
|
-
|
|
42
|
-
// The style + animation interpreter lives in ./style; re-exported so consumers keep
|
|
43
|
-
// importing it from "./render".
|
|
44
|
-
export { styleToCss, keyframesCss } from "./style";
|
|
45
|
-
|
|
46
|
-
// An action is a bare string (→ workflow) or a declarative envelope (§2e-3). The
|
|
47
|
-
// binding layer (streamed.tsx) interprets it; primitives just forward node.action.
|
|
48
|
-
/** Instance context riding with a bubbled server action — WHICH component fired and its
|
|
49
|
-
* current slice, so the channel can forward the submitted state (e.g. a wizard's answers)
|
|
50
|
-
* without reaching into the store. Pure transport metadata; the SDK interprets none of it. */
|
|
51
|
-
export interface ActionMeta {
|
|
52
|
-
chatId?: string;
|
|
53
|
-
nodeId?: string;
|
|
54
|
-
/** Which component this instance is — so the server needs no registry to find out. */
|
|
55
|
-
type?: string;
|
|
56
|
-
state?: Record<string, unknown>;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export type ActionHandler = (action: string | ActionSpec, data: Record<string, unknown>, meta?: ActionMeta) => void;
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* A leaf's action, with the node's declared `analytics` attached (UNOVERSE_ANALYTICS.md).
|
|
63
|
-
*
|
|
64
|
-
* Authors write `analytics:` beside `action:` on the node, where the data it reports is
|
|
65
|
-
* already in scope. The two travel together from here so `dispatchAction` remains the ONE
|
|
66
|
-
* place an interaction is understood: leaves stay dumb, and no leaf can forget to report.
|
|
67
|
-
* A node with no `analytics` block passes its action through untouched, unallocated.
|
|
68
|
-
*/
|
|
69
|
-
export function nodeAction(node: UnoverseNode, fallback: string): string | ActionSpec {
|
|
70
|
-
const action = node.action ?? fallback;
|
|
71
|
-
if (!node.analytics?.event) return action;
|
|
72
|
-
return typeof action === "string"
|
|
73
|
-
? { type: action, analytics: node.analytics }
|
|
74
|
-
: { ...action, analytics: node.analytics };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Resolves a `ComponentSlot` node against the store (provided by the template
|
|
79
|
-
* renderer). Returns the rendered leaves (or the slot's fallback). Components
|
|
80
|
-
* don't use slots, so this is undefined on the component render path.
|
|
81
|
-
*/
|
|
82
|
-
export type SlotResolver = (node: UnoverseNode, key?: React.Key) => ReactNode;
|
|
83
|
-
|
|
84
|
-
// cssWidth moved to core/apps.ts (computeAppWidth needs it and core imports
|
|
85
|
-
// nothing from the React layer); re-exported to keep this import path working.
|
|
86
|
-
export { cssWidth };
|
|
87
|
-
|
|
88
|
-
export function renderNode(
|
|
89
|
-
node: UnoverseNode,
|
|
90
|
-
data: Record<string, unknown>,
|
|
91
|
-
onAction: ActionHandler | undefined,
|
|
92
|
-
theme: ResolvedTheme,
|
|
93
|
-
key?: React.Key,
|
|
94
|
-
slot?: SlotResolver,
|
|
95
|
-
/**
|
|
96
|
-
* The shared pieces this definition REFERENCES, by name. Present only when the server
|
|
97
|
-
* sends references instead of inlining copies of them; absent for every definition
|
|
98
|
-
* served the old way, in which case no `Ref` reaches this function at all.
|
|
99
|
-
*/
|
|
100
|
-
defs?: Record<string, UnoverseNode>,
|
|
101
|
-
): ReactNode {
|
|
102
|
-
// visibleWhen — conditional visibility (§2e-3). A bare field name is a truthiness test
|
|
103
|
-
// (an empty array counts as empty); an object is a `Condition` (eq/ne/in, or truthy) so
|
|
104
|
-
// one field can drive tabs/wizard steps without a boolean per state. The predicate is
|
|
105
|
-
// shared with `Switch` and `style.when` (core/conditions.ts) — one state semantics.
|
|
106
|
-
if (node.visibleWhen != null && !visible(node.visibleWhen, data)) return null;
|
|
107
|
-
|
|
108
|
-
// `skeleton: true` — the protocol's LOADING semantic: while the node's bound field has
|
|
109
|
-
// no data yet, render its Skeleton ghost IN PLACE (inside the node's own styled box, so
|
|
110
|
-
// the ghost occupies exactly the space the content will). Variant inferred from the
|
|
111
|
-
// node type; the ghost's entire look is theme data (LAW 1). This is a generic BEHAVIOR
|
|
112
|
-
// (no data yet → ghost), not a style or a UX shape — the one judgment only the
|
|
113
|
-
// renderer can make, since only it watches binds resolve over time.
|
|
114
|
-
if (node.skeleton) {
|
|
115
|
-
const field = node.bind?.value ?? node.bind?.src ?? node.bind?.items;
|
|
116
|
-
if (field && !visible(field, data)) {
|
|
117
|
-
const variant = node.type === "Image" ? "image" : node.type === "Each" ? "card" : "text";
|
|
118
|
-
return (
|
|
119
|
-
<BoxView key={key} node={{ type: "Box", style: node.style }} data={data} theme={theme}>
|
|
120
|
-
<SkeletonView variant={variant} theme={theme} />
|
|
121
|
-
</BoxView>
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// STATE-OWNED SIZING (docs/design/05): a plain node's `appWidth` = an ALWAYS-OPEN
|
|
127
|
-
// panel (e.g. the core chat column). The ONE declared value both grows the app
|
|
128
|
-
// (computeAppWidth) and fixes the panel's box here — they can never disagree, and
|
|
129
|
-
// nothing elastic remains for a resize to land on. Slot panels are injected at their
|
|
130
|
-
// frame (template.tsx); a layout root never carries appWidth (guard-enforced).
|
|
131
|
-
const panelW = (node as { appWidth?: string }).appWidth;
|
|
132
|
-
// ComponentSlot AND Template keep their appWidth: both are slot-dispatched places
|
|
133
|
-
// whose width applies to their FRAME (app.tsx), not to a box of their own —
|
|
134
|
-
// consuming it here left the mount frame width-less (measured live 2026-08-28).
|
|
135
|
-
if (panelW && node.type !== "ComponentSlot" && node.type !== "Template" && node.type !== "Place") {
|
|
136
|
-
const w = cssWidth(theme.appSize?.[panelW] ?? panelW);
|
|
137
|
-
if (w) node = { ...node, appWidth: undefined, style: { ...(node.style ?? {}), width: w, flex: "0 0 auto" } } as UnoverseNode;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Store-backed / leaf primitives → dispatch to ./primitives.
|
|
141
|
-
if (node.type === "Skeleton") return <SkeletonView key={key} variant={node.variant} theme={theme} />;
|
|
142
|
-
// Icon: DATA path reads `bind.name`; LITERAL path reads the `icon` attribute. `name` is
|
|
143
|
-
// read as a literal too, because an atom that binds `name` (the only spelling the data
|
|
144
|
-
// path reads) has that bind turned INTO a `name` attribute when a Ref supplies the glyph
|
|
145
|
-
// through `with` — so without this an atom can be data-driven or literal, never both.
|
|
146
|
-
if (node.type === "Icon")
|
|
147
|
-
return <IconView key={key} name={node.bind?.name ? (data[node.bind.name] as string) : (node.icon ?? node.name)} style={node.style} theme={theme} />;
|
|
148
|
-
// Orb: procedural thinking/working animation — state is a literal or bound (`bind.state`).
|
|
149
|
-
if (node.type === "Orb") return <OrbView key={key} node={node} data={data} theme={theme} />;
|
|
150
|
-
if (node.type === "Input") return <InputView key={key} node={node} data={data} theme={theme} onAction={onAction} />;
|
|
151
|
-
if (node.type === "Markdown") return <MarkdownView key={key} node={node} data={data} theme={theme} onAction={onAction} />;
|
|
152
|
-
if (node.type === "ComponentSlot" || node.type === "Timeline" || node.type === "Template" || node.type === "Place") return slot ? slot(node, key) : null;
|
|
153
|
-
|
|
154
|
-
// `Ref` — REFERENCE, NOT COPY. The design system's shared pieces travel once and are
|
|
155
|
-
// cached; a definition names one and specialises it here (core/refs.ts: `props` remap,
|
|
156
|
-
// `with` literals, style/action/visibleWhen overrides). This is what stops a button
|
|
157
|
-
// being re-sent inside every definition that uses it.
|
|
158
|
-
//
|
|
159
|
-
// A Ref only arrives when the server sent references, so this branch is inert for
|
|
160
|
-
// anything served the old, inlined way. An unknown name renders NOTHING rather than a
|
|
161
|
-
// broken box: a hole is easier to diagnose than a mystery element.
|
|
162
|
-
if (node.type === "Ref") {
|
|
163
|
-
const resolved = resolveRef(node, defs?.[String((node as { ref?: string }).ref ?? "")]);
|
|
164
|
-
return resolved ? renderNode(resolved, data, onAction, theme, key, slot, defs) : null;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// `Switch` — control flow: render EXACTLY ONE branch, the case matching `data[on]`
|
|
168
|
-
// (else `cases.default`, else nothing). The declarative whole-view swap (wizard step,
|
|
169
|
-
// inline↔focused) — mutually exclusive in one place, vs. a `visibleWhen` per branch. No
|
|
170
|
-
// wrapper element: it is transparent, like an `if`, delegating straight to the branch.
|
|
171
|
-
if (node.type === "Switch") {
|
|
172
|
-
const branch = selectCase(node, data);
|
|
173
|
-
return branch ? renderNode(branch, data, onAction, theme, key, slot, defs) : null;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// `Each` — control flow: map a bound array (bind.items) → the `item` subtree once
|
|
177
|
-
// per item (that item as the data scope), inside the node's own container (a Box).
|
|
178
|
-
// A PRIMITIVE item (string/number — e.g. a `string[]` of follow-up questions) is exposed
|
|
179
|
-
// as `{ value: item }`, so the item binds `value` like any field; object items pass
|
|
180
|
-
// through unchanged. This is what lets an `Each` iterate a plain list of strings.
|
|
181
|
-
if (node.type === "Each") {
|
|
182
|
-
// Literal `items` fallback: a microapp component hardcodes its static option lists
|
|
183
|
-
// in the def (props exist only for state — UNOVERSE_AUTHORING.md). bind wins.
|
|
184
|
-
const src = node.bind?.items ? data[node.bind.items] : node.items;
|
|
185
|
-
const items = Array.isArray(src) ? src : [];
|
|
186
|
-
// `item` is the Each's repeated drawing (renamed from `template` 2026-09-01: one word for
|
|
187
|
-
// a holder NAME (string) on Template, which this branch never sees. Narrow rather than
|
|
188
|
-
// assert, so a definition that puts the wrong one here renders nothing instead of
|
|
189
|
-
// throwing inside the map.
|
|
190
|
-
const tpl = typeof node.item === "object" ? node.item : undefined;
|
|
191
|
-
// WHERE AN ITEM SITS is something only the renderer knows, and a numbered list cannot
|
|
192
|
-
// be authored without it: the data carries no ordinal, and a template cannot count.
|
|
193
|
-
// `index` is 0-based for logic, `position` 1-based because that is what a reader sees.
|
|
194
|
-
// The item's own fields always win, so data that carries either name is untouched.
|
|
195
|
-
// AN ARRAY ITEM IS THE VALUE. `typeof [] === "object"`, so an array took the spread
|
|
196
|
-
// branch and its INDICES became scope keys ({0: "London", 1: "+44…"}) while `value` was
|
|
197
|
-
// never set. A nested Each bound to `value` — a table's row of cells — then found
|
|
198
|
-
// nothing, and every table rendered its headers over empty rows.
|
|
199
|
-
const scopeOf = (item: unknown, i: number): Record<string, unknown> => {
|
|
200
|
-
const base = { index: i, position: i + 1 };
|
|
201
|
-
return item != null && typeof item === "object" && !Array.isArray(item)
|
|
202
|
-
? { ...base, ...(item as Record<string, unknown>) }
|
|
203
|
-
: { ...base, value: item };
|
|
204
|
-
};
|
|
205
|
-
const rendered = tpl ? items.map((item, i) => renderNode(tpl, scopeOf(item, i), onAction, theme, i, slot, defs)) : null;
|
|
206
|
-
return (
|
|
207
|
-
<BoxView key={key} node={node} data={data} theme={theme}>
|
|
208
|
-
{rendered}
|
|
209
|
-
</BoxView>
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Element primitives → dispatch. Children are rendered here (control flow) and handed down.
|
|
214
|
-
const kids = (node.children ?? []).map((c, i) => renderNode(c, data, onAction, theme, i, slot, defs));
|
|
215
|
-
switch (node.type) {
|
|
216
|
-
case "Box":
|
|
217
|
-
case "Stack":
|
|
218
|
-
case "Row":
|
|
219
|
-
case "Column":
|
|
220
|
-
return (
|
|
221
|
-
<BoxView key={key} node={node} data={data} theme={theme} onAction={onAction}>
|
|
222
|
-
{kids}
|
|
223
|
-
</BoxView>
|
|
224
|
-
);
|
|
225
|
-
case "Text":
|
|
226
|
-
return <TextView key={key} node={node} data={data} theme={theme} />;
|
|
227
|
-
case "Image":
|
|
228
|
-
return <ImageView key={key} node={node} data={data} theme={theme} />;
|
|
229
|
-
case "Select":
|
|
230
|
-
return <SelectView key={key} node={node} data={data} theme={theme} onAction={onAction} />;
|
|
231
|
-
case "Button":
|
|
232
|
-
return (
|
|
233
|
-
<ButtonView key={key} node={node} data={data} theme={theme} onAction={onAction}>
|
|
234
|
-
{kids}
|
|
235
|
-
</ButtonView>
|
|
236
|
-
);
|
|
237
|
-
default:
|
|
238
|
-
return (
|
|
239
|
-
<UnknownView key={key} node={node} theme={theme}>
|
|
240
|
-
{kids}
|
|
241
|
-
</UnknownView>
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DEPRECATED ALIAS. `UnoverseComponent` now renders from either a plain `data` object or a
|
|
3
|
-
* live store, so the streamed variant is the same component with the store props filled in.
|
|
4
|
-
*
|
|
5
|
-
* Kept so existing channels keep working while their call sites move over. Nothing here
|
|
6
|
-
* decides anything: it forwards.
|
|
7
|
-
*/
|
|
8
|
-
import { UnoverseComponent, useUnoverseInstance } from "./UnoverseComponent";
|
|
9
|
-
import type { ComponentStore, ResolvedTheme, UnoverseClient } from "./core";
|
|
10
|
-
import type { ActionHandler } from "./render";
|
|
11
|
-
|
|
12
|
-
export { useUnoverseInstance };
|
|
13
|
-
|
|
14
|
-
export interface StreamedUnoverseComponentProps {
|
|
15
|
-
client: UnoverseClient;
|
|
16
|
-
store: ComponentStore;
|
|
17
|
-
chatId: string;
|
|
18
|
-
nodeId: string;
|
|
19
|
-
onAction?: ActionHandler;
|
|
20
|
-
theme?: ResolvedTheme;
|
|
21
|
-
/** Neutral turn state merged into the component's data scope (e.g. `streaming`). */
|
|
22
|
-
extraData?: Record<string, unknown>;
|
|
23
|
-
/** A surface's single occupant fills the surface — see UnoverseComponent.fill. */
|
|
24
|
-
fill?: boolean;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** @deprecated Render `UnoverseComponent` with `store`, `chatId` and `nodeId`. */
|
|
28
|
-
export function StreamedUnoverseComponent(props: StreamedUnoverseComponentProps) {
|
|
29
|
-
return <UnoverseComponent {...props} />;
|
|
30
|
-
}
|
package/vendor/sdk/lib/theme.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useUnoverseTheme — fetch the RESOLVED theme from the MCP server by name.
|
|
3
|
-
*
|
|
4
|
-
* The SDK owns ZERO token values. The server owns the visual (apps/unoverse/
|
|
5
|
-
* design/styles, resolved live) and serves it at `unoverse://theme/{name}`; this hook
|
|
6
|
-
* fetches it (cached in the client). A brand change in design/styles is refresh-only
|
|
7
|
-
* — there is no baked theme in this bundle to rebuild (UNOVERSE_SPEC §2d-1).
|
|
8
|
-
*/
|
|
9
|
-
import { useEffect, useState } from "react";
|
|
10
|
-
import type { ResolvedTheme, UnoverseClient } from "./core";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Ensure the theme's webfont stylesheets are loaded ONCE in the document head.
|
|
14
|
-
* `theme.fonts` is SERVED DATA (`fonts.stylesheets` in an org's styles) — the SDK
|
|
15
|
-
* authors no font choices, it just registers what the server serves. Fonts must live
|
|
16
|
-
* at document level: a shadow root cannot register @font-face, but faces declared in
|
|
17
|
-
* the document apply inside shadow trees via the font cascade.
|
|
18
|
-
*/
|
|
19
|
-
export function ensureFontStylesheets(theme: ResolvedTheme | null): void {
|
|
20
|
-
if (!theme?.fonts?.length || typeof document === "undefined") return;
|
|
21
|
-
for (const href of theme.fonts) {
|
|
22
|
-
if (document.head.querySelector(`link[data-unoverse-font="${CSS.escape(href)}"]`)) continue;
|
|
23
|
-
const link = document.createElement("link");
|
|
24
|
-
link.rel = "stylesheet";
|
|
25
|
-
link.href = href;
|
|
26
|
-
link.setAttribute("data-unoverse-font", href);
|
|
27
|
-
document.head.appendChild(link);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Returns the resolved theme (null until the first fetch resolves).
|
|
32
|
-
* `name: null` = don't fetch yet (the caller is still deriving the name — e.g. waiting
|
|
33
|
-
* for the definition whose `org` names its pack's theme). An unknown name falls back to
|
|
34
|
-
* the neutral `light` so a pack without a theme still renders. */
|
|
35
|
-
export function useUnoverseTheme(client: UnoverseClient, name: string | null = "light"): ResolvedTheme | null {
|
|
36
|
-
const [theme, setTheme] = useState<ResolvedTheme | null>(null);
|
|
37
|
-
useEffect(() => {
|
|
38
|
-
if (!name) return;
|
|
39
|
-
let alive = true;
|
|
40
|
-
client
|
|
41
|
-
.readTheme(name)
|
|
42
|
-
.then((t) => alive && setTheme(t))
|
|
43
|
-
.catch(() => {
|
|
44
|
-
if (!alive || name === "light") return;
|
|
45
|
-
client
|
|
46
|
-
.readTheme("light")
|
|
47
|
-
.then((t) => alive && setTheme(t))
|
|
48
|
-
.catch(() => {});
|
|
49
|
-
});
|
|
50
|
-
return () => {
|
|
51
|
-
alive = false;
|
|
52
|
-
};
|
|
53
|
-
}, [client, name]);
|
|
54
|
-
return theme;
|
|
55
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The double-buffered mount (UNOVERSE_TEMPLATE_MODEL §13.3): renders its LAST
|
|
3
|
-
* COMMITTED arrangement and commits the next one only when the decision allows —
|
|
4
|
-
* so a data ripple can never move the shelf, and the boundary commit is the one
|
|
5
|
-
* designed (and, with theme motion, animated) change. Content inside held
|
|
6
|
-
* instances stays live automatically: the render closure reads slices from the
|
|
7
|
-
* store at render time; only membership and order are buffered here.
|
|
8
|
-
*
|
|
9
|
-
* Fail open (§13.5): a "hold" with no boundary inside the grace window commits
|
|
10
|
-
* anyway — the screen never pays for machinery failure.
|
|
11
|
-
*
|
|
12
|
-
* RENDER-LAYER state, deliberately: the portable core stays frozen and "no UI
|
|
13
|
-
* state in the store" holds.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import React, { useEffect, useRef, useState } from "react";
|
|
17
|
-
import type { ReactNode } from "react";
|
|
18
|
-
import { decideCommit, type Arrangement } from "./decide";
|
|
19
|
-
import { commitArrangement } from "./viewTransition";
|
|
20
|
-
|
|
21
|
-
/** A held-back change commits anyway after this long without a boundary. */
|
|
22
|
-
const GRACE_MS = 4000;
|
|
23
|
-
|
|
24
|
-
export function MountArrangement({
|
|
25
|
-
next,
|
|
26
|
-
directed,
|
|
27
|
-
motion,
|
|
28
|
-
render,
|
|
29
|
-
}: {
|
|
30
|
-
/** The arrangement the store derives RIGHT NOW. */
|
|
31
|
-
next: Arrangement;
|
|
32
|
-
/** The placement grants a director — every delivery settles, so membership
|
|
33
|
-
* waits for the settle (decide.ts). */
|
|
34
|
-
directed: boolean;
|
|
35
|
-
/** Theme-requested motion (§13.4) — absent theme word = instant commits. */
|
|
36
|
-
motion: boolean;
|
|
37
|
-
/** Build the tree for an arrangement; slices are read live inside. */
|
|
38
|
-
render: (arrangement: Arrangement) => ReactNode;
|
|
39
|
-
}) {
|
|
40
|
-
const [committed, setCommitted] = useState<Arrangement | null>(null);
|
|
41
|
-
const graceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
42
|
-
const decision = decideCommit(committed, next, directed);
|
|
43
|
-
|
|
44
|
-
useEffect(() => {
|
|
45
|
-
if (decision === "none") return;
|
|
46
|
-
if (decision === "hold") {
|
|
47
|
-
if (!graceTimer.current) {
|
|
48
|
-
graceTimer.current = setTimeout(() => {
|
|
49
|
-
graceTimer.current = null;
|
|
50
|
-
setCommitted(next);
|
|
51
|
-
}, GRACE_MS);
|
|
52
|
-
}
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
if (graceTimer.current) {
|
|
56
|
-
clearTimeout(graceTimer.current);
|
|
57
|
-
graceTimer.current = null;
|
|
58
|
-
}
|
|
59
|
-
if (decision === "transition") commitArrangement(() => setCommitted(next), motion);
|
|
60
|
-
else setCommitted(next);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
useEffect(
|
|
64
|
-
() => () => {
|
|
65
|
-
if (graceTimer.current) clearTimeout(graceTimer.current);
|
|
66
|
-
},
|
|
67
|
-
[],
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
return <>{render(committed ?? next)}</>;
|
|
71
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The Transition Model (UNOVERSE_TEMPLATE_MODEL §13) — the render layer's
|
|
3
|
-
* designed way of moving interfaces in and out of a mounted template:
|
|
4
|
-
* `decide.ts` (the pure commit decision), `MountArrangement.tsx` (the
|
|
5
|
-
* double-buffer), `viewTransition.ts` (the theme-gated animated commit).
|
|
6
|
-
*/
|
|
7
|
-
export { decideCommit, type Arrangement, type CommitDecision } from "./decide";
|
|
8
|
-
export { MountArrangement } from "./MountArrangement";
|
|
9
|
-
export { commitArrangement } from "./viewTransition";
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The commit IS the animation (UNOVERSE_TEMPLATE_MODEL §13.4): when the theme
|
|
3
|
-
* asks for motion, the boundary commit is wrapped in the browser's View
|
|
4
|
-
* Transitions API — the browser diffs old and new and animates; the THEME's CSS
|
|
5
|
-
* owns every duration and ease. The SDK contributes only this generic behaviour
|
|
6
|
-
* and NO styles. Engines without the API, and themes that author no motion, get
|
|
7
|
-
* the instant commit — today's behaviour, gracefully.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { flushSync } from "react-dom";
|
|
11
|
-
|
|
12
|
-
type WithViewTransition = { startViewTransition?: (cb: () => void) => unknown };
|
|
13
|
-
|
|
14
|
-
export function commitArrangement(commit: () => void, motion: boolean): void {
|
|
15
|
-
const doc = typeof document !== "undefined" ? (document as unknown as WithViewTransition) : undefined;
|
|
16
|
-
if (!motion || typeof doc?.startViewTransition !== "function") {
|
|
17
|
-
commit();
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
// From a microtask, outside React's lifecycle, so flushSync is legal: the
|
|
21
|
-
// API snapshots, the commit flushes the new DOM, the browser animates.
|
|
22
|
-
queueMicrotask(() => {
|
|
23
|
-
try {
|
|
24
|
-
doc.startViewTransition!(() => flushSync(commit));
|
|
25
|
-
} catch {
|
|
26
|
-
commit(); // fail open — the change lands even if the ceremony breaks
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
}
|