@almadar/ui 4.10.6 → 4.11.0

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.
@@ -52082,19 +52082,6 @@ function useTraitStateMachine(traitBindings, slotsActions, options) {
52082
52082
  });
52083
52083
  }
52084
52084
  }
52085
- const LIFECYCLE_EVENTS = /* @__PURE__ */ new Set(["INIT", "LOAD", "$MOUNT", "$UNMOUNT", "$FRAME"]);
52086
- if (!LIFECYCLE_EVENTS.has(normalizedEvent)) {
52087
- for (const { traitName, result } of results) {
52088
- if (!result.executed) continue;
52089
- const orbitalName = orbitalsByTrait?.[traitName];
52090
- if (!orbitalName) continue;
52091
- eventBus.emit(`UI:${orbitalName}.${traitName}.${normalizedEvent}`, payload, {
52092
- orbital: orbitalName,
52093
- trait: traitName,
52094
- fromBridge: true
52095
- });
52096
- }
52097
- }
52098
52085
  if (results.length > 0) {
52099
52086
  setTraitStates(currentManager.getAllStates());
52100
52087
  }
@@ -52213,6 +52200,38 @@ init_EntitySchemaContext();
52213
52200
  init_useEventBus();
52214
52201
  init_logger();
52215
52202
  var xOrbitalLog2 = createLogger("almadar:runtime:cross-orbital");
52203
+ function createHttpTransport(serverUrl) {
52204
+ return {
52205
+ register: async (schema) => {
52206
+ try {
52207
+ const res = await fetch(`${serverUrl}/register`, {
52208
+ method: "POST",
52209
+ headers: { "Content-Type": "application/json" },
52210
+ body: JSON.stringify({ schema })
52211
+ });
52212
+ const result = await res.json();
52213
+ return !!result.success;
52214
+ } catch (err) {
52215
+ console.error("[ServerBridge] Registration failed:", err);
52216
+ return false;
52217
+ }
52218
+ },
52219
+ unregister: async () => {
52220
+ try {
52221
+ await fetch(`${serverUrl}/unregister`, { method: "DELETE" });
52222
+ } catch {
52223
+ }
52224
+ },
52225
+ sendEvent: async (orbitalName, event, payload) => {
52226
+ const res = await fetch(`${serverUrl}/${orbitalName}/events`, {
52227
+ method: "POST",
52228
+ headers: { "Content-Type": "application/json" },
52229
+ body: JSON.stringify({ event, payload })
52230
+ });
52231
+ return res.json();
52232
+ }
52233
+ };
52234
+ }
52216
52235
  var ServerBridgeContext = React128.createContext(null);
