@almadar/ui 3.8.2 → 3.9.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.
@@ -36,7 +36,7 @@ import langGo from 'react-syntax-highlighter/dist/esm/languages/prism/go.js';
36
36
  import langGraphql from 'react-syntax-highlighter/dist/esm/languages/prism/graphql.js';
37
37
  import { isCircuitEvent, schemaToIR, getPage, clearSchemaCache as clearSchemaCache$1, isEntityCall, isInlineTrait } from '@almadar/core';
38
38
  import '@tanstack/react-query';
39
- import { StateMachineManager, createContextFromBindings, interpolateValue, EffectExecutor } from '@almadar/runtime';
39
+ import { StateMachineManager, createContextFromBindings, interpolateValue, createServerEffectHandlers, EffectExecutor, InMemoryPersistence } from '@almadar/runtime';
40
40
 
41
41
  var __defProp = Object.defineProperty;
42
42
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -18869,10 +18869,7 @@ var init_DataList = __esm({
18869
18869
  HStack,
18870
18870
  {
18871
18871
  gap: "xs",
18872
- className: cn(
18873
- "flex-shrink-0 transition-opacity duration-200",
18874
- "opacity-0 group-hover:opacity-100"
18875
- ),
18872
+ className: "flex-shrink-0",
18876
18873
  children: itemActions.map((action, idx) => /* @__PURE__ */ jsxs(
18877
18874
  Button,
18878
18875
  {
@@ -18962,33 +18959,23 @@ var init_DataList = __esm({
18962
18959
  ] }, field.name);
18963
18960
  })
18964
18961
  ] }),
18965
- itemActions && itemActions.length > 0 && /* @__PURE__ */ jsx(
18966
- HStack,
18962
+ itemActions && itemActions.length > 0 && /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-shrink-0", children: itemActions.map((action, idx) => /* @__PURE__ */ jsxs(
18963
+ Button,
18967
18964
  {
18968
- gap: "xs",
18965
+ variant: action.variant ?? "ghost",
18966
+ size: "sm",
18967
+ onClick: handleActionClick(action, itemData),
18968
+ "data-testid": `action-${action.event}`,
18969
18969
  className: cn(
18970
- "flex-shrink-0 transition-opacity duration-200",
18971
- "opacity-0 group-hover:opacity-100"
18970
+ action.variant === "danger" && "text-error hover:bg-error/10"
18972
18971
  ),
18973
- children: itemActions.map((action, idx) => /* @__PURE__ */ jsxs(
18974
- Button,
18975
- {
18976
- variant: action.variant ?? "ghost",
18977
- size: "sm",
18978
- onClick: handleActionClick(action, itemData),
18979
- "data-testid": `action-${action.event}`,
18980
- className: cn(
18981
- action.variant === "danger" && "text-error hover:bg-error/10"
18982
- ),
18983
- children: [
18984
- action.icon && /* @__PURE__ */ jsx(Icon, { name: action.icon, size: "xs", className: "mr-1" }),
18985
- action.label
18986
- ]
18987
- },
18988
- idx
18989
- ))
18990
- }
18991
- )
18972
+ children: [
18973
+ action.icon && /* @__PURE__ */ jsx(Icon, { name: action.icon, size: "xs", className: "mr-1" }),
18974
+ action.label
18975
+ ]
18976
+ },
18977
+ idx
18978
+ )) })
18992
18979
  ]
18993
18980
  }
18994
18981
  ),
@@ -30218,75 +30205,66 @@ var init_List = __esm({
30218
30205
  hasProgress && /* @__PURE__ */ jsx(Box, { className: "ml-auto", children: /* @__PURE__ */ jsx(ProgressIndicator, { value: progressValue }) })
30219
30206
  ] })
30220
30207
  ] }),
