vzcode 1.41.0 → 1.43.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.
package/dist/index.html CHANGED
@@ -20,8 +20,8 @@
20
20
  href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap"
21
21
  rel="stylesheet"
22
22
  />
23
- <script type="module" crossorigin src="/assets/index-DPAeYEiH.js"></script>
24
- <link rel="stylesheet" crossorigin href="/assets/index-DKLiE98a.css">
23
+ <script type="module" crossorigin src="/assets/index-B83lKfVG.js"></script>
24
+ <link rel="stylesheet" crossorigin href="/assets/index-CP9o_hN5.css">
25
25
  </head>
26
26
  <body>
27
27
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vzcode",
3
- "version": "1.41.0",
3
+ "version": "1.43.0",
4
4
  "description": "Multiplayer code editor system",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -140,14 +140,14 @@
140
140
  "body-parser": "^2.2.0",
141
141
  "codemirror": "^6.0.2",
142
142
  "codemirror-copilot": "^0.0.7",
143
- "codemirror-ot": "^4.6.0",
143
+ "codemirror-ot": "^4.7.0",
144
144
  "color-hash": "^2.0.2",
145
145
  "comlink": "^4.4.2",
146
146
  "d3-array": "^3.2.4",
147
147
  "diff-match-patch": "^1.0.5",
148
148
  "dotenv": "^17.2.1",
149
149
  "editcodewithai": "^2.3.0",
150
- "eslint-linter-browserify": "^9.31.0",
150
+ "eslint-linter-browserify": "^9.32.0",
151
151
  "express": "^5.1.0",
152
152
  "ignore": "^7.0.5",
153
153
  "json0-ot-diff": "^1.1.2",
@@ -175,7 +175,7 @@
175
175
  "@types/react-dom": "^18",
176
176
  "@vitejs/plugin-react": "^4.7.0",
177
177
  "concurrently": "^9.2.0",
178
- "cross-env": "^7.0.3",
178
+ "cross-env": "^10.0.0",
179
179
  "eslint": "^9.32.0",
180
180
  "globals": "^16.3.0",
181
181
  "npm-check-updates": "^18.0.2",
@@ -187,7 +187,7 @@
187
187
  "vitest": "^3.2.4"
188
188
  },
189
189
  "optionalDependencies": {
190
- "@rollup/rollup-darwin-arm64": "^4.45.1",
191
- "@rollup/rollup-win32-x64-msvc": "^4.45.1"
190
+ "@rollup/rollup-darwin-arm64": "^4.46.1",
191
+ "@rollup/rollup-win32-x64-msvc": "^4.46.1"
192
192
  }
193
193
  }
@@ -70,12 +70,12 @@ body,
70
70
  }
71
71
 
72
72
  .right {
73
+ display: flex;
73
74
  flex: 1;
74
75
  background-color: white;
75
76
 
76
77
  iframe {
77
- width: 100%;
78
- height: 100%;
78
+ flex: 1;
79
79
  border: none;
80
80
  }
81
81
  }
