FRE-600: Implement Phase 1 WebSocket + Yjs CRDT foundation
- Create TypeScript and Vite configuration for SolidJS - Implement Yjs document structure for screenplay collaboration - Build WebSocket connection manager with exponential backoff reconnection - Create CRDT document manager with undo/redo support - Set up WebSocket sync server with JWT authentication - Add SolidJS reactive bindings for Yjs shared types - Build collaborative editor component - Write unit tests for CRDT operations - Document implementation in analysis/fre600_websocket_foundation.md Architecture: Yjs chosen over Automerge for better ecosystem and Tauri compatibility. WebSocket for sync, WebRTC for video. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
94
src/lib/collaboration/yjs-document.ts
Normal file
94
src/lib/collaboration/yjs-document.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Yjs document structure for screenplay collaboration
|
||||
* Defines the shared types used in the CRDT document
|
||||
*/
|
||||
|
||||
import { Doc, Text, Map as YMap, Array as YArray } from 'yjs';
|
||||
|
||||
/**
|
||||
* Screenplay metadata stored in Yjs Map
|
||||
*/
|
||||
export interface ScreenplayMetadata {
|
||||
title: string;
|
||||
author: string;
|
||||
projectId: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
version: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Character reference for the screenplay
|
||||
*/
|
||||
export interface CharacterRef {
|
||||
id: string;
|
||||
name: string;
|
||||
shortName: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scene metadata
|
||||
*/
|
||||
export interface SceneMeta {
|
||||
id: string;
|
||||
slugline: string;
|
||||
startTime: number; // Position in document
|
||||
duration: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Yjs document with the screenplay structure
|
||||
*/
|
||||
export function createScreenplayDoc(
|
||||
projectId: string,
|
||||
metadata: Partial<ScreenplayMetadata>
|
||||
): Doc {
|
||||
const doc = new Doc();
|
||||
|
||||
// Apply updates from remote clients
|
||||
doc.on('update', (update: Uint8Array, origin: unknown) => {
|
||||
// Origin can be 'local', 'remote', or a WebSocket provider
|
||||
const isLocal = origin === 'local';
|
||||
if (!isLocal) {
|
||||
console.log(`Received update from ${origin}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize shared types
|
||||
const text = doc.getText('main');
|
||||
const meta = doc.getMap<ScreenplayMetadata>('metadata');
|
||||
const characters = doc.getMap<CharacterRef>('characters');
|
||||
const scenes = doc.getMap<SceneMeta>('scenes');
|
||||
|
||||
// Set default metadata
|
||||
const defaultMeta: ScreenplayMetadata = {
|
||||
title: metadata.title || 'Untitled Screenplay',
|
||||
author: metadata.author || '',
|
||||
projectId,
|
||||
createdAt: metadata.createdAt || new Date().toISOString(),
|
||||
updatedAt: metadata.updatedAt || new Date().toISOString(),
|
||||
version: metadata.version || 1,
|
||||
};
|
||||
|
||||
// Initialize metadata if empty
|
||||
if (meta.toJSON().length === 0) {
|
||||
Object.entries(defaultMeta).forEach(([key, value]) => {
|
||||
meta.set(key as keyof ScreenplayMetadata, value);
|
||||
});
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create a shared type from the document
|
||||
*/
|
||||
export function getOrCreateSharedTypes(doc: Doc) {
|
||||
return {
|
||||
text: doc.getText('main'),
|
||||
metadata: doc.getMap<ScreenplayMetadata>('metadata'),
|
||||
characters: doc.getMap<CharacterRef>('characters'),
|
||||
scenes: doc.getMap<SceneMeta>('scenes'),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user