@unoverse-platform/studio 0.1.28 → 0.1.29
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
CHANGED
|
@@ -368,7 +368,12 @@ export function localStudioHost(): Plugin {
|
|
|
368
368
|
// Apps are addressed both ways (unoverse://apps/<org>/<id> and
|
|
369
369
|
// unoverse://templates/<org>/<id>); both load the template kind.
|
|
370
370
|
const kind = /\/(apps|templates)\//.test(uri) ? "template" : /\/atoms\//.test(uri) ? "atom" : "component";
|
|
371
|
-
|
|
371
|
+
// A trailing `@form` is a request for a VARIANT of the definition, not part of
|
|
372
|
+
// its name. This door did not know that convention and looked up the whole
|
|
373
|
+
// string, so every component read as `<name>@refs` answered "definition not
|
|
374
|
+
// found". Strip any variant: this route serves the one form it has, which is
|
|
375
|
+
// also the only form the server serves.
|
|
376
|
+
const ref = uri.replace(/^unoverse:\/\/(components|atoms|apps|templates)\//, "").replace(/@[a-z]+$/, "");
|
|
372
377
|
const d = c.loadDefinition(ref, kind);
|
|
373
378
|
return d ? json(res, d) : json(res, { error: `definition not found: ${uri}` }, 404);
|
|
374
379
|
}
|
package/package.json
CHANGED
package/vendor/sdk/appEngine.tsx
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
setAnalyticsContext,
|
|
22
22
|
setAnalyticsDebug,
|
|
23
23
|
type UseVoiceServiceConfig,
|
|
24
|
+
type UnoverseDefinition,
|
|
24
25
|
} from "./lib";
|
|
25
26
|
|
|
26
27
|
export interface AppConfig {
|
|
@@ -56,7 +57,14 @@ export function AppEngine(cfg: AppConfig) {
|
|
|
56
57
|
// STATE-OWNED WIDTH (rx data): the layout root's `appWidth` = the core surface's
|
|
57
58
|
// constant width; an occupied panel slot's `appWidth` adds to it (computeAppWidth).
|
|
58
59
|
// No declared width → the host keeps its own default. There is no other path.
|
|
59
|
-
|
|
60
|
+
// THE WHOLE DEFINITION, not a chosen four fields. This used to keep `{root, org, layouts,
|
|
61
|
+
// defaultLayout}` and drop everything else, which is a whitelist that silently loses each
|
|
62
|
+
// field the contract gains. It was dropping `stateOrder`, `publicStates`, `initialView`
|
|
63
|
+
// and `stateTree`, quietly running the v2 ladder in legacy mode, and it dropped `defs`
|
|
64
|
+
// too: with shared pieces unreachable, the core panel's `appWidth` went uncounted and the
|
|
65
|
+
// app's width collapsed to the open surface's remainder term, which is negative, so the
|
|
66
|
+
// app rendered 0 pixels wide and blank.
|
|
67
|
+
const [def, setDef] = useState<UnoverseDefinition | null>(null);
|
|
60
68
|
const lastWidthRef = useRef<string | undefined>(undefined);
|
|
61
69
|
// Named app sizes resolve via the served theme (theme.appSize), like every token.
|
|
62
70
|
const theme = useUnoverseTheme(client, def ? (def.org ? `${def.org}/light` : "light") : null);
|
|
@@ -80,10 +88,9 @@ export function AppEngine(cfg: AppConfig) {
|
|
|
80
88
|
// definition too — same resource the renderer loads.
|
|
81
89
|
client
|
|
82
90
|
.connect()
|
|
83
|
-
.then(() => client.readDefinition(`unoverse://templates/${templateId}
|
|
91
|
+
.then(() => client.readDefinition(`unoverse://templates/${templateId}`))
|
|
84
92
|
.then((d) => {
|
|
85
|
-
|
|
86
|
-
if (alive) setDef(t ? { root: t.root, org: t.org, layouts: t.layouts, defaultLayout: t.defaultLayout } : null);
|
|
93
|
+
if (alive) setDef(d ?? null);
|
|
87
94
|
})
|
|
88
95
|
.catch((e) => console.error("[app] template read failed:", e));
|
|
89
96
|
return () => {
|
|
@@ -114,7 +121,7 @@ export function AppEngine(cfg: AppConfig) {
|
|
|
114
121
|
const report = () => {
|
|
115
122
|
// NAME-SYNC: width is computed off the ACTIVE layout (multi-layout templates
|
|
116
123
|
// swap whole arrangements); single-layout defs resolve to their root.
|
|
117
|
-
const w = computeAppWidth(resolveActiveLayout(def
|
|
124
|
+
const w = computeAppWidth(resolveActiveLayout(def, store), store, theme?.appSize, def);
|
|
118
125
|
if (w && w !== lastWidthRef.current) {
|
|
119
126
|
lastWidthRef.current = w;
|
|
120
127
|
window.parent?.postMessage({ type: "unoverse:size", width: w }, "*");
|
|
@@ -183,7 +190,7 @@ export function AppEngine(cfg: AppConfig) {
|
|
|
183
190
|
<StreamedUnoverseTemplate
|
|
184
191
|
client={client}
|
|
185
192
|
store={store}
|
|
186
|
-
uri={`unoverse://templates/${templateId}
|
|
193
|
+
uri={`unoverse://templates/${templateId}`}
|
|
187
194
|
voice={voiceConfig}
|
|
188
195
|
onAction={(action, data, meta) => {
|
|
189
196
|
const t = typeof action === "string" ? action : (action as any)?.type;
|
|
@@ -83,11 +83,11 @@ export function UnoverseComponent({ client, uri, data, store, chatId, nodeId, ex
|
|
|
83
83
|
// LIVE SOURCE. The hook runs either way (hooks cannot be conditional) and no-ops without a
|
|
84
84
|
// store, so one component serves both paths without a wrapper deciding for it.
|
|
85
85
|
const live = useUnoverseInstance(store, chatId, nodeId);
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
const resolvedUri = store ? (live.type ? `unoverse://components/${live.type}
|
|
86
|
+
// The BARE URI, deliberately. This asked for the referenced form (`@refs`) while that
|
|
87
|
+
// form existed; it is withdrawn (server resources.ts refForm), so asking for it now only
|
|
88
|
+
// spreads a suffix that every door has to know how to strip. Studio's own local
|
|
89
|
+
// definition route did not, and answered "definition not found" for every component.
|
|
90
|
+
const resolvedUri = store ? (live.type ? `unoverse://components/${live.type}` : null) : (uri ?? null);
|
|
91
91
|
const sourceData = store ? (extraData ? { ...(live.data ?? {}), ...extraData } : live.data) : data;
|
|
92
92
|
// No theme passed → fetch it from the server (the SDK owns zero token values). The
|
|
93
93
|
// theme REF comes from the definition itself: a template's `org` names its client's
|
|
@@ -196,6 +196,31 @@ export class ComponentStore {
|
|
|
196
196
|
if (changed) this.bump();
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
/**
|
|
200
|
+
* A HIGHER STATE CLEARS THE STATES BELOW IT (STATE_MODEL §5, THE SEVEN RULES rule 4) —
|
|
201
|
+
* when a DELIVERY put it there. Every component sitting in a lower-ranked state releases
|
|
202
|
+
* its claim, so rule 6's re-walk finds nothing below and lands on the BASE. The caller
|
|
203
|
+
* decides delivery-vs-guest (`resolveActiveView().byGuest`): the guest's own navigation
|
|
204
|
+
* clears nothing, or closing a page would have nothing to return to.
|
|
205
|
+
*
|
|
206
|
+
* Retracts to `inline`, not through `retractSlice`: that helper targets the slice's
|
|
207
|
+
* declared initial, and a card whose initial IS the lower state would re-claim it on
|
|
208
|
+
* the next walk and never settle.
|
|
209
|
+
*/
|
|
210
|
+
cancelBelow(active: string, order: string[]): void {
|
|
211
|
+
const below = new Set(order.slice(order.indexOf(active) + 1));
|
|
212
|
+
if (order.indexOf(active) < 0 || !below.size) return;
|
|
213
|
+
let changed = false;
|
|
214
|
+
for (const [key, slice] of this.data) {
|
|
215
|
+
if (slice.lifetime === "conversation") continue;
|
|
216
|
+
if (!below.has((slice.view ?? slice.defaultState) as string)) continue;
|
|
217
|
+
this.data.set(key, { ...slice, ...("defaultState" in slice ? { defaultState: "inline" } : { view: "inline" }) });
|
|
218
|
+
this.touches.set(key, ++this.touchSeq);
|
|
219
|
+
changed = true;
|
|
220
|
+
}
|
|
221
|
+
if (changed) this.bump();
|
|
222
|
+
}
|
|
223
|
+
|
|
199
224
|
closeSurfaces(): void {
|
|
200
225
|
let changed = false;
|
|
201
226
|
for (const [key, slice] of this.data) {
|
|
@@ -123,9 +123,12 @@ export interface UnoverseDefinition {
|
|
|
123
123
|
description?: string;
|
|
124
124
|
/**
|
|
125
125
|
* REFERENCE, DON'T COPY: the shared pieces this definition NAMES, by ref name, each
|
|
126
|
-
* shipped once instead of pasted at every use.
|
|
127
|
-
*
|
|
128
|
-
*
|
|
126
|
+
* shipped once instead of pasted at every use.
|
|
127
|
+
*
|
|
128
|
+
* NEVER PRESENT TODAY. The server withdrew the referenced form (server resources.ts
|
|
129
|
+
* `refForm`) after it left an app rendering zero pixels wide, and no client asks for it.
|
|
130
|
+
* The field and `resolveRef` stay because the resolution is correct and proven; what is
|
|
131
|
+
* missing is end-to-end proof and an answer for every client version in the estate.
|
|
129
132
|
*/
|
|
130
133
|
defs?: Record<string, UnoverseNode>;
|
|
131
134
|
/** EXTERNAL contract — the rare workflow-fed inputs (`input: true`). A contained
|
|
@@ -141,12 +141,6 @@ export function StreamedUnoverseTemplate({ client, store, uri, onAction, theme:
|
|
|
141
141
|
}
|
|
142
142
|
}, [def, store, structureVersion]);
|
|
143
143
|
|
|
144
|
-
// THE SEVEN RULES (2026-08-09) need no effect here: a higher state MASKS the ones
|
|
145
|
-
// below (rule 4 — the ladder alone decides what shows; lower states refresh
|
|
146
|
-
// silently underneath via the store's arrival supersession), and releasing the top
|
|
147
|
-
// state is just the ladder looking again (rule 6). The earlier cancellation WRITE
|
|
148
|
-
// (cancelBelow) was superseded by this pure-masking ruling and deleted.
|
|
149
|
-
|
|
150
144
|
// STATE OWNS LAYOUT (STATE_MODEL v2): the tree this template presents — the active
|
|
151
145
|
// state's layout (multi-layout templates), else the classic root. The resolved node
|
|
152
146
|
// is a stable per-def object, so downstream memos key off it.
|
|
@@ -162,6 +156,24 @@ export function StreamedUnoverseTemplate({ client, store, uri, onAction, theme:
|
|
|
162
156
|
return views;
|
|
163
157
|
}, [activeRoot, def]);
|
|
164
158
|
|
|
159
|
+
// RULE 4: the winner clears the states below it — WHEN A DELIVERY PUT IT THERE.
|
|
160
|
+
// An arrival taking a higher rung means the screen has moved on, so stale claims below
|
|
161
|
+
// it let go and rule 6's re-walk lands on the base. The guest's own navigation is not
|
|
162
|
+
// that: tapping a rail card to open its page must leave the rail's claims intact, or
|
|
163
|
+
// closing the page has nothing to return to. Priority still decides what SHOWS in both
|
|
164
|
+
// cases; this only decides what gets thrown away. Derived above the loading guards so
|
|
165
|
+
// this stays a hook, and off the SAME derivation the render path uses.
|
|
166
|
+
const ladder = def ? resolveActiveView(def, store, surfacedViews) : { view: "", byGuest: false };
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
if (ladder.view && !ladder.byGuest) store.cancelBelow(ladder.view, orderedClaims(def));
|
|
169
|
+
// `structureVersion`: EVERY arrival re-runs this, not only the ones that change the
|
|
170
|
+
// winner. A card landing while a higher state is ALREADY active would otherwise keep
|
|
171
|
+
// its claim forever — the walk never re-ran — and closing the higher state dropped
|
|
172
|
+
// straight onto it (observed: a rail still there after a focus surface closed, because
|
|
173
|
+
// the card's render landed two seconds after the finder took focus). `cancelBelow`
|
|
174
|
+
// only bumps when it changed something, so this settles in one extra pass.
|
|
175
|
+
}, [ladder.view, ladder.byGuest, def, store, structureVersion]);
|
|
176
|
+
|
|
165
177
|
// Loading/error hints carry NO styling — the SDK owns zero styles.
|
|
166
178
|
if (error) return <div>Unoverse error: {error}</div>;
|
|
167
179
|
if (!def || !theme) return <div>Loading {uri}…</div>;
|