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:

  1. Initial load shows all blocks (e.g., 2 blocks)
  2. User clicks on any block
  3. Other blocks suddenly disappear
  4. 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:

Check For:

  1. ✅ Are you reading from this.singularityService.blocks() but updating this.canvasBlocks?
  2. ✅ Are there separate local signals for the same data that Flash Persistence manages?
  3. ✅ Does the effect() overwrite local changes?

The Fix:

Always use service methods to update state, never update local signals directly.


📚 Related Files

Modified Files:

Key Methods:


✅ 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!