30221
- /* @__PURE__ */ jsxs(
30222
- HStack,
30223
- {
30224
- className: cn(
30225
- "flex items-center gap-1 flex-shrink-0 transition-opacity duration-200",
30226
- "opacity-0 group-hover:opacity-100"
30227
- ),
30228
- children: [
30229
- editAction && /* @__PURE__ */ jsx(
30230
- Button,
30231
- {
30232
- variant: "ghost",
30233
- action: editAction.event,
30234
- className: cn(
30235
- "p-2 rounded-lg transition-all duration-200",
30236
- "hover:bg-primary/10 hover:text-primary",
30237
- "text-muted-foreground",
30238
- "active:scale-95"
30239
- ),
30240
- title: editAction.label,
30241
- "data-testid": editAction.event ? `action-${editAction.event}` : void 0,
30242
- children: /* @__PURE__ */ jsx(Pencil, { className: "w-4 h-4" })
30243
- }
30208
+ /* @__PURE__ */ jsxs(HStack, { className: "flex items-center gap-1 flex-shrink-0", children: [
30209
+ editAction && /* @__PURE__ */ jsx(
30210
+ Button,
30211
+ {
30212
+ variant: "ghost",
30213
+ action: editAction.event,
30214
+ className: cn(
30215
+ "p-2 rounded-lg transition-all duration-200",
30216
+ "hover:bg-primary/10 hover:text-primary",
30217
+ "text-muted-foreground",
30218
+ "active:scale-95"
30244
30219
  ),
30245
- viewAction && /* @__PURE__ */ jsx(
30246
- Button,
30247
- {
30248
- variant: "ghost",
30249
- action: viewAction.event,
30250
- className: cn(
30251
- "p-2 rounded-lg transition-all duration-200",
30252
- "hover:bg-muted hover:text-foreground",
30253
- "text-muted-foreground",
30254
- "active:scale-95"
30255
- ),
30256
- title: viewAction.label,
30257
- "data-testid": viewAction.event ? `action-${viewAction.event}` : void 0,
30258
- children: /* @__PURE__ */ jsx(Eye, { className: "w-4 h-4" })
30259
- }
30220
+ title: editAction.label,
30221
+ "data-testid": editAction.event ? `action-${editAction.event}` : void 0,
30222
+ children: /* @__PURE__ */ jsx(Pencil, { className: "w-4 h-4" })
30223
+ }
30224
+ ),
30225
+ viewAction && /* @__PURE__ */ jsx(
30226
+ Button,
30227
+ {
30228
+ variant: "ghost",
30229
+ action: viewAction.event,
30230
+ className: cn(
30231
+ "p-2 rounded-lg transition-all duration-200",
30232
+ "hover:bg-muted hover:text-foreground",
30233
+ "text-muted-foreground",
30234
+ "active:scale-95"
30260
30235
  ),
30261
- (() => {
30262
- const filteredActions = actions.filter(
30263
- (a) => !a.label.toLowerCase().includes("edit") && !a.label.toLowerCase().includes("view") && !a.label.toLowerCase().includes("open")
30264
- );
30265
- return filteredActions.length > 0 ? /* @__PURE__ */ jsx(
30266
- Menu,
30236
+ title: viewAction.label,
30237
+ "data-testid": viewAction.event ? `action-${viewAction.event}` : void 0,
30238
+ children: /* @__PURE__ */ jsx(Eye, { className: "w-4 h-4" })
30239
+ }
30240
+ ),
30241
+ (() => {
30242
+ const filteredActions = actions.filter(
30243
+ (a) => !a.label.toLowerCase().includes("edit") && !a.label.toLowerCase().includes("view") && !a.label.toLowerCase().includes("open")
30244
+ );
30245
+ return filteredActions.length > 0 ? /* @__PURE__ */ jsx(
30246
+ Menu,
30247
+ {
30248
+ trigger: /* @__PURE__ */ jsx(
30249
+ Button,
30267
30250
  {
30268
- trigger: /* @__PURE__ */ jsx(
30269
- Button,
30270
- {
30271
- variant: "ghost",
30272
- className: cn(
30273
- "p-2 rounded-lg transition-all duration-200",
30274
- "hover:bg-muted hover:shadow-sm",
30275
- "text-muted-foreground hover:text-foreground",
30276
- "active:scale-95"
30277
- ),
30278
- children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "w-4 h-4" })
30279
- }
30251
+ variant: "ghost",
30252
+ className: cn(
30253
+ "p-2 rounded-lg transition-all duration-200",
30254
+ "hover:bg-muted hover:shadow-sm",
30255
+ "text-muted-foreground hover:text-foreground",
30256
+ "active:scale-95"
30280
30257
  ),
30281
- items: filteredActions,
30282
- position: "bottom-right"
30258
+ children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "w-4 h-4" })
30283
30259
  }
30284
- ) : null;
30285
- })(),
30286
- hasExplicitClick && /* @__PURE__ */ jsx(ChevronRight, { className: "w-4 h-4 text-muted-foreground/50 group-hover:text-muted-foreground group-hover:translate-x-0.5 transition-all" })
30287
- ]
30288
- }
30289
- )
30260
+ ),
30261
+ items: filteredActions,
30262
+ position: "bottom-right"
30263
+ }
30264
+ ) : null;
30265
+ })(),
30266
+ hasExplicitClick && /* @__PURE__ */ jsx(ChevronRight, { className: "w-4 h-4 text-muted-foreground/50 group-hover:text-muted-foreground group-hover:translate-x-0.5 transition-all" })
30267
+ ] })
30290
30268
  ]
