diff --git a/prd.json b/prd.json index 42f2047..211c373 100644 --- a/prd.json +++ b/prd.json @@ -359,7 +359,7 @@ "Verify in browser using dev-browser skill" ], "priority": 20, - "passes": false, + "passes": true, "notes": "Dependencies: US-045, US-048" }, { @@ -376,7 +376,7 @@ "Typecheck passes" ], "priority": 21, - "passes": false, + "passes": true, "notes": "Dependencies: US-043, US-048" }, { diff --git a/progress.txt b/progress.txt index cb9e998..811e328 100644 --- a/progress.txt +++ b/progress.txt @@ -64,6 +64,9 @@ - `RemoteCursors` at `src/components/editor/RemoteCursors.tsx` renders collaborator cursors on canvas. Uses `useViewport()` to transform flow→screen coordinates. Throttle broadcasts to 50ms via timestamp ref. - Supabase Realtime presence events: `sync` (full state), `join` (arrivals with `newPresences` array), `leave` (departures with `leftPresences` array). Filter `this.userId` to skip own events. - `CollaborationToast` at `src/components/editor/CollaborationToast.tsx` shows join/leave notifications (bottom-left, auto-dismiss 3s). Uses `getUserColor(userId)` for accent color dot. +- Node lock indicators use `EditorContext` (`nodeLocks` Map, `onNodeFocus`, `onNodeBlur`). Each node component checks `nodeLocks.get(id)` for lock state and renders `NodeLockIndicator` + overlay if locked by another user. +- For ephemeral lock state (node editing locks), broadcast via `node-lock` event with `{ nodeId, userId, displayName, lockedAt }`. Send `nodeId: null` to release. +- `AuditTrailRecorder` at `src/lib/collaboration/auditTrail.ts` records node/edge changes to `audit_trail` table. Uses state diffing (previous vs current Maps), 1-second per-entity debounce, and fire-and-forget Supabase inserts. Only records local changes (guarded by `isRemoteUpdateRef` in FlowchartEditor). --- @@ -432,3 +435,38 @@ - The `getUserColor` function is duplicated from PresenceAvatars to avoid circular imports. Both use the same hash-to-color-index algorithm with the same RANDOM_COLORS palette for consistency. - No browser testing tools are available; manual verification is needed. --- + +## 2026-01-24 - US-049 +- What was implemented: Node editing lock indicators that show when another collaborator is editing a node +- Files changed: + - `src/lib/collaboration/realtime.ts` - Added `NodeLock` type, `onNodeLockUpdate` callback to `RealtimeCallbacks`, `node-lock` broadcast listener, and `broadcastNodeLock()` method + - `src/components/editor/NodeLockIndicator.tsx` - New component: renders a colored border and name label overlay on locked nodes + - `src/components/editor/EditorContext.tsx` - Extended context with `NodeLockInfo` type, `nodeLocks` (Map), `onNodeFocus`, and `onNodeBlur` callbacks + - `src/components/editor/nodes/DialogueNode.tsx` - Added lock detection, `NodeLockIndicator` rendering, "Being edited by [name]" overlay, `onFocus`/`onBlur` handlers + - `src/components/editor/nodes/VariableNode.tsx` - Same lock indicator pattern as DialogueNode + - `src/components/editor/nodes/ChoiceNode.tsx` - Same lock indicator pattern as DialogueNode + - `src/app/editor/[projectId]/FlowchartEditor.tsx` - Added `nodeLocks` state (Map), `localLockRef` for tracking own lock, `handleNodeFocus`/`handleNodeBlur` callbacks, `onNodeLockUpdate` handler in RealtimeConnection, lock expiry timer (60s check every 5s), lock cleanup on user leave and component unmount, extended `editorContextValue` with lock state +- **Learnings for future iterations:** + - Node lock uses Supabase Realtime broadcast (like cursors) — ephemeral, not persisted to DB. Event name: `node-lock`. + - Lock broadcasting uses `nodeId: string | null` pattern: non-null to acquire lock, `null` to release. The receiving side maps userId to their current lock. + - Lock expiry uses a 60-second timeout checked every 5 seconds via `setInterval`. The `lockedAt` timestamp is broadcast with the lock payload. + - Each node component accesses lock state via `EditorContext` (`nodeLocks` Map). The `NodeLockInfo` type extends `NodeLock` with a `color` field derived from `getUserColor()`. + - `onFocus`/`onBlur` on the node container div fires when any child input gains/loses focus (focus event bubbles), which naturally maps to "user is editing this node". + - Lock release on disconnect: broadcast `null` lock before calling `connection.disconnect()` in the cleanup return of the mount effect. + - Locks from disconnected users are cleaned up in the same effect that removes cursors (filtered by `presenceUsers` list). + - No browser testing tools are available; manual verification is needed. +--- + +## 2026-01-24 - US-051 +- What was implemented: Audit trail recording that writes all node/edge add/update/delete operations to the `audit_trail` table with debouncing and fire-and-forget semantics +- Files changed: + - `src/lib/collaboration/auditTrail.ts` - New `AuditTrailRecorder` class: tracks previous node/edge state, diffs against current state to detect add/update/delete operations, debounces writes per entity (1 second), merges rapid sequential actions (e.g., add+update=add, add+delete=no-op), fire-and-forget Supabase inserts with error logging, flush on destroy + - `src/app/editor/[projectId]/FlowchartEditor.tsx` - Added `auditRef` (AuditTrailRecorder), initialized in mount effect alongside CRDT manager, records node/edge changes in the CRDT sync effects (only for local changes, skipped for remote updates), destroyed on unmount +- **Learnings for future iterations:** + - The audit recorder piggybacks on the same CRDT sync effects that already compute `nodesForCRDT`/`edgesForCRDT` — this avoids duplicating the React Flow → FlowchartNode/Edge conversion. + - The `isRemoteUpdateRef` guard in the sync effects ensures audit entries are only created for local user actions, not for changes received from other collaborators (those users' own recorders will handle their audit entries). + - Debouncing per entity (1 second) prevents rapid edits (e.g., typing in a text field) from flooding the audit table. The merge logic handles transient states (add+delete within 1s = skip). + - The `destroy()` method flushes pending entries synchronously on unmount, ensuring in-flight edits aren't lost when navigating away. + - Supabase `.insert().then()` pattern provides fire-and-forget writes with error logging — the async operation doesn't block the editing flow. + - No browser testing needed — this is a developer/infrastructure story with no UI changes. +---