@almadar/ui 2.50.0 → 2.51.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.
@@ -10014,7 +10014,9 @@ var CodeBlock = React114__namespace.default.memo(
10014
10014
  showLanguageBadge = true,
10015
10015
  maxHeight = "60vh",
10016
10016
  foldable: foldableProp,
10017
- className
10017
+ className,
10018
+ editable = false,
10019
+ onChange
10018
10020
  }) => {
10019
10021
  const code = typeof rawCode === "string" ? rawCode : String(rawCode ?? "");
10020
10022
  const isOrb = language === "orb";
@@ -10185,7 +10187,37 @@ var CodeBlock = React114__namespace.default.memo(
10185
10187
  ]
10186
10188
  }
10187
10189
  ),
10188
- /* @__PURE__ */ jsxRuntime.jsx(
10190
+ editable ? (
10191
+ /* GAP-51: editable mode — composes the Textarea atom. Plain text editing,
10192
+ no Prism highlighting overlay (follow-up). The textarea is uncontrolled
10193
+ on the value side: we pass `code` as the initial value and forward
10194
+ every keystroke via onChange — the consumer is responsible for
10195
+ debouncing and re-deriving `code` only after the user stops typing
10196
+ so the cursor doesn't fight a re-render. */
10197
+ /* @__PURE__ */ jsxRuntime.jsx(
10198
+ Textarea,
10199
+ {
10200
+ defaultValue: code,
10201
+ onChange: (e) => onChange?.(e.target.value),
10202
+ spellCheck: false,
10203
+ style: {
10204
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
10205
+ fontSize: "13px",
10206
+ lineHeight: "1.5",
10207
+ backgroundColor: "#1e1e1e",
10208
+ color: "#e6e6e6",
10209
+ borderRadius: hasHeader ? "0 0 0.5rem 0.5rem" : "0.5rem",
10210
+ border: "none",
10211
+ padding: "1rem",
10212
+ resize: "none",
10213
+ minHeight: "160px",
10214
+ maxHeight,
10215
+ width: "100%",
10216
+ outline: "none"
10217
+ }
10218
+ }
10219
+ )
10220
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
10189
10221
  "div",
10190
10222
  {
10191
10223
  ref: scrollRef,
@@ -10205,7 +10237,7 @@ var CodeBlock = React114__namespace.default.memo(
10205
10237
  )
10206
10238
  ] });
10207
10239
  },
10208
- (prev, next) => prev.language === next.language && prev.code === next.code && prev.showCopyButton === next.showCopyButton && prev.maxHeight === next.maxHeight && prev.foldable === next.foldable
10240
+ (prev, next) => prev.language === next.language && prev.code === next.code && prev.showCopyButton === next.showCopyButton && prev.maxHeight === next.maxHeight && prev.foldable === next.foldable && prev.editable === next.editable && prev.onChange === next.onChange
10209
10241
  );
10210
10242
  CodeBlock.displayName = "CodeBlock";
10211
10243
 
@@ -32022,7 +32054,7 @@ var Timeline = ({
32022
32054
  title && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h5", weight: "semibold", children: title }),
32023
32055
  /* @__PURE__ */ jsxRuntime.jsx(VStack, { gap: "none", className: "relative", children: items.map((item, idx) => {
32024
32056
  const status = item.status || "pending";
32025
- const style = STATUS_STYLES3[status];
32057
+ const style = STATUS_STYLES3[status] || STATUS_STYLES3.pending;
32026
32058
  const ItemIcon = item.icon || style.icon;
32027
32059
  const isLast = idx === items.length - 1;
32028
32060
  return /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "md", align: "start", className: "relative", children: [
@@ -33657,6 +33689,9 @@ function UISlotRenderer({
33657
33689
  }
33658
33690
  UISlotRenderer.displayName = "UISlotRenderer";
33659
33691
 
33692
+ // runtime/OrbPreview.tsx
33693
+ init_useEventBus();
33694
+
33660
33695
  // runtime/ServerBridge.tsx
33661
33696
  init_useEventBus();
33662
33697
  var ServerBridgeContext = React114.createContext(null);
@@ -33889,7 +33924,7 @@ function SlotBridge() {
33889
33924
  }, [slots, render, clear]);
33890
33925
  return null;
33891
33926
  }
33892
- function TraitInitializer({ traits: traits2, orbitalNames, onNavigate }) {
33927
+ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFallback }) {
33893
33928
  const slotsActions = useSlotsActions();
33894
33929
  const bridge = useServerBridge();
33895
33930
  const entityStore = providers.useEntityStore();
@@ -33930,10 +33965,11 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate }) {
33930
33965
  const fallback = setTimeout(() => {
33931
33966
  if (!initSentRef.current) {
33932
33967
  sendEvent("INIT");
33968
+ onLocalFallback?.();
33933
33969
  }
33934
33970
  }, 5e3);
33935
33971
  return () => clearTimeout(fallback);
33936
- }, [traits2, orbitalNames, sendEvent]);
33972
+ }, [traits2, orbitalNames, sendEvent, onLocalFallback]);
33937
33973
  React114.useEffect(() => {
33938
33974
  if (!bridge.connected || !orbitalNames?.length || initSentRef.current) return;
33939
33975
  initSentRef.current = true;
@@ -33979,7 +34015,7 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate }) {
33979
34015
  }, [bridge.connected, orbitalNames, bridge.sendEvent, slotsActions]);
