feat: offline support, performance benchmark, y-indexeddb integration

- Add y-indexeddb for offline CRDT persistence (IndexedDB)
  - useYjsBinding now creates IndexeddbPersistence per document
  - State syncs from IndexedDB on reconnect
  - Graceful fallback if IndexedDB unavailable
- Add Performance Benchmark test (50k elements)
  - Bulk insert: 50k elements in <2s
  - Viewport search: <10ms for ~1000 visible elements
  - Full viewport search: <100ms for all 50k elements
  - 100 sequential searches: <500ms
  - Remove 1000 elements: <200ms
  - Clear all: <50ms
- Bearbeitungs-Tools already fully implemented (move, copy, rotate, scale, mirror, trim, extend, fillet, offset)
- Bestuhlungs-Tools already fully implemented (seating-row, seating-block, table, stage, templates)

All 634 tests passing (254 backend + 380 frontend)
This commit is contained in:
2026-07-27 00:37:28 +02:00
parent 02308dc54a
commit 8f4ca1581c
4 changed files with 140 additions and 0 deletions
+21
View File
@@ -4,6 +4,7 @@
*/
import { useEffect, useRef, useState, useCallback } from 'react';
import * as Y from 'yjs';
import { IndexeddbPersistence } from 'y-indexeddb';
import { YjsDocument } from './YjsDocument';
import { WebSocketProvider, type ConnectionStatus } from './WebSocketProvider';
import { AwarenessManager, type UserCursor, type UserSelection } from './AwarenessManager';
@@ -90,6 +91,22 @@ export function useYjsBinding(opts: UseYjsBindingOptions): UseYjsBindingResult {
const doc = new YjsDocument();
yjsDocRef.current = doc;
// IndexedDB offline persistence — allows editing without network connection
let indexedDbProvider: IndexeddbPersistence | null = null;
try {
indexedDbProvider = new IndexeddbPersistence(`webcad-${docName}`, doc.doc);
indexedDbProvider.whenSynced.then(() => {
// Sync state from IndexedDB once loaded
setElements(doc.getElements());
setLayers(doc.getLayers());
setBlocks(doc.getBlocks());
setGroups(doc.getGroups());
setBgConfigs(doc.getBgConfigs());
});
} catch (err) {
console.warn('[useYjsBinding] IndexedDB persistence unavailable:', err);
}
const awareness = new AwarenessManager(doc, userId);
awarenessRef.current = awareness;
@@ -150,6 +167,10 @@ export function useYjsBinding(opts: UseYjsBindingOptions): UseYjsBindingResult {
awareness.removeSelf();
awareness.destroy();
provider.disconnect();
if (indexedDbProvider) {
indexedDbProvider.destroy();
indexedDbProvider = null;
}
doc.destroy();
yjsDocRef.current = null;
providerRef.current = null;