diff --git a/packages/desktop-app/src/lib/components/layout/app-shell.svelte b/packages/desktop-app/src/lib/components/layout/app-shell.svelte index 27342ce5f..640ed77d7 100644 --- a/packages/desktop-app/src/lib/components/layout/app-shell.svelte +++ b/packages/desktop-app/src/lib/components/layout/app-shell.svelte @@ -191,8 +191,14 @@ const folderPath = await importService.selectFolder(); if (!folderPath) return; + // Track if we've received step 9 (complete) to know when to unsubscribe + let unsubProgress: (() => void) | null = null; + let importFailed = false; + // Subscribe to progress updates - show step-based messages - const unsubProgress = importService.onProgress((event) => { + // NOTE: Phase 2 runs in background after importDirectory returns, + // so we must NOT unsubscribe until step 9 is received + unsubProgress = importService.onProgress(async (event) => { // Calculate overall progress based on step (9 steps total) // Steps 2-3 have per-file progress, others are single events let progress: number; @@ -206,9 +212,18 @@ progress = Math.round((event.step / 9) * 100); } - // Step 9 (complete) shows success message + // Step 9 (complete) shows success message and triggers cleanup if (event.step === 9) { - statusBar.success(event.message); + if (!importFailed) { + statusBar.success(event.message); + } + // Unsubscribe now that import is fully complete + if (unsubProgress) { + unsubProgress(); + unsubProgress = null; + } + // Refresh collections after background import completes + await collectionsData.loadCollections(); } else { statusBar.show(event.message, progress); } @@ -220,20 +235,27 @@ exclude_patterns: ['design-system', 'node_modules', '.git'], }); - // The completion message is now handled by the progress event (step 9) - // But if there were failures, show an error message + // Phase 1 complete - Phase 2 runs in background + // If there were parsing failures, show error (but don't override progress) if (result.failed > 0) { + importFailed = true; statusBar.error(`Import complete: ${result.successful} imported, ${result.failed} failed`); + // Unsubscribe since we're showing error + if (unsubProgress) { + unsubProgress(); + unsubProgress = null; + } } - - // Refresh collections after import - const { collectionsData } = await import('$lib/stores/collections'); - await collectionsData.loadCollections(); + // NOTE: Do NOT unsubProgress here - Phase 2 still running } catch (error) { log.error('Import failed', error); + importFailed = true; statusBar.error('Import failed: ' + (error instanceof Error ? error.message : String(error))); - } finally { - unsubProgress(); + // Unsubscribe on error + if (unsubProgress) { + unsubProgress(); + unsubProgress = null; + } } }); diff --git a/packages/desktop-app/src/lib/services/import-service.ts b/packages/desktop-app/src/lib/services/import-service.ts index 3f8183375..664cc8c5b 100644 --- a/packages/desktop-app/src/lib/services/import-service.ts +++ b/packages/desktop-app/src/lib/services/import-service.ts @@ -101,6 +101,11 @@ class ImportService { /** * Import all markdown files from a directory + * + * NOTE: This method returns after Phase 1 (parsing) completes, but Phase 2 + * (database operations) continues in background. Progress events will continue + * to be emitted until step 9 (complete). Callers should NOT unsubscribe from + * progress until they receive the step 9 event. */ async importDirectory( directoryPath: string, @@ -108,27 +113,25 @@ class ImportService { ): Promise { log.info('Starting directory import', { path: directoryPath, options }); - // Set up progress listener + // Set up progress listener (kept alive for background Phase 2) await this.setupProgressListener(); - try { - const result = await invoke('import_markdown_directory', { - directoryPath, - options: options || {}, - }); + const result = await invoke('import_markdown_directory', { + directoryPath, + options: options || {}, + }); - log.info('Directory import complete', { - total: result.total_files, - successful: result.successful, - failed: result.failed, - duration_ms: result.duration_ms, - }); + log.info('Directory import Phase 1 complete (Phase 2 running in background)', { + total: result.total_files, + successful: result.successful, + failed: result.failed, + duration_ms: result.duration_ms, + }); - return result; - } finally { - // Clean up progress listener - this.teardownProgressListener(); - } + // NOTE: Do NOT teardown listener here - Phase 2 is still running in background + // The listener will be cleaned up when the caller unsubscribes after step 9 + + return result; } /**