vzcode 2.30.0 → 2.31.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,7 +20,7 @@
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-CD0x02Rq.js"></script>
23
+ <script type="module" crossorigin src="/assets/index-BfDRE_ou.js"></script>
24
24
  <link rel="stylesheet" crossorigin href="/assets/index-Dyt9tpmu.css">
25
25
  </head>
26
26
  <body>
@@ -248,22 +248,50 @@ export const finalizeAIMessage = (shareDBDoc, chatId) => {
248
248
  shareDBDoc.submitOp(op);
249
249
  };
250
250
  /**
251
- * Adds diff data to the most recent AI message
251
+ * Adds diff data to a specific AI message by ID
252
+ * Phase 3 Fix: Use explicit message ID instead of assuming
253
+ * last message is AI, preventing bugs with rapid message sends
252
254
  */
253
- export const addDiffToAIMessage = (shareDBDoc, chatId, diffData, beforeCommitId) => {
255
+ export const addDiffToAIMessage = (shareDBDoc, chatId, diffData, beforeCommitId, // Commit ID before AI changes
256
+ messageId) => {
254
257
  const chat = shareDBDoc.data.chats[chatId];
258
+ if (!chat || !chat.messages) {
259
+ console.warn(`Chat ${chatId} not found or has no messages`);
260
+ return;
261
+ }
255
262
  const messages = [...chat.messages];
256
- // Find the most recent AI message
257
- const lastAIMessageIndex = messages.length - 1;
258
- if (lastAIMessageIndex >= 0 &&
259
- messages[lastAIMessageIndex].role === 'assistant') {
263
+ let targetMessageIndex = -1;
264
+ // Phase 3 Fix: Use explicit message ID if provided
265
+ if (messageId) {
266
+ targetMessageIndex = messages.findIndex((msg) => msg.id === messageId);
267
+ if (targetMessageIndex === -1 ||
268
+ messages[targetMessageIndex].role !== 'assistant') {
269
+ console.warn(`AI message with id ${messageId} not found in chat ${chatId}`);
270
+ return;
271
+ }
272
+ }
273
+ else {
274
+ // Fallback: Find the most recent AI message (safer than
275
+ // just assuming last message)
276
+ for (let i = messages.length - 1; i >= 0; i--) {
277
+ if (messages[i].role === 'assistant') {
278
+ targetMessageIndex = i;
279
+ break;
280
+ }
281
+ }
282
+ if (targetMessageIndex === -1) {
283
+ console.warn(`No AI message found in chat ${chatId}`);
284
+ return;
285
+ }
286
+ }
287
+ // Update the target message with diff data
288
+ if (targetMessageIndex >= 0) {
260
289
  const newMessage = {
261
- ...messages[lastAIMessageIndex],
290
+ ...messages[targetMessageIndex],
262
291
  diffData,
263
- ...(beforeCommitId && { beforeCommitId }), // Add beforeCommitId for VizHub integration
292
+ ...(beforeCommitId && { beforeCommitId }),
264
293
  };
265
- // Use type assertion to extend the message with diffData
266
- messages[lastAIMessageIndex] = newMessage;
294
+ messages[targetMessageIndex] = newMessage;
267
295
  const messageOp = diff(shareDBDoc.data, {
268
296
  ...shareDBDoc.data,
269
297
  chats: {
@@ -276,6 +304,8 @@ export const addDiffToAIMessage = (shareDBDoc, chatId, diffData, beforeCommitId)
276
304
  },
277
305
  });
278
306
  shareDBDoc.submitOp(messageOp);
307
+ DEBUG &&
308
+ console.log(`addDiffToAIMessage: Successfully added diff to message ${targetMessageIndex} in chat ${chatId}`);
279
309
  }
280
310
  };
281
311
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vzcode",
3
- "version": "2.30.0",
3
+ "version": "2.31.0",
4
4
  "description": "Multiplayer code editor system",
5
5
  "main": "src/index.ts",
6
6
  "type": "module",
@@ -189,6 +189,7 @@ export const getOrCreateEditor = async ({
189
189
  rainbowBracketsEnabled = true,
190
190
  setIsAIChatOpen,
191
191
  setAIChatMessage,
192
+ enableBuiltInAIChat = true,
192
193
  }: {
193
194
  // TODO pass this in from the outside
194
195
  paneId?: PaneId;
@@ -226,6 +227,10 @@ export const getOrCreateEditor = async ({
226
227
  rainbowBracketsEnabled?: boolean; // New parameter type
227
228
  setIsAIChatOpen: (isAIChatOpen: boolean) => void;
228
229
  setAIChatMessage: (message: string) => void;
230
+ // When false, skip the "Implement the TODO" widget, which opens
231
+ // VZCode's built-in AI chat (hosts that own the AI entry point
232
+ // disable it).
233
+ enableBuiltInAIChat?: boolean;
229
234
  }): Promise<ExtendedEditorCacheValue> => {
230
235
  // Cache hit
231
236
 
@@ -580,7 +585,7 @@ export const getOrCreateEditor = async ({
580
585
  return todoPlugin;
581
586
  }
582
587
 
583
- if (enableToDoPlugin) {
588
+ if (enableToDoPlugin && enableBuiltInAIChat) {
584
589
  extensions.push(createToDoPlugin());
585
590
  }
586
591
  }
@@ -22,12 +22,14 @@ export const CodeEditor = ({
22
22
  customInteractRules,
23
23
  aiCopilotEndpoint,
24
24
  esLintSource,
25
+ enableBuiltInAIChat = true,
25
26
  }: {
26
27
  customInteractRules?: Array<InteractRule>;
27
28
  aiCopilotEndpoint?: string;
28
29
  esLintSource: (
29
30
  view: EditorView,
30
31
  ) => Promise<readonly Diagnostic[]>;
32
+ enableBuiltInAIChat?: boolean;
31
33
  }) => {
32
34
  const {
33
35
  activePane,
@@ -118,6 +120,7 @@ export const CodeEditor = ({
118
120
  esLintSource,
119
121
  setIsAIChatOpen,
120
122
  setAIChatMessage,
123
+ enableBuiltInAIChat,
121
124
  });
122
125
 
123
126
  if (isMounted) {
@@ -143,6 +146,7 @@ export const CodeEditor = ({
143
146
  esLintSource,
144
147
  setIsAIChatOpen,
145
148
  setAIChatMessage,
149
+ enableBuiltInAIChat,
146
150
  ]);
147
151
 
148
152
  // Every time the active file switches from one file to another,
@@ -541,6 +541,7 @@ export const useVZCodeState = ({
541
541
  activeChatId,
542
542
  aiChatMode,
543
543
  autoForkAndRetryAI,
544
+ onAIChatMessageSubmitted,
544
545
  DEBUG,
545
546
  setAIErrorMessage,
546
547
  ],
@@ -10,10 +10,25 @@ import { VoiceChatModal } from './VZSidebar/VoiceChatModal';
10
10
  // * The sidebar
11
11
  // * The settings modal
12
12
  // * The create file modal
13
- export const VZLeft = ({ enableUsernameField = true }) => {
13
+ //
14
+ // `enableAIChat` is forwarded to `VZSidebar` so a host can hide the
15
+ // "Edit with AI" sidebar button. `undefined` falls back to the
16
+ // compile-time feature flag.
17
+ export const VZLeft = ({
18
+ enableUsernameField = true,
19
+ enableAIChat,
20
+ aiChatButtonEvent,
21
+ }: {
22
+ enableUsernameField?: boolean;
23
+ enableAIChat?: boolean;
24
+ aiChatButtonEvent?: string;
25
+ } = {}) => {
14
26
  return (
15
27
  <div className="left">
16
- <VZSidebar />
28
+ <VZSidebar
29
+ enableAIChat={enableAIChat}
30
+ aiChatButtonEvent={aiChatButtonEvent}
31
+ />
17
32
  <VZSettings
18
33
  enableUsernameField={enableUsernameField}
19
34
  />
@@ -28,6 +28,7 @@ const PaneView = ({
28
28
  aiAssistClickOverride,
29
29
  aiCopilotEndpoint,
30
30
  esLintSource,
31
+ enableBuiltInAIChat,
31
32
  }) => {
32
33
  // This prevents the CodeEditor from rendering
33
34
  // during SSR.
@@ -67,6 +68,7 @@ const PaneView = ({
67
68
  customInteractRules={customInteractRules}
68
69
  aiCopilotEndpoint={aiCopilotEndpoint}
69
70
  esLintSource={esLintSource}
71
+ enableBuiltInAIChat={enableBuiltInAIChat}
70
72
  />
71
73
  );
72
74
  })()}
@@ -104,6 +106,8 @@ export const VZMiddle = ({
104
106
  aiCopilotEndpoint,
105
107
  customInteractRules,
106
108
  esLintSource,
109
+ enableAIChat = true,
110
+ aiChatButtonEvent,
107
111
  }: {
108
112
  enableAIAssist?: boolean;
109
113
  aiAssistEndpoint?: string;
@@ -115,7 +119,14 @@ export const VZMiddle = ({
115
119
  esLintSource: (
116
120
  view: EditorView,
117
121
  ) => Promise<readonly Diagnostic[]>;
122
+ enableAIChat?: boolean;
123
+ aiChatButtonEvent?: string;
118
124
  }) => {
125
+ // The built-in AI chat (including the TODO widget, which prefills a
126
+ // chat message) is only reachable when the feature is enabled and the
127
+ // host hasn't taken over the sidebar button with a custom event.
128
+ const enableBuiltInAIChat =
129
+ enableAIChat && !aiChatButtonEvent;
119
130
  const { codeEditorWidth } = useContext(
120
131
  SplitPaneResizeContext,
121
132
  );
@@ -152,6 +163,7 @@ export const VZMiddle = ({
152
163
  aiAssistClickOverride={aiAssistClickOverride}
153
164
  aiCopilotEndpoint={aiCopilotEndpoint}
154
165
  esLintSource={esLintSource}
166
+ enableBuiltInAIChat={enableBuiltInAIChat}
155
167
  />
156
168
  <CodeErrorOverlay
157
169
  errorMessage={errorMessage}
@@ -16,6 +16,10 @@ import { CheckboxWidget } from './CheckboxWidget';
16
16
  import { TextInputWidget } from './TextInputWidget';
17
17
  import { DropdownWidget } from './DropdownWidget';
18
18
  import { ColorWidget } from './ColorWidget';
19
+ import {
20
+ getNestedProperty,
21
+ setNestedProperty,
22
+ } from './utils';
19
23
 
20
24
  export const VisualEditor = () => {
21
25
  const { files, submitOperation } =
@@ -66,11 +70,12 @@ export const VisualEditor = () => {
66
70
  [property]: newValue,
67
71
  }));
68
72
 
69
- // Update config.json
70
- const newConfigData = {
71
- ...configData,
72
- [property]: newValue,
73
- };
73
+ // Update config.json with support for nested properties
74
+ const newConfigData = setNestedProperty(
75
+ configData,
76
+ property,
77
+ newValue,
78
+ );
74
79
 
75
80
  submitOperation((document: VizContent) => ({
76
81
  ...document,
@@ -97,11 +102,12 @@ export const VisualEditor = () => {
97
102
  [property]: newValue,
98
103
  }));
99
104
 
100
- // Update config.json
101
- const newConfigData = {
102
- ...configData,
103
- [property]: newValue,
104
- };
105
+ // Update config.json with support for nested properties
106
+ const newConfigData = setNestedProperty(
107
+ configData,
108
+ property,
109
+ newValue,
110
+ );
105
111
 
106
112
  submitOperation((document: VizContent) => ({
107
113
  ...document,
@@ -128,11 +134,12 @@ export const VisualEditor = () => {
128
134
  [property]: newValue,
129
135
  }));
130
136
 
131
- // Update config.json
132
- const newConfigData = {
133
- ...configData,
134
- [property]: newValue,
135
- };
137
+ // Update config.json with support for nested properties
138
+ const newConfigData = setNestedProperty(
139
+ configData,
140
+ property,
141
+ newValue,
142
+ );
136
143
 
137
144
  submitOperation((document: VizContent) => ({
138
145
  ...document,
@@ -156,11 +163,12 @@ export const VisualEditor = () => {
156
163
  [property]: newValue,
157
164
  }));
158
165
 
159
- // Update config.json
160
- const newConfigData = {
161
- ...configData,
162
- [property]: newValue,
163
- };
166
+ // Update config.json with support for nested properties
167
+ const newConfigData = setNestedProperty(
168
+ configData,
169
+ property,
170
+ newValue,
171
+ );
164
172
 
165
173
  submitOperation((document: VizContent) => ({
166
174
  ...document,
@@ -224,9 +232,10 @@ export const VisualEditor = () => {
224
232
  event.currentTarget.value,
225
233
  );
226
234
 
227
- // Get current hex color from config
235
+ // Get current hex color from config using nested property access
228
236
  const currentHex =
229
- configData[property] || '#000000';
237
+ getNestedProperty<string>(configData, property) ??
238
+ '#000000';
230
239
 
231
240
  // Convert current hex to LCH
232
241
  let hclFromRGB: HCLColor;
@@ -281,11 +290,12 @@ export const VisualEditor = () => {
281
290
  [`${property}_l`]: newhcl[2],
282
291
  }));
283
292
 
284
- // Update config.json with hex value
285
- const newConfigData = {
286
- ...configData,
287
- [property]: newHex,
288
- };
293
+ // Update config.json with hex value using nested property access
294
+ const newConfigData = setNestedProperty(
295
+ configData,
296
+ property,
297
+ newHex,
298
+ );
289
299
 
290
300
  submitOperation((document: VizContent) => ({
291
301
  ...document,
@@ -320,23 +330,36 @@ export const VisualEditor = () => {
320
330
  } = {};
321
331
  visualEditorWidgets.forEach((widget) => {
322
332
  if (widget.type === 'slider') {
323
- newLocalValues[widget.property] =
324
- configData[widget.property];
333
+ newLocalValues[widget.property] = getNestedProperty(
334
+ configData,
335
+ widget.property,
336
+ );
325
337
  } else if (widget.type === 'checkbox') {
326
- newLocalValues[widget.property] =
327
- configData[widget.property];
338
+ newLocalValues[widget.property] = getNestedProperty(
339
+ configData,
340
+ widget.property,
341
+ );
328
342
  } else if (widget.type === 'textInput') {
329
- newLocalValues[widget.property] =
330
- configData[widget.property];
343
+ newLocalValues[widget.property] = getNestedProperty(
344
+ configData,
345
+ widget.property,
346
+ );
331
347
  } else if (widget.type === 'dropdown') {
332
- newLocalValues[widget.property] =
333
- configData[widget.property];
348
+ newLocalValues[widget.property] = getNestedProperty(
349
+ configData,
350
+ widget.property,
351
+ );
334
352
  } else if (widget.type === 'color') {
335
- newLocalValues[widget.property] =
336
- configData[widget.property];
353
+ newLocalValues[widget.property] = getNestedProperty(
354
+ configData,
355
+ widget.property,
356
+ );
337
357
 
338
358
  // Convert hex to LCH for internal state
339
- const hexColor = configData[widget.property];
359
+ const hexColor = getNestedProperty<string>(
360
+ configData,
361
+ widget.property,
362
+ );
340
363
 
341
364
  if (localValues[widget.property] !== hexColor) {
342
365
  if (hexColor) {
@@ -414,8 +437,14 @@ export const VisualEditor = () => {
414
437
  if (widgetConfig.type === 'slider') {
415
438
  // Use local value if available, otherwise fall back to config value
416
439
  const currentValue =
417
- localValues[widgetConfig.property] ??
418
- configData[widgetConfig.property];
440
+ (localValues[
441
+ widgetConfig.property
442
+ ] as number) ??
443
+ getNestedProperty<number>(
444
+ configData,
445
+ widgetConfig.property,
446
+ ) ??
447
+ widgetConfig.min;
419
448
 
420
449
  return (
421
450
  <SliderWidget
@@ -434,8 +463,14 @@ export const VisualEditor = () => {
434
463
  } else if (widgetConfig.type === 'checkbox') {
435
464
  // Use local value if available, otherwise fall back to config value
436
465
  const currentValue =
437
- localValues[widgetConfig.property] ??
438
- configData[widgetConfig.property];
466
+ (localValues[
467
+ widgetConfig.property
468
+ ] as boolean) ??
469
+ getNestedProperty<boolean>(
470
+ configData,
471
+ widgetConfig.property,
472
+ ) ??
473
+ false;
439
474
 
440
475
  return (
441
476
  <CheckboxWidget
@@ -451,8 +486,14 @@ export const VisualEditor = () => {
451
486
  } else if (widgetConfig.type === 'textInput') {
452
487
  // Use local value if available, otherwise fall back to config value
453
488
  const currentValue =
454
- localValues[widgetConfig.property] ??
455
- configData[widgetConfig.property];
489
+ (localValues[
490
+ widgetConfig.property
491
+ ] as string) ??
492
+ getNestedProperty<string>(
493
+ configData,
494
+ widgetConfig.property,
495
+ ) ??
496
+ '';
456
497
 
457
498
  return (
458
499
  <TextInputWidget
@@ -468,8 +509,15 @@ export const VisualEditor = () => {
468
509
  } else if (widgetConfig.type === 'dropdown') {
469
510
  // Use local value if available, otherwise fall back to config value
470
511
  const currentValue =
471
- localValues[widgetConfig.property] ??
472
- configData[widgetConfig.property];
512
+ (localValues[
513
+ widgetConfig.property
514
+ ] as string) ??
515
+ getNestedProperty<string>(
516
+ configData,
517
+ widgetConfig.property,
518
+ ) ??
519
+ widgetConfig.options?.[0] ??
520
+ '';
473
521
  const isOpen =
474
522
  openDropdown === widgetConfig.property;
475
523
 
@@ -495,8 +543,13 @@ export const VisualEditor = () => {
495
543
  } else if (widgetConfig.type === 'color') {
496
544
  // Use local value if available, otherwise fall back to config value
497
545
  const currentHex =
498
- localValues[widgetConfig.property] ??
499
- configData[widgetConfig.property] ??
546
+ (localValues[
547
+ widgetConfig.property
548
+ ] as string) ??
549
+ getNestedProperty<string>(
550
+ configData,
551
+ widgetConfig.property,
552
+ ) ??
500
553
  '#000000';
501
554
 
502
555
  // Convert hex to LCH for slider values
@@ -100,3 +100,116 @@ export const renderSliderBackground = (
100
100
 
101
101
  ctx.putImageData(imageData, 0, 0);
102
102
  };
103
+
104
+ // Helper function to get nested property value using dot notation
105
+ export const getNestedProperty = <T = unknown>(
106
+ obj: Record<string, unknown>,
107
+ path: string,
108
+ ): T | undefined => {
109
+ // Validate path to prevent empty strings, consecutive dots, or leading/trailing dots
110
+ if (
111
+ !path ||
112
+ path.includes('..') ||
113
+ path.startsWith('.') ||
114
+ path.endsWith('.')
115
+ ) {
116
+ return undefined;
117
+ }
118
+
119
+ const keys = path.split('.');
120
+ let value: unknown = obj;
121
+ for (const key of keys) {
122
+ // Skip empty keys that might result from splitting
123
+ if (!key) {
124
+ return undefined;
125
+ }
126
+ if (value === null || value === undefined) {
127
+ return undefined;
128
+ }
129
+ // Type guard to ensure value is an object before accessing property
130
+ if (typeof value !== 'object') {
131
+ return undefined;
132
+ }
133
+ value = (value as Record<string, unknown>)[key];
134
+ }
135
+ return value as T | undefined;
136
+ };
137
+
138
+ // Helper function to set nested property value using dot notation
139
+ export const setNestedProperty = <
140
+ T extends Record<string, unknown>,
141
+ >(
142
+ obj: T,
143
+ path: string,
144
+ value: unknown,
145
+ ): T => {
146
+ // Validate path to prevent empty strings, consecutive dots, or leading/trailing dots
147
+ if (
148
+ !path ||
149
+ path.includes('..') ||
150
+ path.startsWith('.') ||
151
+ path.endsWith('.')
152
+ ) {
153
+ throw new Error(
154
+ `Invalid property path "${path}" - path cannot be empty, contain consecutive dots, or have leading/trailing dots`,
155
+ );
156
+ }
157
+
158
+ const keys = path.split('.');
159
+
160
+ // Check for empty keys
161
+ if (keys.some((key) => !key)) {
162
+ throw new Error(
163
+ `Invalid property path "${path}" - path contains empty segments`,
164
+ );
165
+ }
166
+
167
+ const newObj = { ...obj };
168
+ let current: Record<string, unknown> = newObj;
169
+
170
+ // List of dangerous keys that could lead to prototype pollution
171
+ const dangerousKeys = [
172
+ '__proto__',
173
+ 'constructor',
174
+ 'prototype',
175
+ ];
176
+
177
+ for (let i = 0; i < keys.length - 1; i++) {
178
+ const key = keys[i];
179
+
180
+ // Guard against prototype pollution
181
+ if (dangerousKeys.includes(key)) {
182
+ throw new Error(
183
+ `Cannot set property "${key}" - potential prototype pollution`,
184
+ );
185
+ }
186
+
187
+ // Create nested object if it doesn't exist or isn't a plain object
188
+ if (
189
+ current[key] == null ||
190
+ typeof current[key] !== 'object' ||
191
+ Array.isArray(current[key])
192
+ ) {
193
+ current[key] = {};
194
+ } else {
195
+ // Clone the nested object to avoid mutation
196
+ current[key] = {
197
+ ...(current[key] as Record<string, unknown>),
198
+ };
199
+ }
200
+ current = current[key] as Record<string, unknown>;
201
+ }
202
+
203
+ // Guard against prototype pollution for the final key
204
+ const finalKey = keys[keys.length - 1];
205
+ if (dangerousKeys.includes(finalKey)) {
206
+ throw new Error(
207
+ `Cannot set property "${finalKey}" - potential prototype pollution`,
208
+ );
209
+ }
210
+
211
+ // Set the final value
212
+ current[finalKey] = value;
213
+
214
+ return newObj;
215
+ };
@@ -40,14 +40,22 @@ import { useDragAndDrop } from './useDragAndDrop';
40
40
  import { createAICopyPasteHandlers } from './aiCopyPaste';
41
41
  import {
42
42
  enableLiveKit,
43
- enableAIChat,
43
+ enableAIChat as enableAIChatFeatureFlag,
44
44
  } from '../featureFlags';
45
+ import { handleAIChatButtonClick } from '../aiChatButtonEvent';
45
46
  import './styles.scss';
46
47
  import { VisualEditor } from './VisualEditor';
47
48
 
48
49
  const enableConnectionStatus = true;
49
50
 
50
51
  export const VZSidebar = ({
52
+ // Host-provided override so consumers (e.g. VizHub Studio, which has
53
+ // its own top-bar "AI Edit" entry point) can hide the "Edit with AI"
54
+ // sidebar button. Defaults to the compile-time feature flag.
55
+ enableAIChat = enableAIChatFeatureFlag,
56
+ // When set, clicking the button dispatches this custom event instead
57
+ // of opening VZCode's built-in AI chat, letting the host handle it.
58
+ aiChatButtonEvent,
51
59
  createFileTooltipText = (
52
60
  <>
53
61
  <strong>New file</strong>
@@ -135,6 +143,8 @@ export const VZSidebar = ({
135
143
  voiceChatToolTipText?: React.ReactNode;
136
144
  aiChatToolTipText?: React.ReactNode;
137
145
  visualEditorToolTipText?: React.ReactNode;
146
+ enableAIChat?: boolean;
147
+ aiChatButtonEvent?: string;
138
148
  }) => {
139
149
  const {
140
150
  files,
@@ -341,11 +351,17 @@ export const VZSidebar = ({
341
351
  <i
342
352
  id="ai-chat-icon"
343
353
  className="icon-button icon-button-dark"
344
- onClick={() => {
345
- setIsAIChatOpen(true);
346
- setIsSearchOpen(false);
347
- setSidebarView(true); // Switch to AI chat view
348
- }}
354
+ onClick={() =>
355
+ handleAIChatButtonClick({
356
+ aiChatButtonEvent,
357
+ target: window,
358
+ openAIChat: () => {
359
+ setIsAIChatOpen(true);
360
+ setIsSearchOpen(false);
361
+ setSidebarView(true); // Switch to AI chat view
362
+ },
363
+ })
364
+ }
349
365
  >
350
366
  <SparklesSVG />
351
367
  </i>
@@ -411,6 +427,7 @@ export const VZSidebar = ({
411
427
  setIsVisualEditorOpen(true);
412
428
  setIsAIChatOpen(false);
413
429
  setIsSearchOpen(false);
430
+ setSidebarView(false); // Switch to files view width for visual editor
414
431
  }}
415
432
  >
416
433
  <AdjustmentSVG />