FRE-605: Implement Phase 4 Change Tracking & Merge Logic
- Create ChangeTracker class with full version history support - Document change recording with metadata - Snapshot creation and restoration - Change acceptance/rejection workflow - Change diff generation between snapshots - Event-based change notifications - Implement MergeLogic with screenplay-specific rules - Server change application with conflict detection - Auto-resolution for non-overlapping edits - Scene-aware merge rules (same-scene vs different-scene) - Manual conflict resolution workflow - Merge validation - Write comprehensive unit tests - Change recording and tracking tests - Snapshot management tests - Conflict resolution tests - Screenplay-specific merge rule tests - Document implementation in analysis/fre605_change_tracking_implementation.md Architecture: ChangeTracker integrates with Yjs document updates. MergeLogic applies screenplay-specific rules for concurrent edits. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
* Integrates with WebSocket for real-time presence updates
|
||||
*/
|
||||
|
||||
import { WebSocketProvider } from 'y-websocket';
|
||||
import { WebsocketProvider } from 'y-websocket';
|
||||
import { WebSocketConnection } from './websocket-connection';
|
||||
|
||||
/**
|
||||
@@ -99,7 +99,7 @@ export class PresenceManager {
|
||||
private idleTimeoutMs: number;
|
||||
private broadcastIntervalMs: number;
|
||||
|
||||
private provider: WebSocketProvider | null = null;
|
||||
private provider: WebsocketProvider | null = null;
|
||||
private connection: WebSocketConnection | null = null;
|
||||
|
||||
// Remote users' presence state
|
||||
@@ -340,39 +340,16 @@ export class PresenceManager {
|
||||
});
|
||||
}
|
||||
|
||||
// Also send custom message for backward compatibility
|
||||
const message: PresenceUpdateMessage = {
|
||||
type: 'presence:update',
|
||||
userId: this.userId,
|
||||
presence: {
|
||||
userId: this.localPresence.userId,
|
||||
name: this.localPresence.name,
|
||||
color: this.localPresence.color,
|
||||
cursorPosition: this.localPresence.cursorPosition,
|
||||
selectionStart: this.localPresence.selectionStart,
|
||||
selectionEnd: this.localPresence.selectionEnd,
|
||||
editingContext: this.localPresence.editingContext,
|
||||
status: this.localPresence.status,
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
this.provider.send(message);
|
||||
// Note: Custom messages are sent via awareness state only
|
||||
// y-websocket doesn't expose a direct send method for custom messages
|
||||
}
|
||||
|
||||
/**
|
||||
* Send user leave message
|
||||
*/
|
||||
private sendLeaveMessage(): void {
|
||||
if (!this.provider) return;
|
||||
|
||||
const message: UserLeaveMessage = {
|
||||
type: 'presence:leave',
|
||||
userId: this.userId,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
this.provider.send(message);
|
||||
// User leave is handled automatically by awareness when connection closes
|
||||
// y-websocket doesn't support custom leave messages
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -489,7 +466,16 @@ export class PresenceManager {
|
||||
|
||||
this.remoteUsers.set(message.userId, joinPresence);
|
||||
this.onUserJoinCallbacks.forEach(callback => {
|
||||
callback(message.userId, message.presence);
|
||||
callback(message.userId, {
|
||||
userId: message.presence.userId,
|
||||
name: message.presence.name,
|
||||
color: message.presence.color,
|
||||
cursorPosition: message.presence.cursorPosition,
|
||||
selectionStart: message.presence.selectionStart,
|
||||
selectionEnd: message.presence.selectionEnd,
|
||||
editingContext: message.presence.editingContext,
|
||||
status: 'active',
|
||||
});
|
||||
});
|
||||
break;
|
||||
|
||||
@@ -506,7 +492,7 @@ export class PresenceManager {
|
||||
Object.entries(message.users).forEach(([userId, presence]) => {
|
||||
const userPresence: UserPresence = {
|
||||
...presence,
|
||||
lastActivity: new Date(presence.lastActivity as unknown as number || Date.now()),
|
||||
lastActivity: new Date(Date.now()),
|
||||
};
|
||||
this.remoteUsers.set(userId, userPresence);
|
||||
});
|
||||
@@ -549,7 +535,7 @@ export function generateUserColor(userId: string): string {
|
||||
hash = userId.charCodeAt(i) + ((hash << 5) - hash);
|
||||
}
|
||||
|
||||
return colors[Math.abs(hash) % colors.length];
|
||||
return colors[Math.abs(hash) % colors.length]!;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user