@almadar/ui 2.50.1 → 2.52.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.
@@ -19388,13 +19388,13 @@ var MapViewImpl = React125.lazy(async () => {
19388
19388
  shadowSize: [41, 41]
19389
19389
  });
19390
19390
  L.Marker.prototype.options.icon = defaultIcon;
19391
- const { useEffect: useEffect87, useRef: useRef87, useCallback: useCallback124, useState: useState124 } = React125__namespace.default;
19391
+ const { useEffect: useEffect88, useRef: useRef88, useCallback: useCallback124, useState: useState124 } = React125__namespace.default;
19392
19392
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
19393
19393
  const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
19394
19394
  function MapUpdater({ centerLat, centerLng, zoom }) {
19395
19395
  const map = useMap();
19396
- const prevRef = useRef87({ centerLat, centerLng, zoom });
19397
- useEffect87(() => {
19396
+ const prevRef = useRef88({ centerLat, centerLng, zoom });
19397
+ useEffect88(() => {
19398
19398
  const prev = prevRef.current;
19399
19399
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
19400
19400
  map.setView([centerLat, centerLng], zoom);
@@ -19405,7 +19405,7 @@ var MapViewImpl = React125.lazy(async () => {
19405
19405
  }
19406
19406
  function MapClickHandler({ onMapClick }) {
19407
19407
  const map = useMap();
19408
- useEffect87(() => {
19408
+ useEffect88(() => {
19409
19409
  if (!onMapClick) return;
19410
19410
  const handler = (e) => {
19411
19411
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -22118,7 +22118,9 @@ var CodeBlock = React125__namespace.default.memo(
22118
22118
  showLanguageBadge = true,
22119
22119
  maxHeight = "60vh",
22120
22120
  foldable: foldableProp,
22121
- className
22121
+ className,
22122
+ editable = false,
22123
+ onChange
22122
22124
  }) => {
22123
22125
  const code = typeof rawCode === "string" ? rawCode : String(rawCode ?? "");
22124
22126
  const isOrb = language === "orb";
@@ -22289,7 +22291,37 @@ var CodeBlock = React125__namespace.default.memo(
22289
22291
  ]
22290
22292
  }
22291
22293
  ),
22292
- /* @__PURE__ */ jsxRuntime.jsx(
22294
+ editable ? (
22295
+ /* GAP-51: editable mode — composes the Textarea atom. Plain text editing,
22296
+ no Prism highlighting overlay (follow-up). The textarea is uncontrolled
22297
+ on the value side: we pass `code` as the initial value and forward
22298
+ every keystroke via onChange — the consumer is responsible for
22299
+ debouncing and re-deriving `code` only after the user stops typing
22300
+ so the cursor doesn't fight a re-render. */
22301
+ /* @__PURE__ */ jsxRuntime.jsx(
22302
+ Textarea,
22303
+ {
22304
+ defaultValue: code,
22305
+ onChange: (e) => onChange?.(e.target.value),
22306
+ spellCheck: false,
22307
+ style: {
22308
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
22309
+ fontSize: "13px",
22310
+ lineHeight: "1.5",
22311
+ backgroundColor: "#1e1e1e",
22312
+ color: "#e6e6e6",
22313
+ borderRadius: hasHeader ? "0 0 0.5rem 0.5rem" : "0.5rem",
22314
+ border: "none",
22315
+ padding: "1rem",
22316
+ resize: "none",
22317
+ minHeight: "160px",
22318
+ maxHeight,
22319
+ width: "100%",
22320
+ outline: "none"
22321
+ }
22322
+ }
22323
+ )
22324
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
22293
22325
  "div",
22294
22326
  {
22295
22327
  ref: scrollRef,
@@ -22309,7 +22341,7 @@ var CodeBlock = React125__namespace.default.memo(
22309
22341
  )
22310
22342
  ] });
22311
22343
  },
22312
- (prev, next) => prev.language === next.language && prev.code === next.code && prev.showCopyButton === next.showCopyButton && prev.maxHeight === next.maxHeight && prev.foldable === next.foldable
22344
+ (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
22313
22345
  );
22314
22346
  CodeBlock.displayName = "CodeBlock";
22315
22347
 
@@ -46481,6 +46513,9 @@ function OrbitalProvider({
46481
46513
  );
46482
46514
  }
46483
46515
  OrbitalProvider.displayName = "OrbitalProvider";
46516
+
46517
+ // runtime/OrbPreview.tsx
46518
+ init_useEventBus();
46484
46519
  function useResolvedSchema(schema, pageName) {
46485
46520
  const [loading, setLoading] = React125.useState(true);
46486
46521
  const [error, setError] = React125.useState(null);
@@ -47250,7 +47285,7 @@ function SlotBridge() {
47250
47285
  }, [slots, render, clear]);
47251
47286
  return null;
47252
47287
  }
47253
- function TraitInitializer({ traits: traits2, orbitalNames, onNavigate }) {
47288
+ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFallback }) {
47254
47289
  const slotsActions = useSlotsActions();
47255
47290
  const bridge = useServerBridge();
47256
47291
  const entityStore = useEntityStore();
@@ -47291,10 +47326,11 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate }) {
47291
47326
  const fallback = setTimeout(() => {
47292
47327
  if (!initSentRef.current) {
47293
47328
  sendEvent("INIT");
47329
+ onLocalFallback?.();
47294
47330
  }
47295
47331
  }, 5e3);
47296
47332
  return () => clearTimeout(fallback);
47297
- }, [traits2, orbitalNames, sendEvent]);
47333
+ }, [traits2, orbitalNames, sendEvent, onLocalFallback]);
47298
47334
  React125.useEffect(() => {
47299
47335
  if (!bridge.connected || !orbitalNames?.length || initSentRef.current) return;
47300
47336
  initSentRef.current = true;
@@ -47340,7 +47376,7 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate }) {
47340
47376
  }, [bridge.connected, orbitalNames, bridge.sendEvent, slotsActions]);
47341
47377
  return null;
47342
47378
  }
47343
- function SchemaRunner({ schema, serverUrl, mockData, pageName, onNavigate }) {
47379
+ function SchemaRunner({ schema, serverUrl, mockData, pageName, onNavigate, onLocalFallback }) {
47344
47380
  const { traits: traits2, allEntities, ir } = useResolvedSchema(schema, pageName);
47345
47381
  const allPageTraits = React125.useMemo(() => {
47346
47382
  if (pageName && traits2.length > 0) return traits2;
@@ -47380,7 +47416,15 @@ function SchemaRunner({ schema, serverUrl, mockData, pageName, onNavigate }) {
47380
47416
  }
47381
47417
  }, [mockKey, serverUrl, mockData, entityStore]);
47382
47418
  const inner = /* @__PURE__ */ jsxRuntime.jsx(VerificationProvider, { enabled: true, children: /* @__PURE__ */ jsxRuntime.jsx(SlotsProvider, { children: /* @__PURE__ */ jsxRuntime.jsxs(EntitySchemaProvider, { entities: Array.from(allEntities.values()), children: [
47383
- /* @__PURE__ */ jsxRuntime.jsx(TraitInitializer, { traits: allPageTraits, orbitalNames: serverUrl ? orbitalNames : void 0, onNavigate }),
47419
+ /* @__PURE__ */ jsxRuntime.jsx(
47420
+ TraitInitializer,
47421
+ {
47422
+ traits: allPageTraits,
47423
+ orbitalNames: serverUrl ? orbitalNames : void 0,
47424
+ onNavigate,
47425
+ onLocalFallback
47426
+ }
47427
+ ),
47384
47428
  /* @__PURE__ */ jsxRuntime.jsx(SlotBridge, {}),
47385
47429
  /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "min-h-full p-4", children: /* @__PURE__ */ jsxRuntime.jsx(UISlotRenderer, { includeHud: true, hudMode: "inline", includeFloating: true }) })
47386
47430
  ] }) }) });
@@ -47397,6 +47441,16 @@ function OrbPreview({
47397
47441
  className,
47398
47442
  serverUrl
47399
47443
  }) {
47444
+ const [localFallback, setLocalFallback] = React125.useState(false);
47445
+ const eventBus = useEventBus();
47446
+ const handleLocalFallback = React125.useCallback(() => {
47447
+ if (localFallback) return;
47448
+ setLocalFallback(true);
47449
+ eventBus.emit("UI:NOTIFY", {
47450
+ message: "Preview server unreachable \u2014 running locally without server-side state.",
47451
+ severity: "warning"
47452
+ });
47453
+ }, [localFallback, eventBus]);
47400
47454
  const parseResult = React125.useMemo(() => {
47401
47455
  let parsed;
47402
47456
  if (typeof schema === "string") {
@@ -47454,13 +47508,26 @@ function OrbPreview({
47454
47508
  el.addEventListener("click", handler, true);
47455
47509
  return () => el.removeEventListener("click", handler, true);
47456
47510
  }, [pages, handleNavigate]);
47457
- return /* @__PURE__ */ jsxRuntime.jsx(
47511
+ return /* @__PURE__ */ jsxRuntime.jsxs(
47458
47512
  Box,
47459
47513
  {
47460
47514
  ref: containerRef,
47461
47515
  className: `overflow-auto border border-[var(--color-border)] rounded-[var(--radius-md)] ${className ?? ""}`,
47462
47516
  style: { height },
47463
- children: /* @__PURE__ */ jsxRuntime.jsx(OrbitalProvider, { initialData: effectiveMockData, skipTheme: true, verification: true, children: /* @__PURE__ */ jsxRuntime.jsx(UISlotProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(SchemaRunner, { schema: parsedSchema, serverUrl, mockData: effectiveMockData, pageName: currentPage, onNavigate: handleNavigate }) }) })
47517
+ children: [
47518
+ 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." }) }),
47519
+ /* @__PURE__ */ jsxRuntime.jsx(OrbitalProvider, { initialData: effectiveMockData, skipTheme: true, verification: true, children: /* @__PURE__ */ jsxRuntime.jsx(UISlotProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(
47520
+ SchemaRunner,
47521
+ {
47522
+ schema: parsedSchema,
47523
+ serverUrl,
47524
+ mockData: effectiveMockData,
47525
+ pageName: currentPage,
47526
+ onNavigate: handleNavigate,
47527
+ onLocalFallback: handleLocalFallback
47528
+ }
47529
+ ) }) })
47530
+ ]
47464
47531
  }
47465
47532
  );
47466
47533
  }
@@ -47708,12 +47775,18 @@ var OrbPreviewNodeInner = (props) => {
47708
47775
  el = el.parentElement;
47709
47776
  if (!el || el === contentRef.current) break;
47710
47777
  }
47778
+ const containerNode = {
47779
+ orbitalName: data.orbitalName,
47780
+ traitName: data.traitName,
47781
+ transitionEvent: data.transitionEvent
47782
+ };
47711
47783
  const containerPath = el?.dataset?.patternPath;
47712
47784
  if (!containerPath) {
47713
- eventBus.emit("UI:PATTERN_INSERT", {
47785
+ eventBus.emit("UI:PATTERN_DROP", {
47714
47786
  parentPath: "root",
47715
47787
  patternType: payload.data.type,
47716
- index: 0
47788
+ index: 0,
47789
+ containerNode
47717
47790
  });
47718
47791
  return;
47719
47792
  }
@@ -47730,12 +47803,13 @@ var OrbPreviewNodeInner = (props) => {
47730
47803
  break;
47731
47804
  }
47732
47805
  }
47733
- eventBus.emit("UI:PATTERN_INSERT", {
47806
+ eventBus.emit("UI:PATTERN_DROP", {
47734
47807
  parentPath: containerPath,
47735
47808
  patternType: payload.data.type,
47736
- index: insertIndex
47809
+ index: insertIndex,
47810
+ containerNode
47737
47811
  });
47738
- }, [eventBus]);
47812
+ }, [eventBus, data.orbitalName, data.traitName, data.transitionEvent]);
47739
47813
  const handlePreviewDragOver = React125.useCallback((e) => {
47740
47814
  if (!e.dataTransfer.types.includes(ALMADAR_DND_MIME)) return;
47741
47815
  e.preventDefault();
@@ -48338,6 +48412,11 @@ function OrbInspector({ node, schema, editable = false, onSchemaChange, onClose
48338
48412
  ] }),
48339
48413
  /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex-1 overflow-y-auto", children: activeTab === "code" ? (
48340
48414
  /* ── Code Tab ── */
48415
+ /* GAP-51: when editable, the CodeBlock molecule renders the existing
48416
+ Textarea atom internally and forwards keystrokes via UI:CODE_CHANGE
48417
+ on the EventBus. The consumer (builder workspace) listens, debounces,
48418
+ parses via safeParseOrbitalSchema, and calls setSchema. Read-only
48419
+ consumers (editable=false) see the existing syntax-highlighted view. */
48341
48420
  /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-2", children: /* @__PURE__ */ jsxRuntime.jsx(
48342
48421
  CodeBlock,
48343
48422
  {
@@ -48345,7 +48424,9 @@ function OrbInspector({ node, schema, editable = false, onSchemaChange, onClose
48345
48424
  language: "orb",
48346
48425
  showCopyButton: true,
48347
48426
  showLanguageBadge: true,
48348
- maxHeight: "100%"
48427
+ maxHeight: "100%",
48428
+ editable,
48429
+ onChange: editable ? (code) => eventBus.emit("UI:CODE_CHANGE", { code }) : void 0
48349
48430
  }
48350
48431
  ) })
48351
48432
  ) : (
@@ -48638,6 +48719,8 @@ function FlowCanvasInner({
48638
48719
  height = 500,
48639
48720
  onNodeClick,
48640
48721
  onLevelChange,
48722
+ onOrbitalDoubleClick,
48723
+ cosmicEntryLevel = "expanded",
48641
48724
  initialOrbital,
48642
48725
  initialLevel,
48643
48726
  initialSelectedNode,
@@ -48711,11 +48794,24 @@ function FlowCanvasInner({
48711
48794
  }
48712
48795
  if (level === "overview") {
48713
48796
  const d = node.data;
48714
- setExpandedOrbital(d.orbitalName ?? node.id);
48797
+ const orbitalName = d.orbitalName ?? node.id;
48798
+ if (onOrbitalDoubleClick && (cosmicEntryLevel === "overview" || cosmicEntryLevel === "both")) {
48799
+ onOrbitalDoubleClick(orbitalName);
48800
+ return;
48801
+ }
48802
+ setExpandedOrbital(orbitalName);
48715
48803
  setLevel("expanded");
48716
- onLevelChange?.("expanded", d.orbitalName ?? node.id);
48804
+ onLevelChange?.("expanded", orbitalName);
48805
+ return;
48717
48806
  }
48718
- }, [level, onLevelChange, atBehaviorLevel, composeLevel]);
48807
+ if (level === "expanded") {
48808
+ const d = node.data;
48809
+ const orbitalName = d.orbitalName ?? node.id;
48810
+ if (orbitalName && onOrbitalDoubleClick && (cosmicEntryLevel === "expanded" || cosmicEntryLevel === "both")) {
48811
+ onOrbitalDoubleClick(orbitalName);
48812
+ }
48813
+ }
48814
+ }, [level, onLevelChange, onOrbitalDoubleClick, cosmicEntryLevel, atBehaviorLevel, composeLevel]);
48719
48815
  const handleNodeClick = React125.useCallback((_, node) => {
48720
48816
  const nodeData = node.data;
48721
48817
  if (level === "expanded") {
@@ -48810,10 +48906,10 @@ function FlowCanvasInner({
48810
48906
  return /* @__PURE__ */ jsxRuntime.jsx(ScreenSizeContext.Provider, { value: screenSize, children: /* @__PURE__ */ jsxRuntime.jsx(PatternSelectionContext.Provider, { value: patternSelectionValue, children: /* @__PURE__ */ jsxRuntime.jsxs(
48811
48907
  Box,
48812
48908
  {
48813
- className: `flex ${className ?? ""}`,
48909
+ className: `flex h-full ${className ?? ""}`,
48814
48910
  style: { width, height },
48815
48911
  children: [
48816
- /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "relative flex-1 min-w-0", children: [
48912
+ /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "relative flex-1 min-w-0 h-full", children: [
48817
48913
  /* @__PURE__ */ jsxRuntime.jsxs(
48818
48914
  react.ReactFlow,
48819
48915
  {
@@ -48830,6 +48926,7 @@ function FlowCanvasInner({
48830
48926
  minZoom: 0.1,
48831
48927
  maxZoom: 2,
48832
48928
  fitView: true,
48929
+ fitViewOptions: { padding: 0.15 },
48833
48930
  nodesDraggable: true,
48834
48931
  elementsSelectable: true,
48835
48932
  proOptions: { hideAttribution: true },
@@ -49249,7 +49346,11 @@ var AvlOrbitalsCosmicZoom = ({
49249
49346
  color = "var(--color-primary, #4A90D9)",
49250
49347
  animated = true,
49251
49348
  width = "100%",
49252
- height = 450
49349
+ height = 450,
49350
+ highlightedOrbital,
49351
+ onOrbitalSelect,
49352
+ minZoom = 0.4,
49353
+ maxZoom = 3
49253
49354
  }) => {
49254
49355
  const parsedSchema = React125.useMemo(() => {
49255
49356
  if (typeof schemaProp === "string") return JSON.parse(schemaProp);
@@ -49280,10 +49381,83 @@ var AvlOrbitalsCosmicZoom = ({
49280
49381
  );
49281
49382
  const [selected, setSelected] = React125.useState(null);
49282
49383
  const handleSelect = React125.useCallback(
49283
- (name) => setSelected((prev) => prev === name ? null : name),
49284
- []
49384
+ (name) => {
49385
+ setSelected((prev) => prev === name ? null : name);
49386
+ onOrbitalSelect?.(name);
49387
+ },
49388
+ [onOrbitalSelect]
49285
49389
  );
49286
49390
  const selectedView = orbitalViews.find((o) => o.name === selected);
49391
+ const [zoom, setZoom] = React125.useState(1);
49392
+ const [pan, setPan] = React125.useState({ x: 0, y: 0 });
49393
+ const dragStateRef = React125.useRef(null);
49394
+ const transformWrapperRef = React125.useRef(null);
49395
+ const clampZoom = React125.useCallback(
49396
+ (z) => Math.max(minZoom, Math.min(maxZoom, z)),
49397
+ [minZoom, maxZoom]
49398
+ );
49399
+ const handlePointerDown = React125.useCallback((e) => {
49400
+ if (e.target.closest("[data-orbital-tile]")) return;
49401
+ dragStateRef.current = {
49402
+ startX: e.clientX,
49403
+ startY: e.clientY,
49404
+ panX: pan.x,
49405
+ panY: pan.y
49406
+ };
49407
+ e.target.setPointerCapture(e.pointerId);
49408
+ }, [pan]);
49409
+ const handlePointerMove = React125.useCallback((e) => {
49410
+ const drag = dragStateRef.current;
49411
+ if (!drag) return;
49412
+ setPan({
49413
+ x: drag.panX + (e.clientX - drag.startX),
49414
+ y: drag.panY + (e.clientY - drag.startY)
49415
+ });
49416
+ }, []);
49417
+ const handlePointerUp = React125.useCallback((e) => {
49418
+ if (!dragStateRef.current) return;
49419
+ dragStateRef.current = null;
49420
+ try {
49421
+ e.target.releasePointerCapture(e.pointerId);
49422
+ } catch {
49423
+ }
49424
+ }, []);
49425
+ const panRef = React125.useRef(pan);
49426
+ const zoomRef = React125.useRef(zoom);
49427
+ React125.useEffect(() => {
49428
+ panRef.current = pan;
49429
+ }, [pan]);
49430
+ React125.useEffect(() => {
49431
+ zoomRef.current = zoom;
49432
+ }, [zoom]);
49433
+ React125.useEffect(() => {
49434
+ const wrapper = transformWrapperRef.current;
49435
+ if (!wrapper) return;
49436
+ const wheelListener = (e) => {
49437
+ e.preventDefault();
49438
+ const rect = wrapper.getBoundingClientRect();
49439
+ const cursorX = e.clientX - rect.left;
49440
+ const cursorY = e.clientY - rect.top;
49441
+ const currentZoom = zoomRef.current;
49442
+ const currentPan = panRef.current;
49443
+ const worldX = (cursorX - currentPan.x) / currentZoom;
49444
+ const worldY = (cursorY - currentPan.y) / currentZoom;
49445
+ const delta = e.deltaY > 0 ? -0.1 : 0.1;
49446
+ const nextZoom = clampZoom(currentZoom * (1 + delta));
49447
+ const nextPanX = cursorX - worldX * nextZoom;
49448
+ const nextPanY = cursorY - worldY * nextZoom;
49449
+ setZoom(nextZoom);
49450
+ setPan({ x: nextPanX, y: nextPanY });
49451
+ };
49452
+ wrapper.addEventListener("wheel", wheelListener, { passive: false });
49453
+ return () => wrapper.removeEventListener("wheel", wheelListener);
49454
+ }, [clampZoom]);
49455
+ const zoomIn = React125.useCallback(() => setZoom((z) => clampZoom(z * 1.2)), [clampZoom]);
49456
+ const zoomOut = React125.useCallback(() => setZoom((z) => clampZoom(z / 1.2)), [clampZoom]);
49457
+ const resetZoom = React125.useCallback(() => {
49458
+ setZoom(1);
49459
+ setPan({ x: 0, y: 0 });
49460
+ }, []);
49287
49461
  return /* @__PURE__ */ jsxRuntime.jsxs(
49288
49462
  Box,
49289
49463
  {
@@ -49293,59 +49467,115 @@ var AvlOrbitalsCosmicZoom = ({
49293
49467
  style: { width, height: containerH },
49294
49468
  children: [
49295
49469
  /* @__PURE__ */ jsxRuntime.jsx(
49296
- EventWireOverlay,
49470
+ "div",
49297
49471
  {
49298
- orbitalViews,
49299
- crossLinks,
49300
- color,
49301
- animated,
49302
- containerW,
49303
- containerH
49472
+ ref: transformWrapperRef,
49473
+ onPointerDown: handlePointerDown,
49474
+ onPointerMove: handlePointerMove,
49475
+ onPointerUp: handlePointerUp,
49476
+ onPointerCancel: handlePointerUp,
49477
+ style: {
49478
+ position: "absolute",
49479
+ inset: 0,
49480
+ overflow: "hidden",
49481
+ cursor: dragStateRef.current ? "grabbing" : "grab",
49482
+ touchAction: "none"
49483
+ },
49484
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
49485
+ "div",
49486
+ {
49487
+ style: {
49488
+ position: "absolute",
49489
+ inset: 0,
49490
+ transform: `translate(${pan.x}px, ${pan.y}px) scale(${zoom})`,
49491
+ transformOrigin: "0 0"
49492
+ },
49493
+ children: [
49494
+ /* @__PURE__ */ jsxRuntime.jsx(
49495
+ EventWireOverlay,
49496
+ {
49497
+ orbitalViews,
49498
+ crossLinks,
49499
+ color,
49500
+ animated,
49501
+ containerW,
49502
+ containerH
49503
+ }
49504
+ ),
49505
+ orbitalViews.map((view) => {
49506
+ const isHighlighted = view.name === highlightedOrbital;
49507
+ return /* @__PURE__ */ jsxRuntime.jsx(
49508
+ Box,
49509
+ {
49510
+ role: "button",
49511
+ tabIndex: 0,
49512
+ "data-orbital-tile": "true",
49513
+ onClick: () => handleSelect(view.name),
49514
+ onKeyDown: (e) => {
49515
+ if (e.key === "Enter" || e.key === " ") handleSelect(view.name);
49516
+ },
49517
+ "aria-label": `Orbital: ${view.name}${isHighlighted ? " (highlighted)" : ""}`,
49518
+ position: "absolute",
49519
+ style: {
49520
+ left: view.cx - UNIT_DISPLAY_W / 2,
49521
+ top: view.cy - UNIT_DISPLAY_H / 2,
49522
+ width: UNIT_DISPLAY_W,
49523
+ height: UNIT_DISPLAY_H,
49524
+ cursor: "pointer",
49525
+ transition: "transform 0.2s ease, filter 0.2s ease, box-shadow 0.3s ease",
49526
+ transform: selected === view.name ? "scale(1.05)" : "scale(1)",
49527
+ filter: selected && selected !== view.name ? "opacity(0.5)" : "none",
49528
+ // GAP-52: persistent highlight ring (independent from user selection)
49529
+ boxShadow: isHighlighted ? `0 0 0 3px ${color}, 0 0 24px 4px ${color}` : "none",
49530
+ borderRadius: isHighlighted ? "12px" : void 0,
49531
+ zIndex: isHighlighted ? 11 : selected === view.name ? 10 : 1
49532
+ },
49533
+ children: /* @__PURE__ */ jsxRuntime.jsx(
49534
+ AvlOrbitalUnit,
49535
+ {
49536
+ entityName: view.entityName,
49537
+ fields: view.fieldCount,
49538
+ persistence: view.persistence,
49539
+ traits: view.traits,
49540
+ pages: view.pages,
49541
+ color,
49542
+ animated: animated && (selected === view.name || isHighlighted)
49543
+ }
49544
+ )
49545
+ },
49546
+ view.name
49547
+ );
49548
+ }),
49549
+ selectedView && /* @__PURE__ */ jsxRuntime.jsx(
49550
+ InfoPanel,
49551
+ {
49552
+ view: selectedView,
49553
+ crossLinks,
49554
+ color
49555
+ }
49556
+ )
49557
+ ]
49558
+ }
49559
+ )
49304
49560
  }
49305
49561
  ),
49306
- orbitalViews.map((view) => /* @__PURE__ */ jsxRuntime.jsx(
49562
+ /* @__PURE__ */ jsxRuntime.jsxs(
49307
49563
  Box,
49308
49564
  {
49309
- role: "button",
49310
- tabIndex: 0,
49311
- onClick: () => handleSelect(view.name),
49312
- onKeyDown: (e) => {
49313
- if (e.key === "Enter" || e.key === " ") handleSelect(view.name);
49314
- },
49315
- "aria-label": `Orbital: ${view.name}`,
49316
49565
  position: "absolute",
49317
49566
  style: {
49318
- left: view.cx - UNIT_DISPLAY_W / 2,
49319
- top: view.cy - UNIT_DISPLAY_H / 2,
49320
- width: UNIT_DISPLAY_W,
49321
- height: UNIT_DISPLAY_H,
49322
- cursor: "pointer",
49323
- transition: "transform 0.2s ease, filter 0.2s ease",
49324
- transform: selected === view.name ? "scale(1.05)" : "scale(1)",
49325
- filter: selected && selected !== view.name ? "opacity(0.5)" : "none",
49326
- zIndex: selected === view.name ? 10 : 1
49567
+ top: 12,
49568
+ right: 12,
49569
+ display: "flex",
49570
+ flexDirection: "column",
49571
+ gap: 4,
49572
+ zIndex: 30
49327
49573
  },
49328
- children: /* @__PURE__ */ jsxRuntime.jsx(
49329
- AvlOrbitalUnit,
49330
- {
49331
- entityName: view.entityName,
49332
- fields: view.fieldCount,
49333
- persistence: view.persistence,
49334
- traits: view.traits,
49335
- pages: view.pages,
49336
- color,
49337
- animated: animated && selected === view.name
49338
- }
49339
- )
49340
- },
49341
- view.name
49342
- )),
49343
- selectedView && /* @__PURE__ */ jsxRuntime.jsx(
49344
- InfoPanel,
49345
- {
49346
- view: selectedView,
49347
- crossLinks,
49348
- color
49574
+ children: [
49575
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "secondary", size: "sm", onClick: zoomIn, title: "Zoom in", action: "COSMIC_ZOOM_IN", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "plus", size: "sm" }) }),
49576
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "secondary", size: "sm", onClick: zoomOut, title: "Zoom out", action: "COSMIC_ZOOM_OUT", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "minus", size: "sm" }) }),
49577
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "secondary", size: "sm", onClick: resetZoom, title: "Reset", action: "COSMIC_ZOOM_RESET", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "maximize", size: "sm" }) })
49578
+ ]
49349
49579
  }
49350
49580
  )
49351
49581
  ]
@@ -1210,6 +1210,30 @@ interface FlowCanvasProps {
1210
1210
  transition?: string;
1211
1211
  }) => void;
1212
1212
  onLevelChange?: (level: ViewLevel, orbital?: string) => void;
1213
+ /**
1214
+ * GAP-52: fired when the user double-clicks an orbital. Consumers (e.g. the
1215
+ * builder workspace) use this as the trigger to enter cosmic mode
1216
+ * (`AvlOrbitalsCosmicZoom`) for the focused orbital.
1217
+ *
1218
+ * The level at which this fires is controlled by `cosmicEntryLevel` (default
1219
+ * `'expanded'`). At `'expanded'` the existing overview→expanded drill is
1220
+ * preserved — the callback fires only on the second double-click. At
1221
+ * `'overview'` the callback fires on the FIRST double-click and the existing
1222
+ * drill is suppressed for that interaction. `'both'` fires at either level.
1223
+ *
1224
+ * The callback runs unconditionally — persona / permission gating is the
1225
+ * consumer's responsibility.
1226
+ */
1227
+ onOrbitalDoubleClick?: (orbital: string) => void;
1228
+ /**
1229
+ * GAP-53: which level the `onOrbitalDoubleClick` callback fires at.
1230
+ * - `'expanded'` (default, non-breaking) — fires only at L2 expanded; the
1231
+ * first overview double-click still drills overview→expanded.
1232
+ * - `'overview'` — fires at L1 overview on the FIRST double-click. The
1233
+ * overview→expanded drill is suppressed when the callback is provided.
1234
+ * - `'both'` — fires at either level.
1235
+ */
1236
+ cosmicEntryLevel?: 'expanded' | 'overview' | 'both';
1213
1237
  initialOrbital?: string;
1214
1238
  /** Start at Level 2 (expanded) when initialOrbital is set. Default: 'overview'. */
1215
1239
  initialLevel?: ViewLevel;
@@ -1368,6 +1392,29 @@ interface AvlOrbitalsCosmicZoomProps {
1368
1392
  width?: number | string;
1369
1393
  /** Container height */
1370
1394
  height?: number | string;
1395
+ /**
1396
+ * GAP-52: name of the orbital to highlight with a persistent ring/glow.
1397
+ * Independent from user-driven selection (click). Used by the builder workspace
1398
+ * when entering cosmic mode from a focused orbital — the focused orbital is
1399
+ * highlighted while the user can still click any other orbital to select it.
1400
+ */
1401
+ highlightedOrbital?: string;
1402
+ /**
1403
+ * GAP-55: fired when the user clicks an orbital tile. Consumers (e.g. the
1404
+ * builder workspace) use this as the trigger to drill INTO the clicked
1405
+ * orbital — typically by switching back to the canvas tab and opening the
1406
+ * clicked orbital at L2 expanded. Local `selected` toggle (visual highlight +
1407
+ * info panel) still fires regardless of whether the callback is provided.
1408
+ */
1409
+ onOrbitalSelect?: (orbital: string) => void;
1410
+ /**
1411
+ * GAP-54: minimum zoom factor when scroll-wheel zooming. Default 0.4.
1412
+ */
1413
+ minZoom?: number;
1414
+ /**
1415
+ * GAP-54: maximum zoom factor when scroll-wheel zooming. Default 3.
1416
+ */
1417
+ maxZoom?: number;
1371
1418
  }
1372
1419
  declare const AvlOrbitalsCosmicZoom: React__default.FC<AvlOrbitalsCosmicZoomProps>;
1373
1420