@lobehub/lobehub 2.0.0-next.289 → 2.0.0-next.290

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/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.290](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.289...v2.0.0-next.290)
6
+
7
+ <sup>Released on **2026-01-15**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Fix internal editor onTextChange issue and add test case.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Fix internal editor onTextChange issue and add test case, closes [#11509](https://github.com/lobehub/lobe-chat/issues/11509) ([e5eb03e](https://github.com/lobehub/lobe-chat/commit/e5eb03e))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ## [Version 2.0.0-next.289](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.288...v2.0.0-next.289)
6
31
 
7
32
  <sup>Released on **2026-01-15**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Fix internal editor onTextChange issue and add test case."
6
+ ]
7
+ },
8
+ "date": "2026-01-15",
9
+ "version": "2.0.0-next.290"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "fixes": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.289",
3
+ "version": "2.0.0-next.290",
4
4
  "description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -0,0 +1,630 @@
1
+ /**
2
+ * @vitest-environment happy-dom
3
+ */
4
+ import { type IEditor, moment } from '@lobehub/editor';
5
+ import { useEditor } from '@lobehub/editor/react';
6
+ import { act, cleanup, render, waitFor } from '@testing-library/react';
7
+ import { memo, useEffect, useRef } from 'react';
8
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
9
+
10
+ import InternalEditor, { type InternalEditorProps } from './InternalEditor';
11
+
12
+ // Suppress console.warn for expected errors in tests
13
+ const originalWarn = console.warn;
14
+ beforeEach(() => {
15
+ console.warn = vi.fn();
16
+ });
17
+
18
+ afterEach(() => {
19
+ console.warn = originalWarn;
20
+ cleanup();
21
+ });
22
+
23
+ /**
24
+ * Test wrapper component that creates a real editor using useEditor hook
25
+ * This ensures all plugins and services are properly initialized
26
+ */
27
+ interface TestWrapperProps extends Omit<InternalEditorProps, 'editor'> {
28
+ onEditorReady?: (editor: IEditor) => void;
29
+ }
30
+
31
+ const TestWrapper = memo<TestWrapperProps>(({ onEditorReady, ...props }) => {
32
+ const editor = useEditor();
33
+ const readyRef = useRef(false);
34
+
35
+ useEffect(() => {
36
+ if (editor && !readyRef.current) {
37
+ readyRef.current = true;
38
+ onEditorReady?.(editor);
39
+ }
40
+ }, [editor, onEditorReady]);
41
+
42
+ if (!editor) return null;
43
+ return <InternalEditor editor={editor} {...props} />;
44
+ });
45
+
46
+ TestWrapper.displayName = 'TestWrapper';
47
+
48
+ /**
49
+ * Test wrapper for tests that need custom plugins (no toolbar dependencies)
50
+ */
51
+ const MinimalTestWrapper = memo<TestWrapperProps>(({ onEditorReady, plugins, ...props }) => {
52
+ const editor = useEditor();
53
+ const readyRef = useRef(false);
54
+
55
+ useEffect(() => {
56
+ if (editor && !readyRef.current) {
57
+ readyRef.current = true;
58
+ onEditorReady?.(editor);
59
+ }
60
+ }, [editor, onEditorReady]);
61
+
62
+ if (!editor) return null;
63
+
64
+ // Use minimal plugins that don't require toolbar services
65
+ const minimalPlugins = plugins || [];
66
+
67
+ return <InternalEditor editor={editor} plugins={minimalPlugins} {...props} />;
68
+ });
69
+
70
+ MinimalTestWrapper.displayName = 'MinimalTestWrapper';
71
+
72
+ describe('InternalEditor', () => {
73
+ describe('rendering', () => {
74
+ it('should render editor with real editor instance', async () => {
75
+ const { container } = render(<MinimalTestWrapper />);
76
+
77
+ await act(async () => {
78
+ await moment();
79
+ });
80
+
81
+ // Editor should be rendered
82
+ expect(container.querySelector('[data-lexical-editor]')).not.toBeNull();
83
+ });
84
+
85
+ it('should render with custom placeholder', async () => {
86
+ const placeholder = 'Start typing here...';
87
+ const { container } = render(<MinimalTestWrapper placeholder={placeholder} />);
88
+
89
+ await act(async () => {
90
+ await moment();
91
+ });
92
+
93
+ expect(container.textContent).toContain(placeholder);
94
+ });
95
+
96
+ it('should apply custom styles', async () => {
97
+ const customStyle = { backgroundColor: 'red', paddingTop: 100 };
98
+ const { container } = render(<MinimalTestWrapper style={customStyle} />);
99
+
100
+ await act(async () => {
101
+ await moment();
102
+ });
103
+
104
+ // Find the Editor component's container
105
+ const editorContainer = container.querySelector('[data-lexical-editor]')?.closest('div');
106
+ // The style should include paddingBottom: 64 (default) merged with custom styles
107
+ expect(editorContainer).toBeTruthy();
108
+ });
109
+ });
110
+
111
+ describe('onInit callback', () => {
112
+ it('should call onInit when editor initializes', async () => {
113
+ const onInit = vi.fn();
114
+
115
+ render(<MinimalTestWrapper onInit={onInit} />);
116
+
117
+ await act(async () => {
118
+ await moment();
119
+ });
120
+
121
+ await waitFor(() => {
122
+ expect(onInit).toHaveBeenCalled();
123
+ });
124
+ });
125
+
126
+ it('should pass editor instance to onInit', async () => {
127
+ const onInit = vi.fn();
128
+
129
+ render(<MinimalTestWrapper onInit={onInit} />);
130
+
131
+ await act(async () => {
132
+ await moment();
133
+ });
134
+
135
+ await waitFor(() => {
136
+ expect(onInit).toHaveBeenCalledWith(
137
+ expect.objectContaining({ getDocument: expect.any(Function) }),
138
+ );
139
+ });
140
+ });
141
+
142
+ it('should not throw error when initialized with empty content', async () => {
143
+ const onInit = vi.fn();
144
+ let editorInstance: IEditor | undefined;
145
+
146
+ // This test ensures the fix for "setEditorState: the editor state is empty" error
147
+ // When editor initializes with empty/undefined content, it should not throw
148
+ const { container } = render(
149
+ <MinimalTestWrapper
150
+ onEditorReady={(e) => {
151
+ editorInstance = e;
152
+ }}
153
+ onInit={onInit}
154
+ />,
155
+ );
156
+
157
+ await act(async () => {
158
+ await moment();
159
+ });
160
+
161
+ // Editor should initialize without error
162
+ await waitFor(() => {
163
+ expect(onInit).toHaveBeenCalled();
164
+ expect(editorInstance).toBeDefined();
165
+ });
166
+
167
+ // Editor should be rendered
168
+ expect(container.querySelector('[data-lexical-editor]')).not.toBeNull();
169
+
170
+ // Getting document should work (returns empty content)
171
+ const text = editorInstance!.getDocument('text') as unknown as string;
172
+ expect(text).toBeDefined();
173
+ });
174
+ });
175
+
176
+ describe('onContentChange callback', () => {
177
+ it('should call onContentChange when content changes via setDocument', async () => {
178
+ const onContentChange = vi.fn();
179
+ let editorInstance: IEditor | undefined;
180
+
181
+ render(
182
+ <MinimalTestWrapper
183
+ onContentChange={onContentChange}
184
+ onEditorReady={(e) => {
185
+ editorInstance = e;
186
+ }}
187
+ />,
188
+ );
189
+
190
+ await act(async () => {
191
+ await moment();
192
+ });
193
+
194
+ // Wait for editor to be ready
195
+ await waitFor(() => {
196
+ expect(editorInstance).toBeDefined();
197
+ });
198
+
199
+ // Change content using editor API
200
+ await act(async () => {
201
+ editorInstance!.setDocument('text', 'Hello World');
202
+ await moment();
203
+ });
204
+
205
+ await waitFor(
206
+ () => {
207
+ expect(onContentChange).toHaveBeenCalled();
208
+ },
209
+ { timeout: 2000 },
210
+ );
211
+ });
212
+
213
+ it('should call onContentChange when markdown content is set', async () => {
214
+ const onContentChange = vi.fn();
215
+ let editorInstance: IEditor | undefined;
216
+
217
+ render(
218
+ <MinimalTestWrapper
219
+ onContentChange={onContentChange}
220
+ onEditorReady={(e) => {
221
+ editorInstance = e;
222
+ }}
223
+ />,
224
+ );
225
+
226
+ await act(async () => {
227
+ await moment();
228
+ });
229
+
230
+ await waitFor(() => {
231
+ expect(editorInstance).toBeDefined();
232
+ });
233
+
234
+ // Change content using markdown
235
+ await act(async () => {
236
+ editorInstance!.setDocument('markdown', '# Hello\n\nThis is a paragraph.');
237
+ await moment();
238
+ });
239
+
240
+ await waitFor(
241
+ () => {
242
+ expect(onContentChange).toHaveBeenCalled();
243
+ },
244
+ { timeout: 2000 },
245
+ );
246
+ });
247
+
248
+ it('should track multiple content changes', async () => {
249
+ const onContentChange = vi.fn();
250
+ let editorInstance: IEditor | undefined;
251
+
252
+ render(
253
+ <MinimalTestWrapper
254
+ onContentChange={onContentChange}
255
+ onEditorReady={(e) => {
256
+ editorInstance = e;
257
+ }}
258
+ />,
259
+ );
260
+
261
+ await act(async () => {
262
+ await moment();
263
+ });
264
+
265
+ await waitFor(() => {
266
+ expect(editorInstance).toBeDefined();
267
+ });
268
+
269
+ // First change
270
+ await act(async () => {
271
+ editorInstance!.setDocument('text', 'First content');
272
+ await moment();
273
+ });
274
+
275
+ // Second change
276
+ await act(async () => {
277
+ editorInstance!.setDocument('text', 'Second content');
278
+ await moment();
279
+ });
280
+
281
+ // Third change
282
+ await act(async () => {
283
+ editorInstance!.setDocument('text', 'Third content');
284
+ await moment();
285
+ });
286
+
287
+ await waitFor(
288
+ () => {
289
+ // Should have multiple calls for different content changes
290
+ expect(onContentChange.mock.calls.length).toBeGreaterThanOrEqual(2);
291
+ },
292
+ { timeout: 2000 },
293
+ );
294
+ });
295
+ });
296
+
297
+ describe('editor content methods', () => {
298
+ it('should allow getting document as markdown', async () => {
299
+ let editorInstance: IEditor | undefined;
300
+
301
+ render(
302
+ <MinimalTestWrapper
303
+ onEditorReady={(e) => {
304
+ editorInstance = e;
305
+ }}
306
+ />,
307
+ );
308
+
309
+ await act(async () => {
310
+ await moment();
311
+ });
312
+
313
+ await waitFor(() => {
314
+ expect(editorInstance).toBeDefined();
315
+ });
316
+
317
+ await act(async () => {
318
+ editorInstance!.setDocument('text', 'Test content');
319
+ await moment();
320
+ });
321
+
322
+ const markdown = editorInstance!.getDocument('markdown') as unknown as string;
323
+ expect(markdown).toContain('Test content');
324
+ });
325
+
326
+ it('should allow getting document as JSON', async () => {
327
+ let editorInstance: IEditor | undefined;
328
+
329
+ render(
330
+ <MinimalTestWrapper
331
+ onEditorReady={(e) => {
332
+ editorInstance = e;
333
+ }}
334
+ />,
335
+ );
336
+
337
+ await act(async () => {
338
+ await moment();
339
+ });
340
+
341
+ await waitFor(() => {
342
+ expect(editorInstance).toBeDefined();
343
+ });
344
+
345
+ await act(async () => {
346
+ editorInstance!.setDocument('text', 'Test content');
347
+ await moment();
348
+ });
349
+
350
+ const json = editorInstance!.getDocument('json');
351
+ expect(json).toBeDefined();
352
+ expect(typeof json).toBe('object');
353
+ });
354
+
355
+ it('should allow getting document as text', async () => {
356
+ let editorInstance: IEditor | undefined;
357
+
358
+ render(
359
+ <MinimalTestWrapper
360
+ onEditorReady={(e) => {
361
+ editorInstance = e;
362
+ }}
363
+ />,
364
+ );
365
+
366
+ await act(async () => {
367
+ await moment();
368
+ });
369
+
370
+ await waitFor(() => {
371
+ expect(editorInstance).toBeDefined();
372
+ });
373
+
374
+ await act(async () => {
375
+ editorInstance!.setDocument('markdown', '# Heading\n\nParagraph');
376
+ await moment();
377
+ });
378
+
379
+ const text = editorInstance!.getDocument('text') as unknown as string;
380
+ expect(text).toContain('Heading');
381
+ expect(text).toContain('Paragraph');
382
+ });
383
+ });
384
+
385
+ describe('lexical editor access', () => {
386
+ it('should expose getLexicalEditor method', async () => {
387
+ let editorInstance: IEditor | undefined;
388
+
389
+ render(
390
+ <MinimalTestWrapper
391
+ onEditorReady={(e) => {
392
+ editorInstance = e;
393
+ }}
394
+ />,
395
+ );
396
+
397
+ await act(async () => {
398
+ await moment();
399
+ });
400
+
401
+ await waitFor(() => {
402
+ expect(editorInstance).toBeDefined();
403
+ });
404
+
405
+ const lexicalEditor = editorInstance!.getLexicalEditor?.();
406
+ expect(lexicalEditor).toBeDefined();
407
+ });
408
+
409
+ it('should allow registering custom update listeners', async () => {
410
+ const updateListener = vi.fn();
411
+ let editorInstance: IEditor | undefined;
412
+
413
+ render(
414
+ <MinimalTestWrapper
415
+ onEditorReady={(e) => {
416
+ editorInstance = e;
417
+ }}
418
+ />,
419
+ );
420
+
421
+ await act(async () => {
422
+ await moment();
423
+ });
424
+
425
+ await waitFor(() => {
426
+ expect(editorInstance).toBeDefined();
427
+ });
428
+
429
+ const lexicalEditor = editorInstance!.getLexicalEditor?.();
430
+ expect(lexicalEditor).toBeDefined();
431
+
432
+ if (lexicalEditor) {
433
+ const unregister = lexicalEditor.registerUpdateListener(updateListener);
434
+
435
+ // Trigger an update
436
+ await act(async () => {
437
+ editorInstance!.setDocument('text', 'Updated content');
438
+ await moment();
439
+ });
440
+
441
+ expect(updateListener).toHaveBeenCalled();
442
+
443
+ // Cleanup
444
+ unregister();
445
+ }
446
+ });
447
+ });
448
+
449
+ describe('custom plugins', () => {
450
+ it('should accept custom plugins array', async () => {
451
+ const CustomPlugin = () => null;
452
+
453
+ const { container } = render(<MinimalTestWrapper plugins={[CustomPlugin]} />);
454
+
455
+ await act(async () => {
456
+ await moment();
457
+ });
458
+
459
+ // Should render without error
460
+ expect(container.querySelector('[data-lexical-editor]')).not.toBeNull();
461
+ });
462
+
463
+ it('should accept extra plugins prepended to base plugins', async () => {
464
+ const ExtraPlugin = () => null;
465
+
466
+ // Note: extraPlugins requires base plugins which need toolbar services
467
+ // We test this with minimal plugins instead
468
+ const { container } = render(<MinimalTestWrapper plugins={[ExtraPlugin]} />);
469
+
470
+ await act(async () => {
471
+ await moment();
472
+ });
473
+
474
+ // Should render without error
475
+ expect(container.querySelector('[data-lexical-editor]')).not.toBeNull();
476
+ });
477
+ });
478
+
479
+ describe('window.__editor assignment', () => {
480
+ it('should assign editor to window.__editor for debugging', async () => {
481
+ let editorInstance: IEditor | undefined;
482
+
483
+ render(
484
+ <MinimalTestWrapper
485
+ onEditorReady={(e) => {
486
+ editorInstance = e;
487
+ }}
488
+ />,
489
+ );
490
+
491
+ await act(async () => {
492
+ await moment();
493
+ });
494
+
495
+ await waitFor(() => {
496
+ expect(editorInstance).toBeDefined();
497
+ });
498
+
499
+ expect(window.__editor).toBe(editorInstance);
500
+ });
501
+
502
+ it('should clear window.__editor on unmount', async () => {
503
+ let editorInstance: IEditor | undefined;
504
+
505
+ const { unmount } = render(
506
+ <MinimalTestWrapper
507
+ onEditorReady={(e) => {
508
+ editorInstance = e;
509
+ }}
510
+ />,
511
+ );
512
+
513
+ await act(async () => {
514
+ await moment();
515
+ });
516
+
517
+ await waitFor(() => {
518
+ expect(editorInstance).toBeDefined();
519
+ });
520
+
521
+ expect(window.__editor).toBe(editorInstance);
522
+
523
+ unmount();
524
+
525
+ expect(window.__editor).toBeUndefined();
526
+ });
527
+ });
528
+
529
+ describe('callback stability', () => {
530
+ it('should maintain stable onContentChange behavior across re-renders', async () => {
531
+ const onContentChange = vi.fn();
532
+ let editorInstance: IEditor | undefined;
533
+
534
+ const { rerender } = render(
535
+ <MinimalTestWrapper
536
+ onContentChange={onContentChange}
537
+ onEditorReady={(e) => {
538
+ editorInstance = e;
539
+ }}
540
+ />,
541
+ );
542
+
543
+ await act(async () => {
544
+ await moment();
545
+ });
546
+
547
+ await waitFor(() => {
548
+ expect(editorInstance).toBeDefined();
549
+ });
550
+
551
+ // Re-render with same props
552
+ rerender(
553
+ <MinimalTestWrapper
554
+ onContentChange={onContentChange}
555
+ onEditorReady={(e) => {
556
+ editorInstance = e;
557
+ }}
558
+ />,
559
+ );
560
+
561
+ await act(async () => {
562
+ await moment();
563
+ });
564
+
565
+ // Change content after re-render
566
+ await act(async () => {
567
+ editorInstance!.setDocument('text', 'Content after rerender');
568
+ await moment();
569
+ });
570
+
571
+ await waitFor(
572
+ () => {
573
+ expect(onContentChange).toHaveBeenCalled();
574
+ },
575
+ { timeout: 2000 },
576
+ );
577
+ });
578
+
579
+ it('should use updated callback when onContentChange prop changes', async () => {
580
+ const firstCallback = vi.fn();
581
+ const secondCallback = vi.fn();
582
+ let editorInstance: IEditor | undefined;
583
+
584
+ const { rerender } = render(
585
+ <MinimalTestWrapper
586
+ onContentChange={firstCallback}
587
+ onEditorReady={(e) => {
588
+ editorInstance = e;
589
+ }}
590
+ />,
591
+ );
592
+
593
+ await act(async () => {
594
+ await moment();
595
+ });
596
+
597
+ await waitFor(() => {
598
+ expect(editorInstance).toBeDefined();
599
+ });
600
+
601
+ // Change callback prop
602
+ rerender(
603
+ <MinimalTestWrapper
604
+ onContentChange={secondCallback}
605
+ onEditorReady={(e) => {
606
+ editorInstance = e;
607
+ }}
608
+ />,
609
+ );
610
+
611
+ await act(async () => {
612
+ await moment();
613
+ });
614
+
615
+ // Trigger content change
616
+ await act(async () => {
617
+ editorInstance!.setDocument('text', 'New content');
618
+ await moment();
619
+ });
620
+
621
+ await waitFor(
622
+ () => {
623
+ // Second callback should be called
624
+ expect(secondCallback).toHaveBeenCalled();
625
+ },
626
+ { timeout: 2000 },
627
+ );
628
+ });
629
+ });
630
+ });
@@ -14,7 +14,7 @@ import {
14
14
  ReactToolbarPlugin,
15
15
  } from '@lobehub/editor';