@@ -41,6 +41,12 @@ export const json1PresenceDisplay = ({
41
41
  //Added variable for cursor position
42
42
  cursorPosition = {};
43
43
 
44
+ // Flag to prevent multiple pending updates
45
+ pendingUpdate = false;
46
+
47
+ // Flag to prevent multiple pending scroll updates
48
+ pendingScrollUpdate = false;
49
+
44
50
  constructor(view: EditorView) {
45
51
  // Initialize decorations to empty array so CodeMirror doesn't crash.
46
52
  this.decorations = RangeSet.of([]);
@@ -146,13 +152,22 @@ export const json1PresenceDisplay = ({
146
152
  true,
147
153
  );
148
154
 
149
- // This dispatch triggers the re-rendering of decorations.
150
- // It now correctly runs after a deletion.
151
- setTimeout(() => {
152
- view.dispatch({
153
- annotations: [presenceAnnotation.of(true)],
155
+ // Safely dispatch decoration updates without causing race conditions
156
+ // Use requestAnimationFrame to ensure we're not in the middle of an update
157
+ if (!this.pendingUpdate) {
158
+ this.pendingUpdate = true;
159
+ requestAnimationFrame(() => {
160
+ this.pendingUpdate = false;
161
+ // Check if view is still valid and not currently updating
162
+ if (view.state && view.dom.isConnected) {
163
+ view.dispatch({
164
+ annotations: [
165
+ presenceAnnotation.of(true),
166
+ ],
167
+ });
168
+ }
154
169
  });
155
- }, 0);
170
+ }
156
171
 
157
172
  if (enableAutoFollowRef.current) {
158
173
  this.scrollToCursor(view);
@@ -162,12 +177,25 @@ export const json1PresenceDisplay = ({
162
177
  }
163
178
  // Method to scroll the view to keep the cursor in view
164
179
  scrollToCursor(view) {
165
- for (const id in this.cursorPosition) {
166
- //getting the cursor position of the other cursor
167
- const cursorPos = this.cursorPosition[id];
168
- view.dispatch({
169
- //if the other person's cursor has jumped off screen, we will follow it by scrolling there directly.
170
- effects: EditorView.scrollIntoView(cursorPos),
180
+ // Debounce scroll updates to prevent conflicts with typing
181
+ if (!this.pendingScrollUpdate) {
182
+ this.pendingScrollUpdate = true;
183
+ requestAnimationFrame(() => {
184
+ this.pendingScrollUpdate = false;
185
+ // Check if view is still valid
186
+ if (view.state && view.dom.isConnected) {
187
+ for (const id in this.cursorPosition) {
188
+ //getting the cursor position of the other cursor
189
+ const cursorPos = this.cursorPosition[id];
190
+ view.dispatch({
191
+ //if the other person's cursor has jumped off screen, we will follow it by scrolling there directly.
192
+ effects:
193
+ EditorView.scrollIntoView(cursorPos),
194
+ });
195
+ // Only scroll to the first cursor to avoid multiple dispatches
196
+ break;
197
+ }
198
+ }
171
199
  });
172
200
  }
173
201
  }
@@ -1,4 +1,9 @@
1
- import { useContext, useEffect, useMemo } from 'react';
1
+ import {
2
+ useContext,
3
+ useEffect,
4
+ useMemo,
5
+ useRef,
6
+ } from 'react';
2
7
  import {
3
8
  createRuntime,
4
9
  VizHubRuntime,
@@ -9,12 +14,11 @@ import { vizFilesToFileCollection } from '@vizhub/viz-utils';
9
14
 
10
15
  const enableIframe = true;
11
16
 
12
- // Singleton runtime instance
13
- // TODO use refs, and cleanup on unmount
14
- let runtime: VizHubRuntime = null;
15
- let isFirstRun = true;
16
-
17
17
  export const VZRight = () => {
18
+ const iframeRef = useRef<HTMLIFrameElement>(null);
19
+ const runtimeRef = useRef<VizHubRuntime | null>(null);
20
+ const isFirstRunRef = useRef(true);
21
+
18
22
  // Get access to the current files.
19
23
  const { content } = useContext(VZCodeContext);
20
24
 
@@ -32,13 +36,10 @@ export const VZRight = () => {
32
36
  if (!files) return;
33
37
 
34
38
  // Initialize the runtime only once
35
- if (!runtime) {
39
+ if (!runtimeRef.current && iframeRef.current) {
36
40
  const worker = new BuildWorker();
37
- const iframe = document.getElementById(
38
- 'viz-iframe',
39
- ) as HTMLIFrameElement;
40
- runtime = createRuntime({
41
- iframe,
41
+ runtimeRef.current = createRuntime({
42
+ iframe: iframeRef.current,
42
43
  worker,
43
44
  setBuildErrorMessage: (error) => {
44
45
  if (error) {
@@ -49,14 +50,14 @@ export const VZRight = () => {
49
50
  }
50
51
 
51
52
  // Run code in the iframe
52
- if (isFirstRun || isInteracting) {
53
- runtime.run({
53
+ if (isFirstRunRef.current || isInteracting) {
54
+ runtimeRef.current?.run({
54
55
  files,
55
56
  enableHotReloading: true,
56
57
  enableSourcemap: true,
57
58
  vizId: 'example-viz',
58
59
  });
59
- isFirstRun = false;
60
+ isFirstRunRef.current = false;
60
61
  }
61
62
 
62
63
  // TODO use refs, and cleanup on unmount
@@ -69,7 +70,7 @@ export const VZRight = () => {
69
70
  return (
70
71
  <div className="right">
71
72
  {enableIframe ? (
72
- <iframe id="viz-iframe"></iframe>
73
+ <iframe ref={iframeRef}></iframe>
73
74
  ) : null}
74
75
  </div>
75
76
  );
@@ -5,29 +5,18 @@ import {
5
5
  memo,
6
6
  } from 'react';
7
7
  import { Message } from './Message';
8
- import { StreamingMessage } from './StreamingMessage';
9
8
  import { TypingIndicator } from './TypingIndicator';
10
-
11
- interface MessageData {
12
- id: string;
13
- role: 'user' | 'assistant';
14
- content: string;
15
- timestamp: number;
16
- }
17
-
18
- interface MessageListProps {
19
- messages: MessageData[];
20
- aiScratchpad?: string;
21
- aiStatus?: string;
22
- isLoading: boolean;
23
- }
9
+ import { VizChatMessage } from '@vizhub/viz-types';
24
10
 
25
11
  const MessageListComponent = ({
26
12
  messages,
27
- aiScratchpad,
28
13
  aiStatus,
29
14
  isLoading,
30
- }: MessageListProps) => {
15
+ }: {
16
+ messages: VizChatMessage[];
17
+ aiStatus?: string;
18
+ isLoading: boolean;
19
+ }) => {
31
20
  const messagesEndRef = useRef<HTMLDivElement>(null);
32
21
 
33
22
  const scrollToBottom = useCallback(() => {
@@ -38,7 +27,17 @@ const MessageListComponent = ({
38
27
 
39
28
  useEffect(() => {
40
29
  scrollToBottom();
41
- }, [messages, aiScratchpad]);
30
+ }, [messages]);
31
+
32
+ // Check if AI generation has started (last message is from assistant)
33
+ const lastMessage = messages[messages.length - 1];
34
+ const aiGenerationStarted =
35
+ lastMessage?.role === 'assistant' &&
36
+ lastMessage.content !== '';
37
+
38
+ // Show typing indicator only when loading and AI generation hasn't started yet
39
+ const showTypingIndicator =
40
+ isLoading && !aiGenerationStarted;
42
41
 
43
42
  return (
44
43
  <div className="ai-chat-messages">
@@ -52,15 +51,7 @@ const MessageListComponent = ({
52
51
  />
53
52
  ))}
54
53
 
55
- {/* Show streaming content if available */}
56
- {aiScratchpad && (
57
- <StreamingMessage
58
- content={aiScratchpad}
59
- status={aiStatus}
60
- />
61
- )}
62
-
63
- {isLoading && !aiScratchpad && <TypingIndicator />}
54
+ {showTypingIndicator && <TypingIndicator />}
64
55
  <div ref={messagesEndRef} />
65
56
  </div>
66
57
  );
@@ -27,7 +27,6 @@ export const AIChat = () => {
27
27
  // Get current chat data from content
28
28
  const currentChat = content?.chats?.[currentChatId];
29
29
  const rawMessages = currentChat?.messages || [];
30
- const aiScratchpad = currentChat?.aiScratchpad;
31
30
  const aiStatus = currentChat?.aiStatus;
32
31
 
33
32
  // Transform messages to ensure they have required id field - memoized to avoid recreation
@@ -87,7 +86,6 @@ export const AIChat = () => {
87
86
  <div className="ai-chat-container">
88
87
  <MessageList
89
88
  messages={messages}
90
- aiScratchpad={aiScratchpad}
91
89
  aiStatus={aiStatus}
92
90
  isLoading={isLoading}
93
91
  />
@@ -17,7 +17,14 @@ export const createAICopyPasteHandlers = (
17
17
  ) => {
18
18
  // Copy for AI - formats all files and copies to clipboard
19
19
  const handleCopyForAI = async () => {
20
- if (!files) return;
20
+ if (!files || Object.keys(files).length === 0) {
21
+ setCopyButtonText('No files to copy');
22
+ setTimeout(
23
+ () => setCopyButtonText('Copy for AI'),
24
+ 2000,
25
+ );
26
+ return;
27
+ }
21
28
 
22
29
  try {
23
30
  const fileCollection =
@@ -546,15 +546,17 @@ export const VZSidebar = ({
546
546
  )}
547
547
  </div>
548
548
  </div>
549
- {!isAIChatOpen && !isSearchOpen && filesExist && (
549
+ {!isAIChatOpen && !isSearchOpen && (
550
550
  <div className="ai-buttons">
551
- <button
552
- className="ai-button copy-button"
553
- onClick={handleCopyForAI}
554
- title="Copy files formatted for AI"
555
- >
556
- {copyButtonText}
557
- </button>
551
+ {filesExist && (
552
+ <button
553
+ className="ai-button copy-button"
554
+ onClick={handleCopyForAI}
555
+ title="Copy files formatted for AI"
556
+ >
557
+ {copyButtonText}
558
+ </button>
559
+ )}
558
560
  <button
559
561
  className="ai-button paste-button"
560
562
  onClick={handlePasteForAI}
@@ -0,0 +1,250 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { setSearchResultsReducer } from './searchReducer';
3
+ import { VZAction, VZState, createInitialState } from '.';
4
+ import { defaultTheme } from '../themes';
5
+
6
+ // Mock ShareDBDoc for testing
7
+ const createMockShareDBDoc = (files: any) => ({
8
+ data: { files },
9
+ });
10
+
11
+ const initialState: VZState = createInitialState({
12
+ defaultTheme,
13
+ });
14
+
15
+ describe('searchReducer', () => {
16
+ describe('setSearchResultsReducer', () => {
17
+ it('should find case-insensitive matches', () => {
18
+ const mockFiles = {
19
+ 'file1.js': {
20
+ name: 'file1.js',
21
+ text: 'const Hello = "world";\nconst goodbye = "HELLO";',
22
+ },
23
+ 'file2.js': {
24
+ name: 'file2.js',
25
+ text: 'function hello() {\n return "Hello World";\n}',
26
+ },
27
+ };
28
+
29
+ const stateWithSearch = {
30
+ ...initialState,
31
+ search: {
32
+ ...initialState.search,
33
+ pattern: 'hello', // lowercase search pattern
34
+ },
35
+ };
36
+
37
+ const action: VZAction = {
38
+ type: 'set_search_results',
39
+ files: createMockShareDBDoc(mockFiles),
40
+ };
41
+
42
+ const newState = setSearchResultsReducer(
43
+ stateWithSearch,
44
+ action,
45
+ );
46
+
47
+ // Should find matches in both files despite different cases
48
+ expect(
49
+ Object.keys(newState.search.results),
50
+ ).toHaveLength(2);
51
+
52
+ // Check file1.js results
53
+ const file1Results =
54
+ newState.search.results['file1.js'];
55
+ expect(file1Results).toBeDefined();
56
+ expect(file1Results.matches).toHaveLength(2); // "Hello" and "HELLO"
57
+ expect(file1Results.matches[0].line).toBe(1);
58
+ expect(file1Results.matches[0].index).toBe(6); // position of "Hello"
59
+ expect(file1Results.matches[1].line).toBe(2);
60
+ expect(file1Results.matches[1].index).toBe(18); // position of "HELLO"
61
+
62
+ // Check file2.js results
63
+ const file2Results =
64
+ newState.search.results['file2.js'];
65
+ expect(file2Results).toBeDefined();
66
+ expect(file2Results.matches).toHaveLength(2); // "hello" and "Hello"
67
+ expect(file2Results.matches[0].line).toBe(1);
68
+ expect(file2Results.matches[0].index).toBe(9); // position of "hello"
69
+ expect(file2Results.matches[1].line).toBe(2);
70
+ expect(file2Results.matches[1].index).toBe(10); // position of "Hello"
71
+ });
72
+
73
+ it('should find exact case matches', () => {
74
+ const mockFiles = {
75
+ 'file1.js': {
76
+ name: 'file1.js',
77
+ text: 'const hello = "world";',
78
+ },
79
+ };
80
+
81
+ const stateWithSearch = {
82
+ ...initialState,
83
+ search: {
84
+ ...initialState.search,
85
+ pattern: 'hello', // exact case match
86
+ },
87
+ };
88
+
89
+ const action: VZAction = {
90
+ type: 'set_search_results',
91
+ files: createMockShareDBDoc(mockFiles),
92
+ };
93
+
94
+ const newState = setSearchResultsReducer(
95
+ stateWithSearch,
96
+ action,
97
+ );
98
+
99
+ expect(
100
+ Object.keys(newState.search.results),
101
+ ).toHaveLength(1);
102
+ const file1Results =
103
+ newState.search.results['file1.js'];
104
+ expect(file1Results.matches).toHaveLength(1);
105
+ expect(file1Results.matches[0].line).toBe(1);
106
+ expect(file1Results.matches[0].index).toBe(6);
107
+ });
108
+
109
+ it('should not find matches when pattern does not exist', () => {
110
+ const mockFiles = {
111
+ 'file1.js': {
112
+ name: 'file1.js',
113
+ text: 'const world = "test";',
114
+ },
115
+ };
116
+
117
+ const stateWithSearch = {
118
+ ...initialState,
119
+ search: {
120
+ ...initialState.search,
121
+ pattern: 'hello',
122
+ },
123
+ };
124
+
125
+ const action: VZAction = {
126
+ type: 'set_search_results',
127
+ files: createMockShareDBDoc(mockFiles),
128
+ };
129
+
130
+ const newState = setSearchResultsReducer(
131
+ stateWithSearch,
132
+ action,
133
+ );
134
+
135
+ expect(
136
+ Object.keys(newState.search.results),
137
+ ).toHaveLength(0);
138
+ });
139
+
140
+ it('should handle uppercase search pattern', () => {
141
+ const mockFiles = {
142
+ 'file1.js': {
143
+ name: 'file1.js',
144
+ text: 'const Hello = "world";\nfunction hello() {}',
145
+ },
146
+ };
147
+
148
+ const stateWithSearch = {
149
+ ...initialState,
150
+ search: {
151
+ ...initialState.search,
152
+ pattern: 'HELLO', // uppercase search pattern
153
+ },
154
+ };
155
+
156
+ const action: VZAction = {
157
+ type: 'set_search_results',
158
+ files: createMockShareDBDoc(mockFiles),
159
+ };
160
+
161
+ const newState = setSearchResultsReducer(
162
+ stateWithSearch,
163
+ action,
164
+ );
165
+
166
+ expect(
167
+ Object.keys(newState.search.results),
168
+ ).toHaveLength(1);
169
+ const file1Results =
170
+ newState.search.results['file1.js'];
171
+ expect(file1Results.matches).toHaveLength(2); // Should find both "Hello" and "hello"
172
+ });
173
+
174
+ it('should handle mixed case search pattern', () => {
175
+ const mockFiles = {
176
+ 'file1.js': {
177
+ name: 'file1.js',
178
+ text: 'const HeLLo = "world";\nconst hello = "test";\nconst HELLO = "end";',
179
+ },
180
+ };
181
+
182
+ const stateWithSearch = {
183
+ ...initialState,
184
+ search: {
185
+ ...initialState.search,
186
+ pattern: 'HeLLo', // mixed case search pattern
187
+ },
188
+ };
189
+
190
+ const action: VZAction = {
191
+ type: 'set_search_results',
192
+ files: createMockShareDBDoc(mockFiles),
193
+ };
194
+
195
+ const newState = setSearchResultsReducer(
196
+ stateWithSearch,
197
+ action,
198
+ );
199
+
200
+ expect(
201
+ Object.keys(newState.search.results),
202
+ ).toHaveLength(1);
203
+ const file1Results =
204
+ newState.search.results['file1.js'];
205
+ expect(file1Results.matches).toHaveLength(3); // Should find all three variations
206
+ });
207
+
208
+ it('should skip files without text content', () => {
209
+ const mockFiles = {
210
+ 'file1.js': {
211
+ name: 'file1.js',
212
+ text: 'const hello = "world";',
213
+ },
214
+ 'file2.js': {
215
+ name: 'file2.js',
216
+ // no text property
217
+ },
218
+ };
219
+
220
+ const stateWithSearch = {
221
+ ...initialState,
222
+ search: {
223
+ ...initialState.search,
224
+ pattern: 'hello',
225
+ },
226
+ };
227
+
228
+ const action: VZAction = {
229
+ type: 'set_search_results',
230
+ files: createMockShareDBDoc(mockFiles),
231
+ };
232
+
233
+ const newState = setSearchResultsReducer(
234
+ stateWithSearch,
235
+ action,
236
+ );
237
+
238
+ // Should only find results in file1.js
239
+ expect(
240
+ Object.keys(newState.search.results),
241
+ ).toHaveLength(1);
242
+ expect(
243
+ newState.search.results['file1.js'],
244
+ ).toBeDefined();
245
+ expect(
246
+ newState.search.results['file2.js'],
247
+ ).toBeUndefined();
248
+ });
249
+ });
250
+ });
@@ -24,7 +24,9 @@ function searchPattern(
24
24
  const matches = [];
25
25
 
26
26
  for (let j = 0; j < lines.length; j++) {
27
- const index = lines[j].indexOf(pattern);
27
+ const index = lines[j]
28
+ .toLowerCase()
29
+ .indexOf(pattern.toLowerCase());
28
30
 
29
31
  if (index !== -1) {
30
32
  matches.push({
@@ -2,6 +2,8 @@
2
2
  // This file is the central point where the OT types are imported.
3
3
  // Localized to one file so it's easy to change it in future.
4
4
  import OTJSON1Presence from 'sharedb-client-browser/dist/ot-json1-presence-umd.cjs';
5
+ import jsondiff from 'json0-ot-diff';
6
+ import diffMatchPatch from 'diff-match-patch';
5
7
 
6
8
  export const { json1Presence, textUnicode } =
7
9
  OTJSON1Presence;
@@ -11,3 +13,12 @@ export const otType = json1Presence.type;
11
13
 
12
14
  // Applies an OT op to an object.
13
15
  export const apply = otType.apply;
16
+
17
+ export const diff = (a, b) =>
18
+ jsondiff(
19
+ a,
20
+ b,
21
+ diffMatchPatch,
22
+ json1Presence,
23
+ textUnicode,
24
+ );
package/src/runCode.ts CHANGED
@@ -1,31 +1,14 @@
1
1
  import { SubmitOperation } from './types';
2
2
  import { VizContent } from '@vizhub/viz-types';
3
- import { createSubmitOperation } from './submitOperation';
4
3
 
5
4
  /**
6
5
  * Creates a runCode function that triggers code execution by flashing `isInteracting` to `true`.
7
6
  * This works for both client-side (with submitOperation) and server-side (with ShareDB document).
8
7
  */
9
8
  export const createRunCodeFunction = (
10
- submitOperationOrDoc:
11
- | SubmitOperation
12
- | { data: any; submitOp: (ops: any) => void },
9
+ submitOperation: SubmitOperation,
13
10
  ) => {
14
11
  return () => {
15
- let submitOperation: SubmitOperation;
16
-
17
- // Check if this is a client-side submitOperation or server-side ShareDB document
18
- if (typeof submitOperationOrDoc === 'function') {
19
- // Client-side: already a submitOperation function
20
- submitOperation =
21
- submitOperationOrDoc as SubmitOperation;
22
- } else {
23
- // Server-side: create submitOperation from ShareDB document
24
- submitOperation = createSubmitOperation(
25
- submitOperationOrDoc,
26
- );
27
- }
28
-
29
12
  // Use the unified submitOperation approach for both client and server
30
13
  submitOperation((content: VizContent) => ({
31
14
  ...content,
@@ -39,7 +22,7 @@ export const createRunCodeFunction = (
39
22
  submitOperation(
40
23
  ({ isInteracting, ...newDocument }) => newDocument,
41
24
  );
42
- }, 0);
25
+ }, 100);
43
26
  };
44
27
  };
45
28
 
@@ -48,11 +31,9 @@ export const createRunCodeFunction = (
48
31
  * This is useful for maintaining compatibility with existing code that expects a ref.
49
32
  */
50
33
  export const createRunCodeRef = (
51
- submitOperationOrDoc:
52
- | SubmitOperation
53
- | { data: any; submitOp: (ops: any) => void },
34
+ submitOperation: SubmitOperation,
54
35
  ) => {
55
36
  return {
56
- current: createRunCodeFunction(submitOperationOrDoc),
37
+ current: createRunCodeFunction(submitOperation),
57
38
  };
58
39
  };