Skip to content

Commit c6cc574

Browse files
authored
improvement: [rtc] migrate y-js to Loro (#4831)
This moves from loro to yjs. There may be a few edge cases, but this is a huge improvement over yjs. Better devx and performance, and fewer bugs. Closes #4699 Closes #4694 Closes #4693 Closes #4692
1 parent 6587fc3 commit c6cc574

File tree

16 files changed

+983
-1093
lines changed

16 files changed

+983
-1093
lines changed

frontend/package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@
113113
"js-cookie": "^3.0.5",
114114
"katex": "^0.16.22",
115115
"lodash-es": "^4.17.21",
116+
"loro-codemirror": "^0.1.6",
117+
"loro-crdt": "^1.5.4",
116118
"lucide-react": "^0.503.0",
117119
"lz-string": "^1.5.0",
118120
"marked": "^15.0.11",
@@ -150,9 +152,6 @@
150152
"vega-loader": "^4.5.3",
151153
"vscode-languageserver-protocol": "^3.17.5",
152154
"web-vitals": "^4.2.4",
153-
"y-codemirror.next": "^0.3.5",
154-
"y-websocket": "^2.1.0",
155-
"yjs": "^13.6.26",
156155
"zod": "^3.24.3"
157156
},
158157
"scripts": {
@@ -238,6 +237,8 @@
238237
"turbo": "^2.5.2",
239238
"typescript": "^5.8.3",
240239
"vite": "^6.3.2",
240+
"vite-plugin-top-level-await": "^1.5.0",
241+
"vite-plugin-wasm": "^3.4.1",
241242
"vite-tsconfig-paths": "^5.1.4",
242243
"vitest": "^3.1.1"
243244
},

frontend/pnpm-lock.yaml

+71-320
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/src/components/editor/cell/code/cell-editor.tsx

+37-8
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,15 @@ import { useSplitCellCallback } from "../useSplitCell";
3434
import { invariant } from "@/utils/invariant";
3535
import { connectionAtom } from "@/core/network/connection";
3636
import { WebSocketState } from "@/core/websocket/types";
37-
import { realTimeCollaboration } from "@/core/codemirror/rtc/extension";
37+
import {
38+
connectedDocAtom,
39+
realTimeCollaboration,
40+
} from "@/core/codemirror/rtc/extension";
3841
import { store } from "@/core/state/jotai";
3942
import { useDeleteCellCallback } from "../useDeleteCell";
4043
import { useSaveNotebook } from "@/core/saving/save-component";
44+
import { DelayMount } from "@/components/utils/delay-mount";
45+
import { Button } from "@/components/ui/button";
4146

4247
export interface CellEditorProps
4348
extends Pick<CellRuntimeState, "status">,
@@ -234,9 +239,10 @@ const CellEditorInternal = ({
234239
saveOrNameNotebook,
235240
]);
236241

242+
const rtcEnabled = getFeatureFlag("rtc_v2");
237243
const handleInitializeEditor = useEvent(() => {
238244
// If rtc is enabled, use collaborative editing
239-
if (getFeatureFlag("rtc_v2")) {
245+
if (rtcEnabled) {
240246
const rtc = realTimeCollaboration(
241247
cellId,
242248
(code) => {
@@ -259,13 +265,15 @@ const CellEditorInternal = ({
259265
});
260266
setEditorView(ev);
261267
// Initialize the language adapter
262-
switchLanguage(ev, getInitialLanguageAdapter(ev.state).type);
268+
if (!rtcEnabled) {
269+
switchLanguage(ev, getInitialLanguageAdapter(ev.state).type);
270+
}
263271
});
264272

265273
const handleReconfigureEditor = useEvent(() => {
266274
invariant(editorViewRef.current !== null, "Editor view is not initialized");
267275
// If rtc is enabled, use collaborative editing
268-
if (getFeatureFlag("rtc_v2")) {
276+
if (rtcEnabled) {
269277
const rtc = realTimeCollaboration(cellId, (code) => {
270278
// It's not really a formatting change,
271279
// but this means it won't be marked as stale
@@ -292,7 +300,7 @@ const CellEditorInternal = ({
292300

293301
const handleDeserializeEditor = useEvent(() => {
294302
invariant(serializedEditorState, "Editor view is not initialized");
295-
if (getFeatureFlag("rtc_v2")) {
303+
if (rtcEnabled) {
296304
const rtc = realTimeCollaboration(
297305
cellId,
298306
(code) => {
@@ -303,6 +311,7 @@ const CellEditorInternal = ({
303311
code,
304312
);
305313
extensions.push(rtc.extension);
314+
code = rtc.code;
306315
}
307316

308317
const ev = new EditorView({
@@ -316,7 +325,9 @@ const CellEditorInternal = ({
316325
),
317326
});
318327
// Initialize the language adapter
319-
switchLanguage(ev, getInitialLanguageAdapter(ev.state).type);
328+
if (!rtcEnabled) {
329+
switchLanguage(ev, getInitialLanguageAdapter(ev.state).type);
330+
}
320331
setEditorView(ev);
321332
// Clear the serialized state so that we don't re-create the editor next time
322333
cellActions.clearSerializedEditorState({ cellId });
@@ -507,9 +518,27 @@ function WithWaitUntilConnected<T extends {}>(
507518
) {
508519
const WaitUntilConnectedComponent = (props: T) => {
509520
const connection = useAtomValue(connectionAtom);
521+
const [rtcDoc, setRtcDoc] = useAtom(connectedDocAtom);
510522

511-
if (connection.state === WebSocketState.CONNECTING) {
512-
return null;
523+
if (
524+
connection.state === WebSocketState.CONNECTING ||
525+
rtcDoc === undefined
526+
) {
527+
return (
528+
<div className="flex h-full w-full items-baseline p-4">
529+
<DelayMount milliseconds={1000} fallback={null}>
530+
<span>Waiting for real-time collaboration connection...</span>
531+
<Button
532+
variant="link"
533+
onClick={() => {
534+
setRtcDoc("disabled");
535+
}}
536+
>
537+
Turn off real-time collaboration
538+
</Button>
539+
</DelayMount>
540+
</div>
541+
);
513542
}
514543

515544
return <Component {...props} />;

frontend/src/components/editor/renderers/vertical-layout/useDelayVisibility.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ export function useDelayVisibility(numCells: number, mode: AppMode) {
2828
focusCellByName(cellName);
2929
} else {
3030
// Otherwise focus on the first cell
31-
focusFirstEditor();
31+
try {
32+
focusFirstEditor();
33+
} catch (error) {
34+
Logger.warn("Error focusing first editor", error);
35+
}
3236
}
3337
});
3438
}

frontend/src/core/codemirror/rtc/__tests__/cell-manager.test.ts

-137
This file was deleted.

frontend/src/core/codemirror/rtc/cell-manager.ts

-101
This file was deleted.

0 commit comments

Comments
 (0)