Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 33 additions & 11 deletions packages/desktop-app/src/lib/components/layout/app-shell.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}
Expand All @@ -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;
}
}
});

Expand Down
37 changes: 20 additions & 17 deletions packages/desktop-app/src/lib/services/import-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,34 +101,37 @@ 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,
options?: ImportOptions
): Promise<BatchImportResult> {
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<BatchImportResult>('import_markdown_directory', {
directoryPath,
options: options || {},
});
const result = await invoke<BatchImportResult>('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;
}

/**
Expand Down