@eventcatalog/core 5.0.0-beta.5 → 5.0.0-beta.7

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.
Files changed (84) hide show
  1. package/dist/analytics/analytics.cjs +1 -1
  2. package/dist/analytics/analytics.js +2 -2
  3. package/dist/analytics/log-build.cjs +1 -1
  4. package/dist/analytics/log-build.js +4 -4
  5. package/dist/{chunk-SHHO3JJI.js → chunk-3VBB2ZMP.js} +1 -1
  6. package/dist/{chunk-EBDTVZDW.js → chunk-BOKAG5XC.js} +4 -4
  7. package/dist/{chunk-XQXMSQA5.js → chunk-FARBIZ7Y.js} +1 -1
  8. package/dist/{chunk-43AEF3JE.js → chunk-S7ODHVII.js} +1 -1
  9. package/dist/{chunk-YYMBYOQR.js → chunk-SJJKWZYZ.js} +1 -1
  10. package/dist/constants.cjs +1 -1
  11. package/dist/constants.js +1 -1
  12. package/dist/eventcatalog.cjs +1 -1
  13. package/dist/eventcatalog.js +11 -11
  14. package/dist/federation/federate.js +4 -4
  15. package/dist/generate.cjs +1 -1
  16. package/dist/generate.js +3 -3
  17. package/dist/runtime-dependencies/@hocuspocus__provider.mjs +3 -0
  18. package/dist/runtime-dependencies/@hocuspocus__server.mjs +3 -0
  19. package/dist/runtime-dependencies/@mcp-b__webmcp-local-relay.mjs +3 -0
  20. package/dist/runtime-dependencies/@mcp-b__webmcp-polyfill.mjs +3 -0
  21. package/dist/runtime-dependencies/manifest.json +8 -0
  22. package/dist/runtime-dependencies/react-hotkeys-hook.mjs +3 -0
  23. package/dist/runtime-dependencies/y-protocols.mjs +3 -0
  24. package/dist/runtime-dependencies/y-protocols__awareness.mjs +3 -0
  25. package/dist/runtime-dependencies/yjs.mjs +3 -0
  26. package/dist/utils/cli-logger.cjs +1 -1
  27. package/dist/utils/cli-logger.js +2 -2
  28. package/eventcatalog/astro.config.mjs +2 -0
  29. package/eventcatalog/integrations/studio-server.mjs +17 -0
  30. package/eventcatalog/src/features/integrations/eventcatalog-features.ts +20 -1
  31. package/eventcatalog/src/features/mcp/apps/canvas/shared.ts +39 -0
  32. package/eventcatalog/src/features/mcp/apps/canvas/styles.css +22 -0
  33. package/eventcatalog/src/features/mcp/apps/canvas/view.tsx +269 -0
  34. package/eventcatalog/src/features/mcp/apps/generated/canvas.html +209 -0
  35. package/eventcatalog/src/features/mcp/apps/generated/viewer.html +2 -2
  36. package/eventcatalog/src/features/mcp/apps/shared/app-view.tsx +2 -1
  37. package/eventcatalog/src/features/mcp/mcp-apps.ts +6 -3
  38. package/eventcatalog/src/features/mcp/mcp-server.ts +17 -4
  39. package/eventcatalog/src/features/studio/agent-choreography.ts +378 -0
  40. package/eventcatalog/src/features/studio/canvas-actions.ts +287 -0
  41. package/eventcatalog/src/features/studio/canvas-doc.ts +280 -0
  42. package/eventcatalog/src/features/studio/catalog-resources.ts +152 -0
  43. package/eventcatalog/src/features/studio/catalog.ts +171 -0
  44. package/eventcatalog/src/features/studio/components/CanvasControls.tsx +153 -0
  45. package/eventcatalog/src/features/studio/components/CanvasHeader.tsx +332 -0
  46. package/eventcatalog/src/features/studio/components/Comments.tsx +455 -0
  47. package/eventcatalog/src/features/studio/components/ConnectAgentDialog.tsx +217 -0
  48. package/eventcatalog/src/features/studio/components/ContainerChooser.tsx +145 -0
  49. package/eventcatalog/src/features/studio/components/JoinForm.tsx +58 -0
  50. package/eventcatalog/src/features/studio/components/LeftPanel.tsx +218 -0
  51. package/eventcatalog/src/features/studio/components/Presence.tsx +284 -0
  52. package/eventcatalog/src/features/studio/components/PropertiesPanel.tsx +75 -0
  53. package/eventcatalog/src/features/studio/components/StudioDesigner.tsx +760 -0
  54. package/eventcatalog/src/features/studio/components/canvas-nodes.tsx +79 -0
  55. package/eventcatalog/src/features/studio/components/icons.ts +48 -0
  56. package/eventcatalog/src/features/studio/components/status.ts +22 -0
  57. package/eventcatalog/src/features/studio/edges.ts +54 -0
  58. package/eventcatalog/src/features/studio/flow-state.ts +92 -0
  59. package/eventcatalog/src/features/studio/grouping.ts +133 -0
  60. package/eventcatalog/src/features/studio/hooks/presence-sender.ts +46 -0
  61. package/eventcatalog/src/features/studio/hooks/presence-store.ts +115 -0
  62. package/eventcatalog/src/features/studio/hooks/use-canvas-webmcp.ts +337 -0
  63. package/eventcatalog/src/features/studio/hooks/use-comments.ts +47 -0
  64. package/eventcatalog/src/features/studio/hooks/use-follow-camera.ts +99 -0
  65. package/eventcatalog/src/features/studio/hooks/use-studio-flow.ts +423 -0
  66. package/eventcatalog/src/features/studio/layout.ts +39 -0
  67. package/eventcatalog/src/features/studio/levels.ts +216 -0
  68. package/eventcatalog/src/features/studio/node-types.ts +173 -0
  69. package/eventcatalog/src/features/studio/pages/[id].astro +38 -0
  70. package/eventcatalog/src/features/studio/pages/index.astro +8 -0
  71. package/eventcatalog/src/features/studio/pages/webmcp-relay.ts +27 -0
  72. package/eventcatalog/src/features/studio/placement.ts +197 -0
  73. package/eventcatalog/src/features/studio/server/canvas-mcp.ts +475 -0
  74. package/eventcatalog/src/features/studio/server/runtime.ts +215 -0
  75. package/eventcatalog/src/features/studio/server/tool-icons.ts +30 -0
  76. package/eventcatalog/src/features/studio/studio.css +89 -0
  77. package/eventcatalog/src/features/studio/tool-sync.ts +75 -0
  78. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +9 -2
  79. package/eventcatalog/src/utils/feature.ts +4 -0
  80. package/package.json +11 -4
  81. package/eventcatalog/public/studio-bg.png +0 -0
  82. package/eventcatalog/src/components/Studio/StudioPageModal.tsx +0 -150
  83. package/eventcatalog/src/pages/studio.astro +0 -316
  84. package/dist/{chunk-ZEY2XUJN.js → chunk-AYNZXUB5.js} +7 -7