33980
34016
  return null;
33981
34017
  }
33982
- function SchemaRunner({ schema, serverUrl, mockData, pageName, onNavigate }) {
34018
+ function SchemaRunner({ schema, serverUrl, mockData, pageName, onNavigate, onLocalFallback }) {
33983
34019
  const { traits: traits2, allEntities, ir } = useResolvedSchema(schema, pageName);
33984
34020
  const allPageTraits = React114.useMemo(() => {
33985
34021
  if (pageName && traits2.length > 0) return traits2;
@@ -34019,7 +34055,15 @@ function SchemaRunner({ schema, serverUrl, mockData, pageName, onNavigate }) {
34019
34055
  }
34020
34056
  }, [mockKey, serverUrl, mockData, entityStore]);
34021
34057
  const inner = /* @__PURE__ */ jsxRuntime.jsx(providers.VerificationProvider, { enabled: true, children: /* @__PURE__ */ jsxRuntime.jsx(SlotsProvider, { children: /* @__PURE__ */ jsxRuntime.jsxs(EntitySchemaProvider, { entities: Array.from(allEntities.values()), children: [
34022
- /* @__PURE__ */ jsxRuntime.jsx(TraitInitializer, { traits: allPageTraits, orbitalNames: serverUrl ? orbitalNames : void 0, onNavigate }),
34058
+ /* @__PURE__ */ jsxRuntime.jsx(
34059
+ TraitInitializer,
34060
+ {
34061
+ traits: allPageTraits,
34062
+ orbitalNames: serverUrl ? orbitalNames : void 0,
34063
+ onNavigate,
34064
+ onLocalFallback
34065
+ }
34066
+ ),
34023
34067
  /* @__PURE__ */ jsxRuntime.jsx(SlotBridge, {}),
34024
34068
  /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "min-h-full p-4", children: /* @__PURE__ */ jsxRuntime.jsx(UISlotRenderer, { includeHud: true, hudMode: "inline", includeFloating: true }) })
34025
34069
  ] }) }) });
