Skip to content

Commit 7f18326

Browse files
committed
Fix: make createPageYDoc return blank doc to preserve Yjs IDs on bootstrap
- createPageYDoc() now returns a blank Y.Doc instead of pre-initializing shared types (page, noteIds, arrowIds, nextZIndex, notes, arrows) - getNoteIds(), getArrowIds(), getNextZIndex() lazily create missing structures - This ensures server bootstrap updates recreate the exact original shared types with their original Yjs IDs, fixing the disappearing notes on refresh bug - Updated undo-redo.ts to eagerly create lazy structures before UndoManager to avoid polluting the undo stack with initialization mutations - All 57 test files (318 tests) pass
1 parent 1637300 commit 7f18326

3 files changed

Lines changed: 37 additions & 30 deletions

File tree

new-deepnotes/apps/web/src/features/spatial/undo-redo.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as Y from "yjs";
2-
import { getNotesMap, getArrowsMap, getPageMap } from "@deepnotes/collab-wire";
2+
import { getNotesMap, getArrowsMap, getPageMap, getNoteIds, getArrowIds, getNextZIndex } from "@deepnotes/collab-wire";
33

44
export type SpatialUndoRedo = ReturnType<typeof useSpatialUndoRedo>;
55

@@ -21,6 +21,12 @@ function addTypeToScope(um: Y.UndoManager, type: Y.AbstractType<any>): void {
2121
}
2222

