Perf: Optimize embedded SurrealDB for ~1000x faster node updates#861
Conversation
Code Review SummaryReview Type: Initial Review Overall Assessment: This is a well-executed performance optimization PR that delivers dramatic improvements (~1000x faster node updates, ~300x faster get_node). The changes are architecturally sound and follow good engineering practices. The PR makes targeted optimizations without introducing technical debt. Requirements Check
Findings🟢 Strengths1. Correct Root Cause Analysis 2. Good Use of Optimized Helper Methods
These follow the principle of fetching only what's needed. 3. Proper Async Embedding Queue 4. Schema Validation Optimization 5. Tokio Runtime Configuration 🟡 Suggestions for Improvement1. [Improvement] Duplicated Title Logic in Bulk Operations 📁 The title computation logic is duplicated twice in fn compute_title_value(node_type: &str, parent_id: Option<&str>, content: &str) -> String {
if node_type == "date" || node_type == "schema" {
"NONE".to_string()
} else if parent_id.is_none() || node_type == "task" || node_type == "collection" {
let stripped = crate::utils::strip_markdown(content);
let escaped_title = Self::escape_surql_string(&stripped);
format!("\"{}\"", escaped_title)
} else {
"NONE".to_string()
}
}This would improve maintainability and reduce risk of the logic diverging. 2. [Improvement] Doc Comment Stale Reference 📁 The doc comment example still references 🟢 NitpicksNit: Debug Logging in Hot Path 📁 Multiple Nit: Emoji in Logging 📁 The use of emojis in log messages (e.g., Security ReviewNo security concerns identified. The changes:
Recommendation✅ APPROVE This PR delivers significant performance improvements with clean, well-documented code. The architectural decision to defer The ~1000x performance improvement for node updates is a major win for user experience. |
malibio
left a comment
There was a problem hiding this comment.
Review complete - recommending APPROVE. See the detailed review comment above for full analysis.
- Extract `compute_title_for_bulk_insert()` helper function to eliminate duplicated title computation logic in `bulk_create_hierarchy` and `bulk_create_hierarchy_root_notify` methods - Update stale doc comment for `update_with_version_check_returning_node` to accurately reflect the method signature and return type Addresses reviewer recommendations from PR #861: - [Improvement] Duplicated title logic in bulk operations - [Improvement] Doc comment stale reference Skipped (per reviewer's note these are acceptable as-is): - [Nitpick] Debug logging levels - reviewer noted "acceptable as-is" - [Nitpick] Emoji in logging - reviewer noted "acceptable for a small team" Co-Authored-By: Claude <noreply@anthropic.com>
Review Recommendations Addressed ✅Addressed (2/2 Improvements)
Skipped (2/2 Nitpicks)Per reviewer's notes that these are acceptable:
Commits
Test Status
|
## Problem Node updates in the Tauri desktop app were taking 10+ seconds due to expensive `batch_fetch_memberships` graph traversals being called within `get_node()`. This was traced to commit 60b4109 (Collections System). ## Solution - Remove `batch_fetch_memberships` from `get_node()`, `get_nodes_by_ids()`, `query_nodes()`, and `get_children()` - membership data should be fetched explicitly when needed, not on every node fetch - Add `get_parent_id()` for efficient parent ID lookup without full node fetch - Add `node_exists()` for efficient existence checks - Make embedding queue async with `tokio::spawn()` to not block updates - Limit schema validation to `task` nodes only (major performance impact) ## Additional Optimizations - Set `opt-level = 3` in Cargo.toml for speed optimization (critical for SurrealDB) - Configure tokio runtime with 10MiB thread stack (SurrealDB recommendation) - Update dev-proxy.rs to use `update_node_with_occ` API ## Results - Node updates: 10,000ms → 9-17ms (~1000x faster) - get_node: 700-800ms → 2-3ms (~300x faster) Co-Authored-By: Claude <noreply@anthropic.com>
- Extract `compute_title_for_bulk_insert()` helper function to eliminate duplicated title computation logic in `bulk_create_hierarchy` and `bulk_create_hierarchy_root_notify` methods - Update stale doc comment for `update_with_version_check_returning_node` to accurately reflect the method signature and return type Addresses reviewer recommendations from PR #861: - [Improvement] Duplicated title logic in bulk operations - [Improvement] Doc comment stale reference Skipped (per reviewer's note these are acceptable as-is): - [Nitpick] Debug logging levels - reviewer noted "acceptable as-is" - [Nitpick] Emoji in logging - reviewer noted "acceptable for a small team" Co-Authored-By: Claude <noreply@anthropic.com>
Phase 1 of race condition fixes for indent/outdent operations: Backend changes: - move_node_with_occ now returns Result<Node, ...> instead of Result<(), ...> - update_node_with_version_bump returns the updated node with new version - Tauri move_node command returns the node to frontend Frontend changes: - BackendAdapter interface updated: moveNode returns Promise<Node> - All callers (indent, outdent, child promotion) sync version from response - Added pending-operations.ts to track in-flight moves - SharedNodeStore waits for pending moves before UPDATE to avoid conflicts This eliminates the need to guess version+1 after moves, fixing ~90% of version conflicts during rapid Tab/Shift+Tab sequences. Related: #790 (Sync Infrastructure - Phase 2/3 documented in comments) Co-Authored-By: Claude <noreply@anthropic.com>
Updated dev-proxy HTTP endpoint to match Tauri command changes: - Added version field to SetParentRequest for OCC - Changed from move_node to move_node_with_occ - Returns updated Node with new version instead of 204 No Content This aligns browser dev mode with the Tauri IPC behavior. Co-Authored-By: Claude <noreply@anthropic.com>
When creating or moving nodes, the `insert_after_node_id` parameter is now treated as a best-effort hint rather than a strict contract. If the sibling has moved to a different parent or been deleted (due to race conditions during rapid indent/outdent operations), the operation falls back to appending at the end rather than failing. This prevents data loss where user-typed content would be silently lost because the CREATE request failed with "Sibling has different parent" errors. Changes: - node_service.rs: Validate sibling in create_node_with_parent, clear hint if stale - surreal_store.rs: Fall back to append in move_node if sibling not found - Updated test to expect new forgiving behavior Relates to: Phase 2 of architect recommendations for issue #790 Co-Authored-By: Claude <noreply@anthropic.com>
When flushAndWaitForNodes() triggered pending operations, it was calling the raw operation function directly instead of the executeOperation wrapper. This bypassed the executingOperations tracking, allowing concurrent operations on the same node and causing version conflicts (409 errors). The fix stores the executeOperation wrapper (which tracks execution state) in pendingOperations instead of the raw operation function. This ensures that flushAndWaitForNodes() properly respects the execution state and prevents concurrent operations on the same node. This fixes the remaining 409 version conflicts during rapid indent/outdent operations where content updates and move operations race. Co-Authored-By: Claude <noreply@anthropic.com>
…rors When indenting a node, the target parent might not be persisted to the database yet (e.g., when rapidly pressing Enter twice to create nested nodes). The move operation would fail with "Invalid parent node" because it tried to set a parent that doesn't exist in the DB. The fix adds the target parent to the list of nodes to flush before the move operation, ensuring the parent is created in the database before we try to move a child under it. Co-Authored-By: Claude <noreply@anthropic.com>
When flushAndWaitForNodes was called, it would clear the debounce timeout and then call pending.operation(). However, if the timeout callback was already executing (started just before clearTimeout), both would run, causing duplicate database operations and version conflicts. The fix checks executingOperations before starting the operation in flush. If the operation is already executing, we just wait for it to complete rather than starting a second instance. Co-Authored-By: Claude <noreply@anthropic.com>
The error message from the backend is "Node not found: <id>" but the code was checking for "NodeNotFound" (no space) or "does not exist". This caused the fallback to CREATE to not trigger, resulting in errors being logged instead of graceful recovery. The fix uses case-insensitive matching and checks for "not found" which catches all variations of the error message. Co-Authored-By: Claude <noreply@anthropic.com>
…version conflicts The updateNode path in SharedNodeStore was missing the check for pending move operations that setNode already had. When a user typed content and then quickly indented (Tab), the following race occurred: 1. Content update queued with debounced persistence 2. Tab triggers flushNodeSaves() which starts the UPDATE 3. Tab also triggers moveNode() which runs in parallel 4. Both operations read version 1 from local state 5. MOVE returns first and bumps version to 2 6. UPDATE arrives with stale version 1 → 409 conflict The fix adds the same pending move check to: - updateNode path (line 869+) - persistBatchedChanges path (line 2562+) - batch race recovery path (line 2610+) Now all UPDATE paths wait for any pending move operation to complete and re-read the node's version before sending to the backend, ensuring version consistency. Co-Authored-By: Claude <noreply@anthropic.com>
748e05a to
4cf8443
Compare
Code Review Summary (Re-Review)Review Type: Re-Review after review recommendations addressed + significant new commits Overall Assessment: This PR has evolved significantly beyond the original performance optimization scope. While the core performance improvements remain solid, the PR now includes substantial race condition fixes for the indent/outdent flow. This is high-quality work that addresses complex concurrency issues. Recommend APPROVE. New Changes Since Initial ReviewRace Condition Fixes (Commits 7b4496f..4cf8443)The PR now includes comprehensive fixes for version conflicts during rapid Tab/Shift+Tab sequences:
Findings🟢 Strengths1. Correct Race Condition Analysis
2. Proper Module Extraction 3. Defensive Error Handling const errorMsg = updateError instanceof Error ? updateError.message.toLowerCase() : '';
const isNodeNotFound =
errorMsg.includes('not found') ||
errorMsg.includes('does not exist') ||
errorMsg.includes('nodenotfound');4. Backend API Improvement 🟡 Suggestions for Improvement1. [Improvement] Duplicated Outdent Logic The outdent logic for unpersisted nodes has significant duplication between the function updateStructureTreeForOutdent(
structureTree: StructureTree,
nodeId: string,
oldParentId: string,
newParentId: string
) {
const newParentChildren = structureTree.getChildrenWithOrder(newParentId);
const oldParentIndex = newParentChildren.findIndex(c => c.nodeId === oldParentId);
let insertOrder: number;
if (oldParentIndex >= 0) {
const oldParentOrder = newParentChildren[oldParentIndex].order;
const nextSibling = newParentChildren[oldParentIndex + 1];
insertOrder = nextSibling ? (oldParentOrder + nextSibling.order) / 2 : oldParentOrder + 1.0;
} else {
insertOrder = newParentChildren.length > 0
? newParentChildren[newParentChildren.length - 1].order + 1.0
: 1.0;
}
structureTree.moveInMemoryRelationship(oldParentId, newParentId, nodeId, insertOrder);
}2. [Improvement] Direct Object Mutation Direct mutation of the node object returned from store is risky: (currentNode as typeof currentNode & { insertAfterNodeId?: string | null }).insertAfterNodeId = null;This assumes 🟢 NitpicksNit: Debug Logging Verbosity The Nit: Sequential Move Operations in Outdent The sibling transfers are now sequential ( Security ReviewNo security concerns identified. The changes:
Test StatusThe test for sibling validation was correctly updated to expect the new forgiving behavior: - async fn test_sibling_must_have_same_parent() {
+ async fn test_sibling_with_different_parent_falls_back_to_append() {Recommendation✅ APPROVE This PR delivers:
The suggested improvements are maintainability enhancements that can be addressed in follow-up work. The core implementation is sound and the architectural decisions are appropriate. Outstanding work on the race condition analysis and fixes. |
malibio
left a comment
There was a problem hiding this comment.
Re-review complete - recommending APPROVE. The PR now includes both performance optimizations (~1000x faster) and comprehensive race condition fixes. See detailed review comment for full analysis.
- Created calculateOutdentInsertOrder() helper function for computing fractional order when outdenting nodes - Replaced 3 duplicate code blocks (34 lines total) with helper calls - Improves maintainability and prevents logic divergence Addresses reviewer recommendation from PR #861 re-review. Co-Authored-By: Claude <noreply@anthropic.com>
Review Recommendations Addressed ✅Addressed (1/2 Suggestions)📁 reactive-node-service.svelte.ts - Extracted Skipped (1/2 Suggestions + 2/2 Nitpicks)
Summary
|
Sequential execution preserves sibling order during outdent. Parallel is possible with pre-calculated fractional orders but adds complexity for minimal gain in typical use cases (0-3 siblings). Co-Authored-By: Claude <noreply@anthropic.com>
#870 Part 1) ## Part 1: Unit Test Coverage Gaps ### TypeScript (Frontend) - Add 13 tests for `calculateOutdentInsertOrderPure()` in reactive-node-service - Refactored to pure function for testability - Tests edge cases: empty parent, missing old parent, single/many children - Tests fractional order precision and various positions - Add 6 tests for `flushAndWaitForNodes()` in shared-node-store-coverage - Tests flushing specific nodes, concurrent flush requests - Tests timeout behavior, mix of pending/non-pending nodes - Tests error reporting for failed operations - Add 10 tests for pending-operations module - Now imports actual module exports instead of reimplementing logic - Tests trackMoveOperation, waitForPendingMoveOperations, getPendingMoveOperation - Tests race condition prevention patterns (Issue #662 scenario) ### Rust (Backend) - Add 3 tests for `node_exists()` in surreal_store - Tests existing node returns true, non-existent returns false - Tests returns false after deletion - Add 3 tests for `get_parent_id()` in surreal_store - Tests root node returns None, child returns correct parent ID - Tests non-existent node returns None - Add 4 tests for `get_node_type()` in surreal_store - Tests correct type for text, task, date nodes - Tests non-existent node returns None ## Test Summary - Frontend: +28 tests (3501 → 3529) - Rust: +10 tests (767 → 777) Part 2 (test infrastructure improvements) to be addressed separately. Co-Authored-By: Claude <noreply@anthropic.com>
## Part 2A: Browser Integration Test Infrastructure - Added `rapid-keyboard-operations.test.ts` with 13 tests for: - Keyboard event sequencing (Enter→Tab, Tab→Shift+Tab patterns) - Focus management during rapid operations - High-frequency event handling (500+ events) - Event prevention and bubbling behavior ## Part 2B: Stress Tests for Rapid Indent/Outdent - Added `rapid-hierarchy-operations.test.ts` with 17 tests for: - `calculateOutdentInsertOrderPure()` edge cases - Floating point precision limits (50+ rapid insertions) - Concurrent operation simulation - Out-of-order completion patterns - PR #861 race condition scenarios ## Part 2C: test:integration:full Command - Added `test:integration:full` script that runs: - Unit tests (bun run test) - Browser tests (bun run test:browser) - Performance tests (bun run test:perf) - Rust tests (bun run rust:test) ## Documentation - Updated testing-guide.md with: - Stress test documentation and examples - Instructions for running tests - Future E2E testing considerations Test Results: - Frontend: 3546 tests passed (45 new tests added) - Browser: 90 tests passed (13 new tests added) - Rust: 777 tests passed Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…etection (#878) * Docs: Add explicit warning about gh script directory requirement Agents repeatedly make the mistake of running `bun run gh:*` commands from subdirectories like packages/desktop-app/ instead of the repo root. These commands fail with "Script not found" because the gh scripts are defined in the root package.json. Added: - Prominent warning in startup sequence step 4 with correct/wrong examples - Reminder notes on steps 7 and 8 (assign/status commands) - New entry in "Common mistakes agents make" list Co-Authored-By: Claude <noreply@anthropic.com> * Test: Add comprehensive unit tests for PR #861 helper functions (closes #870 Part 1) ## Part 1: Unit Test Coverage Gaps ### TypeScript (Frontend) - Add 13 tests for `calculateOutdentInsertOrderPure()` in reactive-node-service - Refactored to pure function for testability - Tests edge cases: empty parent, missing old parent, single/many children - Tests fractional order precision and various positions - Add 6 tests for `flushAndWaitForNodes()` in shared-node-store-coverage - Tests flushing specific nodes, concurrent flush requests - Tests timeout behavior, mix of pending/non-pending nodes - Tests error reporting for failed operations - Add 10 tests for pending-operations module - Now imports actual module exports instead of reimplementing logic - Tests trackMoveOperation, waitForPendingMoveOperations, getPendingMoveOperation - Tests race condition prevention patterns (Issue #662 scenario) ### Rust (Backend) - Add 3 tests for `node_exists()` in surreal_store - Tests existing node returns true, non-existent returns false - Tests returns false after deletion - Add 3 tests for `get_parent_id()` in surreal_store - Tests root node returns None, child returns correct parent ID - Tests non-existent node returns None - Add 4 tests for `get_node_type()` in surreal_store - Tests correct type for text, task, date nodes - Tests non-existent node returns None ## Test Summary - Frontend: +28 tests (3501 → 3529) - Rust: +10 tests (767 → 777) Part 2 (test infrastructure improvements) to be addressed separately. Co-Authored-By: Claude <noreply@anthropic.com> * Address review: Add helper function and document real timer usage Reviewer nitpicks addressed: - Added `uniqueNodeId()` helper function for generating unique test node IDs - Added JSDoc explaining why unique IDs are needed (global module state) - Added comment block explaining why real timers are required for race condition tests (fake timers don't properly simulate Promise.race) Co-Authored-By: Claude <noreply@anthropic.com> * Test: Add stress tests for race condition detection (closes #870 Part 2) ## Part 2A: Browser Integration Test Infrastructure - Added `rapid-keyboard-operations.test.ts` with 13 tests for: - Keyboard event sequencing (Enter→Tab, Tab→Shift+Tab patterns) - Focus management during rapid operations - High-frequency event handling (500+ events) - Event prevention and bubbling behavior ## Part 2B: Stress Tests for Rapid Indent/Outdent - Added `rapid-hierarchy-operations.test.ts` with 17 tests for: - `calculateOutdentInsertOrderPure()` edge cases - Floating point precision limits (50+ rapid insertions) - Concurrent operation simulation - Out-of-order completion patterns - PR #861 race condition scenarios ## Part 2C: test:integration:full Command - Added `test:integration:full` script that runs: - Unit tests (bun run test) - Browser tests (bun run test:browser) - Performance tests (bun run test:perf) - Rust tests (bun run rust:test) ## Documentation - Updated testing-guide.md with: - Stress test documentation and examples - Instructions for running tests - Future E2E testing considerations Test Results: - Frontend: 3546 tests passed (45 new tests added) - Browser: 90 tests passed (13 new tests added) - Rust: 777 tests passed Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Docs: Add UX bug → regression test workflow to testing guide Documents the workflow for converting discovered UX bugs into regression tests: - Workflow steps: Discover → Fix → Test → Commit - Table mapping bug types to test file locations - Concrete example with Backspace→Enter sequence - Commit message convention for bug fixes with tests Key principle: Manual testing finds bugs, automated tests prevent regressions. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
* Perf: Optimize embedded SurrealDB for ~1000x faster node updates ## Problem Node updates in the Tauri desktop app were taking 10+ seconds due to expensive `batch_fetch_memberships` graph traversals being called within `get_node()`. This was traced to commit 60b4109 (Collections System). ## Solution - Remove `batch_fetch_memberships` from `get_node()`, `get_nodes_by_ids()`, `query_nodes()`, and `get_children()` - membership data should be fetched explicitly when needed, not on every node fetch - Add `get_parent_id()` for efficient parent ID lookup without full node fetch - Add `node_exists()` for efficient existence checks - Make embedding queue async with `tokio::spawn()` to not block updates - Limit schema validation to `task` nodes only (major performance impact) ## Additional Optimizations - Set `opt-level = 3` in Cargo.toml for speed optimization (critical for SurrealDB) - Configure tokio runtime with 10MiB thread stack (SurrealDB recommendation) - Update dev-proxy.rs to use `update_node_with_occ` API ## Results - Node updates: 10,000ms → 9-17ms (~1000x faster) - get_node: 700-800ms → 2-3ms (~300x faster) Co-Authored-By: Claude <noreply@anthropic.com> * Address review: Extract duplicated title logic and fix stale doc comment - Extract `compute_title_for_bulk_insert()` helper function to eliminate duplicated title computation logic in `bulk_create_hierarchy` and `bulk_create_hierarchy_root_notify` methods - Update stale doc comment for `update_with_version_check_returning_node` to accurately reflect the method signature and return type Addresses reviewer recommendations from PR #861: - [Improvement] Duplicated title logic in bulk operations - [Improvement] Doc comment stale reference Skipped (per reviewer's note these are acceptable as-is): - [Nitpick] Debug logging levels - reviewer noted "acceptable as-is" - [Nitpick] Emoji in logging - reviewer noted "acceptable for a small team" Co-Authored-By: Claude <noreply@anthropic.com> * Fix: moveNode returns updated Node to prevent version conflicts Phase 1 of race condition fixes for indent/outdent operations: Backend changes: - move_node_with_occ now returns Result<Node, ...> instead of Result<(), ...> - update_node_with_version_bump returns the updated node with new version - Tauri move_node command returns the node to frontend Frontend changes: - BackendAdapter interface updated: moveNode returns Promise<Node> - All callers (indent, outdent, child promotion) sync version from response - Added pending-operations.ts to track in-flight moves - SharedNodeStore waits for pending moves before UPDATE to avoid conflicts This eliminates the need to guess version+1 after moves, fixing ~90% of version conflicts during rapid Tab/Shift+Tab sequences. Related: #790 (Sync Infrastructure - Phase 2/3 documented in comments) Co-Authored-By: Claude <noreply@anthropic.com> * Fix: Dev proxy set_parent endpoint returns Node with OCC Updated dev-proxy HTTP endpoint to match Tauri command changes: - Added version field to SetParentRequest for OCC - Changed from move_node to move_node_with_occ - Returns updated Node with new version instead of 204 No Content This aligns browser dev mode with the Tauri IPC behavior. Co-Authored-By: Claude <noreply@anthropic.com> * Fix: Treat insert_after_node_id as best-effort hint to prevent data loss When creating or moving nodes, the `insert_after_node_id` parameter is now treated as a best-effort hint rather than a strict contract. If the sibling has moved to a different parent or been deleted (due to race conditions during rapid indent/outdent operations), the operation falls back to appending at the end rather than failing. This prevents data loss where user-typed content would be silently lost because the CREATE request failed with "Sibling has different parent" errors. Changes: - node_service.rs: Validate sibling in create_node_with_parent, clear hint if stale - surreal_store.rs: Fall back to append in move_node if sibling not found - Updated test to expect new forgiving behavior Relates to: Phase 2 of architect recommendations for issue #790 Co-Authored-By: Claude <noreply@anthropic.com> * Fix: Store executeOperation wrapper to prevent concurrent flush races When flushAndWaitForNodes() triggered pending operations, it was calling the raw operation function directly instead of the executeOperation wrapper. This bypassed the executingOperations tracking, allowing concurrent operations on the same node and causing version conflicts (409 errors). The fix stores the executeOperation wrapper (which tracks execution state) in pendingOperations instead of the raw operation function. This ensures that flushAndWaitForNodes() properly respects the execution state and prevents concurrent operations on the same node. This fixes the remaining 409 version conflicts during rapid indent/outdent operations where content updates and move operations race. Co-Authored-By: Claude <noreply@anthropic.com> * Fix: Flush target parent before indent to prevent 'Invalid parent' errors When indenting a node, the target parent might not be persisted to the database yet (e.g., when rapidly pressing Enter twice to create nested nodes). The move operation would fail with "Invalid parent node" because it tried to set a parent that doesn't exist in the DB. The fix adds the target parent to the list of nodes to flush before the move operation, ensuring the parent is created in the database before we try to move a child under it. Co-Authored-By: Claude <noreply@anthropic.com> * Fix: Prevent double-execution in flushAndWaitForNodes When flushAndWaitForNodes was called, it would clear the debounce timeout and then call pending.operation(). However, if the timeout callback was already executing (started just before clearTimeout), both would run, causing duplicate database operations and version conflicts. The fix checks executingOperations before starting the operation in flush. If the operation is already executing, we just wait for it to complete rather than starting a second instance. Co-Authored-By: Claude <noreply@anthropic.com> * Fix: Improve 'node not found' error detection for UPDATE→CREATE fallback The error message from the backend is "Node not found: <id>" but the code was checking for "NodeNotFound" (no space) or "does not exist". This caused the fallback to CREATE to not trigger, resulting in errors being logged instead of graceful recovery. The fix uses case-insensitive matching and checks for "not found" which catches all variations of the error message. Co-Authored-By: Claude <noreply@anthropic.com> * Fix: Wait for pending move operations in all UPDATE paths to prevent version conflicts The updateNode path in SharedNodeStore was missing the check for pending move operations that setNode already had. When a user typed content and then quickly indented (Tab), the following race occurred: 1. Content update queued with debounced persistence 2. Tab triggers flushNodeSaves() which starts the UPDATE 3. Tab also triggers moveNode() which runs in parallel 4. Both operations read version 1 from local state 5. MOVE returns first and bumps version to 2 6. UPDATE arrives with stale version 1 → 409 conflict The fix adds the same pending move check to: - updateNode path (line 869+) - persistBatchedChanges path (line 2562+) - batch race recovery path (line 2610+) Now all UPDATE paths wait for any pending move operation to complete and re-read the node's version before sending to the backend, ensuring version consistency. Co-Authored-By: Claude <noreply@anthropic.com> * Address review: Extract duplicated outdent insert order logic - Created calculateOutdentInsertOrder() helper function for computing fractional order when outdenting nodes - Replaced 3 duplicate code blocks (34 lines total) with helper calls - Improves maintainability and prevents logic divergence Addresses reviewer recommendation from PR #861 re-review. Co-Authored-By: Claude <noreply@anthropic.com> * Add comment about potential parallel optimization for sibling transfers Sequential execution preserves sibling order during outdent. Parallel is possible with pre-calculated fractional orders but adds complexity for minimal gain in typical use cases (0-3 siblings). Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
…etection (#878) * Docs: Add explicit warning about gh script directory requirement Agents repeatedly make the mistake of running `bun run gh:*` commands from subdirectories like packages/desktop-app/ instead of the repo root. These commands fail with "Script not found" because the gh scripts are defined in the root package.json. Added: - Prominent warning in startup sequence step 4 with correct/wrong examples - Reminder notes on steps 7 and 8 (assign/status commands) - New entry in "Common mistakes agents make" list Co-Authored-By: Claude <noreply@anthropic.com> * Test: Add comprehensive unit tests for PR #861 helper functions (closes #870 Part 1) ## Part 1: Unit Test Coverage Gaps ### TypeScript (Frontend) - Add 13 tests for `calculateOutdentInsertOrderPure()` in reactive-node-service - Refactored to pure function for testability - Tests edge cases: empty parent, missing old parent, single/many children - Tests fractional order precision and various positions - Add 6 tests for `flushAndWaitForNodes()` in shared-node-store-coverage - Tests flushing specific nodes, concurrent flush requests - Tests timeout behavior, mix of pending/non-pending nodes - Tests error reporting for failed operations - Add 10 tests for pending-operations module - Now imports actual module exports instead of reimplementing logic - Tests trackMoveOperation, waitForPendingMoveOperations, getPendingMoveOperation - Tests race condition prevention patterns (Issue #662 scenario) ### Rust (Backend) - Add 3 tests for `node_exists()` in surreal_store - Tests existing node returns true, non-existent returns false - Tests returns false after deletion - Add 3 tests for `get_parent_id()` in surreal_store - Tests root node returns None, child returns correct parent ID - Tests non-existent node returns None - Add 4 tests for `get_node_type()` in surreal_store - Tests correct type for text, task, date nodes - Tests non-existent node returns None ## Test Summary - Frontend: +28 tests (3501 → 3529) - Rust: +10 tests (767 → 777) Part 2 (test infrastructure improvements) to be addressed separately. Co-Authored-By: Claude <noreply@anthropic.com> * Address review: Add helper function and document real timer usage Reviewer nitpicks addressed: - Added `uniqueNodeId()` helper function for generating unique test node IDs - Added JSDoc explaining why unique IDs are needed (global module state) - Added comment block explaining why real timers are required for race condition tests (fake timers don't properly simulate Promise.race) Co-Authored-By: Claude <noreply@anthropic.com> * Test: Add stress tests for race condition detection (closes #870 Part 2) ## Part 2A: Browser Integration Test Infrastructure - Added `rapid-keyboard-operations.test.ts` with 13 tests for: - Keyboard event sequencing (Enter→Tab, Tab→Shift+Tab patterns) - Focus management during rapid operations - High-frequency event handling (500+ events) - Event prevention and bubbling behavior ## Part 2B: Stress Tests for Rapid Indent/Outdent - Added `rapid-hierarchy-operations.test.ts` with 17 tests for: - `calculateOutdentInsertOrderPure()` edge cases - Floating point precision limits (50+ rapid insertions) - Concurrent operation simulation - Out-of-order completion patterns - PR #861 race condition scenarios ## Part 2C: test:integration:full Command - Added `test:integration:full` script that runs: - Unit tests (bun run test) - Browser tests (bun run test:browser) - Performance tests (bun run test:perf) - Rust tests (bun run rust:test) ## Documentation - Updated testing-guide.md with: - Stress test documentation and examples - Instructions for running tests - Future E2E testing considerations Test Results: - Frontend: 3546 tests passed (45 new tests added) - Browser: 90 tests passed (13 new tests added) - Rust: 777 tests passed Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Docs: Add UX bug → regression test workflow to testing guide Documents the workflow for converting discovered UX bugs into regression tests: - Workflow steps: Discover → Fix → Test → Commit - Table mapping bug types to test file locations - Concrete example with Backspace→Enter sequence - Commit message convention for bug fixes with tests Key principle: Manual testing finds bugs, automated tests prevent regressions. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
* Perf: Optimize embedded SurrealDB for ~1000x faster node updates ## Problem Node updates in the Tauri desktop app were taking 10+ seconds due to expensive `batch_fetch_memberships` graph traversals being called within `get_node()`. This was traced to commit 60b4109 (Collections System). ## Solution - Remove `batch_fetch_memberships` from `get_node()`, `get_nodes_by_ids()`, `query_nodes()`, and `get_children()` - membership data should be fetched explicitly when needed, not on every node fetch - Add `get_parent_id()` for efficient parent ID lookup without full node fetch - Add `node_exists()` for efficient existence checks - Make embedding queue async with `tokio::spawn()` to not block updates - Limit schema validation to `task` nodes only (major performance impact) ## Additional Optimizations - Set `opt-level = 3` in Cargo.toml for speed optimization (critical for SurrealDB) - Configure tokio runtime with 10MiB thread stack (SurrealDB recommendation) - Update dev-proxy.rs to use `update_node_with_occ` API ## Results - Node updates: 10,000ms → 9-17ms (~1000x faster) - get_node: 700-800ms → 2-3ms (~300x faster) Co-Authored-By: Claude <noreply@anthropic.com> * Address review: Extract duplicated title logic and fix stale doc comment - Extract `compute_title_for_bulk_insert()` helper function to eliminate duplicated title computation logic in `bulk_create_hierarchy` and `bulk_create_hierarchy_root_notify` methods - Update stale doc comment for `update_with_version_check_returning_node` to accurately reflect the method signature and return type Addresses reviewer recommendations from PR #861: - [Improvement] Duplicated title logic in bulk operations - [Improvement] Doc comment stale reference Skipped (per reviewer's note these are acceptable as-is): - [Nitpick] Debug logging levels - reviewer noted "acceptable as-is" - [Nitpick] Emoji in logging - reviewer noted "acceptable for a small team" Co-Authored-By: Claude <noreply@anthropic.com> * Fix: moveNode returns updated Node to prevent version conflicts Phase 1 of race condition fixes for indent/outdent operations: Backend changes: - move_node_with_occ now returns Result<Node, ...> instead of Result<(), ...> - update_node_with_version_bump returns the updated node with new version - Tauri move_node command returns the node to frontend Frontend changes: - BackendAdapter interface updated: moveNode returns Promise<Node> - All callers (indent, outdent, child promotion) sync version from response - Added pending-operations.ts to track in-flight moves - SharedNodeStore waits for pending moves before UPDATE to avoid conflicts This eliminates the need to guess version+1 after moves, fixing ~90% of version conflicts during rapid Tab/Shift+Tab sequences. Related: #790 (Sync Infrastructure - Phase 2/3 documented in comments) Co-Authored-By: Claude <noreply@anthropic.com> * Fix: Dev proxy set_parent endpoint returns Node with OCC Updated dev-proxy HTTP endpoint to match Tauri command changes: - Added version field to SetParentRequest for OCC - Changed from move_node to move_node_with_occ - Returns updated Node with new version instead of 204 No Content This aligns browser dev mode with the Tauri IPC behavior. Co-Authored-By: Claude <noreply@anthropic.com> * Fix: Treat insert_after_node_id as best-effort hint to prevent data loss When creating or moving nodes, the `insert_after_node_id` parameter is now treated as a best-effort hint rather than a strict contract. If the sibling has moved to a different parent or been deleted (due to race conditions during rapid indent/outdent operations), the operation falls back to appending at the end rather than failing. This prevents data loss where user-typed content would be silently lost because the CREATE request failed with "Sibling has different parent" errors. Changes: - node_service.rs: Validate sibling in create_node_with_parent, clear hint if stale - surreal_store.rs: Fall back to append in move_node if sibling not found - Updated test to expect new forgiving behavior Relates to: Phase 2 of architect recommendations for issue #790 Co-Authored-By: Claude <noreply@anthropic.com> * Fix: Store executeOperation wrapper to prevent concurrent flush races When flushAndWaitForNodes() triggered pending operations, it was calling the raw operation function directly instead of the executeOperation wrapper. This bypassed the executingOperations tracking, allowing concurrent operations on the same node and causing version conflicts (409 errors). The fix stores the executeOperation wrapper (which tracks execution state) in pendingOperations instead of the raw operation function. This ensures that flushAndWaitForNodes() properly respects the execution state and prevents concurrent operations on the same node. This fixes the remaining 409 version conflicts during rapid indent/outdent operations where content updates and move operations race. Co-Authored-By: Claude <noreply@anthropic.com> * Fix: Flush target parent before indent to prevent 'Invalid parent' errors When indenting a node, the target parent might not be persisted to the database yet (e.g., when rapidly pressing Enter twice to create nested nodes). The move operation would fail with "Invalid parent node" because it tried to set a parent that doesn't exist in the DB. The fix adds the target parent to the list of nodes to flush before the move operation, ensuring the parent is created in the database before we try to move a child under it. Co-Authored-By: Claude <noreply@anthropic.com> * Fix: Prevent double-execution in flushAndWaitForNodes When flushAndWaitForNodes was called, it would clear the debounce timeout and then call pending.operation(). However, if the timeout callback was already executing (started just before clearTimeout), both would run, causing duplicate database operations and version conflicts. The fix checks executingOperations before starting the operation in flush. If the operation is already executing, we just wait for it to complete rather than starting a second instance. Co-Authored-By: Claude <noreply@anthropic.com> * Fix: Improve 'node not found' error detection for UPDATE→CREATE fallback The error message from the backend is "Node not found: <id>" but the code was checking for "NodeNotFound" (no space) or "does not exist". This caused the fallback to CREATE to not trigger, resulting in errors being logged instead of graceful recovery. The fix uses case-insensitive matching and checks for "not found" which catches all variations of the error message. Co-Authored-By: Claude <noreply@anthropic.com> * Fix: Wait for pending move operations in all UPDATE paths to prevent version conflicts The updateNode path in SharedNodeStore was missing the check for pending move operations that setNode already had. When a user typed content and then quickly indented (Tab), the following race occurred: 1. Content update queued with debounced persistence 2. Tab triggers flushNodeSaves() which starts the UPDATE 3. Tab also triggers moveNode() which runs in parallel 4. Both operations read version 1 from local state 5. MOVE returns first and bumps version to 2 6. UPDATE arrives with stale version 1 → 409 conflict The fix adds the same pending move check to: - updateNode path (line 869+) - persistBatchedChanges path (line 2562+) - batch race recovery path (line 2610+) Now all UPDATE paths wait for any pending move operation to complete and re-read the node's version before sending to the backend, ensuring version consistency. Co-Authored-By: Claude <noreply@anthropic.com> * Address review: Extract duplicated outdent insert order logic - Created calculateOutdentInsertOrder() helper function for computing fractional order when outdenting nodes - Replaced 3 duplicate code blocks (34 lines total) with helper calls - Improves maintainability and prevents logic divergence Addresses reviewer recommendation from PR #861 re-review. Co-Authored-By: Claude <noreply@anthropic.com> * Add comment about potential parallel optimization for sibling transfers Sequential execution preserves sibling order during outdent. Parallel is possible with pre-calculated fractional orders but adds complexity for minimal gain in typical use cases (0-3 siblings). Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
…etection (#878) * Docs: Add explicit warning about gh script directory requirement Agents repeatedly make the mistake of running `bun run gh:*` commands from subdirectories like packages/desktop-app/ instead of the repo root. These commands fail with "Script not found" because the gh scripts are defined in the root package.json. Added: - Prominent warning in startup sequence step 4 with correct/wrong examples - Reminder notes on steps 7 and 8 (assign/status commands) - New entry in "Common mistakes agents make" list Co-Authored-By: Claude <noreply@anthropic.com> * Test: Add comprehensive unit tests for PR #861 helper functions (closes #870 Part 1) ## Part 1: Unit Test Coverage Gaps ### TypeScript (Frontend) - Add 13 tests for `calculateOutdentInsertOrderPure()` in reactive-node-service - Refactored to pure function for testability - Tests edge cases: empty parent, missing old parent, single/many children - Tests fractional order precision and various positions - Add 6 tests for `flushAndWaitForNodes()` in shared-node-store-coverage - Tests flushing specific nodes, concurrent flush requests - Tests timeout behavior, mix of pending/non-pending nodes - Tests error reporting for failed operations - Add 10 tests for pending-operations module - Now imports actual module exports instead of reimplementing logic - Tests trackMoveOperation, waitForPendingMoveOperations, getPendingMoveOperation - Tests race condition prevention patterns (Issue #662 scenario) ### Rust (Backend) - Add 3 tests for `node_exists()` in surreal_store - Tests existing node returns true, non-existent returns false - Tests returns false after deletion - Add 3 tests for `get_parent_id()` in surreal_store - Tests root node returns None, child returns correct parent ID - Tests non-existent node returns None - Add 4 tests for `get_node_type()` in surreal_store - Tests correct type for text, task, date nodes - Tests non-existent node returns None ## Test Summary - Frontend: +28 tests (3501 → 3529) - Rust: +10 tests (767 → 777) Part 2 (test infrastructure improvements) to be addressed separately. Co-Authored-By: Claude <noreply@anthropic.com> * Address review: Add helper function and document real timer usage Reviewer nitpicks addressed: - Added `uniqueNodeId()` helper function for generating unique test node IDs - Added JSDoc explaining why unique IDs are needed (global module state) - Added comment block explaining why real timers are required for race condition tests (fake timers don't properly simulate Promise.race) Co-Authored-By: Claude <noreply@anthropic.com> * Test: Add stress tests for race condition detection (closes #870 Part 2) ## Part 2A: Browser Integration Test Infrastructure - Added `rapid-keyboard-operations.test.ts` with 13 tests for: - Keyboard event sequencing (Enter→Tab, Tab→Shift+Tab patterns) - Focus management during rapid operations - High-frequency event handling (500+ events) - Event prevention and bubbling behavior ## Part 2B: Stress Tests for Rapid Indent/Outdent - Added `rapid-hierarchy-operations.test.ts` with 17 tests for: - `calculateOutdentInsertOrderPure()` edge cases - Floating point precision limits (50+ rapid insertions) - Concurrent operation simulation - Out-of-order completion patterns - PR #861 race condition scenarios ## Part 2C: test:integration:full Command - Added `test:integration:full` script that runs: - Unit tests (bun run test) - Browser tests (bun run test:browser) - Performance tests (bun run test:perf) - Rust tests (bun run rust:test) ## Documentation - Updated testing-guide.md with: - Stress test documentation and examples - Instructions for running tests - Future E2E testing considerations Test Results: - Frontend: 3546 tests passed (45 new tests added) - Browser: 90 tests passed (13 new tests added) - Rust: 777 tests passed Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Docs: Add UX bug → regression test workflow to testing guide Documents the workflow for converting discovered UX bugs into regression tests: - Workflow steps: Discover → Fix → Test → Commit - Table mapping bug types to test file locations - Concrete example with Backspace→Enter sequence - Commit message convention for bug fixes with tests Key principle: Manual testing finds bugs, automated tests prevent regressions. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
* Perf: Optimize embedded SurrealDB for ~1000x faster node updates ## Problem Node updates in the Tauri desktop app were taking 10+ seconds due to expensive `batch_fetch_memberships` graph traversals being called within `get_node()`. This was traced to commit c8cd3f97 (Collections System). ## Solution - Remove `batch_fetch_memberships` from `get_node()`, `get_nodes_by_ids()`, `query_nodes()`, and `get_children()` - membership data should be fetched explicitly when needed, not on every node fetch - Add `get_parent_id()` for efficient parent ID lookup without full node fetch - Add `node_exists()` for efficient existence checks - Make embedding queue async with `tokio::spawn()` to not block updates - Limit schema validation to `task` nodes only (major performance impact) ## Additional Optimizations - Set `opt-level = 3` in Cargo.toml for speed optimization (critical for SurrealDB) - Configure tokio runtime with 10MiB thread stack (SurrealDB recommendation) - Update dev-proxy.rs to use `update_node_with_occ` API ## Results - Node updates: 10,000ms → 9-17ms (~1000x faster) - get_node: 700-800ms → 2-3ms (~300x faster) Co-Authored-By: Claude <noreply@anthropic.com> * Address review: Extract duplicated title logic and fix stale doc comment - Extract `compute_title_for_bulk_insert()` helper function to eliminate duplicated title computation logic in `bulk_create_hierarchy` and `bulk_create_hierarchy_root_notify` methods - Update stale doc comment for `update_with_version_check_returning_node` to accurately reflect the method signature and return type Addresses reviewer recommendations from PR #861: - [Improvement] Duplicated title logic in bulk operations - [Improvement] Doc comment stale reference Skipped (per reviewer's note these are acceptable as-is): - [Nitpick] Debug logging levels - reviewer noted "acceptable as-is" - [Nitpick] Emoji in logging - reviewer noted "acceptable for a small team" Co-Authored-By: Claude <noreply@anthropic.com> * Fix: moveNode returns updated Node to prevent version conflicts Phase 1 of race condition fixes for indent/outdent operations: Backend changes: - move_node_with_occ now returns Result<Node, ...> instead of Result<(), ...> - update_node_with_version_bump returns the updated node with new version - Tauri move_node command returns the node to frontend Frontend changes: - BackendAdapter interface updated: moveNode returns Promise<Node> - All callers (indent, outdent, child promotion) sync version from response - Added pending-operations.ts to track in-flight moves - SharedNodeStore waits for pending moves before UPDATE to avoid conflicts This eliminates the need to guess version+1 after moves, fixing ~90% of version conflicts during rapid Tab/Shift+Tab sequences. Related: #790 (Sync Infrastructure - Phase 2/3 documented in comments) Co-Authored-By: Claude <noreply@anthropic.com> * Fix: Dev proxy set_parent endpoint returns Node with OCC Updated dev-proxy HTTP endpoint to match Tauri command changes: - Added version field to SetParentRequest for OCC - Changed from move_node to move_node_with_occ - Returns updated Node with new version instead of 204 No Content This aligns browser dev mode with the Tauri IPC behavior. Co-Authored-By: Claude <noreply@anthropic.com> * Fix: Treat insert_after_node_id as best-effort hint to prevent data loss When creating or moving nodes, the `insert_after_node_id` parameter is now treated as a best-effort hint rather than a strict contract. If the sibling has moved to a different parent or been deleted (due to race conditions during rapid indent/outdent operations), the operation falls back to appending at the end rather than failing. This prevents data loss where user-typed content would be silently lost because the CREATE request failed with "Sibling has different parent" errors. Changes: - node_service.rs: Validate sibling in create_node_with_parent, clear hint if stale - surreal_store.rs: Fall back to append in move_node if sibling not found - Updated test to expect new forgiving behavior Relates to: Phase 2 of architect recommendations for issue #790 Co-Authored-By: Claude <noreply@anthropic.com> * Fix: Store executeOperation wrapper to prevent concurrent flush races When flushAndWaitForNodes() triggered pending operations, it was calling the raw operation function directly instead of the executeOperation wrapper. This bypassed the executingOperations tracking, allowing concurrent operations on the same node and causing version conflicts (409 errors). The fix stores the executeOperation wrapper (which tracks execution state) in pendingOperations instead of the raw operation function. This ensures that flushAndWaitForNodes() properly respects the execution state and prevents concurrent operations on the same node. This fixes the remaining 409 version conflicts during rapid indent/outdent operations where content updates and move operations race. Co-Authored-By: Claude <noreply@anthropic.com> * Fix: Flush target parent before indent to prevent 'Invalid parent' errors When indenting a node, the target parent might not be persisted to the database yet (e.g., when rapidly pressing Enter twice to create nested nodes). The move operation would fail with "Invalid parent node" because it tried to set a parent that doesn't exist in the DB. The fix adds the target parent to the list of nodes to flush before the move operation, ensuring the parent is created in the database before we try to move a child under it. Co-Authored-By: Claude <noreply@anthropic.com> * Fix: Prevent double-execution in flushAndWaitForNodes When flushAndWaitForNodes was called, it would clear the debounce timeout and then call pending.operation(). However, if the timeout callback was already executing (started just before clearTimeout), both would run, causing duplicate database operations and version conflicts. The fix checks executingOperations before starting the operation in flush. If the operation is already executing, we just wait for it to complete rather than starting a second instance. Co-Authored-By: Claude <noreply@anthropic.com> * Fix: Improve 'node not found' error detection for UPDATE→CREATE fallback The error message from the backend is "Node not found: <id>" but the code was checking for "NodeNotFound" (no space) or "does not exist". This caused the fallback to CREATE to not trigger, resulting in errors being logged instead of graceful recovery. The fix uses case-insensitive matching and checks for "not found" which catches all variations of the error message. Co-Authored-By: Claude <noreply@anthropic.com> * Fix: Wait for pending move operations in all UPDATE paths to prevent version conflicts The updateNode path in SharedNodeStore was missing the check for pending move operations that setNode already had. When a user typed content and then quickly indented (Tab), the following race occurred: 1. Content update queued with debounced persistence 2. Tab triggers flushNodeSaves() which starts the UPDATE 3. Tab also triggers moveNode() which runs in parallel 4. Both operations read version 1 from local state 5. MOVE returns first and bumps version to 2 6. UPDATE arrives with stale version 1 → 409 conflict The fix adds the same pending move check to: - updateNode path (line 869+) - persistBatchedChanges path (line 2562+) - batch race recovery path (line 2610+) Now all UPDATE paths wait for any pending move operation to complete and re-read the node's version before sending to the backend, ensuring version consistency. Co-Authored-By: Claude <noreply@anthropic.com> * Address review: Extract duplicated outdent insert order logic - Created calculateOutdentInsertOrder() helper function for computing fractional order when outdenting nodes - Replaced 3 duplicate code blocks (34 lines total) with helper calls - Improves maintainability and prevents logic divergence Addresses reviewer recommendation from PR #861 re-review. Co-Authored-By: Claude <noreply@anthropic.com> * Add comment about potential parallel optimization for sibling transfers Sequential execution preserves sibling order during outdent. Parallel is possible with pre-calculated fractional orders but adds complexity for minimal gain in typical use cases (0-3 siblings). Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
…etection (#878) * Docs: Add explicit warning about gh script directory requirement Agents repeatedly make the mistake of running `bun run gh:*` commands from subdirectories like packages/desktop-app/ instead of the repo root. These commands fail with "Script not found" because the gh scripts are defined in the root package.json. Added: - Prominent warning in startup sequence step 4 with correct/wrong examples - Reminder notes on steps 7 and 8 (assign/status commands) - New entry in "Common mistakes agents make" list Co-Authored-By: Claude <noreply@anthropic.com> * Test: Add comprehensive unit tests for PR #861 helper functions (closes #870 Part 1) ## Part 1: Unit Test Coverage Gaps ### TypeScript (Frontend) - Add 13 tests for `calculateOutdentInsertOrderPure()` in reactive-node-service - Refactored to pure function for testability - Tests edge cases: empty parent, missing old parent, single/many children - Tests fractional order precision and various positions - Add 6 tests for `flushAndWaitForNodes()` in shared-node-store-coverage - Tests flushing specific nodes, concurrent flush requests - Tests timeout behavior, mix of pending/non-pending nodes - Tests error reporting for failed operations - Add 10 tests for pending-operations module - Now imports actual module exports instead of reimplementing logic - Tests trackMoveOperation, waitForPendingMoveOperations, getPendingMoveOperation - Tests race condition prevention patterns (Issue #662 scenario) ### Rust (Backend) - Add 3 tests for `node_exists()` in surreal_store - Tests existing node returns true, non-existent returns false - Tests returns false after deletion - Add 3 tests for `get_parent_id()` in surreal_store - Tests root node returns None, child returns correct parent ID - Tests non-existent node returns None - Add 4 tests for `get_node_type()` in surreal_store - Tests correct type for text, task, date nodes - Tests non-existent node returns None ## Test Summary - Frontend: +28 tests (3501 → 3529) - Rust: +10 tests (767 → 777) Part 2 (test infrastructure improvements) to be addressed separately. Co-Authored-By: Claude <noreply@anthropic.com> * Address review: Add helper function and document real timer usage Reviewer nitpicks addressed: - Added `uniqueNodeId()` helper function for generating unique test node IDs - Added JSDoc explaining why unique IDs are needed (global module state) - Added comment block explaining why real timers are required for race condition tests (fake timers don't properly simulate Promise.race) Co-Authored-By: Claude <noreply@anthropic.com> * Test: Add stress tests for race condition detection (closes #870 Part 2) ## Part 2A: Browser Integration Test Infrastructure - Added `rapid-keyboard-operations.test.ts` with 13 tests for: - Keyboard event sequencing (Enter→Tab, Tab→Shift+Tab patterns) - Focus management during rapid operations - High-frequency event handling (500+ events) - Event prevention and bubbling behavior ## Part 2B: Stress Tests for Rapid Indent/Outdent - Added `rapid-hierarchy-operations.test.ts` with 17 tests for: - `calculateOutdentInsertOrderPure()` edge cases - Floating point precision limits (50+ rapid insertions) - Concurrent operation simulation - Out-of-order completion patterns - PR #861 race condition scenarios ## Part 2C: test:integration:full Command - Added `test:integration:full` script that runs: - Unit tests (bun run test) - Browser tests (bun run test:browser) - Performance tests (bun run test:perf) - Rust tests (bun run rust:test) ## Documentation - Updated testing-guide.md with: - Stress test documentation and examples - Instructions for running tests - Future E2E testing considerations Test Results: - Frontend: 3546 tests passed (45 new tests added) - Browser: 90 tests passed (13 new tests added) - Rust: 777 tests passed Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Docs: Add UX bug → regression test workflow to testing guide Documents the workflow for converting discovered UX bugs into regression tests: - Workflow steps: Discover → Fix → Test → Commit - Table mapping bug types to test file locations - Concrete example with Backspace→Enter sequence - Commit message convention for bug fixes with tests Key principle: Manual testing finds bugs, automated tests prevent regressions. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
Summary
batch_fetch_membershipsfromget_node()and related methods - was causing 700-800ms overhead per callget_parent_id()andnode_exists()helper methodsopt-level = 3for speed optimization (critical for SurrealDB performance)Performance Results
Test plan
bun run test:all)create_nodes_from_markdownstill working (11 tests pass)Related Issues
🤖 Generated with Claude Code