@almadar/ui 6.6.0 → 6.7.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.
@@ -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,22 @@ function PluginRuntimeMount({
54169
54207
  () => new Set(plugin.schema.orbitals.map((o) => o.name)),
54170
54208
  [plugin.schema]
54171
54209
  );
54210
+ const snapshotAllStates = useCallback(() => {
54211
+ const states = [];
54212
+ if (!mockRuntime) return states;
54213
+ for (const orbital of plugin.schema.orbitals) {
54214
+ for (const traitName of traitNamesOf(orbital)) {
54215
+ const raw = mockRuntime.getState(orbital.name, traitName);
54216
+ if (isTraitState(raw)) states.push([orbital.name, traitName, raw]);
54217
+ }
54218
+ }
54219
+ return states;
54220
+ }, [mockRuntime, plugin.schema]);
54172
54221
  useEffect(() => {
54173
54222
  let cancelled = false;
54174
54223
  void registrationReady.then(() => {
54175
54224
  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
- }
54225
+ const states = snapshotAllStates();
54183
54226
  if (states.length > 0) onDispatched(plugin.id, null, states);
54184
54227
  }).catch((err) => {
54185
54228
  if (!cancelled) onError(plugin.id, err instanceof Error ? err.message : String(err));
@@ -54187,7 +54230,24 @@ function PluginRuntimeMount({
54187
54230
  return () => {
54188
54231
  cancelled = true;
54189
54232
  };
54190
- }, [registrationReady, mockRuntime, plugin, onDispatched, onError]);
54233
+ }, [registrationReady, mockRuntime, plugin, onDispatched, onError, snapshotAllStates]);
54234
+ useEffect(() => {
54235
+ if (!mockRuntime) return;
54236
+ let timer = null;
54237
+ const scheduleResync = () => {
54238
+ if (timer !== null) return;
54239
+ timer = setTimeout(() => {
54240
+ timer = null;
54241
+ const states = snapshotAllStates();
54242
+ if (states.length > 0) onDispatched(plugin.id, null, states);
54243
+ }, 0);
54244
+ };
54245
+ const unsub = mockRuntime.getEventBus().onAny(scheduleResync);
54246
+ return () => {
54247
+ unsub();
54248
+ if (timer !== null) clearTimeout(timer);
54249
+ };
54250
+ }, [mockRuntime, plugin.id, onDispatched, snapshotAllStates]);
54191
54251
  const dispatch = useCallback(
54192
54252
  async (row, payload) => {
54193
54253
  await registrationReady;
@@ -54304,7 +54364,21 @@ function OrbitalPluginHost({
54304
54364
  ] });
54305
54365
  }
54306
54366
 
54367
+ // types/slot-host.ts
54368
+ function assertUniqueSlotsPerHost(manifest) {
54369
+ const seenBySlot = /* @__PURE__ */ new Map();
54370
+ for (const [regionId, region] of Object.entries(manifest.regions)) {
54371
+ const existingRegionId = seenBySlot.get(region.slot);
54372
+ if (existingRegionId !== void 0) {
54373
+ throw new Error(
54374
+ `assertUniqueSlotsPerHost: regions "${existingRegionId}" and "${regionId}" both bind slot "${region.slot}" \u2014 a slot may be mounted by only one region per host.`
54375
+ );
54376
+ }
54377
+ seenBySlot.set(region.slot, regionId);
54378
+ }
54379
+ }
54380
+
54307
54381
  // runtime/index.ts
54308
54382
  init_perf();
54309
54383
 
54310
- export { BrowserPlayground, OrbPreview, OrbitalPluginHost, clearSchemaCache, createClientEffectHandlers, profilerOnRender, useDeclaredCaptureTable, useOrbitalPluginHost, usePerfBuffer, useResolvedSchema, useTraitStateMachine };
54384
+ 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.7.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.204.0",
126
126
  "@almadar/syntax": "^1.16.0",
127
127
  "@dnd-kit/core": "^6.3.1",
128
128
  "@dnd-kit/sortable": "^10.0.0",