@unoverse-platform/studio 0.1.2 → 0.1.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unoverse-platform/studio",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "description": "Unoverse Studio: the local authoring app. Reads your own rx/ folder, no backend.",
6
6
  "scripts": {
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "@tailwindcss/vite": "^4.0.0",
15
- "@unoverse-platform/base": "^0.2.0",
15
+ "@unoverse-platform/base": "^0.3.0",
16
16
  "@unoverse-platform/marketplace": "*",
17
17
  "@vitejs/plugin-react": "^4.3.4",
18
18
  "oidc-client-ts": "^3.4.1",
@@ -32,6 +32,21 @@ export function NativeAppHost({
32
32
  const [html, setHtml] = useState<string | null>(null);
33
33
  const [error, setError] = useState<string | null>(null);
34
34
  const frame = useRef<HTMLIFrameElement>(null);
35
+ // STATE-OWNED APP WIDTH (docs/design/05): the app computes its width off the active
36
+ // layout and posts it up as `unoverse:size` (appEngine.tsx) — the HOST owns the resize.
37
+ // Without this listener the frame never widens and a panel surface renders clipped.
38
+ const [appWidth, setAppWidth] = useState<string | null>(null);
39
+ useEffect(() => {
40
+ const onSize = (e: MessageEvent) => {
41
+ if (e.source === frame.current?.contentWindow && e.data?.type === "unoverse:size" && e.data.width) {
42
+ setAppWidth(String(e.data.width));
43
+ }
44
+ };
45
+ window.addEventListener("message", onSize);
46
+ return () => window.removeEventListener("message", onSize);
47
+ }, []);
48
+ // A different app starts back at the host default until it reports.
49
+ useEffect(() => setAppWidth(null), [templateId]);
35
50
 
36
51
  // Pull the app shell from the universe — one resources/read, then the client closes.
37
52
  useEffect(() => {
@@ -94,13 +109,24 @@ export function NativeAppHost({
94
109
  if (!html)
95
110
  return <div className="flex h-full items-center justify-center text-sm opacity-60">Loading the app from the universe…</div>;
96
111
  return (
97
- <iframe
98
- ref={frame}
99
- srcDoc={html}
100
- onLoad={postConfig}
101
- title="unoverse app"
102
- style={{ width: "100%", height: "100%", border: 0, display: "block" }}
103
- allow="microphone; autoplay"
104
- />
112
+ <div style={{ width: "100%", height: "100%", display: "flex", justifyContent: "center" }}>
113
+ <iframe
114
+ ref={frame}
115
+ srcDoc={html}
116
+ onLoad={postConfig}
117
+ title="unoverse app"
118
+ // The app's reported width wins (panel surfaces widen it); capped at the pane.
119
+ // The host animates the resize (appEngine contract).
120
+ style={{
121
+ width: appWidth ?? "100%",
122
+ maxWidth: "100%",
123
+ height: "100%",
124
+ border: 0,
125
+ display: "block",
126
+ transition: "width 300ms ease",
127
+ }}
128
+ allow="microphone; autoplay"
129
+ />
130
+ </div>
105
131
  );
106
132
  }
@@ -285,6 +285,19 @@ export function TemplatesView({ theme: defaultTheme, themeName, org = "all" }: {
285
285
  // because Mock rendering shares this machinery (store, states, width).
286
286
  const liveIsNative = live && Boolean(binding?.workflow) && (!hasAuth || Boolean(auth?.isAuthenticated));
287
287
  const canConnect = false;
288
+ // NATIVE width authority: the app computes its width off ITS live store and posts
289
+ // `unoverse:size` up (appEngine contract). The in-page store below is DEAD by design
290
+ // (canConnect=false), so computeAppWidth against it pins the wrapper at the base
291
+ // layout's width forever — the wrapper must follow the app's reported width instead.
292
+ const [nativeWidth, setNativeWidth] = useState<string | null>(null);
293
+ useEffect(() => {
294
+ const onSize = (e: MessageEvent) => {
295
+ if (e.data?.type === "unoverse:size" && e.data.width) setNativeWidth(String(e.data.width));
296
+ };
297
+ window.addEventListener("message", onSize);
298
+ return () => window.removeEventListener("message", onSize);
299
+ }, []);
300
+ useEffect(() => setNativeWidth(null), [selected, live]);
288
301
  const rawConn = useUnoverseConnection(config, session, store, { enabled: canConnect });
289
302
  // HARD INVARIANT (workbench doc Mode A — "Mock does not connect with server"): a Mock
290
303
  // session must NEVER reach the backend. The SDK only gates the inbound SSE stream on
@@ -871,7 +884,11 @@ export function TemplatesView({ theme: defaultTheme, themeName, org = "all" }: {
871
884
  // TRUE width, never clamped: a panel is its declared width always, so
872
885
  // an app wider than the device box CLIPS at the edge (like a real
873
886
  // too-small screen) — it must never squeeze into garbage.
874
- width: computeAppWidth(resolveActiveLayout(selectedDef as never, store), store, theme?.appSize) ?? "100%",
887
+ // NATIVE mode: the app's own reported width wins (unoverse:size) — the
888
+ // in-page store is dead there and computeAppWidth against it lies.
889
+ width: liveIsNative
890
+ ? (nativeWidth ?? "100%")
891
+ : (computeAppWidth(resolveActiveLayout(selectedDef as never, store), store, theme?.appSize) ?? "100%"),
875
892
  margin: "0 auto",
876
893
  transition: "width 200ms",
877
894
  ...((selectedDef?.fluidHeight ?? true) ? { height: "100%" } : {}),
@@ -154,6 +154,9 @@ export function useUnoverseConnection(
154
154
  };
155
155
  });
156
156
  });
157
+ // The SDK fire-and-forgets the standalone GET SSE open on `initialized`; a failure
158
+ // lands ONLY here. Unset, a dead inbound stream is invisible — no events, no error.
159
+ client.onerror = (e) => console.error("[unoverse] client error:", e);
157
160
  clientRef.current = client;
158
161
 
159
162
  // The server pushes the live stream as `notifications/unoverse/event` over the
@@ -129,7 +129,12 @@ export function computeAppWidth(root: unknown, store: ComponentStore, appSize?:
129
129
  if (n !== r) {
130
130
  if (n.type === "ComponentSlot") {
131
131
  const claim = n.select?.where;
132
- if (claim?.field === "defaultState" && typeof claim.eq === "string") surfaces.set(claim.eq, rw(n.appWidth));
132
+ // `appWidth: flex` — the surface takes the REMAINING host space. DATA WINS: an
133
+ // org may define `flex` as a real appSize token (bpp: calc(100% − chat)) and
134
+ // that resolves like any name; the marker below is the theme-free fallback,
135
+ // resolved as "fill the host" when the view is active.
136
+ if (claim?.field === "defaultState" && typeof claim.eq === "string")
137
+ surfaces.set(claim.eq, rw(n.appWidth) ?? (n.appWidth === "flex" ? "flex" : undefined));
133
138
  } else if (n.appWidth !== undefined) {
134
139
  const w = rw(n.appWidth);
135
140
  if (w) widths.push(w); // a plain panel (the core column) is ALWAYS open
@@ -153,6 +158,9 @@ export function computeAppWidth(root: unknown, store: ComponentStore, appSize?:
153
158
  }
154
159
  }
155
160
  const activePanel = active ? surfaces.get(active) : undefined;
161
+ // An ACTIVE flex surface fills the host viewport (the host caps at its pane); the
162
+ // surface flexes into whatever the fixed panels leave.
163
+ if (activePanel === "flex") return "100%";
156
164
  if (activePanel) widths.push(activePanel);
157
165
  if (!widths.length) return undefined;
158
166
  return widths.length === 1 ? widths[0] : `calc(${widths.join(" + ")})`;
package/vite.config.ts CHANGED
@@ -44,8 +44,11 @@ export default defineConfig({
44
44
  // path resolves to nothing). The old order preferred vendor — so the moment a
45
45
  // publish ran vendor-sdk.mjs, the monorepo's own Studio silently switched onto a
46
46
  // FROZEN SDK copy and live SDK edits stopped appearing (2026-07-28).
47
- "@unoverse/sdk": existsSync(resolve(__dirname, "../unoverse/web/sdk/src/lib/index.ts"))
48
- ? resolve(__dirname, "../unoverse/web/sdk/src/lib/index.ts")
47
+ // apps/, not packages/. This said "../unoverse/..." (packages/unoverse, which does
48
+ // not exist), so existsSync was always false and the monorepo's own Studio silently
49
+ // ran on the STALE vendored copy — the exact failure the comment above warns about.
50
+ "@unoverse/sdk": existsSync(resolve(__dirname, "../../apps/unoverse/web/sdk/src/lib/index.ts"))
51
+ ? resolve(__dirname, "../../apps/unoverse/web/sdk/src/lib/index.ts")
49
52
  : resolve(__dirname, "vendor/sdk/lib/index.ts"),
50
53
  },
51
54
  },