Problem
BaseNodeViewer bypasses SharedNodeStore and directly calls database service methods, creating dual persistence paths that cause conflicts and violate single-source-of-truth principle.
Current Architecture (Broken)
BaseNodeViewer → databaseService → database
ReactiveNodeService → SharedNodeStore → tauriNodeService → database
Direct Database Calls in BaseNodeViewer
databaseService.getNode() - checking node existence
databaseService.deleteNode() - deleting nodes
databaseService.updateNode() - updating structural changes
databaseService.getNodesByOriginId() - loading children
databaseService.saveNodeWithParent() - saving content (debounced, placeholder-aware)
Issues Caused
- Duplicate INSERTs - Both paths try to create nodes (temporarily fixed with
source.type !== 'viewer' workaround)
- Conflicting deletes - Not covered by workaround
- Conflicting structural updates - Not covered by workaround
- Tests can't catch bugs - Tests use HttpAdapter, real app uses TauriNodeService, SharedNodeStore persistence never tested
- Inconsistent state - SharedNodeStore.persistedNodeIds tracking doesn't know about BaseNodeViewer's direct saves
Temporary Workaround (Added in #232)
Added && source.type !== 'viewer' check in SharedNodeStore to skip persisting viewer-sourced updates. This prevents duplicate INSERTs but doesn't address all conflicts.
IMPORTANT: This workaround must be removed as part of this refactoring.
Solution
Refactor BaseNodeViewer to use SharedNodeStore for ALL database operations.
Target Architecture
BaseNodeViewer → ReactiveNodeService → SharedNodeStore → tauriNodeService → database
Changes Needed
-
Remove direct database calls from BaseNodeViewer
- Replace
databaseService.getNodesByOriginId() with SharedNodeStore query
- Replace
databaseService.saveNodeWithParent() with sharedNodeStore.updateNode()
- Replace
databaseService.deleteNode() with sharedNodeStore.deleteNode()
- Replace
databaseService.updateNode() with sharedNodeStore.updateNode()
-
Move debouncing to SharedNodeStore
- Transfer 500ms debounce logic from BaseNodeViewer
- Make SharedNodeStore placeholder-aware (don't persist empty nodes)
- Preserve FOREIGN KEY coordination (wait for parent saves before child structural updates)
-
Remove temporary workaround
- Delete
&& source.type !== 'viewer' check from SharedNodeStore.updateNode() (line ~284)
- Delete
&& source.type !== 'viewer' check from SharedNodeStore.setNode() (line ~367)
- SharedNodeStore should handle ALL persistence for viewer-sourced updates
-
Update tests
- SharedNodeStore persistence will now be exercised by integration tests
- Tests will catch dual persistence bugs automatically
Complexity
- Medium-High
- Requires careful migration of BaseNodeViewer's sophisticated save logic
- Must preserve placeholder handling and FOREIGN KEY coordination
- Need comprehensive testing to ensure no regressions
Related Issues
Acceptance Criteria
Problem
BaseNodeViewer bypasses SharedNodeStore and directly calls database service methods, creating dual persistence paths that cause conflicts and violate single-source-of-truth principle.
Current Architecture (Broken)
Direct Database Calls in BaseNodeViewer
databaseService.getNode()- checking node existencedatabaseService.deleteNode()- deleting nodesdatabaseService.updateNode()- updating structural changesdatabaseService.getNodesByOriginId()- loading childrendatabaseService.saveNodeWithParent()- saving content (debounced, placeholder-aware)Issues Caused
source.type !== 'viewer'workaround)Temporary Workaround (Added in #232)
Added
&& source.type !== 'viewer'check in SharedNodeStore to skip persisting viewer-sourced updates. This prevents duplicate INSERTs but doesn't address all conflicts.IMPORTANT: This workaround must be removed as part of this refactoring.
Solution
Refactor BaseNodeViewer to use SharedNodeStore for ALL database operations.
Target Architecture
Changes Needed
Remove direct database calls from BaseNodeViewer
databaseService.getNodesByOriginId()with SharedNodeStore querydatabaseService.saveNodeWithParent()withsharedNodeStore.updateNode()databaseService.deleteNode()withsharedNodeStore.deleteNode()databaseService.updateNode()withsharedNodeStore.updateNode()Move debouncing to SharedNodeStore
Remove temporary workaround
&& source.type !== 'viewer'check from SharedNodeStore.updateNode() (line ~284)&& source.type !== 'viewer'check from SharedNodeStore.setNode() (line ~367)Update tests
Complexity
Related Issues
Acceptance Criteria
databaseService.*calls removed from BaseNodeViewer