@@ -0,0 +1,332 @@
1
+ import { memo, useEffect, useRef, useState, type ReactNode } from 'react';
2
+ import { Bot, Check, ChevronDown, Link2, Pencil, Plus } from 'lucide-react';
3
+ import { usePeople, type Peer, type PresenceStore } from '../hooks/presence-store';
4
+ import type { WebMcpStatus } from '../hooks/use-canvas-webmcp';
5
+ import type { Status, Transport } from '../hooks/use-studio-flow';
6
+ import { CONNECTION_DOT, STATUS } from './status';
7
+
8
+ /**
9
+ * The canvas's header, like Figma or Miro: two small floating bars. On the left the canvas (its title, and a
10
+ * menu), on the right who's here, connecting an agent and sharing.
11
+ */
12
+
13
+ const barClass =
14
+ 'pointer-events-auto flex h-10 items-center rounded-xl border shadow-md bg-[rgb(var(--ec-card-bg))] border-[rgb(var(--ec-page-border))]';
15
+ const menuClass =
16
+ 'absolute top-full z-50 mt-2 min-w-52 rounded-xl border p-1 shadow-xl bg-[rgb(var(--ec-card-bg))] border-[rgb(var(--ec-page-border))] text-[rgb(var(--ec-page-text))]';
17
+ const menuItemClass =
18
+ 'flex w-full items-center gap-2 rounded-lg px-2.5 py-1.5 text-left text-xs hover:bg-[rgb(var(--ec-page-border)/0.5)]';
19
+ const MAX_AVATARS = 4;
20
+
21
+ /** Closes a popover on outside clicks and Escape */
22
+ function usePopover() {
23
+ const [open, setOpen] = useState(false);
24
+ const ref = useRef<HTMLDivElement>(null);
25
+ useEffect(() => {
26
+ if (!open) return;
27
+ const onPointerDown = (event: PointerEvent) => {
28
+ if (!ref.current?.contains(event.target as globalThis.Node)) setOpen(false);
29
+ };
30
+ const onKeyDown = (event: KeyboardEvent) => event.key === 'Escape' && setOpen(false);
31
+ window.addEventListener('pointerdown', onPointerDown);
32
+ window.addEventListener('keydown', onKeyDown);
33
+ return () => {
34
+ window.removeEventListener('pointerdown', onPointerDown);
35
+ window.removeEventListener('keydown', onKeyDown);
36
+ };
37
+ }, [open]);
38
+ return { open, setOpen, ref };
39
+ }
40
+
41
+ function Avatar({ peer, size = 28, children }: { peer: Peer; size?: number; children?: ReactNode }) {
42
+ return (
43
+ <span
44
+ className="flex items-center justify-center rounded-full text-[11px] font-semibold text-white ring-2 ring-[rgb(var(--ec-card-bg))]"
45
+ style={{ backgroundColor: peer.color, width: size, height: size }}
46
+ >
47
+ {children ?? (peer.agent ? <Bot size={14} /> : peer.name.slice(0, 2).toUpperCase())}
48
+ </span>
49
+ );
50
+ }
51
+
52
+ function CanvasMenu({
53
+ title,
54
+ status,
55
+ transport,
56
+ onRetitle,
57
+ shareUrl,
58
+ newCanvasUrl,
59
+ }: {
60
+ title?: string;
61
+ status: Status;
62
+ transport: Transport;
63
+ onRetitle: (title: string) => void;
64
+ shareUrl?: string;
65
+ newCanvasUrl?: string;
66
+ }) {
67
+ const menu = usePopover();
68
+ const titleInput = useRef<HTMLInputElement>(null);
69
+ const [draft, setDraft] = useState<string | null>(null);
70
+ const statusLabel = {
71
+ connected: transport === 'tools' ? 'Connected through the chat' : 'Connected',
72
+ connecting: 'Connecting…',
73
+ disconnected: 'Offline, reconnecting…',
74
+ }[status];
75
+ const statusColor = CONNECTION_DOT[status];
76
+
77
+ return (
78
+ <div ref={menu.ref} className="relative">
79
+ <div className={`${barClass} gap-1 pl-3 pr-1`}>
80
+ <span title={statusLabel} className={`h-2 w-2 shrink-0 rounded-full ${statusColor}`} />
81
+ <input
82
+ ref={titleInput}
83
+ aria-label="Canvas title"
84
+ value={draft ?? title ?? ''}
85
+ placeholder="Untitled canvas"
86
+ onChange={(e) => setDraft(e.target.value)}
87
+ onBlur={() => {
88
+ if (draft !== null && draft.trim() !== (title ?? '')) onRetitle(draft.trim());
89
+ setDraft(null);
90
+ }}
91
+ onKeyDown={(e) => e.key === 'Enter' && e.currentTarget.blur()}
92
+ size={Math.max(12, (draft ?? title ?? 'Untitled canvas').length)}
93
+ className="max-w-64 truncate rounded-md bg-transparent px-1.5 py-1 text-sm font-semibold placeholder:font-semibold placeholder:text-[rgb(var(--ec-page-text-muted))] hover:bg-[rgb(var(--ec-page-border)/0.4)] focus:bg-[rgb(var(--ec-input-bg))] focus:outline-none"
94
+ />
95
+ <button
96
+ aria-label="Canvas menu"
97
+ onClick={() => menu.setOpen((open) => !open)}
98
+ className="rounded-md p-1.5 text-[rgb(var(--ec-icon-color))] hover:bg-[rgb(var(--ec-page-border)/0.5)]"
99
+ >
100
+ <ChevronDown size={14} />
101
+ </button>
102
+ </div>
103
+ {menu.open && (
104
+ <div className={`${menuClass} left-0`}>
105
+ <button
106
+ className={menuItemClass}
107
+ onClick={() => {
108
+ menu.setOpen(false);
109
+ titleInput.current?.focus();
110
+ titleInput.current?.select();
111
+ }}
112
+ >
113
+ <Pencil size={14} /> Rename canvas
114
+ </button>
115
+ <button
116
+ className={menuItemClass}
117
+ onClick={() => {
118
+ void navigator.clipboard.writeText(shareUrl ?? window.location.href);
119
+ menu.setOpen(false);
120
+ }}
121
+ >
122
+ <Link2 size={14} /> Copy link
123
+ </button>
124
+ {newCanvasUrl && (
125
+ <a className={menuItemClass} href={newCanvasUrl}>
126
+ <Plus size={14} /> New canvas
127
+ </a>
128
+ )}
129
+ <div className="my-1 h-px bg-[rgb(var(--ec-page-border))]" />
130
+ <p className="flex items-center gap-2 px-2.5 py-1.5 text-[11px] text-[rgb(var(--ec-page-text-muted))]">
131
+ <span className={`h-1.5 w-1.5 rounded-full ${statusColor}`} />
132
+ {statusLabel}
133
+ </p>
134
+ </div>
135
+ )}
136
+ </div>
137
+ );
138
+ }
139
+
140
+ /** You: change the name others see you by */
141
+ function YouAvatar({ peer, onRename }: { peer: Peer; onRename: (name: string) => void }) {
142
+ const popover = usePopover();
143
+ const [draft, setDraft] = useState(peer.name);
144
+ const commit = () => {
145
+ const trimmed = draft.trim();
146
+ if (trimmed && trimmed !== peer.name) onRename(trimmed);
147
+ popover.setOpen(false);
148
+ };
149
+
150
+ return (
151
+ <div ref={popover.ref} className="relative">
152
+ <button
153
+ title={`${peer.name} (you)`}
154
+ onClick={() => {
155
+ setDraft(peer.name);
156
+ popover.setOpen((open) => !open);
157
+ }}
158
+ className="block rounded-full transition-transform hover:scale-110"
159
+ >
160
+ <Avatar peer={peer} />
161
+ </button>
162
+ {popover.open && (
163
+ <div className={`${menuClass} right-0 w-60 space-y-2 p-3`}>
164
+ <label className="block space-y-1.5 text-[11px] font-medium text-[rgb(var(--ec-page-text-muted))]">
165
+ Your name on this canvas
166
+ <input
167
+ autoFocus
168
+ value={draft}
169
+ onChange={(e) => setDraft(e.target.value)}
170
+ onKeyDown={(e) => e.key === 'Enter' && commit()}
171
+ className="w-full rounded-md border px-2 py-1.5 text-xs bg-[rgb(var(--ec-input-bg))] border-[rgb(var(--ec-input-border))] text-[rgb(var(--ec-input-text))]"
172
+ />
173
+ </label>
174
+ <button
175
+ onClick={commit}
176
+ className="w-full rounded-md px-2 py-1.5 text-xs font-medium bg-[rgb(var(--ec-button-bg))] text-[rgb(var(--ec-button-text))]"
177
+ >
178
+ Save
179
+ </button>
180
+ </div>
181
+ )}
182
+ </div>
183
+ );
184
+ }
185
+
186
+ /** Who's here: click someone to jump to them; everyone past the first few is in a list */
187
+ function People({
188
+ peers,
189
+ clientId,
190
+ onJumpTo,
191
+ onRename,
192
+ }: {
193
+ peers: Peer[];
194
+ clientId: number | null;
195
+ onJumpTo: (peer: Peer) => void;
196
+ onRename: (name: string) => void;
197
+ }) {
198
+ const overflow = usePopover();
199
+ const you = peers.find((peer) => peer.clientId === clientId);
200
+ const others = peers.filter((peer) => peer.clientId !== clientId);
201
+ const shown = others.slice(0, MAX_AVATARS);
202
+ const hidden = others.slice(MAX_AVATARS);
203
+ const describe = (peer: Peer) => `${peer.name}${peer.agent ? ` (AI agent)${peer.activity ? `: ${peer.activity}` : ''}` : ''}`;
204
+
205
+ return (
206
+ <div className="flex items-center gap-1.5">
207
+ <div className="flex -space-x-1.5">
208
+ {shown.map((peer) => (
209
+ <button
210
+ key={peer.clientId}
211
+ title={`${describe(peer)}. Click to jump to them`}
212
+ onClick={() => onJumpTo(peer)}
213
+ className="block rounded-full transition-transform hover:z-10 hover:scale-110"
214
+ >
215
+ <Avatar peer={peer} />
216
+ </button>
217
+ ))}
218
+ {hidden.length > 0 && (
219
+ <div ref={overflow.ref} className="relative">
220
+ <button
221
+ title={`${hidden.length} more`}
222
+ onClick={() => overflow.setOpen((open) => !open)}
223
+ className="flex h-7 w-7 items-center justify-center rounded-full text-[11px] font-semibold ring-2 ring-[rgb(var(--ec-card-bg))] bg-[rgb(var(--ec-page-border))] text-[rgb(var(--ec-page-text))]"
224
+ >
225
+ +{hidden.length}
226
+ </button>
227
+ {overflow.open && (
228
+ <div className={`${menuClass} right-0`}>
229
+ {hidden.map((peer) => (
230
+ <button
231
+ key={peer.clientId}
232
+ className={menuItemClass}
233
+ onClick={() => {
234
+ onJumpTo(peer);
235
+ overflow.setOpen(false);
236
+ }}
237
+ >
238
+ <Avatar peer={peer} size={20} />
239
+ {describe(peer)}
240
+ </button>
241
+ ))}
242
+ </div>
243
+ )}
244
+ </div>
245
+ )}
246
+ </div>
247
+ {you && <YouAvatar peer={you} onRename={onRename} />}
248
+ </div>
249
+ );
250
+ }
251
+
252
+ export default memo(function CanvasHeader({
253
+ title,
254
+ onRetitle,
255
+ status,
256
+ transport,
257
+ presence,
258
+ clientId,
259
+ onJumpTo,
260
+ onRename,
261
+ onConnectAgent,
262
+ webMcp,
263
+ shareUrl,
264
+ newCanvasUrl,
265
+ }: {
266
+ title?: string;
267
+ onRetitle: (title: string) => void;
268
+ status: Status;
269
+ transport: Transport;
270
+ presence: PresenceStore | null;
271
+ clientId: number | null;
272
+ onJumpTo: (peer: Peer) => void;
273
+ onRename: (name: string) => void;
274
+ onConnectAgent: () => void;
275
+ webMcp: WebMcpStatus;
276
+ shareUrl?: string;
277
+ newCanvasUrl?: string;
278
+ }) {
279
+ const [copied, setCopied] = useState(false);
280
+ const peers = usePeople(presence);
281
+ const agentsHere = peers.some((peer) => peer.agent);
282
+
283
+ const share = async () => {
284
+ await navigator.clipboard.writeText(shareUrl ?? window.location.href);
285
+ setCopied(true);
286
+ setTimeout(() => setCopied(false), 1500);
287
+ };
288
+
289
+ return (
290
+ <div className="pointer-events-none absolute left-3 right-3 top-3 z-10 flex items-start justify-between gap-3">
291
+ <CanvasMenu
292
+ title={title}
293
+ status={status}
294
+ transport={transport}
295
+ onRetitle={onRetitle}
296
+ shareUrl={shareUrl}
297
+ newCanvasUrl={newCanvasUrl}
298
+ />
299
+
300
+ <div className={`${barClass} gap-2 px-1.5`}>
301
+ <div className="pl-1">
302
+ <People peers={peers} clientId={clientId} onJumpTo={onJumpTo} onRename={onRename} />
303
+ </div>
304
+ <div className="h-5 w-px bg-[rgb(var(--ec-page-border))]" />
305
+ <button
306
+ onClick={onConnectAgent}
307
+ title={
308
+ webMcp.state === 'on'
309
+ ? `Connect an AI agent to this canvas. WebMCP is on in this tab${webMcp.lastCall ? ` (last: ${webMcp.lastCall})` : ''}`
310
+ : 'Connect an AI agent to this canvas'
311
+ }
312
+ className="relative flex h-7 items-center gap-1.5 rounded-lg px-2.5 text-xs font-medium hover:bg-[rgb(var(--ec-page-border)/0.5)]"
313
+ >
314
+ <Bot size={15} />
315
+ Connect agent
316
+ {(webMcp.state === 'on' || agentsHere) && (
317
+ <span
318
+ className={`absolute left-5 top-1 h-1.5 w-1.5 rounded-full ring-2 ring-[rgb(var(--ec-card-bg))] ${STATUS.success.dot}`}
319
+ />
320
+ )}
321
+ </button>
322
+ <button
323
+ onClick={share}
324
+ className="flex h-7 items-center gap-1.5 rounded-lg px-3 text-xs font-semibold bg-[rgb(var(--ec-button-bg))] text-[rgb(var(--ec-button-text))] hover:bg-[rgb(var(--ec-button-bg-hover))]"
325
+ >
326
+ {copied ? <Check size={14} /> : null}
327
+ {copied ? 'Link copied' : 'Share'}
328
+ </button>
329
+ </div>
330
+ </div>
331
+ );
332
+ });