@@ -34036,6 +34080,16 @@ function OrbPreview({
34036
34080
  className,
34037
34081
  serverUrl
34038
34082
  }) {
34083
+ const [localFallback, setLocalFallback] = React114.useState(false);
34084
+ const eventBus = useEventBus();
34085
+ const handleLocalFallback = React114.useCallback(() => {
34086
+ if (localFallback) return;
34087
+ setLocalFallback(true);
34088
+ eventBus.emit("UI:NOTIFY", {
34089
+ message: "Preview server unreachable \u2014 running locally without server-side state.",
34090
+ severity: "warning"
34091
+ });
34092
+ }, [localFallback, eventBus]);
34039
34093
  const parseResult = React114.useMemo(() => {
34040
34094
  let parsed;
34041
34095
  if (typeof schema === "string") {
@@ -34093,13 +34147,26 @@ function OrbPreview({
34093
34147
  el.addEventListener("click", handler, true);
34094
34148
  return () => el.removeEventListener("click", handler, true);
34095
34149
  }, [pages, handleNavigate]);
34096
- return /* @__PURE__ */ jsxRuntime.jsx(
34150
+ return /* @__PURE__ */ jsxRuntime.jsxs(
34097
34151
  Box,
34098
34152
  {
34099
34153
  ref: containerRef,
34100
34154
  className: `overflow-auto border border-[var(--color-border)] rounded-[var(--radius-md)] ${className ?? ""}`,
34101
34155
  style: { height },
34102
- children: /* @__PURE__ */ jsxRuntime.jsx(providers.OrbitalProvider, { initialData: effectiveMockData, skipTheme: true, verification: true, children: /* @__PURE__ */ jsxRuntime.jsx(context.UISlotProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(SchemaRunner, { schema: parsedSchema, serverUrl, mockData: effectiveMockData, pageName: currentPage, onNavigate: handleNavigate }) }) })
34156
+ children: [
34157
+ localFallback && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "px-3 py-2 bg-[var(--color-warning)] bg-opacity-10 border-b border-[var(--color-warning)] flex items-center gap-2", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-[var(--color-warning-foreground)] flex-1", children: "Preview server unreachable \u2014 running locally. Server-side state and persistence are disabled." }) }),
34158
+ /* @__PURE__ */ jsxRuntime.jsx(providers.OrbitalProvider, { initialData: effectiveMockData, skipTheme: true, verification: true, children: /* @__PURE__ */ jsxRuntime.jsx(context.UISlotProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(
34159
+ SchemaRunner,
34160
+ {
34161
+ schema: parsedSchema,
34162
+ serverUrl,
34163
+ mockData: effectiveMockData,
34164
+ pageName: currentPage,
34165
+ onNavigate: handleNavigate,
34166
+ onLocalFallback: handleLocalFallback
34167
+ }
34168
+ ) }) })
34169
+ ]
34103
34170
  }
34104
34171
  );
34105
34172
  }
@@ -9969,7 +9969,9 @@ var CodeBlock = React114__default.memo(
9969
9969
  showLanguageBadge = true,
9970
9970
  maxHeight = "60vh",
9971
9971
  foldable: foldableProp,
9972
- className
9972
+ className,
9973
+ editable = false,
9974
+ onChange
9973
9975
  }) => {
9974
9976
  const code = typeof rawCode === "string" ? rawCode : String(rawCode ?? "");
9975
9977
  const isOrb = language === "orb";
@@ -10140,7 +10142,37 @@ var CodeBlock = React114__default.memo(
10140
10142
  ]
10141
10143
  }
10142
10144
  ),
10143
- /* @__PURE__ */ jsx(
10145
+ editable ? (
10146
+ /* GAP-51: editable mode — composes the Textarea atom. Plain text editing,
10147
+ no Prism highlighting overlay (follow-up). The textarea is uncontrolled
10148
+ on the value side: we pass `code` as the initial value and forward
10149
+ every keystroke via onChange — the consumer is responsible for
10150
+ debouncing and re-deriving `code` only after the user stops typing
10151
+ so the cursor doesn't fight a re-render. */
10152
+ /* @__PURE__ */ jsx(
10153
+ Textarea,
10154
+ {
10155
+ defaultValue: code,
10156
+ onChange: (e) => onChange?.(e.target.value),
10157
+ spellCheck: false,
10158
+ style: {
10159
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
10160
+ fontSize: "13px",
10161
+ lineHeight: "1.5",
10162
+ backgroundColor: "#1e1e1e",
10163
+ color: "#e6e6e6",
10164
+ borderRadius: hasHeader ? "0 0 0.5rem 0.5rem" : "0.5rem",
10165
+ border: "none",
10166
+ padding: "1rem",
10167
+ resize: "none",
10168
+ minHeight: "160px",
10169
+ maxHeight,
10170
+ width: "100%",
10171
+ outline: "none"
10172
+ }
10173
+ }
10174
+ )
10175
+ ) : /* @__PURE__ */ jsx(
10144
10176
  "div",
10145
10177
  {
10146
10178
  ref: scrollRef,
@@ -10160,7 +10192,7 @@ var CodeBlock = React114__default.memo(
10160
10192
  )
10161
10193
  ] });
10162
10194
  },
10163
- (prev, next) => prev.language === next.language && prev.code === next.code && prev.showCopyButton === next.showCopyButton && prev.maxHeight === next.maxHeight && prev.foldable === next.foldable
10195
+ (prev, next) => prev.language === next.language && prev.code === next.code && prev.showCopyButton === next.showCopyButton && prev.maxHeight === next.maxHeight && prev.foldable === next.foldable && prev.editable === next.editable && prev.onChange === next.onChange
10164
10196
  );
10165
10197
  CodeBlock.displayName = "CodeBlock";
10166
10198
 
