@almadar/ui 6.6.0 → 6.8.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.
@@ -9,6 +9,7 @@ import { a as EntityBindingSource, h as ServerBridgeTransport } from '../EntityB
9
9
  export { b as EntitySchemaContextValue, c as EntitySchemaProvider, d as EntitySchemaProviderProps, e as ServerBridgeContextValue, f as ServerBridgeProvider, i as ServerClientEffect, T as TraitContext, k as TraitContextValue, l as TraitInstance, m as TraitProvider, n as TraitProviderProps, o as useEntitySchema, p as useEntitySchemaOptional, q as useServerBridge, r as useTrait, s as useTraitContext } from '../EntityBindingContext-Bn3ePJQC.js';
10
10
  export { PERF_NAMESPACE, PerfEntry, PreparedPreviewSchema, adjustSchemaForMockData, buildMockData, clearPerf, perfEnd, perfStart, perfTime, prepareSchemaForPreview, wrapCallbackForEvent } from '@almadar/runtime/ui';
11
11
  import React__default, { ReactNode } from 'react';
12
+ export { S as SlotHostManifest, a as SlotHostRegion, b as assertUniqueSlotsPerHost } from '../slot-host-Czc2JAxo.js';
12
13
  export { p as profilerOnRender, u as usePerfBuffer } from '../perf-DaVdsbU0.js';
13
14
  import '../event-bus-types-Bl78kokd.js';
14
15
  import '../useUISlots-GNwGLlW2.js';
@@ -326,6 +327,34 @@ declare function BrowserPlayground({ schema, mode, initialPagePath, height, clas
326
327
  * preview has (in-memory persist, mock services); `deny` is an opt-in
327
328
  * host policy, never a default sandbox.
328
329
  *
330
+ * OUTBOUND = the runtime's internal event bus, not the direct dispatch
331
+ * response. `response.emittedEvents` (mock mode) reflects only the effects
332
+ * of the trait `dispatch()` DIRECTLY targeted — a sibling trait that reacts
333
+ * through the runtime's own cascade (`setupEventListeners`, a SEPARATE
334
+ * `processOrbitalEvent` call whose response is discarded) never shows up
335
+ * there, so its emits never reached the ambient bus (verified 2026-09-04:
336
+ * vim-mode's `Shell` relays `PLUGIN_ENABLED` -> `VimStudioBridge`'s
337
+ * `ENABLED` arm emits `STATUS` + 2x `REGISTER_COMMAND`, none of which
338
+ * reached the studio's status bar/palette). Fix: for `mode: 'mock'`, one
339
+ * `runtime.getEventBus().onAny(...)` subscription per plugin (mounted
340
+ * alongside the runtime, not per-dispatch) is the ONLY outbound path — it
341
+ * sees every emit, direct or cascaded, since every `emit` effect passes
342
+ * through that same bus. `mode: 'server'` has no local bus to subscribe to
343
+ * (the runtime lives on the remote transport), so it keeps reading
344
+ * `response.emittedEvents` for outbound — same single-hop-only limitation
345
+ * this fix removes for mock mode, unaddressed here (no `mode: 'server'`
346
+ * consumer exists yet; see `docs/Almadar_UI_Gaps.md`).
347
+ *
348
+ * RELAY RULE: the plugin's `inbound[]` is its host→plugin vocabulary — one
349
+ * `UI:<busEvent>` bus event per row, dispatching `trigger` at
350
+ * `orbital`/`trait`. Anything a plugin emits under one of those SAME
351
+ * busEvent names (or under an inbound `trigger` name) is a relay for its
352
+ * own siblings' `listens {}` (the runtime's internal event bus already
353
+ * carries that fan-out), never a fresh request back to the host, and must
354
+ * NEVER be re-broadcast — doing so re-fires the very `UI:<busEvent>`
355
+ * listener that produced it, a synchronous self-feeding loop (verified
356
+ * 2026-09-04, see `docs/Almadar_UI_Gaps.md`).
357
+ *
329
358
  * @packageDocumentation
330
359
  */
331
360
 
@@ -50993,7 +50993,9 @@ function UISlotComponentInner({
50993
50993
  className,
50994
50994
  children,
50995
50995
  pattern,
50996
- sourceTrait
50996
+ sourceTrait,
50997
+ fallback,
50998
+ mode = "replace"
50997
50999
  }) {
50998
51000
  const { slots, clear } = useUISlots();
50999
51001
  const eventBus = useEventBus();
@@ -51045,12 +51047,26 @@ function UISlotComponentInner({
51045
51047
  );
51046
51048
  }
51047
51049
  if (!content) {
51050
+ if (fallback !== void 0) {
51051
+ return /* @__PURE__ */ jsx(
51052
+ Box,
51053
+ {
51054
+ id: `slot-${slot}`,
51055
+ className: cn("ui-slot", `ui-slot-${slot}`, className),
51056
+ "data-testid": `ui-slot-${slot}`,
51057
+ "data-slot-mode": "fallback",
51058
+ children: fallback
51059
+ }
51060
+ );
51061
+ }
51048
51062
  if (!portal) {
51049
51063
  return /* @__PURE__ */ jsx(
51050
51064
  Box,
51051
51065
  {
51052
51066
  id: `slot-${slot}`,
51053
- className: cn("ui-slot", `ui-slot-${slot}`, className)
51067
+ className: cn("ui-slot", `ui-slot-${slot}`, className),
51068
+ "data-testid": `ui-slot-${slot}`,
51069
+ "data-slot-mode": "empty"
51054
51070
  }
51055
51071
  );
51056
51072
  }
@@ -51067,29 +51083,51 @@ function UISlotComponentInner({
51067
51083
  clear(slot);
51068
51084
  };
51069
51085
  if (portal) {
51070
- if (contained) {
51071
- return renderContainedPortal(t, slot, content, handleDismiss);
51072
- }
51073
- return /* @__PURE__ */ jsx(
51074
- SlotPortal,
51086
+ const inlineFallback = mode === "append" && fallback !== void 0 ? /* @__PURE__ */ jsx(
51087
+ Box,
51075
51088
  {
51076
- slot,
51077
- content,
51078
- position,
51079
- onDismiss: handleDismiss
51089
+ id: `slot-${slot}-fallback`,
51090
+ className: cn("ui-slot", `ui-slot-${slot}-fallback`, className),
51091
+ "data-testid": `ui-slot-${slot}-fallback`,
51092
+ "data-slot-mode": "append",
51093
+ children: fallback
51080
51094
  }
51081
- );
51095
+ ) : null;
51096
+ if (contained) {
51097
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
51098
+ inlineFallback,
51099
+ renderContainedPortal(t, slot, content, handleDismiss)
51100
+ ] });
51101
+ }
51102
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
51103
+ inlineFallback,
51104
+ /* @__PURE__ */ jsx(
51105
+ SlotPortal,
51106
+ {
51107
+ slot,
51108
+ content,
51109
+ position,
51110
+ onDismiss: handleDismiss
51111
+ }
51112
+ )
51113
+ ] });
51082
51114
  }
