TROUBLESHOOTING FLOW SYNC
Ecosistema completo para organización y gestión de contenido con interfaces espaciales
Troubleshooting: Flow Loading Synchronization Issue
Status: ✅ FIXED
Date: October 23, 2025
Issue: Blocks disappear after clicking when flow is loaded
🐛 Issue Description
When loading a flow with multiple blocks:
- Initial load shows all blocks (e.g., 2 blocks)
- User clicks on any block
- Other blocks suddenly disappear
- Only 1 block remains visible
Logs
logic-builder.component.ts:348 🔄 Received 0 blocks from Flash Persistence
logic-builder.component.ts:373 🔗 Received 0 connections from Flash Persistence
...
logic-builder.component.ts:348 🔄 Received 1 blocks from Flash Persistence ← Only 1 block!
...
logic-builder.component.ts:2735 ✅ Flow loaded: menso flow (flow_1761254148303)
logic-builder.component.ts:2736 - Blocks: 2 ← Flow has 2 blocks!
logic-builder.component.ts:2737 - Connections: 0
...
block-library.component.ts:352 🔍 [Block Library] Selected block changed: block_1761254144554
logic-builder.component.ts:348 🔄 Received 1 blocks from Flash Persistence ← Re-sync shows only 1 block
🔍 Root Cause Analysis
The Problem: Two Separate Signal Systems
The Logic Builder component had two separate signal systems that were out of sync:
1. Local Component Signals (NOT persisted)
// In logic-builder.component.ts
canvasBlocks = signal<LogicBlock[]>([]); // Local component signal
canvasConnections = signal<LogicConnection[]>([]); // Local component signal
2. Flash Persistence Signals (Persisted to storage)
// In singularity.service.ts
private readonly _blocks = autoFlashState<LogicBlock[]>(
'singularity:blocks:canvas',
[]
);
private readonly _connections = autoFlashState<LogicConnection[]>(
'singularity:connections:canvas',
[]
);
The Synchronization Mechanism
The component uses an effect() to sync FROM Flash Persistence TO local signals:
// Constructor effect - Syncs Flash Persistence → Local Signal
effect(() => {
const blocks = this.singularityService.blocks(); // Read from Flash Persistence
console.log(`🔄 Received ${blocks.length} blocks from Flash Persistence`);
this.canvasBlocks.set(blocks); // Update local signal
...
});
This is ONE-WAY sync: Flash Persistence → Local Signal
What Was Happening
Old loadFlow() Implementation (BROKEN)
async loadFlow(flowId: string): Promise<void> {
const flow = await this.storage.get<LogicFlow>(flowId);
// ❌ PROBLEM: Updates LOCAL signal only
this.canvasBlocks.set(flow.blocks || []); // 2 blocks shown in UI
this.canvasConnections.set(flow.connections || []);
// Flash Persistence still has 1 block from previous session!
}
Timeline of Events:
1. User navigates to Logic Builder
└─ Flash Persistence loads: 1 block (from previous session)
2. Component effect() runs
└─ Syncs from Flash Persistence → Local signal
└─ UI shows: 1 block
3. Auto-loads "menso flow" (has 2 blocks)
└─ loadFlow() updates LOCAL signal: 2 blocks
└─ UI shows: 2 blocks ✅
└─ Flash Persistence still has: 1 block ⚠️
4. User clicks on a block
└─ Triggers some reactive update
└─ effect() re-runs
└─ Syncs from Flash Persistence (1 block) → Local signal
└─ UI shows: 1 block ❌ (OTHER BLOCK DISAPPEARS!)
The Disconnect
┌────────────────────────────────────────┐
│ Flash Persistence (Service) │
│ ✅ Persistent across sessions │
│ 📦 Saved to storage-system │
│ 🔄 Single source of truth │
│ │
│ Blocks: [block1] ← 1 block │
└────────────────────────────────────────┘
↓ ONE-WAY SYNC (effect)
↓
┌────────────────────────────────────────┐
│ Local Signals (Component) │
│ ⚠️ Temporary, not persisted │
│ 🎨 For UI rendering only │
│ │
│ Blocks: [block1, block2] ← 2 blocks│
└────────────────────────────────────────┘
CONFLICT: Flash has 1 block, Local has 2 blocks
RESULT: effect() re-runs → Local overwritten to match Flash (1 block)
✅ The Fix
Update Flash Persistence Instead of Local Signals
Instead of updating the local signal directly, call the service's setBlocks() and setConnections() methods, which update Flash Persistence:
New loadFlow() Implementation (FIXED)
async loadFlow(flowId: string): Promise<void> {
const flow = await this.storage.get<LogicFlow>(flowId);
console.log(`📥 Loading flow: ${flow.name} with ${flow.blocks?.length || 0} blocks`);
// ✅ CRITICAL FIX: Update Flash Persistence via service
// This ensures perfect synchronization between component and storage
this.singularityService.setBlocks(flow.blocks || []);
this.singularityService.setConnections(flow.connections || []);
// Restore block positions (local Map, not persisted)
this.blockPositions.clear();
flow.blocks?.forEach(block => {
if (block.position) {
this.blockPositions.set(block.id, block.position);
}
});
console.log(`✅ Flow loaded and synced to Flash Persistence ✅`);
}
New Service Method Added
// In singularity.service.ts
setConnections(connections: LogicConnection[]): void {
this._connections.set(connections);
this._isModified.set(true);
this.addToUndoStack({ type: 'set_connections', data: connections, timestamp: new Date() });
}
How It Works Now
┌────────────────────────────────────────┐
│ Flash Persistence (Service) │
│ ✅ UPDATED via setBlocks() │
│ 📦 Saved to storage-system │
│ 🔄 Single source of truth │
│ │
│ Blocks: [block1, block2] ← 2 blocks│
└────────────────────────────────────────┘
↓ ONE-WAY SYNC (effect)
↓
┌────────────────────────────────────────┐
│ Local Signals (Component) │
│ ✅ Auto-synced from Flash │
│ 🎨 Always matches Flash │
│ │
│ Blocks: [block1, block2] ← 2 blocks│
└────────────────────────────────────────┘
PERFECT SYNC: Flash has 2 blocks, Local has 2 blocks ✅
Fixed Timeline:
1. User navigates to Logic Builder
└─ Flash Persistence loads: 1 block (from previous session)
2. Component effect() runs
└─ Syncs from Flash Persistence → Local signal
└─ UI shows: 1 block
3. Auto-loads "menso flow" (has 2 blocks)
└─ loadFlow() calls singularityService.setBlocks(flow.blocks)
└─ Flash Persistence updated: 2 blocks ✅
└─ effect() automatically re-runs
└─ Syncs from Flash Persistence (2 blocks) → Local signal
└─ UI shows: 2 blocks ✅
4. User clicks on a block
└─ Triggers reactive update
└─ effect() re-runs
└─ Syncs from Flash Persistence (2 blocks) → Local signal
└─ UI STILL shows: 2 blocks ✅ (NOTHING DISAPPEARS!)
🎯 Key Principles
1. Single Source of Truth
Flash Persistence is the ONLY source of truth. Local signals are just mirrors for UI rendering.
2. Always Update Flash Persistence
Never update local signals directly. Always update Flash Persistence via service methods, which will automatically sync to local signals via effect().
3. One-Way Data Flow
Flash Persistence → (effect) → Local Signals → UI
↑
└─ Update via service methods (setBlocks, setConnections, addBlock, etc.)
4. Trust the Effect
The effect() in the constructor handles ALL synchronization automatically. No manual syncing needed.
🔍 How to Identify Similar Issues
Symptoms:
- Data appears, then disappears
- Changes revert after user interaction
- Logs show different counts (Flash vs local)
- UI state doesn't persist across actions
Check For:
- ✅ Are you reading from
this.singularityService.blocks()but updatingthis.canvasBlocks? - ✅ Are there separate local signals for the same data that Flash Persistence manages?
- ✅ Does the
effect()overwrite local changes?
The Fix:
Always use service methods to update state, never update local signals directly.
📚 Related Files
Modified Files:
libs/singularity-lib/src/lib/components/logic-builder/logic-builder.component.tsloadFlow()method updated to usesingularityService.setBlocks/setConnections()
libs/singularity-lib/src/lib/services/singularity.service.ts- Added
setConnections()method
- Added
Key Methods:
SingularityService.setBlocks(blocks: LogicBlock[])SingularityService.setConnections(connections: LogicConnection[])← NEWSingularityService.addBlock(block: LogicBlock)SingularityService.updateBlock(block: LogicBlock)SingularityService.updateBlockPosition(blockId, position)
✅ Verification
After fix, you should see in logs:
📥 Loading flow: menso flow with 2 blocks
✅ Flow loaded: menso flow (flow_1761254148303)
- Blocks: 2
- Connections: 0
- Synced to Flash Persistence ✅
🔄 Received 2 blocks from Flash Persistence ← Correct count!
And when you click a block:
🔍 [Block Library] Selected block changed: block_xyz
🔄 Received 2 blocks from Flash Persistence ← Still 2 blocks!
🎉 Summary
Problem: Local signals and Flash Persistence were out of sync
Solution: Always update Flash Persistence via service methods
Result: Perfect synchronization, blocks never disappear
The correct answer was: The initial state (what Flash Persistence has) is the source of truth, not what you see after clicking. But now, both match perfectly!