diff --git a/src/stores/timelineStore.ts b/src/stores/timelineStore.ts index ae66a01..0be74fb 100644 --- a/src/stores/timelineStore.ts +++ b/src/stores/timelineStore.ts @@ -283,8 +283,23 @@ export const useTimelineStore = create( return { timelines: newTimelines }; }); - // Load target state's graph (nodes, edges, and groups - types are global) - // IMPORTANT: Use loadGraphState for atomic update to prevent React Flow errors + /** + * ═══════════════════════════════════════════════════════════════════════════ + * SYNC POINT 5: timeline → graphStore + * ═══════════════════════════════════════════════════════════════════════════ + * + * When: Timeline state switch (user navigates to different state in timeline) + * What: Loads target state's graph (nodes, edges, groups) into graphStore + * Source of Truth: timelineStore (targetState.graph) + * Direction: timeline.states[targetStateId].graph → graphStore + * + * When switching between timeline states, we load the target state's graph + * into the editor. Types and labels remain the same (document-level config), + * only nodes/edges/groups change between states. + * + * IMPORTANT: Uses loadGraphState for atomic update to prevent React Flow + * "Parent node not found" errors when groups and their children load. + */ const graphStore = useGraphStore.getState(); graphStore.loadGraphState({ nodes: targetState.graph.nodes as unknown as Actor[], diff --git a/src/stores/workspace/useActiveDocument.ts b/src/stores/workspace/useActiveDocument.ts index 3884609..3396424 100644 --- a/src/stores/workspace/useActiveDocument.ts +++ b/src/stores/workspace/useActiveDocument.ts @@ -64,7 +64,20 @@ export function useActiveDocument() { labels: [], }); - // Load active document into graphStore when it changes + /** + * ═══════════════════════════════════════════════════════════════════════════ + * SYNC POINT 1: Document → graphStore + * ═══════════════════════════════════════════════════════════════════════════ + * + * When: Active document switches (document tab change) + * What: Loads nodes, edges, groups, types, labels from document into graphStore + * Source of Truth: ConstellationDocument (persistent storage) + * Direction: document.timeline.currentState → graphStore (working copy) + * + * This is the entry point for loading a document's data into the editor. + * It deserializes the document's current timeline state and populates the + * graphStore with the working copy that React Flow will render. + */ useEffect(() => { if (activeDocument && activeDocumentId) { console.log(`Loading document into graph editor: ${activeDocumentId}`, activeDocument.metadata.title); @@ -181,7 +194,19 @@ export function useActiveDocument() { labels: graphLabels as LabelConfig[], }; - // Update the timeline's current state with the new graph data (nodes, edges, and groups) + /** + * ═══════════════════════════════════════════════════════════════════════════ + * SYNC POINT 2: graphStore → timeline current state + * ═══════════════════════════════════════════════════════════════════════════ + * + * When: Graph changes detected (node/edge/group add/delete/move) + * What: Updates timeline.states[currentStateId].graph with latest graphStore data + * Source of Truth: graphStore (working copy) + * Direction: graphStore → timeline.states[currentStateId].graph + * + * This keeps the timeline's current state in sync with the editor's working copy. + * When the document is saved, this updated timeline will be serialized to storage. + */ useTimelineStore.getState().saveCurrentGraph({ nodes: graphNodes as never[], edges: graphEdges as never[], diff --git a/src/stores/workspaceStore.ts b/src/stores/workspaceStore.ts index fb67745..913966f 100644 --- a/src/stores/workspaceStore.ts +++ b/src/stores/workspaceStore.ts @@ -786,7 +786,20 @@ export const useWorkspaceStore = create((set, get) // and are managed via workspaceStore's type management actions. // We do NOT copy them from graphStore because the document is the source of truth. - // Save timeline data if exists + /** + * ═══════════════════════════════════════════════════════════════════════════ + * SYNC POINT 3: timeline → document + * ═══════════════════════════════════════════════════════════════════════════ + * + * When: Document save (auto-save or manual) + * What: Serializes entire timeline (all states) to document.timeline + * Source of Truth: timelineStore (transient working copy) + * Direction: timelineStore → document.timeline → localStorage + * + * This persists the complete timeline structure to storage. The timeline's + * current state has already been updated by SYNC POINT 2, so we're saving + * the latest graph data along with all historical timeline branches. + */ const timelineState = useTimelineStore.getState(); const timeline = timelineState.timelines.get(documentId); @@ -1002,6 +1015,23 @@ export const useWorkspaceStore = create((set, get) } }, + /** + * ═══════════════════════════════════════════════════════════════════════════ + * SYNC POINT 4: document types → graphStore + * ═══════════════════════════════════════════════════════════════════════════ + * + * When: Type management operations (add/update/delete node/edge types, labels) + * What: Updates document types/labels and syncs to graphStore if document is active + * Source of Truth: ConstellationDocument (document.nodeTypes, document.edgeTypes, document.labels) + * Direction: document → graphStore (if active document) + * + * Type configurations are document-level properties. When modified, changes + * are persisted to the document first, then synced to graphStore if this is + * the currently active document. This ensures the editor always displays the + * correct types for the current document. + * + * All type operations use atomic transactions with rollback (Phase 3.1). + */ addNodeTypeToDocument: (documentId: string, nodeType) => { const state = get(); const doc = state.documents.get(documentId);