51083
51115
  const slotContent = /* @__PURE__ */ jsx(SlotContentRenderer, { content, onDismiss: handleDismiss });
51084
51116
  const wrappedContent = suspenseConfig.enabled ? /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(Suspense, { fallback: getSlotFallback(slot, suspenseConfig), children: slotContent }) }) : /* @__PURE__ */ jsx(ErrorBoundary, { children: slotContent });
51085
- return /* @__PURE__ */ jsx(
51117
+ const showFallback = mode === "append" && fallback !== void 0;
51118
+ return /* @__PURE__ */ jsxs(
51086
51119
  Box,
51087
51120
  {
51088
51121
  id: `slot-${slot}`,
51089
51122
  className: cn("ui-slot", `ui-slot-${slot}`, className),
51090
51123
  "data-pattern": content.pattern,
51091
51124
  "data-source-trait": content.sourceTrait,
51092
- children: /* @__PURE__ */ jsx(MaybeTraitScope, { sourceTrait: content.sourceTrait, children: wrappedContent })
51125
+ "data-testid": `ui-slot-${slot}`,
51126
+ "data-slot-mode": showFallback ? "append" : "content",
51127
+ children: [
51128
+ showFallback ? fallback : null,
51129
+ /* @__PURE__ */ jsx(MaybeTraitScope, { sourceTrait: content.sourceTrait, children: wrappedContent })
51130
+ ]
51093
51131
  }
51094
51132
  );
51095
51133
  }
