# Local Storage Persistence Plan for Constellation Analyzer
## 1. Data Format Specification
### JSON Schema Structure
```typescript
interface ConstellationDocument {
// Metadata
metadata: {
version: string; // Schema version (e.g., "1.0.0")
appName: string; // "constellation-analyzer"
createdAt: string; // ISO timestamp
updatedAt: string; // ISO timestamp
lastSavedBy: string; // Browser fingerprint or "unknown"
};
// Graph state
graph: {
nodes: SerializedActor[]; // Simplified Actor[] without React Flow internals
edges: SerializedRelation[]; // Simplified Relation[] without React Flow internals
nodeTypes: NodeTypeConfig[]; // Already serializable
edgeTypes: EdgeTypeConfig[]; // Already serializable
};
// Editor settings (optional - may persist separately)
editorSettings?: EditorSettings;
}
// Simplified node structure for storage
interface SerializedActor {
id: string;
type: string; // React Flow node type (e.g., "custom")
position: { x: number; y: number };
data: ActorData;
selected?: boolean;
dragging?: boolean;
}
// Simplified edge structure for storage
interface SerializedRelation {
id: string;
source: string;
target: string;
type?: string; // React Flow edge type
data: RelationData;
sourceHandle?: string | null;
targetHandle?: string | null;
}
```
**Rationale:**
- Separate metadata for versioning and migration support
- Exclude React Flow-specific runtime properties (measured, width, height, etc.)
- Store minimal required data to reconstruct full state
- Include timestamps for debugging and conflict resolution
---
## 2. Storage Strategy
### Architecture Choice: **Zustand Middleware Pattern**
**Recommended approach:** Create a custom Zustand middleware that intercepts state changes and persists to localStorage.
### Storage Keys Strategy
```typescript
const STORAGE_KEYS = {
GRAPH_STATE: 'constellation:graph:v1', // Main graph data
EDITOR_SETTINGS: 'constellation:editor:v1', // Editor preferences
AUTOSAVE_FLAG: 'constellation:autosave', // Flag for crash recovery
LAST_SAVED: 'constellation:lastSaved', // Timestamp
};
```
**Why separate keys:**
- Allow partial updates (save graph independently from settings)
- Different persistence strategies (graph = debounced, settings = immediate)
- Easier to manage storage quota
### Debouncing Strategy
```typescript
// Debounce configuration
const DEBOUNCE_CONFIG = {
DELAY: 1000, // 1 second after last change
MAX_WAIT: 5000, // Force save every 5 seconds
THROTTLE_NODE_DRAG: 500, // Faster saves during drag operations
};
```
**Implementation approach:**
- Use `lodash.debounce` or custom implementation
- Different debounce times for different operations:
- Node dragging: 500ms (frequent but predictable)
- Adding/deleting: 1000ms (less frequent)
- Typing in properties: 1000ms (standard)
- Max wait ensures data isn't lost even during continuous editing
### When to Save
**Auto-save triggers:**
1. Any GraphStore state mutation (nodes, edges, nodeTypes, edgeTypes)
2. EditorStore settings changes (optional, can be immediate)
3. Before window unload (emergency save)
4. After successful import (to persist imported state)
**Don't save:**
- Temporary UI state (selectedRelationType, hover states)
- React Flow internals (viewport, connection state)
---
## 3. Loading Strategy
### Bootstrap Sequence
```
1. App starts → Check for stored data
2. Validate schema version
3. If valid: Deserialize → Hydrate store
4. If invalid: Attempt migration OR use defaults
5. If corrupted: Show recovery dialog → Load defaults
6. Set up auto-save listeners
```
### Validation Approach
Use runtime validation to ensure data integrity.
**Validation checks:**
- Schema version exists and is supported
- All required fields present
- Node IDs are unique
- Edge source/target references exist in nodes
- Type references (node.data.type) exist in nodeTypes
- Color values are valid hex codes
### Hydration Process
Initialize store with loaded data, adding back React Flow properties with defaults.
---
## 4. Error Handling Strategy
### Error Categories
```typescript
enum PersistenceError {
QUOTA_EXCEEDED = 'quota_exceeded',
CORRUPTED_DATA = 'corrupted_data',
VERSION_MISMATCH = 'version_mismatch',
PARSE_ERROR = 'parse_error',
STORAGE_UNAVAILABLE = 'storage_unavailable',
}
```
### Error Recovery Strategies
| Error | Strategy | User Experience |
|-------|----------|-----------------|
| **Quota Exceeded** | 1. Show warning
2. Compress data (remove whitespace)
3. Offer export to file
4. Continue without auto-save | Toast notification: "Storage full. Save to file to preserve work." |
| **Corrupted Data** | 1. Attempt partial recovery
2. Load default state
3. Log error for debugging
4. Offer to restore from backup | Dialog: "Previous session corrupted. Starting fresh." + Show details |
| **Version Mismatch** | 1. Attempt migration
2. If migration fails, load defaults
3. Preserve old data as backup | Toast: "Updated to new version. Data migrated successfully." |
| **Parse Error** | 1. Clear corrupted data
2. Load defaults
3. Log error | Toast: "Unable to restore previous session." |
| **Storage Unavailable** | 1. Detect private/incognito mode
2. Disable auto-save
3. Show warning | Banner: "Auto-save disabled (private mode). Export to save work." |
### Multi-Tab Synchronization
**Problem:** Multiple tabs open, each saving independently → conflicts
**Solution:** Use `storage` event listener
**Recommendation:** Start with last-write-wins. Add conflict resolution later if needed.
---
## 5. Code Architecture
### Folder Structure
```
/src
/stores
/persistence
constants.ts # Storage keys, config
types.ts # Serialization types
loader.ts # Load and validate data
saver.ts # Save and serialize data
middleware.ts # Zustand middleware for auto-save
migrations.ts # Version migration logic (future)
hooks.ts # React hooks for persistence features (future)
graphStore.ts # Enhanced with persistence
editorStore.ts # Enhanced with persistence
```
### Module Responsibilities
**constants.ts**
- Storage keys
- Debounce configuration
- Current schema version
**types.ts**
- ConstellationDocument interface
- SerializedActor, SerializedRelation interfaces
- PersistenceError enum
**loader.ts**
- Reads from localStorage
- Validates schema
- Deserializes data
- Returns typed ConstellationDocument or null
**saver.ts**
- Serializes current store state
- Writes to localStorage
- Handles quota errors
- Updates lastSaved timestamp
**middleware.ts**
- Intercepts Zustand state changes
- Triggers debounced saves
- Filters what gets persisted
**migrations.ts** (Phase 3)
- Version detection
- Data transformation between versions
- Backward compatibility
**hooks.ts** (Phase 3)
- `usePersistence()` - Monitor save status
- `useAutoSave()` - Manual save trigger
- `useStorageStats()` - Storage quota info
---
## 6. Migration Strategy
### Version Naming Convention
Use semantic versioning: `MAJOR.MINOR.PATCH`
- **MAJOR:** Breaking changes (incompatible schema)
- **MINOR:** New fields (backward compatible)
- **PATCH:** Bug fixes, no schema changes
### Migration Registry
```typescript
// migrations.ts
type Migration = (old: any) => ConstellationDocument;
const MIGRATIONS: Record = {
'0.9.0->1.0.0': (old) => {
// Example: Rename field
return {
...old,
graph: {
...old.graph,
nodes: old.graph.actors.map(actor => ({
...actor,
data: { ...actor.data, label: actor.data.name },
})),
},
};
},
};
```
---
## 7. Implementation Phases
### Phase 1: Core Persistence (MVP) ✅ IMPLEMENTING NOW
- [x] Create serialization types
- [x] Create constants
- [x] Implement saver.ts with debouncing
- [x] Implement loader.ts with basic validation
- [x] Add persistence middleware to graphStore
- [ ] Test save/load cycle
### Phase 2: Error Handling
- [ ] Add quota exceeded handling
- [ ] Add corrupted data recovery
- [ ] Add storage unavailable detection
- [ ] Create user-facing error messages
### Phase 3: Advanced Features
- [ ] Multi-tab synchronization
- [ ] Migration system
- [ ] Backup rotation
- [ ] Storage stats monitoring
### Phase 4: Polish
- [ ] Performance optimization
- [ ] Compression for large graphs
- [ ] Export/import integration
- [ ] User preferences for auto-save behavior
---
## 8. Testing Strategy
### Test Cases
**Manual Tests (Phase 1):**
- Create nodes/edges → Reload page → Verify restored
- Edit node properties → Reload → Verify persisted
- Add custom actor types → Reload → Verify persisted
- Create relations → Reload → Verify persisted
**Integration Tests (Phase 2):**
- Save → Clear → Load → Verify state matches
- Corrupted data → Loads defaults
- Quota exceeded → Handles gracefully
**E2E Tests (Phase 3):**
- Multiple tabs → Changes sync
- Version migration works
---
## Summary
**Key Technical Decisions:**
1. **Architecture:** Zustand middleware pattern for clean separation
2. **Storage:** localStorage with versioned schema
3. **Serialization:** Minimal JSON format, exclude React Flow internals
4. **Debouncing:** 1s delay, 5s max wait, operation-specific tuning
5. **Validation:** Runtime validation on load
6. **Errors:** Graceful degradation with user notifications
7. **Multi-tab:** Storage event listener with last-write-wins
8. **Migration:** Version registry with transformation functions
**Current Version:** 1.0.0
**Current Phase:** Phase 1 (MVP Implementation)