Fix/vault sync timeout 2230#2243
Conversation
…nsai#2230) Root causes: - vault_sync blocked the HTTP connection awaiting full sync completion, causing the frontend 30 s timeout to fire on large directories - File ingestion was sequential; 100 files × ~500 ms/API call = 50 s+ Changes: - ops.rs: vault_sync now spawns a tokio background task and returns {status: "started", vault_id} immediately; callers poll for progress - ops.rs: add vault_sync_status RPC to read live progress - state.rs: global parking_lot::RwLock<HashMap> tracks per-vault sync state (scanned / ingested / total / status); start() guards against duplicate concurrent syncs - types.rs: VaultSyncState + VaultSyncStatus structs - schemas.rs: register openhuman.vault_sync_status controller - sync.rs: two-phase approach — sequential discovery walk (mtime dedup) then buffer_unordered(4) concurrent ingestion to parallelise I/O-bound embedding API calls (~4x throughput improvement) - vault.ts: CoreVaultSyncState type + openhumanVaultSyncStatus() - VaultPanel.tsx: replace blocking await with polling loop; shows live "Syncing… N/M" progress in the button label; cancels timers on unmount; cleanup pollTimers ref - Tests updated for the new two-step async flow
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughConverts vault sync to a background-start + periodic polling flow: backend records per-vault sync state and spawns a background worker; frontend starts sync, polls vault.sync_status for progress updates, updates UI counts, and shows final success or failure toasts. ChangesAsync Vault Sync with Progress Polling
Sequence Diagram(s)sequenceDiagram
participant User
participant VaultPanel
participant TauriAPI
participant Backend
User->>VaultPanel: Click sync button
VaultPanel->>TauriAPI: openhumanVaultSync(vaultId)
TauriAPI->>Backend: vault.sync RPC
Backend->>Backend: state::start + spawn background task
Backend-->>TauriAPI: {status: started, vault_id}
TauriAPI-->>VaultPanel: Response
VaultPanel->>VaultPanel: Start polling timer
loop Poll until terminal
VaultPanel->>TauriAPI: openhumanVaultSyncStatus(vaultId)
TauriAPI->>Backend: vault.sync_status RPC
Backend-->>TauriAPI: VaultSyncState
TauriAPI-->>VaultPanel: Progress update
VaultPanel->>VaultPanel: Update UI counts
end
VaultPanel->>VaultPanel: Clear timers, show final toast, reload vaults
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/openhuman/vault/sync.rs (1)
192-197:⚠️ Potential issue | 🟠 Major | ⚡ Quick winMark fatal preflight failures as failed to avoid false “Completed” status.
These early returns add
errorsbut leavereport.failed == 0, so the background status resolver can classify hard failures asCompleted.Suggested patch
if !root.is_dir() { + report.failed += 1; report .errors .push(format!("root_path is not a directory: {}", vault.root_path)); report.duration_ms = (Utc::now() - started).num_milliseconds(); return report; @@ Err(err) => { + report.failed += 1; report.errors.push(format!("ledger read failed: {err}")); + report.duration_ms = (Utc::now() - started).num_milliseconds(); return report; }Also applies to: 203-206
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/openhuman/vault/sync.rs` around lines 192 - 197, The early-return preflight checks (e.g., the block that checks if !root.is_dir() using variables root, report, vault.root_path and updates report.duration_ms from started) add errors but do not mark the report as failed; update those early-return paths (the block at the root.is_dir() check and the similar block around lines 203-206) to set report.failed = 1 (or increment report.failed) before returning so fatal preflight failures are recorded as failed and not treated as "Completed".
🧹 Nitpick comments (3)
app/src/utils/tauriCommands/vault.test.ts (1)
165-177: ⚡ Quick winAdd direct tests for
openhumanVaultSyncStatuswrapper contract.This file updates
openhumanVaultSync, but the newly addedopenhumanVaultSyncStatuspublic wrapper is still unverified (Tauri guard +openhuman.vault_sync_status+{ vault_id }params). A small dedicated describe block would harden this contract.🧪 Suggested test addition
+ describe('openhumanVaultSyncStatus', () => { + let openhumanVaultSyncStatus: typeof import('./vault').openhumanVaultSyncStatus; + + beforeEach(async () => { + const actual = await vi.importActual<typeof import('./vault')>('./vault'); + openhumanVaultSyncStatus = actual.openhumanVaultSyncStatus; + }); + + test('throws when not running in Tauri', async () => { + mockIsTauri.mockReturnValue(false); + await expect(openhumanVaultSyncStatus('v-1')).rejects.toThrow('Not running in Tauri'); + expect(mockCallCoreRpc).not.toHaveBeenCalled(); + }); + + test('dispatches openhuman.vault_sync_status with vault_id', async () => { + mockCallCoreRpc.mockResolvedValue({ result: { vault_id: 'v-1', status: 'running' }, logs: [] }); + await openhumanVaultSyncStatus('v-1'); + expect(mockCallCoreRpc).toHaveBeenCalledWith({ + method: 'openhuman.vault_sync_status', + params: { vault_id: 'v-1' }, + }); + }); + });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/utils/tauriCommands/vault.test.ts` around lines 165 - 177, Add a dedicated test block for the openhumanVaultSyncStatus wrapper: mock mockCallCoreRpc to resolve a payload like { result: { status: 'completed', vault_id: 'v-1' }, logs: [] }, call openhumanVaultSyncStatus('v-1'), assert mockCallCoreRpc was called with method 'openhuman.vault_sync_status' and params { vault_id: 'v-1' }, and assert the returned resp.result.status and resp.result.vault_id match the mocked values; mirror the structure of the existing openhumanVaultSync test to validate the Tauri guard + RPC contract for openhumanVaultSyncStatus.app/src/components/intelligence/VaultPanel.test.tsx (1)
183-207: ⚡ Quick winAdd a test for
status: 'failed'terminal polling response.Current sync tests only finish on
status: 'completed'. Add one case whereopenhumanVaultSyncStatusreturnsstatus: 'failed'and assert failure toast semantics. This will lock in the terminal-branch behavior.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/intelligence/VaultPanel.test.tsx` around lines 183 - 207, Add a new test in VaultPanel.test.tsx that mirrors the existing sync flow but makes openhumanVaultSyncStatus (mockSyncStatus) return a terminal status of 'failed' via syncState(..., status: 'failed', errors: [...]) and then assert that VaultPanel calls the onToast handler with the failure semantics (e.g., expect(onToast).toHaveBeenCalledWith(expect.objectContaining({ type: 'error', message: expect.stringContaining('failed') })) ). Use the same setup helpers used by the other tests (mockList, mockSync, mockSyncStatus, syncState) and render VaultPanel with a spy onToast to verify the error toast is emitted for the 'failed' terminal response.app/src/components/intelligence/VaultPanel.tsx (1)
117-129: ⚡ Quick winUse namespaced
debuglogging instead ofconsole.*in this frontend flow.The new sync path introduces
console.error/console.debug; this diverges from the project logging pattern.As per coding guidelines “Follow existing patterns for debug logging (e.g. the
debugnpm package with a namespace per area) …”.Also applies to: 141-162
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/intelligence/VaultPanel.tsx` around lines 117 - 129, Replace the raw console calls in the VaultPanel sync path with a namespaced debug logger: remove console.error and console.debug and use the project's debug instance (e.g., create or import a logger namespace like debug('ui-flow:vault-panel')) to log error and debug messages; ensure the error log includes the error object and the toast still uses onToast as before, and keep setBusy(v => ({ ...v, [vault.id]: undefined })) behavior unchanged while updating all other console.* usages in this file (including the block around onToast and the later 141-162 region) to the same namespaced debug logger.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/src/components/intelligence/VaultPanel.tsx`:
- Around line 164-178: The terminal-state handling currently treats st.status
=== 'failed' the same as 'completed' and may show a success-style toast; split
the branch so 'completed' and 'failed' are handled separately: for both cases
still clear pollTimers.current[vaultId], call setBusy and setSyncProgress to
clear state, but for 'failed' call onToast with a failure/error type and an
appropriate title like `Sync failed for "${vaultName}"` (and a message
summarizing st.* fields or the error), and for 'completed' use the existing
success/info logic; update references in this block (st.status,
pollTimers.current, setBusy, setSyncProgress, onToast, vaultId, vaultName)
accordingly so failures are never labeled as success.
In `@src/openhuman/vault/state.rs`:
- Around line 34-39: Add debug/trace logs on the key state branches in the
registry helpers: in start() when a duplicate-running reject occurs (check of
SYNC_STATE and VaultSyncStatus::Running) log a debug message like "start: vault
{vault_id} already syncing" including vault_id and current state before
returning Err; likewise add a debug/trace log in update_progress() when the
map.get(vault_id) is None (the missing-entry no-op) indicating "update_progress:
no entry for vault {vault_id}, ignoring" and also log successful
transitions/updates (e.g., progress/state changes) so callers can see lifecycle
changes; use the existing logging/tracing crate at debug or trace level and
include the function names and vault_id in messages for easier production
debugging.
In `@src/openhuman/vault/sync.rs`:
- Around line 125-134: process_file is using blocking std::fs::read_to_string
which can starve Tokio workers; replace that call with the async
tokio::fs::read_to_string(&file.path).await and map the Result to the same
IngestFileResult::Failed path (preserve rel_path and the error.format), ensuring
tokio::fs is imported and no other blocking APIs remain in process_file
(references: function process_file, types FileToProcess and IngestFileResult).
---
Outside diff comments:
In `@src/openhuman/vault/sync.rs`:
- Around line 192-197: The early-return preflight checks (e.g., the block that
checks if !root.is_dir() using variables root, report, vault.root_path and
updates report.duration_ms from started) add errors but do not mark the report
as failed; update those early-return paths (the block at the root.is_dir() check
and the similar block around lines 203-206) to set report.failed = 1 (or
increment report.failed) before returning so fatal preflight failures are
recorded as failed and not treated as "Completed".
---
Nitpick comments:
In `@app/src/components/intelligence/VaultPanel.test.tsx`:
- Around line 183-207: Add a new test in VaultPanel.test.tsx that mirrors the
existing sync flow but makes openhumanVaultSyncStatus (mockSyncStatus) return a
terminal status of 'failed' via syncState(..., status: 'failed', errors: [...])
and then assert that VaultPanel calls the onToast handler with the failure
semantics (e.g., expect(onToast).toHaveBeenCalledWith(expect.objectContaining({
type: 'error', message: expect.stringContaining('failed') })) ). Use the same
setup helpers used by the other tests (mockList, mockSync, mockSyncStatus,
syncState) and render VaultPanel with a spy onToast to verify the error toast is
emitted for the 'failed' terminal response.
In `@app/src/components/intelligence/VaultPanel.tsx`:
- Around line 117-129: Replace the raw console calls in the VaultPanel sync path
with a namespaced debug logger: remove console.error and console.debug and use
the project's debug instance (e.g., create or import a logger namespace like
debug('ui-flow:vault-panel')) to log error and debug messages; ensure the error
log includes the error object and the toast still uses onToast as before, and
keep setBusy(v => ({ ...v, [vault.id]: undefined })) behavior unchanged while
updating all other console.* usages in this file (including the block around
onToast and the later 141-162 region) to the same namespaced debug logger.
In `@app/src/utils/tauriCommands/vault.test.ts`:
- Around line 165-177: Add a dedicated test block for the
openhumanVaultSyncStatus wrapper: mock mockCallCoreRpc to resolve a payload like
{ result: { status: 'completed', vault_id: 'v-1' }, logs: [] }, call
openhumanVaultSyncStatus('v-1'), assert mockCallCoreRpc was called with method
'openhuman.vault_sync_status' and params { vault_id: 'v-1' }, and assert the
returned resp.result.status and resp.result.vault_id match the mocked values;
mirror the structure of the existing openhumanVaultSync test to validate the
Tauri guard + RPC contract for openhumanVaultSyncStatus.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e6a56674-65ef-4d10-bbf5-7bdc937a32d3
📒 Files selected for processing (10)
app/src/components/intelligence/VaultPanel.test.tsxapp/src/components/intelligence/VaultPanel.tsxapp/src/utils/tauriCommands/vault.test.tsapp/src/utils/tauriCommands/vault.tssrc/openhuman/vault/mod.rssrc/openhuman/vault/ops.rssrc/openhuman/vault/schemas.rssrc/openhuman/vault/state.rssrc/openhuman/vault/sync.rssrc/openhuman/vault/types.rs
…e logs - sync.rs: replace std::fs::read_to_string with tokio::fs::read_to_string to avoid blocking Tokio worker threads under buffer_unordered(4) - VaultPanel.tsx: separate error toast for status='failed' vs completed- with-failures; prevents misleading 'Synced' title when sync terminates before counting file-level failures - state.rs: add debug logs on start rejection (already running) and update_progress no-op (missing entry) per debug logging rule - VaultPanel.test.tsx: add test for status='failed' error toast path
There was a problem hiding this comment.
🧹 Nitpick comments (2)
app/src/components/intelligence/VaultPanel.tsx (1)
135-150: 💤 Low valuePotential state update on unmounted component during in-flight poll.
If the component unmounts while
openhumanVaultSyncStatusis in-flight, the cleanup effect clears the timer but the pending promise will still resolve and callsetBusy/setSyncProgresson an unmounted component. While React 18+ no longer warns about this, it's a minor inefficiency and can cause subtle issues if the component remounts.Consider adding an abort signal or a mounted ref to skip state updates after unmount.
♻️ Optional fix using a mounted ref
const pollTimers = useRef<Record<string, ReturnType<typeof setTimeout>>>({}); + const isMounted = useRef(true); // Cancel all active poll timers on unmount. useEffect(() => { const timers = pollTimers.current; return () => { + isMounted.current = false; for (const t of Object.values(timers)) { clearTimeout(t); } }; }, []);Then guard state updates in the poll function:
} catch (err) { console.error('[ui-flow][vault-panel] sync status poll failed', err); + if (!isMounted.current) return; setBusy(b => ({ ...b, [vaultId]: undefined }));🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/intelligence/VaultPanel.tsx` around lines 135 - 150, The poll function may call setBusy/setSyncProgress/onToast after the component unmounts; modify VaultPanel.tsx to track mounting (e.g., a mountedRef set in useEffect cleanup or pass an AbortSignal to openhumanVaultSyncStatus) and short-circuit any state updates and toasts when unmounted: update the effect that starts poll to set mountedRef.current = true and on cleanup set it false (or cancel the request via AbortController), then inside poll (around the catch and success paths where you call setBusy, setSyncProgress, and onToast) check the mounted flag or abort signal and return early if not mounted so no state updates occur after unmount.app/src/components/intelligence/VaultPanel.test.tsx (1)
209-222: 💤 Low valueConsider adding a test for status poll failure.
The existing test covers
mockSync.mockRejectedValueOnce(sync start failure), but there's no test for whenopenhumanVaultSyncStatusthrows during polling. This would exercise lines 140-149 in the component.🧪 Example test for poll failure
it('emits error toast when sync status poll throws', async () => { mockList .mockResolvedValueOnce({ result: [vault()], logs: [] }); mockSync.mockResolvedValueOnce({ result: { status: 'started', vault_id: 'v-1' }, logs: [] }); mockSyncStatus.mockRejectedValueOnce(new Error('network error')); const onToast = vi.fn(); render(<VaultPanel onToast={onToast} />); await waitFor(() => screen.getByTestId('vault-list')); fireEvent.click(screen.getByText('Sync')); await waitFor(() => expect(onToast).toHaveBeenCalledWith( expect.objectContaining({ type: 'error', title: 'Sync failed', message: 'network error' }) ) ); });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/intelligence/VaultPanel.test.tsx` around lines 209 - 222, Add a test that verifies VaultPanel emits an error toast when the sync status poll fails: in the test suite for VaultPanel, mock the list call (mockList) to return a vault, mock the sync start (mockSync) to resolve with a started status and vault_id, then mock the status poll (mockSyncStatus / openhumanVaultSyncStatus) to reject with an Error; render <VaultPanel onToast={onToast} />, trigger the Sync button, and wait/assert that onToast was called with an error toast containing title 'Sync failed' and the rejection message to cover the polling failure branch (lines around the openhumanVaultSyncStatus polling logic).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@app/src/components/intelligence/VaultPanel.test.tsx`:
- Around line 209-222: Add a test that verifies VaultPanel emits an error toast
when the sync status poll fails: in the test suite for VaultPanel, mock the list
call (mockList) to return a vault, mock the sync start (mockSync) to resolve
with a started status and vault_id, then mock the status poll (mockSyncStatus /
openhumanVaultSyncStatus) to reject with an Error; render <VaultPanel
onToast={onToast} />, trigger the Sync button, and wait/assert that onToast was
called with an error toast containing title 'Sync failed' and the rejection
message to cover the polling failure branch (lines around the
openhumanVaultSyncStatus polling logic).
In `@app/src/components/intelligence/VaultPanel.tsx`:
- Around line 135-150: The poll function may call
setBusy/setSyncProgress/onToast after the component unmounts; modify
VaultPanel.tsx to track mounting (e.g., a mountedRef set in useEffect cleanup or
pass an AbortSignal to openhumanVaultSyncStatus) and short-circuit any state
updates and toasts when unmounted: update the effect that starts poll to set
mountedRef.current = true and on cleanup set it false (or cancel the request via
AbortController), then inside poll (around the catch and success paths where you
call setBusy, setSyncProgress, and onToast) check the mounted flag or abort
signal and return early if not mounted so no state updates occur after unmount.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: fcaeef66-6a84-4433-a457-27a059c92e96
📒 Files selected for processing (4)
app/src/components/intelligence/VaultPanel.test.tsxapp/src/components/intelligence/VaultPanel.tsxsrc/openhuman/vault/state.rssrc/openhuman/vault/sync.rs
🚧 Files skipped from review as they are similar to previous changes (2)
- src/openhuman/vault/state.rs
- src/openhuman/vault/sync.rs
…isfy coverage gate - state::start(), state::set/get, update_progress, rejection path - vault_sync_status: idle fallback, running/completed/failed states, empty-id error - 12 new Rust test cases covering all new branches in state.rs and ops.rs
- tests.rs: break long vault_sync_status().await.unwrap() call to
satisfy cargo fmt (Diff at line 233)
- vault.test.ts: add openhumanVaultSyncStatus describe block (throws
when not in Tauri + dispatches correct RPC method) — covers vault.ts
lines 133, 136-137 that were at 25% coverage
- VaultPanel.test.tsx: add 5 tests to lift diff-coverage from 69% to
target ≥80%:
• poll catch: status poll RPC throws → error toast (lines 141-144,147,149)
• failed toast fallback: errors:[] + failed>0 → 'Failed N file(s)' (line 175)
• skipped_unsupported > 0 in completed toast message (lines 186-187)
• cancels pending poll timer on unmount via clearTimeout spy (line 48,
lines 195-196 via running→reschedule path, line 365 JSX progress label)
|
@senamakel I would be grateful if you could conduct a code review. |
graycyrus
left a comment
There was a problem hiding this comment.
Solid PR — the two-phase discovery + concurrent ingestion approach is clean, the state registry is well-designed, and the polling UI with proper cleanup on unmount is correct. Tests are thorough (happy path, failure, error, unmount timer cleanup). Nice work.
One issue I'd like addressed before merging:
| Severity | File | Finding |
|---|---|---|
| major | ops.rs |
tokio::spawn has no panic guard — task panic leaves vault stuck in Running |
| minor | vault.ts |
CoreVaultSyncReport is now dead code |
…nt lock in Running state
Summary
vault_syncRPC now returns immediately with{ status: "started" }instead of blocking the HTTP connection for up to 50+ secondsvault_sync_statusRPC endpoint lets the frontend poll for live progress (scanned / ingested / total)buffer_unordered(4)— reduces sync time ~4× for large directories (100 files: ~50s → ~12s)VaultPanelshows a liveSyncing… N/Mcounter in the Sync button during background syncProblem
On macOS Apple Silicon, syncing
~/Documents(100+ files) reliably timed out with:Root causes:
vault_syncawaited the fullsync_vault()call before returning an HTTP response — the 30 s frontend timeout fired before ingestion finishedSolution
Non-blocking dispatch (ops.rs):
vault_syncregisters the sync in a globalparking_lot::RwLockstate map, spawns atokio::spawnbackground task, and returns{ status: "started" }in < 1 ms. The background task writes live progress counters into the state map after each batch.Progress polling (ops.rs +
schemas.rs): Newopenhuman.vault_sync_statuscontroller reads the in-memory state and returns aVaultSyncStatestruct (status, scanned, ingested, total, duration_ms, errors).Concurrent ingestion (
sync.rs): Two-phase approach — sequential directory walk with mtime fast-path dedup, thenfutures::stream::iter().buffer_unordered(4)for the embedding API calls. Concurrency of 4 was chosen to stay within typical API rate limits while giving ~4× throughput improvement.Polling UI (VaultPanel.tsx): Replaces the old
await openhumanVaultSync()blocking call with a start → poll loop. Timer refs are cleaned up on component unmount. Button label showsSyncing… N/Monce total is known.Tradeoff: Background state lives in process memory (not persisted). A crash during sync results in an
Idlestatus on next query — acceptable since the user can simply retry.Submission Checklist
VaultPanel.test.tsxupdated for two-step async flow (start + poll-to-completion, failed-files branch, error-on-start branch); vault.test.ts updated for newvault_syncreturn type and newopenhumanVaultSyncStatusfunctionstate.rs,schemas.rs,vault.ts, VaultPanel.tsx are covered by updated tests;pnpm test:coveragepasses locally## RelatedCloses #2230Impact
Configclone, no additional file permissionsvault_sync_statusis additive, old clients that ignore it still workvault_syncresponse shape changes fromVaultSyncReport→{ status, vault_id }— frontend updated in the same PRRelated
VaultSyncStateto SQLite so a crash-restart can surface the last-known statusAI Authored PR Metadata
Linear Issue
Commit & Branch
fix/vault-sync-timeout-223047a21be2457dc348b5be37718a62662ae4a7ee2dValidation Run
pnpm --filter openhuman-app format:check— passed (Prettier + cargo fmt auto-fixes applied inchore: apply auto-fixescommit)pnpm typecheck— passed (0 errors)pnpm debug unit VaultPanel✅ ·pnpm debug unit tauriCommands/vault✅GGML_NATIVE=OFF cargo check --manifest-path Cargo.toml— passed (0 errors, 4 pre-existing warnings in unrelated modules)Validation Blocked
command:pnpm rust:check(Tauri shellcargo check --manifest-path app/src-tauri/Cargo.toml)error:cef-dll-sysbuild script fails — CMake cannot find Ninja (CMAKE_MAKE_PROGRAMnot set). Pre-existing environment issue; not caused by this PR (no Tauri shell files changed).impact:Low — this PR touches only vault (core crate) and src (React); zero changes to src-tauriBehavior Changes
vault_syncRPC returns immediately instead of blocking; callers must pollvault_sync_statusto detect completionSyncing… N/Mprogress and no longer freezes / times out on large directoriesParity Contract
vault_sync_statusregistered in all.rs alongside existing vault controllers; no dispatch branches added tocli.rsorjsonrpc.rsDuplicate / Superseded PR Handling
Summary by CodeRabbit
New Features
Refactor
Tests