@@ -54169,17 +54207,30 @@ function PluginRuntimeMount({
54169
54207
  () => new Set(plugin.schema.orbitals.map((o) => o.name)),
54170
54208
  [plugin.schema]
54171
54209
  );
54210
+ const inboundBusEvents = useMemo(
54211
+ () => new Set(plugin.inbound.map((row) => row.busEvent)),
54212
+ [plugin.inbound]
54213
+ );
54214
+ const inboundTriggerNames = useMemo(
54215
+ () => new Set(plugin.inbound.map((row) => row.trigger)),
54216
+ [plugin.inbound]
54217
+ );
54218
+ const snapshotAllStates = useCallback(() => {
54219
+ const states = [];
54220
+ if (!mockRuntime) return states;
54221
+ for (const orbital of plugin.schema.orbitals) {
54222
+ for (const traitName of traitNamesOf(orbital)) {
54223
+ const raw = mockRuntime.getState(orbital.name, traitName);
54224
+ if (isTraitState(raw)) states.push([orbital.name, traitName, raw]);
54225
+ }
54226
+ }
54227
+ return states;
54228
+ }, [mockRuntime, plugin.schema]);
54172
54229
  useEffect(() => {
54173
54230
  let cancelled = false;
54174
54231
  void registrationReady.then(() => {
54175
54232
  if (cancelled || !mockRuntime) return;
54176
- const states = [];
54177
- for (const orbital of plugin.schema.orbitals) {
54178
- for (const traitName of traitNamesOf(orbital)) {
54179
- const raw = mockRuntime.getState(orbital.name, traitName);
54180
- if (isTraitState(raw)) states.push([orbital.name, traitName, raw]);
54181
- }
54182
- }
54233
+ const states = snapshotAllStates();
54183
54234
  if (states.length > 0) onDispatched(plugin.id, null, states);
54184
54235
  }).catch((err) => {
54185
54236
  if (!cancelled) onError(plugin.id, err instanceof Error ? err.message : String(err));
@@ -54187,7 +54238,39 @@ function PluginRuntimeMount({
54187
54238
  return () => {
54188
54239
  cancelled = true;
54189
54240
  };
54190
- }, [registrationReady, mockRuntime, plugin, onDispatched, onError]);
54241
+ }, [registrationReady, mockRuntime, plugin, onDispatched, onError, snapshotAllStates]);
54242
+ useEffect(() => {
54243
+ if (!mockRuntime) return;
54244
+ let timer = null;
54245
+ const scheduleResync = () => {
54246
+ if (timer !== null) return;
54247
+ timer = setTimeout(() => {
54248
+ timer = null;
54249
+ const states = snapshotAllStates();
54250
+ if (states.length > 0) onDispatched(plugin.id, null, states);
54251
+ }, 0);
54252
+ };
54253
+ const unsub = mockRuntime.getEventBus().onAny(scheduleResync);
54254
+ return () => {
54255
+ unsub();
54256
+ if (timer !== null) clearTimeout(timer);
54257
+ };
54258
+ }, [mockRuntime, plugin.id, onDispatched, snapshotAllStates]);
54259
+ useEffect(() => {
54260
+ if (!mockRuntime) return;
54261
+ const rebroadcast = /* @__PURE__ */ new WeakSet();
54262
+ const unsub = mockRuntime.getEventBus().onAny((event) => {
54263
+ if (rebroadcast.has(event)) return;
54264
+ if (inboundBusEvents.has(event.type) || inboundTriggerNames.has(event.type)) return;
54265
+ const sourceOrbital = event.source?.orbital;
54266
+ if (sourceOrbital === void 0 || !ownOrbitals.has(sourceOrbital)) return;
54267
+ rebroadcast.add(event);
54268
+ bus.emit(`UI:${event.type}`, event.payload, event.source);
54269
+ });
54270
+ return () => {
54271
+ unsub();
54272
+ };
54273
+ }, [mockRuntime, bus, ownOrbitals, inboundBusEvents, inboundTriggerNames]);
54191
54274
  const dispatch = useCallback(
54192
54275
  async (row, payload) => {
54193
54276
  await registrationReady;
@@ -54206,11 +54289,14 @@ function PluginRuntimeMount({
54206
54289
  if (!response.success) {
54207
54290
  onError(plugin.id, response.error ?? `${plugin.id}: ${row.trigger} failed`);
54208
54291
  }
54209
- for (const entry of response.emittedEvents ?? []) {
54210
- if (entry.event === row.trigger) continue;
54211
- const sourceOrbital = entry.source?.orbital;
54212
- if (sourceOrbital !== void 0 && ownOrbitals.has(sourceOrbital)) {
54213
- bus.emit(`UI:${entry.event}`, entry.payload, entry.source);
54292
+ if (!mockRuntime) {
54293
+ for (const entry of response.emittedEvents ?? []) {
54294
+ if (entry.event === row.trigger) continue;
54295
+ if (inboundBusEvents.has(entry.event)) continue;
54296
+ const sourceOrbital = entry.source?.orbital;
54297
+ if (sourceOrbital !== void 0 && ownOrbitals.has(sourceOrbital)) {
54298
+ bus.emit(`UI:${entry.event}`, entry.payload, entry.source);
54299
+ }
54214
54300
  }
54215
54301
  }
54216
54302
  const states = [];
@@ -54220,7 +54306,7 @@ function PluginRuntimeMount({
54220
54306
  }
54221
54307
  onDispatched(plugin.id, row.trigger, states);
54222
54308
  },
54223
- [registrationReady, mockRuntime, mode, transport, plugin, bus, ownOrbitals, onError, onTransition, onDispatched]
54309
+ [registrationReady, mockRuntime, mode, transport, plugin, bus, ownOrbitals, inboundBusEvents, onError, onTransition, onDispatched]
54224
54310
  );
54225
54311
  useEffect(() => {
54226
54312
  const unsubs = plugin.inbound.map(
@@ -54304,7 +54390,21 @@ function OrbitalPluginHost({
54304
54390
  ] });
54305
54391
  }
54306
54392
 
54393
+ // types/slot-host.ts
54394
+ function assertUniqueSlotsPerHost(manifest) {
54395
+ const seenBySlot = /* @__PURE__ */ new Map();
54396
+ for (const [regionId, region] of Object.entries(manifest.regions)) {
54397
+ const existingRegionId = seenBySlot.get(region.slot);
54398
+ if (existingRegionId !== void 0) {
54399
+ throw new Error(
54400
+ `assertUniqueSlotsPerHost: regions "${existingRegionId}" and "${regionId}" both bind slot "${region.slot}" \u2014 a slot may be mounted by only one region per host.`
54401
+ );
54402
+ }
54403
+ seenBySlot.set(region.slot, regionId);
54404
+ }
54405
+ }
54406
+
54307
54407
  // runtime/index.ts
54308
54408
  init_perf();
54309
54409
 
54310
- export { BrowserPlayground, OrbPreview, OrbitalPluginHost, clearSchemaCache, createClientEffectHandlers, profilerOnRender, useDeclaredCaptureTable, useOrbitalPluginHost, usePerfBuffer, useResolvedSchema, useTraitStateMachine };
54410
+ export { BrowserPlayground, OrbPreview, OrbitalPluginHost, assertUniqueSlotsPerHost, clearSchemaCache, createClientEffectHandlers, profilerOnRender, useDeclaredCaptureTable, useOrbitalPluginHost, usePerfBuffer, useResolvedSchema, useTraitStateMachine };
@@ -0,0 +1,47 @@
1
+ import { UISlot } from '@almadar/core';
2
+
3
+ /**
4
+ * Slot-host manifest types (Studio V4 §14, Part I1/I2).
5
+ *
6
+ * A "slot-host" is any shell that lays out named regions and mounts each
7
+ * one through `UISlotComponent` — e.g. a persona workspace declaring
8
+ * `sidebar`/`main`/`drawer`/... A manifest is the declarative region → slot
9
+ * binding for such a host: which compiler `UISlot` each region mounts, and
10
+ * whether the host's own stock content is replaced by slot content or the
11
+ * two are shown together (`UISlotComponent`'s `mode` prop). Substrate
12
+ * (lives in `@almadar/ui`, re-exported by `@almadar/sdk`) so any host — the
13
+ * studio's three persona shells or an embedding app — describes its
14
+ * regions the same way.
15
+ *
16
+ * @packageDocumentation
17
+ */
18
+
19
+ /**
20
+ * One host region's binding to a compiler slot. Mirrors `UISlotComponent`'s
21
+ * `mode` prop exactly — a manifest is just that prop's value, keyed by
22
+ * region, declared as data instead of scattered across JSX call sites.
23
+ */
24
+ interface SlotHostRegion {
25
+ slot: UISlot;
26
+ mode: "replace" | "append";
27
+ }
28
+ /**
29
+ * A host's full region map, keyed by the host's own region ids (e.g. a
30
+ * persona shell's `activityBar`/`sidebar`/`main`/...). `RegionId` is left
31
+ * generic so each host supplies its own literal union.
32
+ */
33
+ interface SlotHostManifest<RegionId extends string = string> {
34
+ regions: Record<RegionId, SlotHostRegion>;
35
+ }
36
+ /**
37
+ * Guards the invariant a slot-host manifest depends on: each `UISlot` may
38
+ * be mounted by at most one region. Two regions sharing a slot would mount
39
+ * `UISlotComponent` twice for the same slot — the second mount clears/reads
40
+ * the same context entry the first one owns, so one of the two regions
41
+ * silently loses its content (or, for a portal slot, double-portals it).
42
+ * Throws naming the two offending regions so the host can find the
43
+ * conflict without re-deriving it from the manifest.
44
+ */
45
+ declare function assertUniqueSlotsPerHost<RegionId extends string>(manifest: SlotHostManifest<RegionId>): void;
46
+
47
+ export { type SlotHostManifest as S, type SlotHostRegion as a, assertUniqueSlotsPerHost as b };
@@ -0,0 +1,47 @@
1
+ import { UISlot } from '@almadar/core';
2
+
3
+ /**
4
+ * Slot-host manifest types (Studio V4 §14, Part I1/I2).
5
+ *
6
+ * A "slot-host" is any shell that lays out named regions and mounts each
7
+ * one through `UISlotComponent` — e.g. a persona workspace declaring
8
+ * `sidebar`/`main`/`drawer`/... A manifest is the declarative region → slot
9
+ * binding for such a host: which compiler `UISlot` each region mounts, and
10
+ * whether the host's own stock content is replaced by slot content or the
11
+ * two are shown together (`UISlotComponent`'s `mode` prop). Substrate
12
+ * (lives in `@almadar/ui`, re-exported by `@almadar/sdk`) so any host — the
13
+ * studio's three persona shells or an embedding app — describes its
14
+ * regions the same way.
15
+ *
16
+ * @packageDocumentation
17
+ */
18
+
19
+ /**
20
+ * One host region's binding to a compiler slot. Mirrors `UISlotComponent`'s
21
+ * `mode` prop exactly — a manifest is just that prop's value, keyed by
22
+ * region, declared as data instead of scattered across JSX call sites.
23
+ */
24
+ interface SlotHostRegion {
25
+ slot: UISlot;
26
+ mode: "replace" | "append";
27
+ }
28
+ /**
29
+ * A host's full region map, keyed by the host's own region ids (e.g. a
30
+ * persona shell's `activityBar`/`sidebar`/`main`/...). `RegionId` is left
31
+ * generic so each host supplies its own literal union.
32
+ */
33
+ interface SlotHostManifest<RegionId extends string = string> {
34
+ regions: Record<RegionId, SlotHostRegion>;
35
+ }
36
+ /**
37
+ * Guards the invariant a slot-host manifest depends on: each `UISlot` may
38
+ * be mounted by at most one region. Two regions sharing a slot would mount
39
+ * `UISlotComponent` twice for the same slot — the second mount clears/reads
40
+ * the same context entry the first one owns, so one of the two regions
41
+ * silently loses its content (or, for a portal slot, double-portals it).
42
+ * Throws naming the two offending regions so the host can find the
43
+ * conflict without re-deriving it from the manifest.
44
+ */
45
+ declare function assertUniqueSlotsPerHost<RegionId extends string>(manifest: SlotHostManifest<RegionId>): void;
46
+
47
+ export { type SlotHostManifest as S, type SlotHostRegion as a, assertUniqueSlotsPerHost as b };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "6.6.0",
3
+ "version": "6.8.0",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -118,11 +118,11 @@
118
118
  "access": "public"
119
119
  },
120
120
  "dependencies": {
121
- "@almadar/core": "^10.83.0",
121
+ "@almadar/core": "^10.84.0",
122
122
  "@almadar/evaluator": "^2.44.0",
123
123
  "@almadar/logger": "^1.12.0",
124
- "@almadar/runtime": "^6.70.0",
125
- "@almadar/std": "^16.203.0",
124
+ "@almadar/runtime": "^6.71.0",
125
+ "@almadar/std": "^16.205.0",
126
126
  "@almadar/syntax": "^1.16.0",
127
127
  "@dnd-kit/core": "^6.3.1",
128
128
  "@dnd-kit/sortable": "^10.0.0",