Skip to content
Documentation GitHub
Data Flow

Import

How external markdown files are analyzed, converted, and imported into an Inklings workspace.



Code: crates/application/src/import/analyze.rs, crates/infrastructure/import/src/import/import_repository.rs

The analysis phase is a read-only scan. The user selects a source folder; the frontend calls analyze_import. AnalyzeImportUseCase requires the ImportExecute capability, validates the path is a directory, then delegates to MarkdownImporter::analyze.

Source type detection: The importer checks for a .obsidian/ directory marker. If present, the vault is treated as an Obsidian source, which activates Obsidian-specific wiki-link conversion and loads obsidian.json for user-configured ignore filters.

Scan rules:

ItemTreatment
.git, .obsidian, node_modules, .vscode, .idea, __pycache__Skipped entirely
Hidden directories (starting with .)Skipped
SymlinksSkipped (security: prevents path escape)
Non-.md filesCounted but not imported
Obsidian user ignore filtersApplied in addition to built-in rules

The scan builds a FolderPreview tree showing the top-level folder structure, and detects potential ImportIssue types: invalid frontmatter, paths exceeding max depth, duplicate slugs, and suspicious path components.

Excluded folder tracking: Each skipped folder is recorded as an ExcludedFolder with an ExclusionReason (BuiltInIgnored, HiddenDirectory, ObsidianUserFilter, or AttachmentFolder). These are returned in ObsidianMetadata.excluded_folders so the import preview dialog can show users exactly which folders were excluded and why, grouped by reason in a collapsible section.

2. Phase 2: Execution — Per-File Processing

Section titled “2. Phase 2: Execution — Per-File Processing”

Code: crates/application/src/import/execute.rs, crates/infrastructure/import/src/import/import_repository.rs

ExecuteImportUseCase::execute_with_progress delegates to MarkdownImporter::execute_with_progress. For each discovered .md file:

Step 1 — Read and parse

The file is read from disk. YAML frontmatter (delimited by ---) is extracted and parsed. Recognized frontmatter keys (e.g., tags, type) are mapped to Inklings metadata.

Step 2 — Wiki-link conversion (Obsidian sources only)

Obsidian wiki-link syntax differs from Inklings syntax. The importer converts all link forms found in the file body:

Input (Obsidian)Output (Inklings)Notes
[[Target Page]][[Target Page|target-page]]Display = title, slug = slugified title
[[Target Page|Display Text]][[Display Text|target-page]]Alias becomes display; slug from target
[[Target Page#Heading]][[Target Page|target-page#heading]]Heading fragment preserved
[[Folder/Target Page]][[Target Page|target-page]]Folder prefix stripped

Plain markdown sources (non-Obsidian) have their links passed through unchanged.

Step 3 — Slug derivation

The page slug is derived from the file’s relative path within the source directory. Path separators become hyphens; non-alphanumeric characters are stripped or replaced. Duplicate slugs use a numeric suffix (-2, -3, etc.).

Step 4 — CreatePageUseCase

The converted content and extracted metadata are passed to CreatePageUseCase. This creates the page with its first block atomically (domain rule: minimum 1 block per page). Parent slug is resolved from the relative file path.

Step 5 — Progress reporting

progress_cb(ImportProgress { phase: Executing, completed, total }) is called after each file. The frontend displays this in real time via the import dialog.

Files are always copied from the source folder to a new workspace at the specified target path. The source folder remains unchanged.

StrategyBehavior
Skip (default)Existing page with same slug is left unchanged
OverwriteExisting page is overwritten with imported content
RenameImported page gets a numeric suffix on the slug

Each successfully created page fires the same WriteEffectCoordinator side effects as a normal page creation:

  • Embedding pipeline: Page queued for vectorization (deferred, debounced)
  • Event log: EntityType::Page / EventType::Created recorded
  • Sync queue: Block update enqueued if sync is enabled
  • FTS5: Automatically updated via SQLite trigger on raw_markdown

If the import is cancelled mid-execution, MarkdownImporter::rollback_import deletes only files confirmed to be within the workspace directory (canonicalized path check prevents escape). Pages already created in SQLite are not rolled back automatically — the Tauri command layer handles compensating deletes.


FailureBehavior
Source path does not existSourceNotFound error before any I/O
Source is a file, not a directoryInvalidSource error
Missing ImportExecute capabilityImportFailed error (permission gate)
Invalid frontmatterImportIssue::InvalidFrontmatter recorded in analysis; file imported with frontmatter stripped
Unresolvable wiki-link targetLink converted with best-effort slug; marked as ghost link at render time
Duplicate slugSuffix appended (-2, -3, …); not an error
CreatePageUseCase failsFile counted in skipped_count; error message added to ImportResult.errors
Symlink in sourceSkipped silently during scan
Path traversal attemptSkipped with warn!; path marked as suspicious in analysis issues

  • Import System — Full import system reference including supported source types and Obsidian vault handling
  • Wiki-link System — Link conversion rules and the [[display|slug]] format
  • Page SystemCreatePageUseCase called for each imported file
  • Embedding System — Each imported page is queued for embedding after creation

Was this page helpful?