30291
30269
  }
30292
30270
  ),
@@ -38140,7 +38118,7 @@ function useTraitStateMachine(traitBindings, slotsActions, options) {
38140
38118
  effects: result.effects,
38141
38119
  traitDefinition: binding.trait
38142
38120
  };
38143
- const handlers = createClientEffectHandlers({
38121
+ const clientHandlers = createClientEffectHandlers({
38144
38122
  eventBus,
38145
38123
  slotSetter: {
38146
38124
  addPattern: (slot, pattern, props) => {
@@ -38155,6 +38133,43 @@ function useTraitStateMachine(traitBindings, slotsActions, options) {
38155
38133
  navigate: optionsRef.current?.navigate,
38156
38134
  notify: optionsRef.current?.notify
38157
38135
  });
38136
+ const persistence = optionsRef.current?.persistence;
38137
+ let handlers = clientHandlers;
38138
+ if (persistence) {
38139
+ const sharedBindings = {
38140
+ entity: payload ?? {},
38141
+ payload: payload || {},
38142
+ state: result.previousState
38143
+ };
38144
+ if (binding.config) {
38145
+ sharedBindings.config = binding.config;
38146
+ }
38147
+ const serverHandlers = createServerEffectHandlers({
38148
+ persistence,
38149
+ eventBus,
38150
+ entityType: linkedEntity,
38151
+ entityId,
38152
+ bindings: sharedBindings,
38153
+ context: {
38154
+ traitName: binding.trait.name,
38155
+ state: result.previousState,
38156
+ transition: `${result.previousState}->${result.newState}`,
38157
+ linkedEntity,
38158
+ entityId
38159
+ },
38160
+ source: { trait: binding.trait.name },
38161
+ callService: optionsRef.current?.callService
38162
+ });
38163
+ handlers = {
38164
+ ...serverHandlers,
38165
+ // Client handlers own UI + emit: keep the slot setter
38166
+ // and pre-prefixed UI:* emit path intact.
38167
+ emit: clientHandlers.emit,
38168
+ renderUI: clientHandlers.renderUI,
38169
+ navigate: clientHandlers.navigate,
38170
+ notify: clientHandlers.notify
38171
+ };
38172
+ }
38158
38173
  const entityFromPayload = payload ?? {};
38159
38174
  const bindingCtx = {
38160
38175
  entity: entityFromPayload,
@@ -38776,7 +38791,7 @@ function applyServerEffects(effects, uiSlots, onNavigate) {
38776
38791
  }
38777
38792
  }
38778
38793
  }
38779
- function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFallback }) {
38794
+ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFallback, persistence }) {
38780
38795
  const slotsActions = useSlotsActions();
38781
38796
  const bridge = useServerBridge();
38782
38797
  const uiSlots = useUISlots();
@@ -38788,7 +38803,7 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFa
38788
38803
  applyServerEffects(effects, uiSlots, onNavigate);
38789
38804
  }
38790
38805
  }, [bridge.connected, bridge.sendEvent, orbitalNames, uiSlots, onNavigate]);
38791
- const opts = orbitalNames ? { onEventProcessed, navigate: onNavigate } : { navigate: onNavigate };
38806
+ const opts = orbitalNames ? { onEventProcessed, navigate: onNavigate } : { navigate: onNavigate, persistence };
38792
38807
  const { sendEvent } = useTraitStateMachine(traits2, slotsActions, opts);