@@ -31977,7 +32009,7 @@ var Timeline = ({
31977
32009
  title && /* @__PURE__ */ jsx(Typography, { variant: "h5", weight: "semibold", children: title }),
31978
32010
  /* @__PURE__ */ jsx(VStack, { gap: "none", className: "relative", children: items.map((item, idx) => {
31979
32011
  const status = item.status || "pending";
31980
- const style = STATUS_STYLES3[status];
32012
+ const style = STATUS_STYLES3[status] || STATUS_STYLES3.pending;
31981
32013
  const ItemIcon = item.icon || style.icon;
31982
32014
  const isLast = idx === items.length - 1;
31983
32015
  return /* @__PURE__ */ jsxs(HStack, { gap: "md", align: "start", className: "relative", children: [
@@ -33612,6 +33644,9 @@ function UISlotRenderer({
33612
33644
  }
33613
33645
  UISlotRenderer.displayName = "UISlotRenderer";
33614
33646
 
33647
+ // runtime/OrbPreview.tsx
33648
+ init_useEventBus();
33649
+
33615
33650
  // runtime/ServerBridge.tsx
33616
33651
  init_useEventBus();
33617
33652
  var ServerBridgeContext = createContext(null);
@@ -33844,7 +33879,7 @@ function SlotBridge() {
33844
33879
  }, [slots, render, clear]);
33845
33880
  return null;
33846
33881
  }
33847
- function TraitInitializer({ traits: traits2, orbitalNames, onNavigate }) {
33882
+ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFallback }) {
33848
33883
  const slotsActions = useSlotsActions();
33849
33884
  const bridge = useServerBridge();
33850
33885
  const entityStore = useEntityStore();
@@ -33885,10 +33920,11 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate }) {
33885
33920
  const fallback = setTimeout(() => {
33886
33921
  if (!initSentRef.current) {
33887
33922
  sendEvent("INIT");
33923
+ onLocalFallback?.();
33888
33924
  }
33889
33925
  }, 5e3);
33890
33926
  return () => clearTimeout(fallback);
33891
- }, [traits2, orbitalNames, sendEvent]);
33927
+ }, [traits2, orbitalNames, sendEvent, onLocalFallback]);
33892
33928
  useEffect(() => {
33893
33929
  if (!bridge.connected || !orbitalNames?.length || initSentRef.current) return;
33894
33930
  initSentRef.current = true;
@@ -33934,7 +33970,7 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate }) {
33934
33970
  }, [bridge.connected, orbitalNames, bridge.sendEvent, slotsActions]);
33935
33971
  return null;
33936
33972
  }
33937
- function SchemaRunner({ schema, serverUrl, mockData, pageName, onNavigate }) {
33973
+ function SchemaRunner({ schema, serverUrl, mockData, pageName, onNavigate, onLocalFallback }) {
33938
33974
  const { traits: traits2, allEntities, ir } = useResolvedSchema(schema, pageName);
33939
33975
  const allPageTraits = useMemo(() => {
33940
33976
  if (pageName && traits2.length > 0) return traits2;
@@ -33974,7 +34010,15 @@ function SchemaRunner({ schema, serverUrl, mockData, pageName, onNavigate }) {
33974
34010
  }
33975
34011
  }, [mockKey, serverUrl, mockData, entityStore]);
33976
34012
  const inner = /* @__PURE__ */ jsx(VerificationProvider, { enabled: true, children: /* @__PURE__ */ jsx(SlotsProvider, { children: /* @__PURE__ */ jsxs(EntitySchemaProvider, { entities: Array.from(allEntities.values()), children: [
33977
- /* @__PURE__ */ jsx(TraitInitializer, { traits: allPageTraits, orbitalNames: serverUrl ? orbitalNames : void 0, onNavigate }),
34013
+ /* @__PURE__ */ jsx(
34014
+ TraitInitializer,
34015
+ {
34016
+ traits: allPageTraits,
34017
+ orbitalNames: serverUrl ? orbitalNames : void 0,
34018
+ onNavigate,
34019
+ onLocalFallback
34020
+ }
34021
+ ),
33978
34022
  /* @__PURE__ */ jsx(SlotBridge, {}),
33979
34023
  /* @__PURE__ */ jsx(Box, { className: "min-h-full p-4", children: /* @__PURE__ */ jsx(UISlotRenderer, { includeHud: true, hudMode: "inline", includeFloating: true }) })
33980
34024
  ] }) }) });
@@ -33991,6 +34035,16 @@ function OrbPreview({
33991
34035
  className,
33992
34036
  serverUrl
33993
34037
  }) {
34038
+ const [localFallback, setLocalFallback] = useState(false);
34039
+ const eventBus = useEventBus();
34040
+ const handleLocalFallback = useCallback(() => {
34041
+ if (localFallback) return;
34042
+ setLocalFallback(true);
34043
+ eventBus.emit("UI:NOTIFY", {
34044
+ message: "Preview server unreachable \u2014 running locally without server-side state.",
34045
+ severity: "warning"
34046
+ });
34047
+ }, [localFallback, eventBus]);
33994
34048
  const parseResult = useMemo(() => {
33995
34049
  let parsed;
33996
34050
  if (typeof schema === "string") {
@@ -34048,13 +34102,26 @@ function OrbPreview({
34048
34102
  el.addEventListener("click", handler, true);
34049
34103
  return () => el.removeEventListener("click", handler, true);
34050
34104
  }, [pages, handleNavigate]);
34051
- return /* @__PURE__ */ jsx(
34105
+ return /* @__PURE__ */ jsxs(
34052
34106
  Box,
34053
34107
  {
34054
34108
  ref: containerRef,
34055
34109
  className: `overflow-auto border border-[var(--color-border)] rounded-[var(--radius-md)] ${className ?? ""}`,
34056
34110
  style: { height },
34057
- children: /* @__PURE__ */ jsx(OrbitalProvider, { initialData: effectiveMockData, skipTheme: true, verification: true, children: /* @__PURE__ */ jsx(UISlotProvider, { children: /* @__PURE__ */ jsx(SchemaRunner, { schema: parsedSchema, serverUrl, mockData: effectiveMockData, pageName: currentPage, onNavigate: handleNavigate }) }) })
34111
+ children: [
34112
+ localFallback && /* @__PURE__ */ jsx(Box, { className: "px-3 py-2 bg-[var(--color-warning)] bg-opacity-10 border-b border-[var(--color-warning)] flex items-center gap-2", children: /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-[var(--color-warning-foreground)] flex-1", children: "Preview server unreachable \u2014 running locally. Server-side state and persistence are disabled." }) }),
34113
+ /* @__PURE__ */ jsx(OrbitalProvider, { initialData: effectiveMockData, skipTheme: true, verification: true, children: /* @__PURE__ */ jsx(UISlotProvider, { children: /* @__PURE__ */ jsx(
34114
+ SchemaRunner,
34115
+ {
34116
+ schema: parsedSchema,
34117
+ serverUrl,
34118
+ mockData: effectiveMockData,
34119
+ pageName: currentPage,
34120
+ onNavigate: handleNavigate,
34121
+ onLocalFallback: handleLocalFallback
34122
+ }
34123
+ ) }) })
34124
+ ]
34058
34125
  }
34059
34126
  );
34060
34127
  }
package/index.css CHANGED
@@ -135,6 +135,46 @@ body {
135
135
  background: var(--color-foreground);
136
136
  }
137
137
 
138
+ /**
139
+ * ReactFlow (@xyflow/react) Controls — theme overrides for FlowCanvas zoom buttons
140
+ *
141
+ * Without these, the bottom-left zoom in/out/fit buttons render as plain white
142
+ * squares from ReactFlow's default stylesheet, ignoring the active theme.
143
+ * Targets: .react-flow__controls + .react-flow__controls-button
144
+ */
145
+ .react-flow__controls {
146
+ background: var(--color-card) !important;
147
+ border: 1px solid var(--color-border) !important;
148
+ border-radius: var(--radius-md) !important;
149
+ box-shadow: var(--shadow-main) !important;
150
+ overflow: hidden;
151
+ }
152
+
153
+ .react-flow__controls-button {
154
+ background: transparent !important;
155
+ border: none !important;
156
+ border-bottom: 1px solid var(--color-border) !important;
157
+ color: var(--color-foreground) !important;
158
+ fill: var(--color-foreground) !important;
159
+ width: 28px;
160
+ height: 28px;
161
+ transition: background var(--transition-fast) var(--transition-timing);
162
+ }
163
+
164
+ .react-flow__controls-button:last-child {
165
+ border-bottom: none !important;
166
+ }
167
+
168
+ .react-flow__controls-button:hover {
169
+ background: var(--color-muted) !important;
170
+ }
171
+
172
+ .react-flow__controls-button svg {
173
+ fill: var(--color-foreground) !important;
174
+ width: 14px;
175
+ height: 14px;
176
+ }
177
+
138
178
  @layer utilities {
139
179
  .shadow-theme { box-shadow: var(--shadow-main); }
140
180
  .shadow-theme-sm { box-shadow: var(--shadow-sm); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "2.50.0",
3
+ "version": "2.51.0",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "main": "./dist/components/index.js",
@@ -122,7 +122,7 @@
122
122
  "@almadar/evaluator": ">=2.5.4",
123
123
  "@almadar/patterns": ">=2.10.0",
124
124
  "@almadar/std": ">=5.2.0",
125
- "@almadar/syntax": ">=1.2.0",
125
+ "@almadar/syntax": ">=1.2.1",
126
126
  "@almadar/runtime": ">=2.0.0",
127
127
  "@xyflow/react": "12.10.1",
128
128
  "clsx": "^2.1.0",