16
16
  import { Editor, useEditorState } from '@lobehub/editor/react';
17
- import { memo, useEffect, useMemo } from 'react';
17
+ import { memo, useEffect, useMemo, useRef } from 'react';
18
18
  import { useTranslation } from 'react-i18next';
19
19
 
20
20
  import type { EditorCanvasProps } from './EditorCanvas';
@@ -102,6 +102,40 @@ const InternalEditor = memo<InternalEditorProps>(
102
102
  };
103
103
  }, [editor]);
104
104
 
105
+ // Use refs for stable references across re-renders
106
+ const previousContentRef = useRef<string | undefined>(undefined);
107
+ const onContentChangeRef = useRef(onContentChange);
108
+ onContentChangeRef.current = onContentChange;
109
+
110
+ // Listen to Lexical updates directly to trigger content change
111
+ // This bypasses @lobehub/editor's onTextChange which has issues with previousContent reset
112
+ useEffect(() => {
113
+ if (!editor) return;
114
+
115
+ const lexicalEditor = editor.getLexicalEditor?.();
116
+ if (!lexicalEditor) return;
117
+
118
+ // Initialize previousContent with current content before registering listener
119
+ previousContentRef.current = JSON.stringify(editor.getDocument('text'));
120
+
121
+ const unregister = lexicalEditor.registerUpdateListener(({ dirtyElements, dirtyLeaves }) => {
122
+ // Only process when there are actual content changes
123
+ if (dirtyElements.size === 0 && dirtyLeaves.size === 0) return;
124
+
125
+ const currentContent = JSON.stringify(editor.getDocument('text'));
126
+
127
+ if (currentContent !== previousContentRef.current) {
128
+ // Content actually changed
129
+ previousContentRef.current = currentContent;
130
+ onContentChangeRef.current?.();
131
+ }
132
+ });
133
+
134
+ return () => {
135
+ unregister();
136
+ };
137
+ }, [editor]); // Only depend on editor, use ref for onContentChange
138
+
105
139
  return (
106
140
  <div
107
141
  onClick={(e) => {
@@ -114,7 +148,6 @@ const InternalEditor = memo<InternalEditorProps>(
114
148
  editor={editor}
115
149
  lineEmptyPlaceholder={finalPlaceholder}
116
150
  onInit={onInit}
117
- onTextChange={onContentChange}
118
151
  placeholder={finalPlaceholder}
119
152
  plugins={plugins}
120
153
  slashOption={slashItems ? { items: slashItems } : undefined}
@@ -180,7 +180,7 @@ describe('DocumentStore - Editor Actions', () => {
180
180
  expect(mockEditor.setDocument).toHaveBeenCalledWith('json', JSON.stringify(editorData));
181
181
  });
182
182
 
183
- it('should set empty placeholder when no content', () => {
183
+ it('should not call setDocument when content is empty to avoid editor error', () => {
184
184
  const { result } = renderHook(() => useDocumentStore());
185
185
  const mockEditor = createMockEditor() as any;
186
186
 
@@ -196,7 +196,9 @@ describe('DocumentStore - Editor Actions', () => {
196
196
  result.current.onEditorInit(mockEditor);
197
197
  });
198
198
 
199
- expect(mockEditor.setDocument).toHaveBeenCalledWith('markdown', ' ');
199
+ // setDocument should NOT be called for empty content
200
+ // This prevents "setEditorState: the editor state is empty" error
201
+ expect(mockEditor.setDocument).not.toHaveBeenCalled();
200
202
  });
201
203
  });
202
204
 
@@ -151,12 +151,14 @@ export const createEditorSlice: StateCreator<
151
151
  }
152
152
  }
153
153
 
154
- // Load markdown content or set empty placeholder
155
- const mdContent = doc.content?.trim() ? doc.content : ' ';
156
- try {
157
- editor.setDocument('markdown', mdContent);
158
- } catch (err) {
159
- console.error('[DocumentStore] Failed to load markdown content:', err);
154
+ // Load markdown content if available
155
+ // Skip setDocument for empty content - let editor use its default empty state
156
+ if (doc.content?.trim()) {
157
+ try {
158
+ editor.setDocument('markdown', doc.content);
159
+ } catch (err) {
160
+ console.error('[DocumentStore] Failed to load markdown content:', err);
161
+ }
160
162
  }
161
163
 
162
164
  set({ editor });