38793
38808
  const initSentRef = useRef(false);
38794
38809
  useEffect(() => {
@@ -38833,7 +38848,7 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFa
38833
38848
  }, [bridge.connected, orbitalNames, bridge.sendEvent, uiSlots, onNavigate]);
38834
38849
  return null;
38835
38850
  }
38836
- function SchemaRunner({ schema, serverUrl, mockData, pageName, onNavigate, onLocalFallback }) {
38851
+ function SchemaRunner({ schema, serverUrl, mockData, pageName, onNavigate, onLocalFallback, persistence }) {
38837
38852
  const { traits: traits2, allEntities, ir } = useResolvedSchema(schema, pageName);
38838
38853
  const allPageTraits = useMemo(() => {
38839
38854
  if (pageName && traits2.length > 0) return traits2;
@@ -38866,7 +38881,8 @@ function SchemaRunner({ schema, serverUrl, mockData, pageName, onNavigate, onLoc
38866
38881
  traits: allPageTraits,
38867
38882
  orbitalNames: serverUrl ? orbitalNames : void 0,
38868
38883
  onNavigate,
38869
- onLocalFallback
38884
+ onLocalFallback,
38885
+ persistence
38870
38886
  }
38871
38887
  ),
38872
38888
  /* @__PURE__ */ jsx(SlotBridge, {}),
@@ -38914,6 +38930,13 @@ function OrbPreview({
38914
38930
  }, [schema, autoMock, serverUrl, mockData]);
38915
38931
  const parsedSchema = parseResult.ok ? parseResult.schema : null;
38916
38932
  const effectiveMockData = parseResult.ok ? parseResult.mockData : {};
38933
+ const persistence = useMemo(() => {
38934
+ if (!parsedSchema || serverUrl) return void 0;
38935
+ if (!autoMock) return void 0;
38936
+ const adapter = new InMemoryPersistence();
38937
+ adapter.seed(effectiveMockData);
38938
+ return adapter;
38939
+ }, [parsedSchema, serverUrl, autoMock, effectiveMockData]);
38917
38940
  const pages = useMemo(() => {
38918
38941
  if (!parsedSchema) return [];
38919
38942
  try {
@@ -38968,7 +38991,8 @@ function OrbPreview({
38968
38991
  mockData: effectiveMockData,
38969
38992
  pageName: currentPage,
38970
38993
  onNavigate: handleNavigate,
38971
- onLocalFallback: handleLocalFallback
38994
+ onLocalFallback: handleLocalFallback,
38995
+ persistence
38972
38996
  }
38973
38997
  ) }) })
38974
38998
  ]
@@ -38,6 +38,17 @@ export interface UseTraitStateMachineOptions {
38
38
  navigate?: (path: string, params?: Record<string, unknown>) => void;
39
39
  /** Notification function for notify effects */
40
40
  notify?: (message: string, type?: 'success' | 'error' | 'warning' | 'info') => void;
41
+ /**
42
+ * Offline-preview persistence layer. When set, the client runtime merges
43
+ * `@almadar/runtime` `createServerEffectHandlers` on top of the default
44
+ * client handlers so `fetch` / `persist` / `set` / `ref` / `deref` /
45
+ * `swap!` / `atomic` / `callService` run against this adapter — matching
46
+ * what `OrbitalServerRuntime` would do on the server. Left unset in the
47
+ * server-bridge path; effects there flow to the real server.
48
+ */
49
+ persistence?: import('@almadar/runtime').PersistenceAdapter;
50
+ /** Optional consumer `call-service` hook forwarded to the mock server handlers. */
51
+ callService?: (service: string, action: string, params: unknown) => Promise<unknown>;
41
52
  }
42
53
  /**
43
54
  * useTraitStateMachine - Manages state machines for multiple traits
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "3.8.2",
3
+ "version": "3.9.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": ">=5.7.0",
122
122
  "@almadar/evaluator": ">=2.9.2",
123
123
  "@almadar/patterns": ">=2.17.1",
124
- "@almadar/runtime": ">=4.3.0",
124
+ "@almadar/runtime": "^4.11.1",
125
125
  "@almadar/std": ">=6.4.1",
126
126
  "@almadar/syntax": ">=1.3.1",
127
127
  "@xyflow/react": "12.10.1",