Import
How external markdown files are analyzed, converted, and imported into an Inklings workspace.
Overview
Section titled “Overview”Step-by-Step Details
Section titled “Step-by-Step Details”1. Phase 1: Analysis (Scan + Preview)
Section titled “1. Phase 1: Analysis (Scan + Preview)”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:
| Item | Treatment |
|---|---|
.git, .obsidian, node_modules, .vscode, .idea, __pycache__ | Skipped entirely |
Hidden directories (starting with .) | Skipped |
| Symlinks | Skipped (security: prevents path escape) |
Non-.md files | Counted but not imported |
| Obsidian user ignore filters | Applied 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.
3. Import Mode
Section titled “3. Import Mode”Files are always copied from the source folder to a new workspace at the specified target path. The source folder remains unchanged.
4. Conflict Strategies
Section titled “4. Conflict Strategies”| Strategy | Behavior |
|---|---|
Skip (default) | Existing page with same slug is left unchanged |
Overwrite | Existing page is overwritten with imported content |
Rename | Imported page gets a numeric suffix on the slug |
5. Side Effects After Import
Section titled “5. Side Effects After Import”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::Createdrecorded - Sync queue: Block update enqueued if sync is enabled
- FTS5: Automatically updated via SQLite trigger on
raw_markdown
6. Rollback
Section titled “6. Rollback”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.
Error Handling
Section titled “Error Handling”| Failure | Behavior |
|---|---|
| Source path does not exist | SourceNotFound error before any I/O |
| Source is a file, not a directory | InvalidSource error |
Missing ImportExecute capability | ImportFailed error (permission gate) |
| Invalid frontmatter | ImportIssue::InvalidFrontmatter recorded in analysis; file imported with frontmatter stripped |
| Unresolvable wiki-link target | Link converted with best-effort slug; marked as ghost link at render time |
| Duplicate slug | Suffix appended (-2, -3, …); not an error |
CreatePageUseCase fails | File counted in skipped_count; error message added to ImportResult.errors |
| Symlink in source | Skipped silently during scan |
| Path traversal attempt | Skipped with warn!; path marked as suspicious in analysis issues |
Related
Section titled “Related”- 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 System —
CreatePageUseCasecalled for each imported file - Embedding System — Each imported page is queued for embedding after creation
Was this page helpful?
Thanks for your feedback!