52217
52236
  function useServerBridge() {
52218
52237
  const ctx = React128.useContext(ServerBridgeContext);
@@ -52225,40 +52244,34 @@ function useServerBridge() {
52225
52244
  function ServerBridgeProvider({
52226
52245
  schema,
52227
52246
  serverUrl,
52247
+ transport: customTransport,
52228
52248
  children
52229
52249
  }) {
52250
+ if (!serverUrl && !customTransport) {
52251
+ throw new Error("ServerBridgeProvider requires either serverUrl or transport");
52252
+ }
52253
+ if (serverUrl && customTransport) {
52254
+ throw new Error("ServerBridgeProvider accepts serverUrl OR transport, not both");
52255
+ }
52230
52256
  const eventBus = useEventBus();
52231
52257
  const [connected, setConnected] = React128.useState(false);
52232
- const registerSchema = React128.useCallback(async () => {
52233
- try {
52234
- const res = await fetch(`${serverUrl}/register`, {
52235
- method: "POST",
52236
- headers: { "Content-Type": "application/json" },
52237
- body: JSON.stringify({ schema })
52238
- });
52239
- const result = await res.json();
52240
- return !!result.success;
52241
- } catch (err) {
52242
- console.error("[ServerBridge] Registration failed:", err);
52243
- return false;
52244
- }
52245
- }, [schema, serverUrl]);
52246
- const unregisterSchema = React128.useCallback(async () => {
52247
- try {
52248
- await fetch(`${serverUrl}/unregister`, { method: "DELETE" });
52249
- } catch {
52250
- }
52251
- }, [serverUrl]);
52258
+ const transport = React128.useMemo(
52259
+ () => customTransport ?? createHttpTransport(serverUrl),
52260
+ [serverUrl, customTransport]
52261
+ );
52262
+ const registerSchema = React128.useCallback(
52263
+ async () => transport.register(schema),
52264
+ [schema, transport]
52265
+ );
52266
+ const unregisterSchema = React128.useCallback(
52267
+ async () => transport.unregister(),
52268
+ [transport]
52269
+ );
52252
52270
  const sendEvent = React128.useCallback(async (orbitalName, event, payload) => {
52253
52271
  const emptyMeta = { success: false, clientEffects: 0, dataEntities: {}, emittedEvents: [] };
52254
52272
  if (!connected) return { effects: [], meta: emptyMeta };
52255
52273
  try {
52256
- const res = await fetch(`${serverUrl}/${orbitalName}/events`, {
52257
- method: "POST",
52258
- headers: { "Content-Type": "application/json" },
52259
- body: JSON.stringify({ event, payload })
52260
- });
52261
- const result = await res.json();
52274
+ const result = await transport.sendEvent(orbitalName, event, payload);
52262
52275
  const effects = [];
52263
52276
  const responseData = result.data || {};
52264
52277
  const dataEntities = {};
@@ -52316,7 +52329,7 @@ function ServerBridgeProvider({
52316
52329
  console.error("[ServerBridge] Event send failed:", err);
52317
52330
  return { effects: [], meta: { ...emptyMeta, error: err instanceof Error ? err.message : String(err) } };
52318
52331
  }
52319
- }, [connected, serverUrl, eventBus]);
52332
+ }, [connected, transport, eventBus]);
52320
52333
  React128.useEffect(() => {
52321
52334
  if (!schema) return;
52322
52335
  let cancelled = false;
@@ -52592,7 +52605,7 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFa
52592
52605
  }, [bridge.connected, orbitalNames, bridge.sendEvent, uiSlots, onNavigate]);
52593
52606
  return null;
52594
52607
  }
52595
- function SchemaRunner({ schema, serverUrl, mockData, pageName, onNavigate, onLocalFallback, persistence }) {
52608
+ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, onNavigate, onLocalFallback, persistence }) {
52596
52609
  const { traits: traits2, allEntities, ir } = useResolvedSchema(schema, pageName);
52597
52610
  const allPageTraits = React128.useMemo(() => {
52598
52611
  if (pageName && traits2.length > 0) return traits2;
@@ -52706,7 +52719,7 @@ function SchemaRunner({ schema, serverUrl, mockData, pageName, onNavigate, onLoc
52706
52719
  TraitInitializer,
52707
52720
  {
52708
52721
  traits: allPageTraits,
52709
- orbitalNames: serverUrl ? pageOrbitalNames : void 0,
52722
+ orbitalNames: serverUrl || transport ? pageOrbitalNames : void 0,
52710
52723
  traitConfigsByName,
52711
52724
  orbitalsByTrait,
52712
52725
  onNavigate,
@@ -52719,8 +52732,8 @@ function SchemaRunner({ schema, serverUrl, mockData, pageName, onNavigate, onLoc
52719
52732
  ]
52720
52733
  }
52721
52734
  ) }) });
52722
- if (serverUrl) {
52723
- return /* @__PURE__ */ jsxRuntime.jsx(ServerBridgeProvider, { schema, serverUrl, children: inner });
52735
+ if (serverUrl || transport) {
52736
+ return /* @__PURE__ */ jsxRuntime.jsx(ServerBridgeProvider, { schema, serverUrl, transport, children: inner });
52724
52737
  }
52725
52738
  return inner;
52726
52739
  }
@@ -52731,8 +52744,12 @@ function OrbPreview({
52731
52744
  height = "400px",
52732
52745
  className,
52733
52746
  serverUrl,
52747
+ transport,
52734
52748
  initialPagePath
52735
52749
  }) {
52750
+ if (serverUrl && transport) {
52751
+ throw new Error("OrbPreview accepts serverUrl OR transport, not both");
52752
+ }
52736
52753
  const [localFallback, setLocalFallback] = React128.useState(false);
52737
52754
  const eventBus = useEventBus();
52738
52755
  const handleLocalFallback = React128.useCallback(() => {
@@ -52754,21 +52771,21 @@ function OrbPreview({
52754
52771
  } else {
52755
52772
  parsed = schema;
52756
52773
  }
52757
- if (autoMock && !serverUrl) {
52774
+ if (autoMock && !serverUrl && !transport) {
52758
52775
  const prepared = prepareSchemaForPreview(parsed);
52759
52776
  return { ok: true, schema: prepared.schema, mockData: prepared.mockData };
52760
52777
  }
52761
52778
  return { ok: true, schema: parsed, mockData: mockData ?? {} };
52762
- }, [schema, autoMock, serverUrl, mockData]);
52779
+ }, [schema, autoMock, serverUrl, transport, mockData]);
52763
52780
  const parsedSchema = parseResult.ok ? parseResult.schema : null;
52764
52781
  const effectiveMockData = parseResult.ok ? parseResult.mockData : {};
52765
52782
  const persistence = React128.useMemo(() => {
52766
- if (!parsedSchema || serverUrl) return void 0;
52783
+ if (!parsedSchema || serverUrl || transport) return void 0;
52767
52784
  if (!autoMock) return void 0;
52768
52785
  const adapter = new runtime.InMemoryPersistence();
52769
52786
  adapter.seed(effectiveMockData);
52770
52787
  return adapter;
52771
- }, [parsedSchema, serverUrl, autoMock, effectiveMockData]);
52788
+ }, [parsedSchema, serverUrl, transport, autoMock, effectiveMockData]);
52772
52789
  const pages = React128.useMemo(() => {
52773
52790
  if (!parsedSchema) return [];
52774
52791
  try {
@@ -52830,6 +52847,7 @@ function OrbPreview({
52830
52847
  {
52831
52848
  schema: parsedSchema,
52832
52849
  serverUrl,
52850
+ transport,
52833
52851
  mockData: effectiveMockData,
52834
52852
  pageName: currentPage,
52835
52853
  onNavigate: handleNavigate,
package/dist/avl/index.js CHANGED
@@ -52036,19 +52036,6 @@ function useTraitStateMachine(traitBindings, slotsActions, options) {
52036
52036
  });
52037
52037
  }
52038
52038
  }
52039
- const LIFECYCLE_EVENTS = /* @__PURE__ */ new Set(["INIT", "LOAD", "$MOUNT", "$UNMOUNT", "$FRAME"]);
52040
- if (!LIFECYCLE_EVENTS.has(normalizedEvent)) {
52041
- for (const { traitName, result } of results) {
52042
- if (!result.executed) continue;
52043
- const orbitalName = orbitalsByTrait?.[traitName];
52044
- if (!orbitalName) continue;
52045
- eventBus.emit(`UI:${orbitalName}.${traitName}.${normalizedEvent}`, payload, {
52046
- orbital: orbitalName,
52047
- trait: traitName,
52048
- fromBridge: true
52049
- });
52050
- }
52051
- }
52052
52039
  if (results.length > 0) {
52053
52040
  setTraitStates(currentManager.getAllStates());
52054
52041
  }
@@ -52167,6 +52154,38 @@ init_EntitySchemaContext();
52167
52154
  init_useEventBus();
52168
52155
  init_logger();
52169
52156
  var xOrbitalLog2 = createLogger("almadar:runtime:cross-orbital");
52157
+ function createHttpTransport(serverUrl) {
52158
+ return {
52159
+ register: async (schema) => {
52160
+ try {
52161
+ const res = await fetch(`${serverUrl}/register`, {
52162
+ method: "POST",
52163
+ headers: { "Content-Type": "application/json" },
52164
+ body: JSON.stringify({ schema })
52165
+ });
52166
+ const result = await res.json();
52167
+ return !!result.success;
52168
+ } catch (err) {
52169
+ console.error("[ServerBridge] Registration failed:", err);
52170
+ return false;
52171
+ }
52172
+ },
52173
+ unregister: async () => {
52174
+ try {
52175
+ await fetch(`${serverUrl}/unregister`, { method: "DELETE" });
52176
+ } catch {
52177
+ }
52178
+ },
52179
+ sendEvent: async (orbitalName, event, payload) => {
52180
+ const res = await fetch(`${serverUrl}/${orbitalName}/events`, {
52181
+ method: "POST",
52182
+ headers: { "Content-Type": "application/json" },
52183
+ body: JSON.stringify({ event, payload })
52184
+ });
52185
+ return res.json();
52186
+ }
52187
+ };
52188
+ }
52170
52189
  var ServerBridgeContext = createContext(null);
52171
52190
  function useServerBridge() {
52172
52191
  const ctx = useContext(ServerBridgeContext);
@@ -52179,40 +52198,34 @@ function useServerBridge() {
52179
52198
  function ServerBridgeProvider({
52180
52199
  schema,
52181
52200
  serverUrl,
52201
+ transport: customTransport,
52182
52202
  children
52183
52203
  }) {
52204
+ if (!serverUrl && !customTransport) {
52205
+ throw new Error("ServerBridgeProvider requires either serverUrl or transport");
52206
+ }
52207
+ if (serverUrl && customTransport) {
52208
+ throw new Error("ServerBridgeProvider accepts serverUrl OR transport, not both");
52209
+ }
52184
52210
  const eventBus = useEventBus();
52185
52211
  const [connected, setConnected] = useState(false);
52186
- const registerSchema = useCallback(async () => {
52187
- try {
52188
- const res = await fetch(`${serverUrl}/register`, {
52189
- method: "POST",
52190
- headers: { "Content-Type": "application/json" },
52191
- body: JSON.stringify({ schema })
52192
- });
52193
- const result = await res.json();
52194
- return !!result.success;
52195
- } catch (err) {
52196
- console.error("[ServerBridge] Registration failed:", err);
52197
- return false;
52198
- }
52199
- }, [schema, serverUrl]);
52200
- const unregisterSchema = useCallback(async () => {
52201
- try {
52202
- await fetch(`${serverUrl}/unregister`, { method: "DELETE" });
52203
- } catch {
52204
- }
52205
- }, [serverUrl]);
52212
+ const transport = useMemo(
52213
+ () => customTransport ?? createHttpTransport(serverUrl),
52214
+ [serverUrl, customTransport]
52215
+ );
52216
+ const registerSchema = useCallback(
52217
+ async () => transport.register(schema),
52218
+ [schema, transport]
52219
+ );
52220
+ const unregisterSchema = useCallback(
52221
+ async () => transport.unregister(),
52222
+ [transport]
52223
+ );
52206
52224
  const sendEvent = useCallback(async (orbitalName, event, payload) => {
52207
52225
  const emptyMeta = { success: false, clientEffects: 0, dataEntities: {}, emittedEvents: [] };
52208
52226
  if (!connected) return { effects: [], meta: emptyMeta };
52209
52227
  try {
52210
- const res = await fetch(`${serverUrl}/${orbitalName}/events`, {
52211
- method: "POST",
52212
- headers: { "Content-Type": "application/json" },
52213
- body: JSON.stringify({ event, payload })
52214
- });
52215
- const result = await res.json();
52228
+ const result = await transport.sendEvent(orbitalName, event, payload);
52216
52229
  const effects = [];
52217
52230
  const responseData = result.data || {};
52218
52231
  const dataEntities = {};
@@ -52270,7 +52283,7 @@ function ServerBridgeProvider({
52270
52283
  console.error("[ServerBridge] Event send failed:", err);
52271
52284
  return { effects: [], meta: { ...emptyMeta, error: err instanceof Error ? err.message : String(err) } };
52272
52285
  }
52273
- }, [connected, serverUrl, eventBus]);
52286
+ }, [connected, transport, eventBus]);
52274
52287
  useEffect(() => {
52275
52288
  if (!schema) return;
52276
52289
  let cancelled = false;
@@ -52546,7 +52559,7 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFa
52546
52559
  }, [bridge.connected, orbitalNames, bridge.sendEvent, uiSlots, onNavigate]);
52547
52560
  return null;
52548
52561
  }
52549
- function SchemaRunner({ schema, serverUrl, mockData, pageName, onNavigate, onLocalFallback, persistence }) {
52562
+ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, onNavigate, onLocalFallback, persistence }) {
52550
52563
  const { traits: traits2, allEntities, ir } = useResolvedSchema(schema, pageName);
52551
52564
  const allPageTraits = useMemo(() => {
52552
52565
  if (pageName && traits2.length > 0) return traits2;
@@ -52660,7 +52673,7 @@ function SchemaRunner({ schema, serverUrl, mockData, pageName, onNavigate, onLoc
52660
52673
  TraitInitializer,
52661
52674
  {
52662
52675
  traits: allPageTraits,
52663
- orbitalNames: serverUrl ? pageOrbitalNames : void 0,
52676
+ orbitalNames: serverUrl || transport ? pageOrbitalNames : void 0,
52664
52677
  traitConfigsByName,
52665
52678
  orbitalsByTrait,
52666
52679
  onNavigate,
@@ -52673,8 +52686,8 @@ function SchemaRunner({ schema, serverUrl, mockData, pageName, onNavigate, onLoc
52673
52686
  ]
52674
52687
  }
52675
52688
  ) }) });
52676
- if (serverUrl) {
52677
- return /* @__PURE__ */ jsx(ServerBridgeProvider, { schema, serverUrl, children: inner });
52689
+ if (serverUrl || transport) {
52690
+ return /* @__PURE__ */ jsx(ServerBridgeProvider, { schema, serverUrl, transport, children: inner });
52678
52691
  }
52679
52692
  return inner;
52680
52693
  }
@@ -52685,8 +52698,12 @@ function OrbPreview({
52685
52698
  height = "400px",
52686
52699
  className,
52687
52700
  serverUrl,
52701
+ transport,
52688
52702
  initialPagePath
52689
52703
  }) {
52704
+ if (serverUrl && transport) {
52705
+ throw new Error("OrbPreview accepts serverUrl OR transport, not both");
52706
+ }
52690
52707
  const [localFallback, setLocalFallback] = useState(false);
52691
52708
  const eventBus = useEventBus();
52692
52709
  const handleLocalFallback = useCallback(() => {
@@ -52708,21 +52725,21 @@ function OrbPreview({
52708
52725
  } else {
52709
52726
  parsed = schema;
52710
52727
  }
52711
- if (autoMock && !serverUrl) {
52728
+ if (autoMock && !serverUrl && !transport) {
52712
52729
  const prepared = prepareSchemaForPreview(parsed);
52713
52730
  return { ok: true, schema: prepared.schema, mockData: prepared.mockData };
52714
52731
  }
52715
52732
  return { ok: true, schema: parsed, mockData: mockData ?? {} };
52716
- }, [schema, autoMock, serverUrl, mockData]);
52733
+ }, [schema, autoMock, serverUrl, transport, mockData]);
52717
52734
  const parsedSchema = parseResult.ok ? parseResult.schema : null;
52718
52735
  const effectiveMockData = parseResult.ok ? parseResult.mockData : {};
52719
52736
  const persistence = useMemo(() => {
52720
- if (!parsedSchema || serverUrl) return void 0;
52737
+ if (!parsedSchema || serverUrl || transport) return void 0;
52721
52738
  if (!autoMock) return void 0;
52722
52739
  const adapter = new InMemoryPersistence();
52723
52740
  adapter.seed(effectiveMockData);
52724
52741
  return adapter;
52725
- }, [parsedSchema, serverUrl, autoMock, effectiveMockData]);
52742
+ }, [parsedSchema, serverUrl, transport, autoMock, effectiveMockData]);
52726
52743
  const pages = useMemo(() => {
52727
52744
  if (!parsedSchema) return [];
52728
52745
  try {
@@ -52784,6 +52801,7 @@ function OrbPreview({
52784
52801
  {
52785
52802
  schema: parsedSchema,
52786
52803
  serverUrl,
52804
+ transport,
52787
52805
  mockData: effectiveMockData,
52788
52806
  pageName: currentPage,
52789
52807
  onNavigate: handleNavigate,
@@ -0,0 +1,34 @@
1
+ /**
2
+ * BrowserPlayground — in-browser Almadar runtime mount.
3
+ *
4
+ * Runs `OrbitalServerRuntime` (mock mode) in-process and threads it through
5
+ * `<OrbPreview>` via the `ServerBridgeTransport` adapter. Equivalent to
6
+ * canonical playground-runtime's server-mode mount, but without Express,
7
+ * fork, or HTTP — invokes `runtime.processOrbitalEvent` directly.
8
+ *
9
+ * Same React tree as `runtime-verify` (and apps/builder server-mode) speak,
10
+ * so any `@almadar/runtime` fix flows in through one bump cycle.
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * <BrowserPlayground schema={schema} mode="mock" height="100%" />
15
+ * ```
16
+ *
17
+ * @packageDocumentation
18
+ */
19
+ import React from 'react';
20
+ import type { OrbitalSchema } from '@almadar/core';
21
+ export interface BrowserPlaygroundProps {
22
+ /** OrbitalSchema to render. */
23
+ schema: OrbitalSchema;
24
+ /** Persistence mode for the in-process runtime. Default: 'mock' (Faker-seeded MockPersistenceAdapter). */
25
+ mode?: 'mock';
26
+ /** Initial page path to render (forwarded to OrbPreview). */
27
+ initialPagePath?: string;
28
+ /** Preview container height. Default: '400px'. */
29
+ height?: string;
30
+ /** CSS class for the outer container. */
31
+ className?: string;
32
+ }
33
+ export declare function BrowserPlayground({ schema, mode, initialPagePath, height, className, }: BrowserPlaygroundProps): React.ReactElement;
34
+ export default BrowserPlayground;
@@ -13,6 +13,7 @@
13
13
  */
14
14
  import React from 'react';
15
15
  import type { OrbitalSchema, EntityData } from '@almadar/core';
16
+ import { type ServerBridgeTransport } from './ServerBridge';
16
17
  export interface OrbPreviewProps {
17
18
  /**
18
19
  * The orbital schema. Accepts a JSON string or an `OrbitalSchema` object
@@ -42,6 +43,12 @@ export interface OrbPreviewProps {
42
43
  className?: string;
43
44
  /** Server URL for dual execution (e.g. "/api/orbitals"). When set, events are forwarded to the server. */
44
45
  serverUrl?: string;
46
+ /**
47
+ * Custom transport for in-process execution. Mutually exclusive with
48
+ * `serverUrl`. Used by `<BrowserPlayground>` to invoke
49
+ * `OrbitalServerRuntime.processOrbitalEvent` directly without HTTP.
50
+ */
51
+ transport?: ServerBridgeTransport;
45
52
  /**
46
53
  * Initial page path to render (e.g. `/deals`). Resolves against the
47
54
  * schema's `pages[]` to seed `currentPage` so the right orbital's traits
@@ -64,7 +71,7 @@ export interface OrbPreviewProps {
64
71
  * <OrbPreview schema={schema} serverUrl="/api/orbitals" />
65
72
  * ```
66
73
  */
67
- export declare function OrbPreview({ schema, mockData, autoMock, height, className, serverUrl, initialPagePath, }: OrbPreviewProps): React.ReactElement;
74
+ export declare function OrbPreview({ schema, mockData, autoMock, height, className, serverUrl, transport, initialPagePath, }: OrbPreviewProps): React.ReactElement;
68
75
  export declare namespace OrbPreview {
69
76
  var displayName: string;
70
77
  }
@@ -1,4 +1,33 @@
1
1
  import type { ReactNode } from 'react';
2
+ import type { BusEventSource, EventPayload } from '@almadar/core';
3
+ interface OrbitalEventResponse {
4
+ success: boolean;
5
+ transitioned: boolean;
6
+ states: Record<string, string>;
7
+ /**
8
+ * Server-cascade events carried back in the response. Each entry has a
9
+ * `source: BusEventSource` stamped by the compiled handler (`emit ...
10
+ * { source: __ORBITAL_SOURCE }`) so the client can re-broadcast on the
11
+ * qualified `UI:Orbital.Trait.EVENT` bus key (gap #13).
12
+ */
13
+ emittedEvents?: Array<{
14
+ event: string;
15
+ payload?: EventPayload;
16
+ source?: BusEventSource;
17
+ }>;
18
+ data?: Record<string, unknown[]>;
19
+ clientEffects?: unknown[];
20
+ /**
21
+ * Same effects as `clientEffects`, paired with the trait that produced
22
+ * each one. When present, prefer this for trait attribution. Falls back
23
+ * to legacy `clientEffects` parsing on older servers.
24
+ */
25
+ clientEffectsByTrait?: Array<{
26
+ traitName: string;
27
+ effect: unknown[];
28
+ }>;
29
+ error?: string;
30
+ }
2
31
  export interface ServerClientEffect {
3
32
  type: 'render-ui' | 'navigate' | 'notify';
4
33
  slot?: string;
@@ -31,13 +60,40 @@ export interface ServerBridgeContextValue {
31
60
  connected: boolean;
32
61
  sendEvent: (orbitalName: string, event: string, payload?: Record<string, unknown>) => Promise<SendEventResult>;
33
62
  }
63
+ /**
64
+ * Transport adapter for ServerBridgeProvider. Decouples the bridge's
65
+ * cascade-rebroadcast / effect-parsing logic from its wire format.
66
+ *
67
+ * - The `serverUrl` mode (default) uses an HTTP transport that POSTs to
68
+ * `/register`, `/unregister`, `/:orbital/events`. This is what canonical
69
+ * playground-runtime (`tools/runtime-verify`) and apps/builder-server
70
+ * speak.
71
+ * - The `transport` mode lets a consumer plug in a direct function-call
72
+ * adapter — used by `<BrowserPlayground>` to invoke
73
+ * `OrbitalServerRuntime.processOrbitalEvent` in-process, no HTTP, no
74
+ * server. Both modes return the same `OrbitalEventResponse` shape so the
75
+ * cascade-rebroadcast logic is identical downstream.
76
+ */
77
+ export interface ServerBridgeTransport {
78
+ register: (schema: unknown) => Promise<boolean>;
79
+ unregister: () => Promise<void>;
80
+ sendEvent: (orbitalName: string, event: string, payload?: Record<string, unknown>) => Promise<OrbitalEventResponse>;
81
+ }
34
82
  /**
35
83
  * Access the server bridge. Returns a no-op stub when outside the provider.
36
84
  */
37
85
  export declare function useServerBridge(): ServerBridgeContextValue;
38
86
  export interface ServerBridgeProviderProps {
39
87
  schema: unknown;
40
- serverUrl: string;
88
+ /** HTTP server URL (canonical playground-runtime / apps/builder-server). */
89
+ serverUrl?: string;
90
+ /**
91
+ * Custom transport adapter. Use this for in-process execution (e.g.
92
+ * `<BrowserPlayground>` invokes `OrbitalServerRuntime.processOrbitalEvent`
93
+ * directly). Mutually exclusive with `serverUrl`.
94
+ */
95
+ transport?: ServerBridgeTransport;
41
96
  children: ReactNode;
42
97
  }
43
- export declare function ServerBridgeProvider({ schema, serverUrl, children, }: ServerBridgeProviderProps): import("react/jsx-runtime").JSX.Element;
98
+ export declare function ServerBridgeProvider({ schema, serverUrl, transport: customTransport, children, }: ServerBridgeProviderProps): import("react/jsx-runtime").JSX.Element;
99
+ export {};
@@ -37,6 +37,7 @@ var langGraphql = require('react-syntax-highlighter/dist/esm/languages/prism/gra
37
37
  var core = require('@almadar/core');
38
38
  require('@tanstack/react-query');
39
39
  var runtime = require('@almadar/runtime');
40
+ var OrbitalServerRuntime = require('@almadar/runtime/OrbitalServerRuntime');
40
41
 
41
42
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
42
43
 
@@ -8229,13 +8230,13 @@ var init_MapView = __esm({
8229
8230
  shadowSize: [41, 41]
8230
8231
  });
8231
8232
  L.Marker.prototype.options.icon = defaultIcon;
8232
- const { useEffect: useEffect68, useRef: useRef65, useCallback: useCallback110, useState: useState102 } = React115__namespace.default;
8233
+ const { useEffect: useEffect69, useRef: useRef65, useCallback: useCallback110, useState: useState103 } = React115__namespace.default;
8233
8234
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
8234
8235
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
8235
8236
  function MapUpdater({ centerLat, centerLng, zoom }) {
8236
8237
  const map = useMap();
8237
8238
  const prevRef = useRef65({ centerLat, centerLng, zoom });
8238
- useEffect68(() => {
8239
+ useEffect69(() => {
8239
8240
  const prev = prevRef.current;
8240
8241
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
8241
8242
  map.setView([centerLat, centerLng], zoom);
@@ -8246,7 +8247,7 @@ var init_MapView = __esm({
8246
8247
  }
8247
8248
  function MapClickHandler({ onMapClick }) {
8248
8249
  const map = useMap();
8249
- useEffect68(() => {
8250
+ useEffect69(() => {
8250
8251
  if (!onMapClick) return;
8251
8252
  const handler = (e) => {
8252
8253
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -8273,7 +8274,7 @@ var init_MapView = __esm({
8273
8274
  showAttribution = true
8274
8275
  }) {
8275
8276
  const eventBus = useEventBus2();
8276
- const [clickedPosition, setClickedPosition] = useState102(null);
8277
+ const [clickedPosition, setClickedPosition] = useState103(null);
8277
8278
  const handleMapClick = useCallback110((lat, lng) => {
8278
8279
  if (showClickedPin) {
8279
8280
  setClickedPosition({ lat, lng });
@@ -38703,19 +38704,6 @@ function useTraitStateMachine(traitBindings, slotsActions, options) {
38703
38704
  });
38704
38705
  }
38705
38706
  }
38706
- const LIFECYCLE_EVENTS = /* @__PURE__ */ new Set(["INIT", "LOAD", "$MOUNT", "$UNMOUNT", "$FRAME"]);
38707
- if (!LIFECYCLE_EVENTS.has(normalizedEvent)) {
38708
- for (const { traitName, result } of results) {
38709
- if (!result.executed) continue;
38710
- const orbitalName = orbitalsByTrait?.[traitName];
38711
- if (!orbitalName) continue;
38712
- eventBus.emit(`UI:${orbitalName}.${traitName}.${normalizedEvent}`, payload, {
38713
- orbital: orbitalName,
38714
- trait: traitName,
38715
- fromBridge: true
38716
- });
38717
- }
38718
- }
38719
38707
  if (results.length > 0) {
38720
38708
  setTraitStates(currentManager.getAllStates());
38721
38709
  }
@@ -38993,6 +38981,38 @@ init_EntitySchemaContext();
38993
38981
  init_useEventBus();
38994
38982
  init_logger();
38995
38983
  var xOrbitalLog2 = createLogger("almadar:runtime:cross-orbital");
38984
+ function createHttpTransport(serverUrl) {
38985
+ return {
38986
+ register: async (schema) => {
38987
+ try {
38988
+ const res = await fetch(`${serverUrl}/register`, {
38989
+ method: "POST",
38990
+ headers: { "Content-Type": "application/json" },
38991
+ body: JSON.stringify({ schema })
38992
+ });
38993
+ const result = await res.json();
38994
+ return !!result.success;
38995
+ } catch (err) {
38996
+ console.error("[ServerBridge] Registration failed:", err);
38997
+ return false;
38998
+ }
38999
+ },
39000
+ unregister: async () => {
39001
+ try {
39002
+ await fetch(`${serverUrl}/unregister`, { method: "DELETE" });
39003
+ } catch {
39004
+ }
39005
+ },
39006
+ sendEvent: async (orbitalName, event, payload) => {
39007
+ const res = await fetch(`${serverUrl}/${orbitalName}/events`, {
39008
+ method: "POST",
39009
+ headers: { "Content-Type": "application/json" },
39010
+ body: JSON.stringify({ event, payload })
39011
+ });
39012
+ return res.json();
39013
+ }
39014
+ };
39015
+ }
38996
39016
  var ServerBridgeContext = React115.createContext(null);
38997
39017
  function useServerBridge() {
38998
39018
  const ctx = React115.useContext(ServerBridgeContext);
@@ -39005,40 +39025,34 @@ function useServerBridge() {
39005
39025
  function ServerBridgeProvider({
39006
39026
  schema,
39007
39027
  serverUrl,
39028
+ transport: customTransport,
39008
39029
  children
39009
39030
  }) {
39031
+ if (!serverUrl && !customTransport) {
39032
+ throw new Error("ServerBridgeProvider requires either serverUrl or transport");
39033
+ }
39034
+ if (serverUrl && customTransport) {
39035
+ throw new Error("ServerBridgeProvider accepts serverUrl OR transport, not both");
39036
+ }
39010
39037
  const eventBus = useEventBus();
39011
39038
  const [connected, setConnected] = React115.useState(false);
39012
- const registerSchema = React115.useCallback(async () => {
39013
- try {
39014
- const res = await fetch(`${serverUrl}/register`, {
39015
- method: "POST",
39016
- headers: { "Content-Type": "application/json" },
39017
- body: JSON.stringify({ schema })
39018
- });
39019
- const result = await res.json();
39020
- return !!result.success;
39021
- } catch (err) {
39022
- console.error("[ServerBridge] Registration failed:", err);
39023
- return false;
39024
- }
39025
- }, [schema, serverUrl]);
39026
- const unregisterSchema = React115.useCallback(async () => {
39027
- try {
39028
- await fetch(`${serverUrl}/unregister`, { method: "DELETE" });
39029
- } catch {
39030
- }
39031
- }, [serverUrl]);
39039
+ const transport = React115.useMemo(
39040
+ () => customTransport ?? createHttpTransport(serverUrl),
39041
+ [serverUrl, customTransport]
39042
+ );
39043
+ const registerSchema = React115.useCallback(
39044
+ async () => transport.register(schema),
39045
+ [schema, transport]
39046
+ );
39047
+ const unregisterSchema = React115.useCallback(
39048
+ async () => transport.unregister(),
39049
+ [transport]
39050
+ );
39032
39051
  const sendEvent = React115.useCallback(async (orbitalName, event, payload) => {
39033
39052
  const emptyMeta = { success: false, clientEffects: 0, dataEntities: {}, emittedEvents: [] };
39034
39053
  if (!connected) return { effects: [], meta: emptyMeta };
39035
39054
  try {
39036
- const res = await fetch(`${serverUrl}/${orbitalName}/events`, {
39037
- method: "POST",
39038
- headers: { "Content-Type": "application/json" },
39039
- body: JSON.stringify({ event, payload })
39040
- });
39041
- const result = await res.json();
39055
+ const result = await transport.sendEvent(orbitalName, event, payload);
39042
39056
  const effects = [];
39043
39057
  const responseData = result.data || {};
39044
39058
  const dataEntities = {};
@@ -39096,7 +39110,7 @@ function ServerBridgeProvider({
39096
39110
  console.error("[ServerBridge] Event send failed:", err);
39097
39111
  return { effects: [], meta: { ...emptyMeta, error: err instanceof Error ? err.message : String(err) } };
39098
39112
  }
39099
- }, [connected, serverUrl, eventBus]);
39113
+ }, [connected, transport, eventBus]);
39100
39114
  React115.useEffect(() => {
39101
39115
  if (!schema) return;
39102
39116
  let cancelled = false;
@@ -39372,7 +39386,7 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFa
39372
39386
  }, [bridge.connected, orbitalNames, bridge.sendEvent, uiSlots, onNavigate]);
39373
39387
  return null;
39374
39388
  }
39375
- function SchemaRunner({ schema, serverUrl, mockData, pageName, onNavigate, onLocalFallback, persistence }) {
39389
+ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, onNavigate, onLocalFallback, persistence }) {
39376
39390
  const { traits: traits2, allEntities, ir } = useResolvedSchema(schema, pageName);
39377
39391
  const allPageTraits = React115.useMemo(() => {
39378
39392
  if (pageName && traits2.length > 0) return traits2;
@@ -39486,7 +39500,7 @@ function SchemaRunner({ schema, serverUrl, mockData, pageName, onNavigate, onLoc
39486
39500
  TraitInitializer,
39487
39501
  {
39488
39502
  traits: allPageTraits,
39489
- orbitalNames: serverUrl ? pageOrbitalNames : void 0,
39503
+ orbitalNames: serverUrl || transport ? pageOrbitalNames : void 0,
39490
39504
  traitConfigsByName,
39491
39505
  orbitalsByTrait,
39492
39506
  onNavigate,
@@ -39499,8 +39513,8 @@ function SchemaRunner({ schema, serverUrl, mockData, pageName, onNavigate, onLoc
39499
39513
  ]
39500
39514
  }
39501
39515
  ) }) });
39502
- if (serverUrl) {
39503
- return /* @__PURE__ */ jsxRuntime.jsx(ServerBridgeProvider, { schema, serverUrl, children: inner });
39516
+ if (serverUrl || transport) {
39517
+ return /* @__PURE__ */ jsxRuntime.jsx(ServerBridgeProvider, { schema, serverUrl, transport, children: inner });
39504
39518
  }
39505
39519
  return inner;
39506
39520
  }
@@ -39511,8 +39525,12 @@ function OrbPreview({
39511
39525
  height = "400px",
39512
39526
  className,
39513
39527
  serverUrl,
39528
+ transport,
39514
39529
  initialPagePath
39515
39530
  }) {
39531
+ if (serverUrl && transport) {
39532
+ throw new Error("OrbPreview accepts serverUrl OR transport, not both");
39533
+ }
39516
39534
  const [localFallback, setLocalFallback] = React115.useState(false);
39517
39535
  const eventBus = useEventBus();
39518
39536
  const handleLocalFallback = React115.useCallback(() => {
@@ -39534,21 +39552,21 @@ function OrbPreview({
39534
39552
  } else {
39535
39553
  parsed = schema;
39536
39554
  }
39537
- if (autoMock && !serverUrl) {
39555
+ if (autoMock && !serverUrl && !transport) {
39538
39556
  const prepared = prepareSchemaForPreview(parsed);
39539
39557
  return { ok: true, schema: prepared.schema, mockData: prepared.mockData };
39540
39558
  }
39541
39559
  return { ok: true, schema: parsed, mockData: mockData ?? {} };
39542
- }, [schema, autoMock, serverUrl, mockData]);
39560
+ }, [schema, autoMock, serverUrl, transport, mockData]);
39543
39561
  const parsedSchema = parseResult.ok ? parseResult.schema : null;
39544
39562
  const effectiveMockData = parseResult.ok ? parseResult.mockData : {};
39545
39563
  const persistence = React115.useMemo(() => {
39546
- if (!parsedSchema || serverUrl) return void 0;
39564
+ if (!parsedSchema || serverUrl || transport) return void 0;
39547
39565
  if (!autoMock) return void 0;
39548
39566
  const adapter = new runtime.InMemoryPersistence();
39549
39567
  adapter.seed(effectiveMockData);
39550
39568
  return adapter;
39551
- }, [parsedSchema, serverUrl, autoMock, effectiveMockData]);
39569
+ }, [parsedSchema, serverUrl, transport, autoMock, effectiveMockData]);
39552
39570
  const pages = React115.useMemo(() => {
39553
39571
  if (!parsedSchema) return [];
39554
39572
  try {
@@ -39610,6 +39628,7 @@ function OrbPreview({
39610
39628
  {
39611
39629
  schema: parsedSchema,
39612
39630
  serverUrl,
39631
+ transport,
39613
39632
  mockData: effectiveMockData,
39614
39633
  pageName: currentPage,
39615
39634
  onNavigate: handleNavigate,
@@ -39622,7 +39641,50 @@ function OrbPreview({
39622
39641
  );
39623
39642
  }
39624
39643
  OrbPreview.displayName = "OrbPreview";
39644
+ function BrowserPlayground({
39645
+ schema,
39646
+ mode = "mock",
39647
+ initialPagePath,
39648
+ height,
39649
+ className
39650
+ }) {
39651
+ const [runtime] = React115.useState(
39652
+ () => new OrbitalServerRuntime.OrbitalServerRuntime({ mode, debug: false })
39653
+ );
39654
+ React115.useEffect(() => {
39655
+ void runtime.register(schema);
39656
+ return () => {
39657
+ runtime.unregisterAll();
39658
+ };
39659
+ }, [runtime, schema]);
39660
+ const transport = React115.useMemo(() => ({
39661
+ register: async (s) => {
39662
+ await runtime.register(s);
39663
+ return true;
39664
+ },
39665
+ unregister: async () => {
39666
+ runtime.unregisterAll();
39667
+ },
39668
+ sendEvent: async (orbitalName, event, payload) => {
39669
+ return runtime.processOrbitalEvent(orbitalName, {
39670
+ event,
39671
+ payload
39672
+ });
39673
+ }
39674
+ }), [runtime]);
39675
+ return /* @__PURE__ */ jsxRuntime.jsx(
39676
+ OrbPreview,
39677
+ {
39678
+ schema,
39679
+ transport,
39680
+ initialPagePath,
39681
+ height,
39682
+ className
39683
+ }
39684
+ );
39685
+ }
39625
39686
 
39687
+ exports.BrowserPlayground = BrowserPlayground;
39626
39688
  exports.EntitySchemaProvider = EntitySchemaProvider;
39627
39689
  exports.OrbPreview = OrbPreview;
39628
39690
  exports.ServerBridgeProvider = ServerBridgeProvider;
@@ -13,6 +13,7 @@ export { TraitProvider, TraitContext, useTraitContext, useTrait, type TraitConte
13
13
  export { SlotsProvider, useSlots, useSlotContent, useSlotsActions, type SlotsState, type SlotState, type SlotPatternEntry, type SlotSource, type SlotsActions, type SlotsProviderProps, } from './ui/SlotsContext';
14
14
  export { createClientEffectHandlers, type ClientEventBus, type SlotSetter, type CreateClientEffectHandlersOptions, } from './createClientEffectHandlers';
15
15
  export { OrbPreview, type OrbPreviewProps } from './OrbPreview';
16
+ export { BrowserPlayground, type BrowserPlaygroundProps } from './BrowserPlayground';
16
17
  export { prepareSchemaForPreview, buildMockData, adjustSchemaForMockData, type PreparedPreviewSchema, } from './prepareSchemaForPreview';
17
- export { ServerBridgeProvider, useServerBridge, type ServerBridgeContextValue, type ServerClientEffect } from './ServerBridge';
18
+ export { ServerBridgeProvider, useServerBridge, type ServerBridgeContextValue, type ServerBridgeTransport, type ServerClientEffect } from './ServerBridge';
18
19
  export type { ResolvedTraitBinding, ResolvedTrait, ResolvedEntity, ResolvedPage, ResolvedIR, } from './types';
@@ -37,6 +37,7 @@ import langGraphql from 'react-syntax-highlighter/dist/esm/languages/prism/graph
37
37
  import { isCircuitEvent, schemaToIR, getPage, clearSchemaCache as clearSchemaCache$1, isEntityCall, isInlineTrait } from '@almadar/core';
38
38
  import '@tanstack/react-query';
39
39
  import { StateMachineManager, createContextFromBindings, interpolateValue, createServerEffectHandlers, EffectExecutor, InMemoryPersistence } from '@almadar/runtime';
40
+ import { OrbitalServerRuntime } from '@almadar/runtime/OrbitalServerRuntime';
40
41
 
41
42
  var __defProp = Object.defineProperty;
42
43
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -8184,13 +8185,13 @@ var init_MapView = __esm({
8184
8185
  shadowSize: [41, 41]
8185
8186
  });
8186
8187
  L.Marker.prototype.options.icon = defaultIcon;
8187
- const { useEffect: useEffect68, useRef: useRef65, useCallback: useCallback110, useState: useState102 } = React115__default;
8188
+ const { useEffect: useEffect69, useRef: useRef65, useCallback: useCallback110, useState: useState103 } = React115__default;
8188
8189
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
8189
8190
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
8190
8191
  function MapUpdater({ centerLat, centerLng, zoom }) {
8191
8192
  const map = useMap();
8192
8193
  const prevRef = useRef65({ centerLat, centerLng, zoom });
8193
- useEffect68(() => {
8194
+ useEffect69(() => {
8194
8195
  const prev = prevRef.current;
8195
8196
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
8196
8197
  map.setView([centerLat, centerLng], zoom);
@@ -8201,7 +8202,7 @@ var init_MapView = __esm({
8201
8202
  }
8202
8203
  function MapClickHandler({ onMapClick }) {
8203
8204
  const map = useMap();
8204
- useEffect68(() => {
8205
+ useEffect69(() => {
8205
8206
  if (!onMapClick) return;
8206
8207
  const handler = (e) => {
8207
8208
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -8228,7 +8229,7 @@ var init_MapView = __esm({
8228
8229
  showAttribution = true
8229
8230
  }) {
8230
8231
  const eventBus = useEventBus2();
8231
- const [clickedPosition, setClickedPosition] = useState102(null);
8232
+ const [clickedPosition, setClickedPosition] = useState103(null);
8232
8233
  const handleMapClick = useCallback110((lat, lng) => {
8233
8234
  if (showClickedPin) {
8234
8235
  setClickedPosition({ lat, lng });
@@ -38658,19 +38659,6 @@ function useTraitStateMachine(traitBindings, slotsActions, options) {
38658
38659
  });
38659
38660
  }
38660
38661
  }
38661
- const LIFECYCLE_EVENTS = /* @__PURE__ */ new Set(["INIT", "LOAD", "$MOUNT", "$UNMOUNT", "$FRAME"]);
38662
- if (!LIFECYCLE_EVENTS.has(normalizedEvent)) {
38663
- for (const { traitName, result } of results) {
38664
- if (!result.executed) continue;
38665
- const orbitalName = orbitalsByTrait?.[traitName];
38666
- if (!orbitalName) continue;
38667
- eventBus.emit(`UI:${orbitalName}.${traitName}.${normalizedEvent}`, payload, {
38668
- orbital: orbitalName,
38669
- trait: traitName,
38670
- fromBridge: true
38671
- });
38672
- }
38673
- }
38674
38662
  if (results.length > 0) {
38675
38663
  setTraitStates(currentManager.getAllStates());
38676
38664
  }
@@ -38948,6 +38936,38 @@ init_EntitySchemaContext();
38948
38936
  init_useEventBus();
38949
38937
  init_logger();
38950
38938
  var xOrbitalLog2 = createLogger("almadar:runtime:cross-orbital");
38939
+ function createHttpTransport(serverUrl) {
38940
+ return {
38941
+ register: async (schema) => {
38942
+ try {
38943
+ const res = await fetch(`${serverUrl}/register`, {
38944
+ method: "POST",
38945
+ headers: { "Content-Type": "application/json" },
38946
+ body: JSON.stringify({ schema })
38947
+ });
38948
+ const result = await res.json();
38949
+ return !!result.success;
38950
+ } catch (err) {
38951
+ console.error("[ServerBridge] Registration failed:", err);
38952
+ return false;
38953
+ }
38954
+ },
38955
+ unregister: async () => {
38956
+ try {
38957
+ await fetch(`${serverUrl}/unregister`, { method: "DELETE" });
38958
+ } catch {
38959
+ }
38960
+ },
38961
+ sendEvent: async (orbitalName, event, payload) => {
38962
+ const res = await fetch(`${serverUrl}/${orbitalName}/events`, {
38963
+ method: "POST",
38964
+ headers: { "Content-Type": "application/json" },
38965
+ body: JSON.stringify({ event, payload })
38966
+ });
38967
+ return res.json();
38968
+ }
38969
+ };
38970
+ }
38951
38971
  var ServerBridgeContext = createContext(null);
38952
38972
  function useServerBridge() {
38953
38973
  const ctx = useContext(ServerBridgeContext);
@@ -38960,40 +38980,34 @@ function useServerBridge() {
38960
38980
  function ServerBridgeProvider({
38961
38981
  schema,
38962
38982
  serverUrl,
38983
+ transport: customTransport,
38963
38984
  children
38964
38985
  }) {
38986
+ if (!serverUrl && !customTransport) {
38987
+ throw new Error("ServerBridgeProvider requires either serverUrl or transport");
38988
+ }
38989
+ if (serverUrl && customTransport) {
38990
+ throw new Error("ServerBridgeProvider accepts serverUrl OR transport, not both");
38991
+ }
38965
38992
  const eventBus = useEventBus();
38966
38993
  const [connected, setConnected] = useState(false);
38967
- const registerSchema = useCallback(async () => {
38968
- try {
38969
- const res = await fetch(`${serverUrl}/register`, {
38970
- method: "POST",
38971
- headers: { "Content-Type": "application/json" },
38972
- body: JSON.stringify({ schema })
38973
- });
38974
- const result = await res.json();
38975
- return !!result.success;
38976
- } catch (err) {
38977
- console.error("[ServerBridge] Registration failed:", err);
38978
- return false;
38979
- }
38980
- }, [schema, serverUrl]);
38981
- const unregisterSchema = useCallback(async () => {
38982
- try {
38983
- await fetch(`${serverUrl}/unregister`, { method: "DELETE" });
38984
- } catch {
38985
- }
38986
- }, [serverUrl]);
38994
+ const transport = useMemo(
38995
+ () => customTransport ?? createHttpTransport(serverUrl),
38996
+ [serverUrl, customTransport]
38997
+ );
38998
+ const registerSchema = useCallback(
38999
+ async () => transport.register(schema),
39000
+ [schema, transport]
39001
+ );
39002
+ const unregisterSchema = useCallback(
39003
+ async () => transport.unregister(),
39004
+ [transport]
39005
+ );
38987
39006
  const sendEvent = useCallback(async (orbitalName, event, payload) => {
38988
39007
  const emptyMeta = { success: false, clientEffects: 0, dataEntities: {}, emittedEvents: [] };
38989
39008
  if (!connected) return { effects: [], meta: emptyMeta };
38990
39009
  try {
38991
- const res = await fetch(`${serverUrl}/${orbitalName}/events`, {
38992
- method: "POST",
38993
- headers: { "Content-Type": "application/json" },
38994
- body: JSON.stringify({ event, payload })
38995
- });
38996
- const result = await res.json();
39010
+ const result = await transport.sendEvent(orbitalName, event, payload);
38997
39011
  const effects = [];
38998
39012
  const responseData = result.data || {};
38999
39013
  const dataEntities = {};
@@ -39051,7 +39065,7 @@ function ServerBridgeProvider({
39051
39065
  console.error("[ServerBridge] Event send failed:", err);
39052
39066
  return { effects: [], meta: { ...emptyMeta, error: err instanceof Error ? err.message : String(err) } };
39053
39067
  }
39054
- }, [connected, serverUrl, eventBus]);
39068
+ }, [connected, transport, eventBus]);
39055
39069
  useEffect(() => {
39056
39070
  if (!schema) return;
39057
39071
  let cancelled = false;
@@ -39327,7 +39341,7 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFa
39327
39341
  }, [bridge.connected, orbitalNames, bridge.sendEvent, uiSlots, onNavigate]);
39328
39342
  return null;
39329
39343
  }
39330
- function SchemaRunner({ schema, serverUrl, mockData, pageName, onNavigate, onLocalFallback, persistence }) {
39344
+ function SchemaRunner({ schema, serverUrl, transport, mockData, pageName, onNavigate, onLocalFallback, persistence }) {
39331
39345
  const { traits: traits2, allEntities, ir } = useResolvedSchema(schema, pageName);
39332
39346
  const allPageTraits = useMemo(() => {
39333
39347
  if (pageName && traits2.length > 0) return traits2;
@@ -39441,7 +39455,7 @@ function SchemaRunner({ schema, serverUrl, mockData, pageName, onNavigate, onLoc
39441
39455
  TraitInitializer,
39442
39456
  {
39443
39457
  traits: allPageTraits,
39444
- orbitalNames: serverUrl ? pageOrbitalNames : void 0,
39458
+ orbitalNames: serverUrl || transport ? pageOrbitalNames : void 0,
39445
39459
  traitConfigsByName,
39446
39460
  orbitalsByTrait,
39447
39461
  onNavigate,
@@ -39454,8 +39468,8 @@ function SchemaRunner({ schema, serverUrl, mockData, pageName, onNavigate, onLoc
39454
39468
  ]
39455
39469
  }
39456
39470
  ) }) });
39457
- if (serverUrl) {
39458
- return /* @__PURE__ */ jsx(ServerBridgeProvider, { schema, serverUrl, children: inner });
39471
+ if (serverUrl || transport) {
39472
+ return /* @__PURE__ */ jsx(ServerBridgeProvider, { schema, serverUrl, transport, children: inner });
39459
39473
  }
39460
39474
  return inner;
39461
39475
  }
@@ -39466,8 +39480,12 @@ function OrbPreview({
39466
39480
  height = "400px",
39467
39481
  className,
39468
39482
  serverUrl,
39483
+ transport,
39469
39484
  initialPagePath
39470
39485
  }) {
39486
+ if (serverUrl && transport) {
39487
+ throw new Error("OrbPreview accepts serverUrl OR transport, not both");
39488
+ }
39471
39489
  const [localFallback, setLocalFallback] = useState(false);
39472
39490
  const eventBus = useEventBus();
39473
39491
  const handleLocalFallback = useCallback(() => {
@@ -39489,21 +39507,21 @@ function OrbPreview({
39489
39507
  } else {
39490
39508
  parsed = schema;
39491
39509
  }
39492
- if (autoMock && !serverUrl) {
39510
+ if (autoMock && !serverUrl && !transport) {
39493
39511
  const prepared = prepareSchemaForPreview(parsed);
39494
39512
  return { ok: true, schema: prepared.schema, mockData: prepared.mockData };
39495
39513
  }
39496
39514
  return { ok: true, schema: parsed, mockData: mockData ?? {} };
39497
- }, [schema, autoMock, serverUrl, mockData]);
39515
+ }, [schema, autoMock, serverUrl, transport, mockData]);
39498
39516
  const parsedSchema = parseResult.ok ? parseResult.schema : null;
39499
39517
  const effectiveMockData = parseResult.ok ? parseResult.mockData : {};
39500
39518
  const persistence = useMemo(() => {
39501
- if (!parsedSchema || serverUrl) return void 0;
39519
+ if (!parsedSchema || serverUrl || transport) return void 0;
39502
39520
  if (!autoMock) return void 0;
39503
39521
  const adapter = new InMemoryPersistence();
39504
39522
  adapter.seed(effectiveMockData);
39505
39523
  return adapter;
39506
- }, [parsedSchema, serverUrl, autoMock, effectiveMockData]);
39524
+ }, [parsedSchema, serverUrl, transport, autoMock, effectiveMockData]);
39507
39525
  const pages = useMemo(() => {
39508
39526
  if (!parsedSchema) return [];
39509
39527
  try {
@@ -39565,6 +39583,7 @@ function OrbPreview({
39565
39583
  {
39566
39584
  schema: parsedSchema,
39567
39585
  serverUrl,
39586
+ transport,
39568
39587
  mockData: effectiveMockData,
39569
39588
  pageName: currentPage,
39570
39589
  onNavigate: handleNavigate,
@@ -39577,5 +39596,47 @@ function OrbPreview({
39577
39596
  );
39578
39597
  }
39579
39598
  OrbPreview.displayName = "OrbPreview";
39599
+ function BrowserPlayground({
39600
+ schema,
39601
+ mode = "mock",
39602
+ initialPagePath,
39603
+ height,
39604
+ className
39605
+ }) {
39606
+ const [runtime] = useState(
39607
+ () => new OrbitalServerRuntime({ mode, debug: false })
39608
+ );
39609
+ useEffect(() => {
39610
+ void runtime.register(schema);
39611
+ return () => {
39612
+ runtime.unregisterAll();
39613
+ };
39614
+ }, [runtime, schema]);
39615
+ const transport = useMemo(() => ({
39616
+ register: async (s) => {
39617
+ await runtime.register(s);
39618
+ return true;
39619
+ },
39620
+ unregister: async () => {
39621
+ runtime.unregisterAll();
39622
+ },
39623
+ sendEvent: async (orbitalName, event, payload) => {
39624
+ return runtime.processOrbitalEvent(orbitalName, {
39625
+ event,
39626
+ payload
39627
+ });
39628
+ }
39629
+ }), [runtime]);
39630
+ return /* @__PURE__ */ jsx(
39631
+ OrbPreview,
39632
+ {
39633
+ schema,
39634
+ transport,
39635
+ initialPagePath,
39636
+ height,
39637
+ className
39638
+ }
39639
+ );
39640
+ }
39580
39641
 
39581
- export { EntitySchemaProvider, OrbPreview, ServerBridgeProvider, SlotsProvider, TraitContext, TraitProvider, adjustSchemaForMockData, buildMockData, clearSchemaCache, createClientEffectHandlers, prepareSchemaForPreview, useEntityDefinition, useEntitySchema, useEntitySchemaOptional, useResolvedSchema, useServerBridge, useSlotContent, useSlots, useSlotsActions, useTrait, useTraitContext, useTraitStateMachine };
39642
+ export { BrowserPlayground, EntitySchemaProvider, OrbPreview, ServerBridgeProvider, SlotsProvider, TraitContext, TraitProvider, adjustSchemaForMockData, buildMockData, clearSchemaCache, createClientEffectHandlers, prepareSchemaForPreview, useEntityDefinition, useEntitySchema, useEntitySchemaOptional, useResolvedSchema, useServerBridge, useSlotContent, useSlots, useSlotsActions, useTrait, useTraitContext, useTraitStateMachine };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "4.10.6",
3
+ "version": "4.11.0",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "main": "./dist/components/index.js",
@@ -121,7 +121,7 @@
121
121
  "@almadar/core": "^7.0.0",
122
122
  "@almadar/evaluator": ">=2.9.2",
123
123
  "@almadar/patterns": ">=2.17.1",
124
- "@almadar/runtime": "^5.5.0",
124
+ "@almadar/runtime": "^5.7.0",
125
125
  "@almadar/std": ">=6.4.1",
126
126
  "@almadar/syntax": ">=1.3.1",
127
127
  "@xyflow/react": "12.10.1",