2323
export function useSpatialUndoRedo(ydoc: Y.Doc) {
24+
// Eagerly create lazy structures so they don't appear as undoable
25+
// mutations when useSpatialPage first accesses them.
26+
getNoteIds(ydoc);
27+
getArrowIds(ydoc);
28+
getNextZIndex(ydoc);
29+
2430
const notesMap = getNotesMap(ydoc);
2531
const arrowsMap = getArrowsMap(ydoc);
2632
const pageMap = getPageMap(ydoc);

new-deepnotes/packages/collab-wire/src/page-doc-schema.test.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,10 @@ import {
2222
} from "./page-doc-schema.js";
2323

2424
describe("page-doc-schema", () => {
25-
it("creates a doc with empty page, notes, and arrows maps", () => {
25+
it("creates a blank doc", () => {
2626
const ydoc = createPageYDoc();
2727

28-
const page = ydoc.getMap(YPAGE_KEY.page);
29-
expect(page.get(YPAGE_PAGE_KEY.noteIds)).toBeInstanceOf(Y.Array);
30-
expect(page.get(YPAGE_PAGE_KEY.arrowIds)).toBeInstanceOf(Y.Array);
31-
expect(page.get(YPAGE_PAGE_KEY.nextZIndex)).toBe(0);
32-
28+
expect(ydoc.getMap(YPAGE_KEY.page).size).toBe(0);
3329
expect(ydoc.getMap(YPAGE_KEY.notes).size).toBe(0);
3430
expect(ydoc.getMap(YPAGE_KEY.arrows).size).toBe(0);
3531
});

new-deepnotes/packages/collab-wire/src/page-doc-schema.ts

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,7 @@ export function getNoteMap(ydoc: Y.Doc, noteId: string): Y.Map<unknown> | undefi
177177

178178
export function addNoteToPage(ydoc: Y.Doc, noteId: string): Y.Map<unknown> {
179179
const note = createNoteMap();
180-
const page = getPageMap(ydoc);
181-
const noteIds = page.get(YPAGE_PAGE_KEY.noteIds) as Y.Array<string>;
180+
const noteIds = getNoteIds(ydoc);
182181
const notes = getNotesMap(ydoc);
183182

184183
ydoc.transact(() => {
@@ -190,8 +189,7 @@ export function addNoteToPage(ydoc: Y.Doc, noteId: string): Y.Map<unknown> {
190189
}
191190

192191
export function removeNoteFromPage(ydoc: Y.Doc, noteId: string): void {
193-
const page = getPageMap(ydoc);
194-
const noteIds = page.get(YPAGE_PAGE_KEY.noteIds) as Y.Array<string>;
192+
const noteIds = getNoteIds(ydoc);
195193
const notes = getNotesMap(ydoc);
196194

197195
ydoc.transact(() => {
@@ -239,8 +237,7 @@ export function getArrowMap(ydoc: Y.Doc, arrowId: string): Y.Map<unknown> | unde
239237

240238
export function addArrowToPage(ydoc: Y.Doc, arrowId: string): Y.Map<unknown> {
241239
const arrow = createArrowMap();
242-
const page = getPageMap(ydoc);
243-
const arrowIds = page.get(YPAGE_PAGE_KEY.arrowIds) as Y.Array<string>;
240+
const arrowIds = getArrowIds(ydoc);
244241
const arrows = getArrowsMap(ydoc);
245242

246243
ydoc.transact(() => {
@@ -252,8 +249,7 @@ export function addArrowToPage(ydoc: Y.Doc, arrowId: string): Y.Map<unknown> {
252249
}
253250

254251
export function removeArrowFromPage(ydoc: Y.Doc, arrowId: string): void {
255-
const page = getPageMap(ydoc);
256-
const arrowIds = page.get(YPAGE_PAGE_KEY.arrowIds) as Y.Array<string>;
252+
const arrowIds = getArrowIds(ydoc);
257253
const arrows = getArrowsMap(ydoc);
258254

259255
ydoc.transact(() => {
@@ -274,15 +270,33 @@ export function getPageMap(ydoc: Y.Doc): Y.Map<unknown> {
274270
}
275271

276272
export function getNoteIds(ydoc: Y.Doc): Y.Array<string> {
277-
return getPageMap(ydoc).get(YPAGE_PAGE_KEY.noteIds) as Y.Array<string>;
273+
const page = getPageMap(ydoc);
274+
let arr = page.get(YPAGE_PAGE_KEY.noteIds) as Y.Array<string> | undefined;
275+
if (arr == null) {
276+
arr = new Y.Array<string>();
277+
page.set(YPAGE_PAGE_KEY.noteIds, arr);
278+
}
279+
return arr;
278280
}
279281

280282
export function getArrowIds(ydoc: Y.Doc): Y.Array<string> {
281-
return getPageMap(ydoc).get(YPAGE_PAGE_KEY.arrowIds) as Y.Array<string>;
283+
const page = getPageMap(ydoc);
284+
let arr = page.get(YPAGE_PAGE_KEY.arrowIds) as Y.Array<string> | undefined;
285+
if (arr == null) {
286+
arr = new Y.Array<string>();
287+
page.set(YPAGE_PAGE_KEY.arrowIds, arr);
288+
}
289+
return arr;
282290
}
283291

284292
export function getNextZIndex(ydoc: Y.Doc): number {
285-
return (getPageMap(ydoc).get(YPAGE_PAGE_KEY.nextZIndex) as number) ?? 0;
293+
const page = getPageMap(ydoc);
294+
const val = page.get(YPAGE_PAGE_KEY.nextZIndex);
295+
if (val === undefined) {
296+
page.set(YPAGE_PAGE_KEY.nextZIndex, 0);
297+
return 0;
298+
}
299+
return (val as number) ?? 0;
286300
}
287301

288302
export function setNextZIndex(ydoc: Y.Doc, value: number): void {
@@ -294,18 +308,9 @@ export function setNextZIndex(ydoc: Y.Doc, value: number): void {
294308
// ------------------------------------------------------------------
295309

296310
export function createPageYDoc(): Y.Doc {
297-
const ydoc = new Y.Doc();
298-
299-
const page = ydoc.getMap(YPAGE_KEY.page);
300-
page.set(YPAGE_PAGE_KEY.noteIds, new Y.Array<string>());
301-
page.set(YPAGE_PAGE_KEY.arrowIds, new Y.Array<string>());
302-
page.set(YPAGE_PAGE_KEY.nextZIndex, 0);
303-
304-
// Ensure top-level maps exist so observers can subscribe immediately.
305-
ydoc.getMap(YPAGE_KEY.notes);
306-
ydoc.getMap(YPAGE_KEY.arrows);
307-
308-
return ydoc;
311+
// Return a completely blank doc so server bootstrap updates recreate
312+
// the exact shared types (same Yjs IDs) that the original session used.
313+
return new Y.Doc();
309314
}
310315

311316
// ------------------------------------------------------------------

0 commit comments

Comments
 (0)