@eventcatalog/core 5.0.0-beta.12 → 5.0.0-beta.13
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.
- package/dist/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +1 -1
- package/dist/analytics/log-build.js +4 -4
- package/dist/catalog-runtime.cjs +1 -1
- package/dist/{chunk-FY6MSP6J.js → chunk-BO2HHVZ3.js} +1 -1
- package/dist/{chunk-CX2VB7AI.js → chunk-JPT3QC5V.js} +1 -1
- package/dist/{chunk-BWC4YKW3.js → chunk-PLTLW45Q.js} +5 -5
- package/dist/{chunk-TSY5CC27.js → chunk-T75DOVFP.js} +1 -1
- package/dist/{chunk-QB7X34OD.js → chunk-XA5W6DXX.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +2 -2
- package/dist/eventcatalog.js +14 -14
- package/dist/federation/federate.js +3 -3
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/runtime-dependencies/@eventcatalog__visualiser__studio-fonts.css.mjs +3 -0
- package/dist/runtime-dependencies/@eventcatalog__visualiser__studio.css.mjs +3 -0
- package/dist/runtime-dependencies/@eventcatalog__visualiser__studio.mjs +3 -0
- package/dist/runtime-dependencies/manifest.json +3 -0
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/eventcatalog/src/components/MDX/NodeGraph/AstroNodeGraph.tsx +11 -3
- package/eventcatalog/src/features/mcp/apps/canvas/shared.ts +3 -3
- package/eventcatalog/src/features/mcp/apps/canvas/styles.css +3 -1
- package/eventcatalog/src/features/mcp/apps/canvas/view.tsx +7 -5
- package/eventcatalog/src/features/mcp/apps/generated/canvas.html +133 -71
- package/eventcatalog/src/features/mcp/apps/generated/viewer.html +66 -66
- package/eventcatalog/src/features/studio/agent-choreography.ts +5 -4
- package/eventcatalog/src/features/studio/agents.ts +28 -0
- package/eventcatalog/src/features/studio/api/canvases-api.ts +1 -1
- package/eventcatalog/src/features/studio/canvas-actions.ts +22 -14
- package/eventcatalog/src/features/studio/canvas-preview.ts +1 -2
- package/eventcatalog/src/features/studio/catalog.ts +1 -4
- package/eventcatalog/src/features/studio/components/CanvasHeader.tsx +246 -115
- package/eventcatalog/src/features/studio/components/CanvasStatusMenu.tsx +7 -3
- package/eventcatalog/src/features/studio/components/ConnectAgentDialog.tsx +272 -119
- package/eventcatalog/src/features/studio/components/ConnectionsChooser.tsx +1 -2
- package/eventcatalog/src/features/studio/components/DeleteCanvasDialog.tsx +1 -1
- package/eventcatalog/src/features/studio/components/JoinForm.tsx +101 -26
- package/eventcatalog/src/features/studio/components/LeftPanel.tsx +102 -241
- package/eventcatalog/src/features/studio/components/NodeDetails.tsx +183 -0
- package/eventcatalog/src/features/studio/components/OpenInStudioDialog.tsx +1 -1
- package/eventcatalog/src/features/studio/components/Presence.tsx +6 -8
- package/eventcatalog/src/features/studio/components/ShareDialog.tsx +277 -141
- package/eventcatalog/src/features/studio/components/ShortcutsDialog.tsx +41 -0
- package/eventcatalog/src/features/studio/components/StudioDesigner.tsx +1005 -354
- package/eventcatalog/src/features/studio/document-name.ts +7 -0
- package/eventcatalog/src/features/studio/from-visualiser.ts +1 -1
- package/eventcatalog/src/features/studio/hooks/presence-sender.ts +19 -2
- package/eventcatalog/src/features/studio/hooks/use-canvas-webmcp.ts +9 -3
- package/eventcatalog/src/features/studio/hooks/use-comments.ts +2 -2
- package/eventcatalog/src/features/studio/hooks/use-studio-flow.ts +162 -23
- package/eventcatalog/src/features/studio/identity.ts +23 -1
- package/eventcatalog/src/features/studio/pages/[id].astro +2 -1
- package/eventcatalog/src/features/studio/pages/index.astro +1 -2
- package/eventcatalog/src/features/studio/server/canvas-mcp.ts +7 -22
- package/eventcatalog/src/features/studio/server/runtime.ts +4 -5
- package/package.json +3 -3
- package/eventcatalog/src/features/studio/canvas-doc.ts +0 -569
- package/eventcatalog/src/features/studio/clipboard.ts +0 -97
- package/eventcatalog/src/features/studio/components/CanvasControls.tsx +0 -119
- package/eventcatalog/src/features/studio/components/Comments.tsx +0 -777
- package/eventcatalog/src/features/studio/components/ContainerChooser.tsx +0 -145
- package/eventcatalog/src/features/studio/components/DragGhost.tsx +0 -115
- package/eventcatalog/src/features/studio/components/Picture.tsx +0 -21
- package/eventcatalog/src/features/studio/components/PropertiesPanel.tsx +0 -212
- package/eventcatalog/src/features/studio/components/canvas-nodes.tsx +0 -271
- package/eventcatalog/src/features/studio/components/icons.ts +0 -53
- package/eventcatalog/src/features/studio/components/status.ts +0 -53
- package/eventcatalog/src/features/studio/edges.ts +0 -56
- package/eventcatalog/src/features/studio/flow-state.ts +0 -121
- package/eventcatalog/src/features/studio/grouping.ts +0 -230
- package/eventcatalog/src/features/studio/hooks/use-counter-scale.ts +0 -20
- package/eventcatalog/src/features/studio/layout.ts +0 -56
- package/eventcatalog/src/features/studio/level-layout.ts +0 -145
- package/eventcatalog/src/features/studio/levels.ts +0 -337
- package/eventcatalog/src/features/studio/node-types.ts +0 -245
- package/eventcatalog/src/features/studio/placement.ts +0 -197
- package/eventcatalog/src/features/studio/studio.css +0 -273
- package/dist/{chunk-KHACCTW2.js → chunk-ZEY2XUJN.js} +4 -4
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Canvases' documents on the collaboration server are named `design:<canvasId>` (scripts and peers join by it)
|
|
2
|
+
const DOCUMENT_PREFIX = 'design:';
|
|
3
|
+
export const canvasDocumentName = (canvasId: string) => `${DOCUMENT_PREFIX}${canvasId}`;
|
|
4
|
+
export const canvasIdFromDocumentName = (name: string) =>
|
|
5
|
+
name.startsWith(DOCUMENT_PREFIX) ? name.slice(DOCUMENT_PREFIX.length) : undefined;
|
|
6
|
+
/** Why the collaboration server refuses a canvas that was deleted (told to anyone who still has it open) */
|
|
7
|
+
export const CANVAS_DELETED_REASON = 'canvas-deleted';
|
|
@@ -2,7 +2,7 @@ import type { Edge, Node } from '@xyflow/react';
|
|
|
2
2
|
import type { VisualiserSnapshot } from '@eventcatalog/visualiser';
|
|
3
3
|
import { getNodeCatalogResource } from '@utils/node-graphs/node-resource';
|
|
4
4
|
import type { DiagramLevelsSpec, NewCanvasEdges, NodeSpecs } from './canvas-actions';
|
|
5
|
-
import { DEFAULT_NODE_SIZE, isCatalogCollection, isGroupType } from '
|
|
5
|
+
import { DEFAULT_NODE_SIZE, isCatalogCollection, isGroupType } from '@eventcatalog/visualiser/studio';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* A canvas started from a diagram (the visualiser's "Open in Studio"): the catalog resources and actors it shows,
|
|
@@ -2,6 +2,11 @@ import type { Awareness } from 'y-protocols/awareness';
|
|
|
2
2
|
|
|
3
3
|
/** About 30 times a second, like Excalidraw and tldraw: smooth for others, without a message on every mousemove */
|
|
4
4
|
export const PRESENCE_INTERVAL_MS = 33;
|
|
5
|
+
/**
|
|
6
|
+
* Once a second when nobody else is on the canvas (people or agents), as tldraw does: nobody sees it, and the
|
|
7
|
+
* collaboration server sends every one back. Someone arriving gets what's waiting straight away.
|
|
8
|
+
*/
|
|
9
|
+
export const ALONE_INTERVAL_MS = 1000;
|
|
5
10
|
|
|
6
11
|
export type PresenceSender = {
|
|
7
12
|
/** Share these fields of our presence: straight away if nothing was sent lately, otherwise with the next send */
|
|
@@ -16,10 +21,15 @@ export type PresenceSender = {
|
|
|
16
21
|
* sends the whole state on every change, so fields changed together (the pointer and the node it drags) go in one
|
|
17
22
|
* message, and the latest value always goes out (the pointer stops where it really stopped).
|
|
18
23
|
*/
|
|
19
|
-
export function createPresenceSender(
|
|
24
|
+
export function createPresenceSender(
|
|
25
|
+
awareness: Awareness,
|
|
26
|
+
intervalMs = PRESENCE_INTERVAL_MS,
|
|
27
|
+
aloneIntervalMs = ALONE_INTERVAL_MS
|
|
28
|
+
): PresenceSender {
|
|
20
29
|
let pending: Record<string, unknown> | undefined;
|
|
21
30
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
22
31
|
let lastSent = -Infinity;
|
|
32
|
+
const alone = () => awareness.getStates().size <= 1;
|
|
23
33
|
|
|
24
34
|
const flush = () => {
|
|
25
35
|
clearTimeout(timer);
|
|
@@ -30,17 +40,24 @@ export function createPresenceSender(awareness: Awareness, intervalMs = PRESENCE
|
|
|
30
40
|
lastSent = performance.now();
|
|
31
41
|
awareness.setLocalState({ ...awareness.getLocalState(), ...fields });
|
|
32
42
|
};
|
|
43
|
+
// Someone arrived: they get what's waiting now, not at the next (slower) send
|
|
44
|
+
const onChange = ({ added }: { added: number[] }) => {
|
|
45
|
+
if (pending && added.some((id) => id !== awareness.clientID)) flush();
|
|
46
|
+
};
|
|
47
|
+
awareness.on('change', onChange);
|
|
33
48
|
|
|
34
49
|
return {
|
|
35
50
|
send: (fields) => {
|
|
36
51
|
pending = { ...pending, ...fields };
|
|
37
52
|
if (timer) return;
|
|
38
|
-
|
|
53
|
+
const interval = alone() ? aloneIntervalMs : intervalMs;
|
|
54
|
+
timer = setTimeout(flush, Math.max(0, interval - (performance.now() - lastSent)));
|
|
39
55
|
},
|
|
40
56
|
flush,
|
|
41
57
|
destroy: () => {
|
|
42
58
|
clearTimeout(timer);
|
|
43
59
|
pending = undefined;
|
|
60
|
+
awareness.off('change', onChange);
|
|
44
61
|
},
|
|
45
62
|
};
|
|
46
63
|
}
|
|
@@ -2,8 +2,14 @@ import { useEffect, useRef, useState } from 'react';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import type * as Y from 'yjs';
|
|
4
4
|
import { initializeWebMCPPolyfill } from '@mcp-b/webmcp-polyfill';
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import {
|
|
6
|
+
replyToThread,
|
|
7
|
+
setThreadResolved,
|
|
8
|
+
type Author,
|
|
9
|
+
CANVAS_STATUSES,
|
|
10
|
+
setCanvasStatus,
|
|
11
|
+
CATALOG_COLLECTIONS,
|
|
12
|
+
} from '@eventcatalog/visualiser/studio';
|
|
7
13
|
import {
|
|
8
14
|
ADD_TO_CANVAS_DESCRIPTION,
|
|
9
15
|
describeCanvas,
|
|
@@ -284,7 +290,7 @@ const createCanvasTools = (get: () => CanvasWebMcpOptions, getStage: () => Agent
|
|
|
284
290
|
name: 'layout_canvas',
|
|
285
291
|
title: 'Tidy the canvas',
|
|
286
292
|
description:
|
|
287
|
-
"Lays the whole canvas out again with EventCatalog's visualiser layout (left to right, connections routed around nodes), for everyone on it.
|
|
293
|
+
"Lays the whole canvas out again with EventCatalog's visualiser layout (left to right, connections routed around nodes), for everyone on it. Call it once when you finish a round of changes, before you hand back to the user, so the canvas is tidy for everyone, and whenever the user asks to tidy up. Don't call it after every change (add_to_canvas already places what you add next to what it connects to while you work), and skip it if the user asked to keep the layout as it is: it moves everything, including what people placed themselves.",
|
|
288
294
|
input: z.object({}),
|
|
289
295
|
execute: async () => {
|
|
290
296
|
await get().reorder();
|
|
@@ -13,9 +13,9 @@ import {
|
|
|
13
13
|
type Author,
|
|
14
14
|
type CommentAnchor,
|
|
15
15
|
type Thread,
|
|
16
|
-
} from '
|
|
16
|
+
} from '@eventcatalog/visualiser/studio';
|
|
17
17
|
|
|
18
|
-
export type { Author, CommentAnchor, Message, Thread } from '
|
|
18
|
+
export type { Author, CommentAnchor, Message, Thread } from '@eventcatalog/visualiser/studio';
|
|
19
19
|
|
|
20
20
|
/** Comment threads in the shared canvas document */
|
|
21
21
|
export function useComments(doc: Y.Doc | null, author: Author) {
|
|
@@ -15,8 +15,6 @@ import {
|
|
|
15
15
|
import {
|
|
16
16
|
addNodes,
|
|
17
17
|
buildNode,
|
|
18
|
-
CANVAS_DELETED_REASON,
|
|
19
|
-
canvasDocumentName,
|
|
20
18
|
connectNodes,
|
|
21
19
|
deleteEdges,
|
|
22
20
|
deleteNodes as deleteNodesInDoc,
|
|
@@ -41,15 +39,36 @@ import {
|
|
|
41
39
|
readLevelLayout,
|
|
42
40
|
setLevelPlaces,
|
|
43
41
|
type StatusChange,
|
|
44
|
-
|
|
42
|
+
setNodeParents,
|
|
45
43
|
type RenderedSizes,
|
|
46
44
|
updateNodeData as updateNodeDataInDoc,
|
|
47
|
-
|
|
45
|
+
keepDragged,
|
|
46
|
+
LOCAL_EDGE_KEYS,
|
|
47
|
+
LOCAL_NODE_KEYS,
|
|
48
|
+
patchShared,
|
|
49
|
+
withPositions,
|
|
50
|
+
createSmoother,
|
|
51
|
+
containerFor,
|
|
52
|
+
toRelativePosition,
|
|
53
|
+
type NodePreview,
|
|
54
|
+
type LayoutResult,
|
|
55
|
+
canGoInContainer,
|
|
56
|
+
getNodeDefinition,
|
|
57
|
+
isGroupType,
|
|
58
|
+
createHotspot as createHotspotInDoc,
|
|
59
|
+
updateHotspot as updateHotspotInDoc,
|
|
60
|
+
deleteHotspot as deleteHotspotInDoc,
|
|
61
|
+
readHotspots,
|
|
62
|
+
getLock,
|
|
63
|
+
setLock,
|
|
64
|
+
type Hotspot,
|
|
65
|
+
type Editing,
|
|
66
|
+
addThreads,
|
|
67
|
+
addHotspots,
|
|
68
|
+
type ModelCanvasContent,
|
|
69
|
+
} from '@eventcatalog/visualiser/studio';
|
|
70
|
+
import { CANVAS_DELETED_REASON, canvasDocumentName } from '../document-name';
|
|
48
71
|
import { applyLayout } from '../agent-choreography';
|
|
49
|
-
import { keepDragged, LOCAL_EDGE_KEYS, LOCAL_NODE_KEYS, patchShared, withPositions } from '../flow-state';
|
|
50
|
-
import { containerFor, toRelativePosition, type NodePreview } from '../grouping';
|
|
51
|
-
import type { LayoutResult } from '../layout';
|
|
52
|
-
import { canGoInContainer, getNodeDefinition, isGroupType } from '../node-types';
|
|
53
72
|
import { startToolSync, type ToolSync } from '../tool-sync';
|
|
54
73
|
import { createPresenceSender, type PresenceSender } from './presence-sender';
|
|
55
74
|
import { createPresenceStore, type PresenceStore } from './presence-store';
|
|
@@ -73,10 +92,14 @@ const LOCAL_ORIGIN = Symbol('studio-local');
|
|
|
73
92
|
export const BROWSER_AGENT_ORIGIN = Symbol('studio-browser-agent');
|
|
74
93
|
/** Status changes: synced, but not undone (undo takes back edits, not decisions about the canvas) */
|
|
75
94
|
const STATUS_ORIGIN = Symbol('studio-status');
|
|
95
|
+
/** A diagram file opened into a new canvas: what it starts with, synced, not undone (nothing to go back to) */
|
|
96
|
+
const OPEN_ORIGIN = Symbol('studio-open');
|
|
76
97
|
/** Where L1 and L2 put what's new on them, kept so it stays there: synced, not undone (nobody did it) */
|
|
77
98
|
const LEVEL_PLACEMENT_ORIGIN = Symbol('studio-level-placement');
|
|
78
99
|
|
|
79
100
|
const NO_LEVEL_LAYOUTS = { 1: new Map(), 2: new Map() } as const;
|
|
101
|
+
/** What someone changing a note or text shows the others as they change it (see `editing` in their presence) */
|
|
102
|
+
const EDITED_FIELDS = ['text', 'fontSize', 'angle'] as const;
|
|
80
103
|
|
|
81
104
|
export type Status = 'connecting' | 'connected' | 'disconnected';
|
|
82
105
|
export type Transport = 'websocket' | 'tools';
|
|
@@ -100,6 +123,7 @@ export function useStudioFlow({
|
|
|
100
123
|
name,
|
|
101
124
|
color,
|
|
102
125
|
picture,
|
|
126
|
+
agent,
|
|
103
127
|
syncViaTools,
|
|
104
128
|
onLayout,
|
|
105
129
|
onContainerResize,
|
|
@@ -111,6 +135,8 @@ export function useStudioFlow({
|
|
|
111
135
|
color: string;
|
|
112
136
|
/** From your sign-in provider, when you're signed in */
|
|
113
137
|
picture?: string;
|
|
138
|
+
/** You're an AI agent here (e.g. a chat's canvas view, which joins as the chat's agent) */
|
|
139
|
+
agent?: boolean;
|
|
114
140
|
/** Sync through MCP tool calls when the WebSocket can't connect (e.g. inside a chat's sandbox) */
|
|
115
141
|
syncViaTools?: ToolSync;
|
|
116
142
|
/** Someone laid the canvas out (us included): called as the new positions arrive, so they can glide there */
|
|
@@ -142,8 +168,14 @@ export function useStudioFlow({
|
|
|
142
168
|
const [transport, setTransport] = useState<Transport>('websocket');
|
|
143
169
|
// Someone deleted the canvas: the server closed it for everyone, and won't open it again
|
|
144
170
|
const [deleted, setDeleted] = useState(false);
|
|
171
|
+
// What's flagged on the canvas (risks, problems, the key parts), on nodes or at places
|
|
172
|
+
const [hotspots, setHotspots] = useState<Hotspot[]>([]);
|
|
173
|
+
// Who locked the canvas (for everyone on it), if it's locked
|
|
174
|
+
const [lockedBy, setLockedBy] = useState<string | null>(null);
|
|
175
|
+
// The server's copy of the canvas has arrived (what's on it is known, e.g. that it's empty)
|
|
176
|
+
const [synced, setSynced] = useState(false);
|
|
145
177
|
const user = useRef<Author>({ name, color });
|
|
146
|
-
user.current = { name, color, ...(picture && { picture }) };
|
|
178
|
+
user.current = { name, color, ...(picture && { picture }), ...(agent && { agent }) };
|
|
147
179
|
const onLayoutRef = useRef(onLayout);
|
|
148
180
|
onLayoutRef.current = onLayout;
|
|
149
181
|
const onContainerResizeRef = useRef(onContainerResize);
|
|
@@ -156,7 +188,14 @@ export function useStudioFlow({
|
|
|
156
188
|
setEdges([]);
|
|
157
189
|
|
|
158
190
|
const doc = new Y.Doc();
|
|
159
|
-
const {
|
|
191
|
+
const {
|
|
192
|
+
nodes: yNodes,
|
|
193
|
+
edges: yEdges,
|
|
194
|
+
threads: yThreads,
|
|
195
|
+
meta: yMeta,
|
|
196
|
+
levelLayouts: yLevelLayouts,
|
|
197
|
+
hotspots: yHotspots,
|
|
198
|
+
} = getCanvasMaps(doc);
|
|
160
199
|
const syncLevelLayouts = (event: Y.YMapEvent<LevelPlace>) => {
|
|
161
200
|
const changed = new Set([...event.keysChanged].map((key) => Number(key.split(':')[0]) as 1 | 2));
|
|
162
201
|
setLevelLayouts((current) => {
|
|
@@ -174,8 +213,13 @@ export function useStudioFlow({
|
|
|
174
213
|
if (event.keysChanged.has('status') || event.keysChanged.has('statusHistory'))
|
|
175
214
|
setCanvasStatusState({ status: getCanvasStatus(meta), history: meta.statusHistory ?? [] });
|
|
176
215
|
if (event.keysChanged.has('layoutAt')) onLayoutRef.current?.();
|
|
216
|
+
if (event.keysChanged.has('lockedBy')) setLockedBy(getLock(doc));
|
|
177
217
|
};
|
|
178
218
|
yMeta.observe(syncMeta);
|
|
219
|
+
setLockedBy(null);
|
|
220
|
+
const syncHotspots = () => setHotspots(readHotspots(doc));
|
|
221
|
+
yHotspots.observe(syncHotspots);
|
|
222
|
+
setHotspots([]);
|
|
179
223
|
setTitle(undefined);
|
|
180
224
|
setStructureKey('');
|
|
181
225
|
setDiagramLevels(undefined);
|
|
@@ -249,6 +293,7 @@ export function useStudioFlow({
|
|
|
249
293
|
// Open isn't connected until the server lets us in (onAuthenticated)
|
|
250
294
|
if (!stopToolSync) setStatus(status === 'connected' && !letIn ? 'connecting' : (status as Status));
|
|
251
295
|
},
|
|
296
|
+
onSynced: ({ state }) => setSynced(state),
|
|
252
297
|
onAuthenticated: () => {
|
|
253
298
|
socketConnected = letIn = true;
|
|
254
299
|
if (!stopToolSync) setStatus('connected');
|
|
@@ -268,6 +313,7 @@ export function useStudioFlow({
|
|
|
268
313
|
});
|
|
269
314
|
setTransport('websocket');
|
|
270
315
|
setDeleted(false);
|
|
316
|
+
setSynced(false);
|
|
271
317
|
|
|
272
318
|
// Back online, or back on the tab: reconnect now rather than when the next retry is due
|
|
273
319
|
const reconnect = () => {
|
|
@@ -285,7 +331,7 @@ export function useStudioFlow({
|
|
|
285
331
|
// A socket that isn't let in within a few seconds (blocked, or never answered) is given up for tool calls
|
|
286
332
|
if (syncViaTools) fallback = setTimeout(() => !socketConnected && syncThroughTools(), SOCKET_TIMEOUT_MS);
|
|
287
333
|
// Undo only takes back your own changes: other people's arrive with the provider as origin, agents' with theirs
|
|
288
|
-
const undoManager = new Y.UndoManager([yNodes, yEdges, yThreads, yLevelLayouts], {
|
|
334
|
+
const undoManager = new Y.UndoManager([yNodes, yEdges, yThreads, yLevelLayouts, yHotspots], {
|
|
289
335
|
captureTimeout: 500,
|
|
290
336
|
trackedOrigins: new Set([null, LOCAL_ORIGIN]),
|
|
291
337
|
});
|
|
@@ -302,6 +348,13 @@ export function useStudioFlow({
|
|
|
302
348
|
// Where others are dragging nodes, from their presence, until they drop them
|
|
303
349
|
let remoteMoves = new Map<string, XYPosition>();
|
|
304
350
|
const settling = new Set<ReturnType<typeof setTimeout>>();
|
|
351
|
+
// Shown gliding, a step a frame: at most one update a frame however many people drag (each presence arriving
|
|
352
|
+
// was an update of its own: ~5 a frame with 7 people dragging), and smooth between their ~30 updates a second.
|
|
353
|
+
// Not what's been dropped since (its drop is in the document, see syncNodes).
|
|
354
|
+
const glide = createSmoother((shown) => {
|
|
355
|
+
const moved = new Map([...shown].filter(([id]) => remoteMoves.has(id)));
|
|
356
|
+
if (moved.size) setNodes((local) => withPositions(local, moved));
|
|
357
|
+
});
|
|
305
358
|
const syncMoves = (_: unknown, origin: unknown) => {
|
|
306
359
|
if (origin === 'local') return;
|
|
307
360
|
const next = new Map<string, XYPosition>();
|
|
@@ -309,10 +362,10 @@ export function useStudioFlow({
|
|
|
309
362
|
if (id === doc.clientID || !state.moving) return;
|
|
310
363
|
Object.entries(state.moving as Record<string, XYPosition>).forEach(([nodeId, position]) => next.set(nodeId, position));
|
|
311
364
|
});
|
|
312
|
-
const moved =
|
|
365
|
+
const moved = [...next].some(([id, { x, y }]) => remoteMoves.get(id)?.x !== x || remoteMoves.get(id)?.y !== y);
|
|
313
366
|
const dropped = [...remoteMoves.keys()].filter((id) => !next.has(id));
|
|
314
367
|
remoteMoves = next;
|
|
315
|
-
if (moved.
|
|
368
|
+
if (moved || dropped.length) glide.set(next);
|
|
316
369
|
if (dropped.length === 0) return;
|
|
317
370
|
// The drop is saved in the document. If it doesn't arrive, back to where the document has it.
|
|
318
371
|
const settle = setTimeout(() => {
|
|
@@ -327,6 +380,40 @@ export function useStudioFlow({
|
|
|
327
380
|
};
|
|
328
381
|
awareness.on('change', syncMoves);
|
|
329
382
|
|
|
383
|
+
// What others are writing on notes and text, and how they're scaling and turning text, as they do it (from their
|
|
384
|
+
// presence): shown on just those nodes, and back to what the document has when they're done (their result is
|
|
385
|
+
// written to the document before their presence is cleared, so it doesn't go back in between)
|
|
386
|
+
let remoteEdits = '';
|
|
387
|
+
let remoteEdited = new Set<string>();
|
|
388
|
+
const syncEdits = (_: unknown, origin: unknown) => {
|
|
389
|
+
if (origin === 'local') return;
|
|
390
|
+
const edits = new Map<string, Record<string, unknown>>();
|
|
391
|
+
awareness.getStates().forEach((state, id) => {
|
|
392
|
+
const editing = state.editing as Editing | null | undefined;
|
|
393
|
+
if (id === doc.clientID || !editing?.id) return;
|
|
394
|
+
const { id: nodeId, ...fields } = editing;
|
|
395
|
+
edits.set(nodeId, fields);
|
|
396
|
+
});
|
|
397
|
+
const key = JSON.stringify([...edits]);
|
|
398
|
+
if (key === remoteEdits) return;
|
|
399
|
+
remoteEdits = key;
|
|
400
|
+
const before = remoteEdited;
|
|
401
|
+
remoteEdited = new Set(edits.keys());
|
|
402
|
+
setNodes((local) => {
|
|
403
|
+
let next: Node[] | undefined;
|
|
404
|
+
local.forEach((node, index) => {
|
|
405
|
+
const edit = edits.get(node.id);
|
|
406
|
+
const savedData = before.has(node.id) ? yNodes.get(node.id)?.data : undefined;
|
|
407
|
+
const changes = edit ?? (savedData && Object.fromEntries(EDITED_FIELDS.map((field) => [field, savedData[field]])));
|
|
408
|
+
if (!changes || Object.entries(changes).every(([field, value]) => node.data[field] === value)) return;
|
|
409
|
+
next ??= [...local];
|
|
410
|
+
next[index] = { ...node, data: { ...node.data, ...changes } };
|
|
411
|
+
});
|
|
412
|
+
return next ?? local;
|
|
413
|
+
});
|
|
414
|
+
};
|
|
415
|
+
awareness.on('change', syncEdits);
|
|
416
|
+
|
|
330
417
|
// Only what changed is replaced, so React Flow re-renders just those nodes
|
|
331
418
|
const syncNodes = (event: Y.YMapEvent<Node>) => {
|
|
332
419
|
if (event.transaction.origin === LOCAL_ORIGIN) return;
|
|
@@ -362,12 +449,15 @@ export function useStudioFlow({
|
|
|
362
449
|
window.removeEventListener('online', reconnect);
|
|
363
450
|
document.removeEventListener('visibilitychange', onVisibilityChange);
|
|
364
451
|
awareness.off('change', syncMoves);
|
|
452
|
+
awareness.off('change', syncEdits);
|
|
453
|
+
glide.stop();
|
|
365
454
|
sender.destroy();
|
|
366
455
|
stopToolSync?.();
|
|
367
456
|
yNodes.unobserve(syncNodes);
|
|
368
457
|
yLevelLayouts.unobserve(syncLevelLayouts);
|
|
369
458
|
yEdges.unobserve(syncEdges);
|
|
370
459
|
yMeta.unobserve(syncMeta);
|
|
460
|
+
yHotspots.unobserve(syncHotspots);
|
|
371
461
|
doc.off('afterTransaction', reopenOnEdit);
|
|
372
462
|
doc.off('afterTransaction', syncStructure);
|
|
373
463
|
presenceStore.destroy();
|
|
@@ -535,11 +625,11 @@ export function useStudioFlow({
|
|
|
535
625
|
[]
|
|
536
626
|
);
|
|
537
627
|
|
|
538
|
-
/** Add a node of a
|
|
628
|
+
/** Add a node of a component (its type, or its key in the components list), centred on a canvas position (in a container, if asked) */
|
|
539
629
|
const addNode = useCallback(
|
|
540
|
-
(
|
|
541
|
-
const definition = getNodeDefinition(
|
|
542
|
-
return definition ? insertNode(type, definition.createData(), center, undefined, inside) : undefined;
|
|
630
|
+
(component: string, center: XYPosition, inside?: string) => {
|
|
631
|
+
const definition = getNodeDefinition(component);
|
|
632
|
+
return definition ? insertNode(definition.type, definition.createData(), center, undefined, inside) : undefined;
|
|
543
633
|
},
|
|
544
634
|
[insertNode]
|
|
545
635
|
);
|
|
@@ -575,13 +665,12 @@ export function useStudioFlow({
|
|
|
575
665
|
[]
|
|
576
666
|
);
|
|
577
667
|
|
|
578
|
-
/** Puts
|
|
579
|
-
const
|
|
580
|
-
(
|
|
668
|
+
/** Puts nodes in containers (or takes them out) together, e.g. a selection dropped in one, as one change */
|
|
669
|
+
const setParents = useCallback(
|
|
670
|
+
(moves: { id: string; parentId: string | undefined; at?: XYPosition }[]) =>
|
|
581
671
|
withDoc((doc) => {
|
|
582
|
-
// Where it was dropped goes first, so it's moved into (or out of) the container from there
|
|
583
672
|
if (dragged.current.size) saveDrop(doc);
|
|
584
|
-
return
|
|
673
|
+
return setNodeParents(doc, moves, renderedSizes());
|
|
585
674
|
}),
|
|
586
675
|
[]
|
|
587
676
|
);
|
|
@@ -605,6 +694,47 @@ export function useStudioFlow({
|
|
|
605
694
|
sharedRef.current?.sender.send({ viewport });
|
|
606
695
|
}, []);
|
|
607
696
|
|
|
697
|
+
/**
|
|
698
|
+
* The note or text being changed here, as it changes (what's written, a text's size and angle), for the others to
|
|
699
|
+
* see as it happens; `null` when done (after the result is saved), sent straight away
|
|
700
|
+
*/
|
|
701
|
+
const sendEditing = useCallback((editing: Editing | null) => {
|
|
702
|
+
const sender = sharedRef.current?.sender;
|
|
703
|
+
if (!sender) return;
|
|
704
|
+
sender.send({ editing: editing && { ...editing, ...(editing.text !== undefined && { text: editing.text.slice(0, 2000) }) } });
|
|
705
|
+
if (!editing) sender.flush();
|
|
706
|
+
}, []);
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* What a diagram file opened into this (new) canvas starts with: its nodes and connections, comments, hotspots and
|
|
710
|
+
* where things are on L1 and L2, in one change that isn't undone
|
|
711
|
+
*/
|
|
712
|
+
const addContent = useCallback(
|
|
713
|
+
(content: ModelCanvasContent) =>
|
|
714
|
+
withDoc((doc) => {
|
|
715
|
+
addNodes(doc, content.nodes, content.edges);
|
|
716
|
+
if (content.threads?.length) addThreads(doc, content.threads);
|
|
717
|
+
if (content.hotspots?.length) addHotspots(doc, content.hotspots);
|
|
718
|
+
setLevelPlaces(doc, 1, content.levelLayouts[1]);
|
|
719
|
+
setLevelPlaces(doc, 2, content.levelLayouts[2]);
|
|
720
|
+
}, OPEN_ORIGIN),
|
|
721
|
+
[]
|
|
722
|
+
);
|
|
723
|
+
|
|
724
|
+
// ---- Hotspots and the lock ----
|
|
725
|
+
|
|
726
|
+
const createHotspot = useCallback(
|
|
727
|
+
(hotspot: Omit<Hotspot, 'id' | 'createdAt'>) => withDoc((doc) => createHotspotInDoc(doc, hotspot)),
|
|
728
|
+
[]
|
|
729
|
+
);
|
|
730
|
+
const updateHotspot = useCallback(
|
|
731
|
+
(id: string, fields: Parameters<typeof updateHotspotInDoc>[2]) => withDoc((doc) => updateHotspotInDoc(doc, id, fields)),
|
|
732
|
+
[]
|
|
733
|
+
);
|
|
734
|
+
const deleteHotspot = useCallback((id: string) => withDoc((doc) => deleteHotspotInDoc(doc, id)), []);
|
|
735
|
+
/** Locks the canvas for everyone (by a name), or unlocks it (null): looked at, not changed */
|
|
736
|
+
const setLocked = useCallback((by: string | null) => withDoc((doc) => setLock(doc, by), STATUS_ORIGIN), []);
|
|
737
|
+
|
|
608
738
|
return {
|
|
609
739
|
nodes,
|
|
610
740
|
edges,
|
|
@@ -625,7 +755,7 @@ export function useStudioFlow({
|
|
|
625
755
|
levelLayouts,
|
|
626
756
|
setLevelLayout,
|
|
627
757
|
insertNodes,
|
|
628
|
-
|
|
758
|
+
setParents,
|
|
629
759
|
title,
|
|
630
760
|
diagramLevels,
|
|
631
761
|
structureKey,
|
|
@@ -634,6 +764,15 @@ export function useStudioFlow({
|
|
|
634
764
|
changeStatus,
|
|
635
765
|
transport,
|
|
636
766
|
deleted,
|
|
767
|
+
hotspots,
|
|
768
|
+
createHotspot,
|
|
769
|
+
updateHotspot,
|
|
770
|
+
deleteHotspot,
|
|
771
|
+
lockedBy,
|
|
772
|
+
setLocked,
|
|
773
|
+
sendEditing,
|
|
774
|
+
synced,
|
|
775
|
+
addContent,
|
|
637
776
|
undo,
|
|
638
777
|
redo,
|
|
639
778
|
...history,
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// Who you are in Studio: the name you give on your first visit, kept in this browser and used on every canvas
|
|
2
|
-
|
|
2
|
+
/** The colours people can go by (as Studio on the web's): on their pointer, avatar and comments */
|
|
3
|
+
export const PERSON_COLORS = ['#ef4444', '#f97316', '#eab308', '#22c55e', '#06b6d4', '#3b82f6', '#8b5cf6', '#ec4899'];
|
|
4
|
+
const COLORS = PERSON_COLORS;
|
|
3
5
|
const NAME_KEY = 'eventcatalog-studio-name';
|
|
6
|
+
const COLOR_KEY = 'eventcatalog-studio-color';
|
|
4
7
|
/** Names people give are at most this long */
|
|
5
8
|
export const MAX_NAME_LENGTH = 60;
|
|
6
9
|
|
|
@@ -35,3 +38,22 @@ export const colorForName = (name: string) => {
|
|
|
35
38
|
for (const char of name) hash = (hash * 31 + char.charCodeAt(0)) | 0;
|
|
36
39
|
return COLORS[Math.abs(hash) % COLORS.length];
|
|
37
40
|
};
|
|
41
|
+
|
|
42
|
+
/** The colour you picked, kept in this browser (else it follows from your name) */
|
|
43
|
+
export const getStoredColor = () => {
|
|
44
|
+
try {
|
|
45
|
+
const color = localStorage.getItem(COLOR_KEY);
|
|
46
|
+
return color && PERSON_COLORS.includes(color) ? color : null;
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const storeColor = (color: string) => {
|
|
53
|
+
try {
|
|
54
|
+
localStorage.setItem(COLOR_KEY, color);
|
|
55
|
+
return true;
|
|
56
|
+
} catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
// Visualiser styles ship in the page head so ClientRouter navigations keep them (see NodeGraph.astro).
|
|
3
3
|
import '@eventcatalog/visualiser/styles-core.css';
|
|
4
|
-
import '
|
|
4
|
+
import '@eventcatalog/visualiser/studio.css';
|
|
5
|
+
import '@eventcatalog/visualiser/studio-fonts.css';
|
|
5
6
|
import VerticalSideBarLayout from '@layouts/VerticalSideBarLayout.astro';
|
|
6
7
|
import { buildUrl } from '@utils/url-builder';
|
|
7
8
|
import StudioDesigner from '../components/StudioDesigner';
|
|
@@ -4,10 +4,9 @@ import { ChevronDown, ChevronLeft, ChevronRight, Copy, EllipsisVertical, Pencil,
|
|
|
4
4
|
import VerticalSideBarLayout from '@layouts/VerticalSideBarLayout.astro';
|
|
5
5
|
import StudioNotice from '../components/StudioNotice.astro';
|
|
6
6
|
import { buildUrl } from '@utils/url-builder';
|
|
7
|
-
import { CANVAS_STATUSES, type CanvasStatus } from '
|
|
7
|
+
import { CANVAS_STATUSES, CANVAS_STATUS_LOOK, STATUS, type CanvasStatus } from '@eventcatalog/visualiser/studio';
|
|
8
8
|
import { MAX_TITLE_LENGTH } from '../limits';
|
|
9
9
|
import { MAX_NAME_LENGTH } from '../identity';
|
|
10
|
-
import { CANVAS_STATUS_LOOK, STATUS } from '../components/status';
|
|
11
10
|
import { CANVASES_API_PATH } from '../api/canvases-api';
|
|
12
11
|
import { startStudio, type CanvasSummary } from '../server/runtime';
|
|
13
12
|
import { getSignedInUser, isSignedIn } from '../server/sign-in';
|
|
@@ -21,10 +21,10 @@ import {
|
|
|
21
21
|
setCanvasStatus,
|
|
22
22
|
setThreadResolved,
|
|
23
23
|
type Author,
|
|
24
|
-
|
|
24
|
+
getLayoutPositions,
|
|
25
|
+
} from '@eventcatalog/visualiser/studio';
|
|
25
26
|
import { ADD_TO_CANVAS_DESCRIPTION, describeCanvas, edgeSpecsSchema, indexCatalog, nodeSpecsSchema } from '../canvas-actions';
|
|
26
27
|
import { getCatalogResources } from '../catalog-resources';
|
|
27
|
-
import { getLayoutPositions } from '../layout';
|
|
28
28
|
import {
|
|
29
29
|
playAddToCanvas,
|
|
30
30
|
playComment,
|
|
@@ -35,7 +35,8 @@ import {
|
|
|
35
35
|
playUpdateNode,
|
|
36
36
|
type AgentStage,
|
|
37
37
|
} from '../agent-choreography';
|
|
38
|
-
import { STUDIO_SOCKET_PATH, findStudioRuntime
|
|
38
|
+
import { STUDIO_SOCKET_PATH, findStudioRuntime } from './runtime';
|
|
39
|
+
import { getAgent } from '../agents';
|
|
39
40
|
import { addToolIcons } from './tool-icons';
|
|
40
41
|
|
|
41
42
|
/**
|
|
@@ -46,21 +47,11 @@ import { addToolIcons } from './tool-icons';
|
|
|
46
47
|
|
|
47
48
|
const loadCanvasView = getMcpAppViewLoader('canvas');
|
|
48
49
|
|
|
49
|
-
const AGENT_COLORS: Record<string, string> = { ChatGPT: '#10a37f', Claude: '#d97757' };
|
|
50
|
-
|
|
51
50
|
// The canvases entrypoint's icon (OpenAI MCP extensions: a 20x20, monochrome SVG in currentColor, 1.33px strokes)
|
|
52
51
|
const CANVAS_ICON_SVG =
|
|
53
52
|
'<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"><rect x="2.5" y="3.5" width="5.5" height="4.5" rx="1"/><rect x="12" y="3.5" width="5.5" height="4.5" rx="1"/><rect x="12" y="12" width="5.5" height="4.5" rx="1"/><path d="M8 5.75h4M14.75 8v4"/></svg>';
|
|
54
53
|
const CANVAS_ICON = { src: `data:image/svg+xml;base64,${btoa(CANVAS_ICON_SVG)}`, mimeType: 'image/svg+xml', sizes: ['20x20'] };
|
|
55
54
|
|
|
56
|
-
/** Who the agent is, from what it tells us or its client's user agent */
|
|
57
|
-
const getAgent = (agentName: string | undefined, userAgent = ''): AgentIdentity => {
|
|
58
|
-
const name =
|
|
59
|
-
agentName?.trim() ||
|
|
60
|
-
(/openai|chatgpt/i.test(userAgent) ? 'ChatGPT' : /claude|anthropic/i.test(userAgent) ? 'Claude' : 'AI agent');
|
|
61
|
-
return { name, color: AGENT_COLORS[name] ?? '#8b5cf6' };
|
|
62
|
-
};
|
|
63
|
-
|
|
64
55
|
const text = (value: unknown, isError = false) => ({
|
|
65
56
|
content: [{ type: 'text' as const, text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }],
|
|
66
57
|
...(isError && { isError }),
|
|
@@ -151,12 +142,6 @@ export function registerCanvasTools(server: McpServer, { catalogUrl, userAgent }
|
|
|
151
142
|
input: {
|
|
152
143
|
canvasId: canvasId.optional(),
|
|
153
144
|
title: z.string().optional().describe('For a new canvas: what it is for'),
|
|
154
|
-
userName: z
|
|
155
|
-
.string()
|
|
156
|
-
.optional()
|
|
157
|
-
.describe(
|
|
158
|
-
'The name of the person you are working with, if you know it: others on the canvas see them by this name. Leave it out if you do not know it.'
|
|
159
|
-
),
|
|
160
145
|
agentName,
|
|
161
146
|
},
|
|
162
147
|
},
|
|
@@ -170,7 +155,7 @@ export function registerCanvasTools(server: McpServer, { catalogUrl, userAgent }
|
|
|
170
155
|
annotations: writes,
|
|
171
156
|
errorMessage: 'Failed to open the canvas',
|
|
172
157
|
load: async (params) => {
|
|
173
|
-
const { title: newTitle, agentName: name
|
|
158
|
+
const { title: newTitle, agentName: name } = params as { title?: string; agentName?: string };
|
|
174
159
|
|
|
175
160
|
// Opened without a canvas (e.g. from an entrypoint): the canvases to pick from, or start one
|
|
176
161
|
if (!params.canvasId && !newTitle) {
|
|
@@ -207,7 +192,7 @@ export function registerCanvasTools(server: McpServer, { catalogUrl, userAgent }
|
|
|
207
192
|
socketUrl,
|
|
208
193
|
canvasUrl: canvasUrl(id),
|
|
209
194
|
mcpUrl: `${catalogUrl}${buildUrl('/docs/mcp', true)}`,
|
|
210
|
-
|
|
195
|
+
agent: getAgent(name, userAgent),
|
|
211
196
|
};
|
|
212
197
|
return {
|
|
213
198
|
forModel: {
|
|
@@ -418,7 +403,7 @@ export function registerCanvasTools(server: McpServer, { catalogUrl, userAgent }
|
|
|
418
403
|
{
|
|
419
404
|
title: 'Tidy a canvas',
|
|
420
405
|
description:
|
|
421
|
-
"Lays the whole canvas out again with EventCatalog's visualiser layout (left to right, connections routed around nodes); everyone on it sees the nodes glide into place.
|
|
406
|
+
"Lays the whole canvas out again with EventCatalog's visualiser layout (left to right, connections routed around nodes); everyone on it sees the nodes glide into place. Call it once when you finish a round of changes, before you hand back to the user, so the canvas is tidy for everyone, and whenever the user asks to tidy up. Don't call it after every change (addToCanvas already places what you add next to what it connects to while you work), and skip it if the user asked to keep the layout as it is: it moves everything, including what people placed themselves.",
|
|
422
407
|
inputSchema: z.object({ canvasId, agentName }),
|
|
423
408
|
annotations: writes,
|
|
424
409
|
},
|
|
@@ -5,17 +5,15 @@ import { Awareness, applyAwarenessUpdate, encodeAwarenessUpdate, removeAwareness
|
|
|
5
5
|
import type { XYPosition } from '@xyflow/react';
|
|
6
6
|
import type { AgentPresence } from '../agent-choreography';
|
|
7
7
|
import {
|
|
8
|
-
CANVAS_DELETED_REASON,
|
|
9
8
|
addCanvasContent,
|
|
10
|
-
canvasDocumentName,
|
|
11
|
-
canvasIdFromDocumentName,
|
|
12
9
|
getCanvasMaps,
|
|
13
10
|
readMeta,
|
|
14
11
|
setMeta,
|
|
15
12
|
getCanvasStatus,
|
|
16
13
|
type CanvasContent,
|
|
17
14
|
type CanvasStatus,
|
|
18
|
-
} from '
|
|
15
|
+
} from '@eventcatalog/visualiser/studio';
|
|
16
|
+
import { CANVAS_DELETED_REASON, canvasDocumentName, canvasIdFromDocumentName } from '../document-name';
|
|
19
17
|
import { getCanvasPreview, type CanvasPreview } from '../canvas-preview';
|
|
20
18
|
import { MAX_TITLE_LENGTH } from '../limits';
|
|
21
19
|
import { fromBase64, toBase64, type SyncRequest, type SyncResponse } from '../tool-sync';
|
|
@@ -33,7 +31,8 @@ const AGENT_PRESENCE_TTL_MS = 25_000;
|
|
|
33
31
|
// Application close codes are 4000-4999
|
|
34
32
|
const CANVAS_DELETED_CLOSE_CODE = 4410;
|
|
35
33
|
|
|
36
|
-
|
|
34
|
+
import type { AgentIdentity } from '../agents';
|
|
35
|
+
export type { AgentIdentity };
|
|
37
36
|
export type CanvasSummary = {
|
|
38
37
|
canvasId: string;
|
|
39
38
|
title?: string;
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"license": "BUSL-1.1",
|
|
9
9
|
"type": "module",
|
|
10
|
-
"version": "5.0.0-beta.
|
|
10
|
+
"version": "5.0.0-beta.13",
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
@@ -141,8 +141,8 @@
|
|
|
141
141
|
"yjs": "^13.6.33",
|
|
142
142
|
"zod": "^4.3.6",
|
|
143
143
|
"@eventcatalog/linter": "1.2.0-beta.2",
|
|
144
|
-
"@eventcatalog/
|
|
145
|
-
"@eventcatalog/
|
|
144
|
+
"@eventcatalog/visualiser": "^5.0.0-beta.6",
|
|
145
|
+
"@eventcatalog/sdk": "2.30.0-beta.1"
|
|
146
146
|
},
|
|
147
147
|
"devDependencies": {
|
|
148
148
|
"@astrojs/check": "^0.9.10",
|