Import System
Status: Implemented Reference epics: INK-826, INK-830 ADRs: ADR-017, ADR-018
Overview
Section titled “Overview”The import system brings existing content into an Inklings workspace from external sources — markdown folders, Obsidian vaults, AnyType exports, and Notion exports. Obsidian is the primary migration source; the others are adjacent ergonomics.
Import is a product surface layered on top of a domain invariant: every imported page is a WorldWrite with origin: Imported and lifecycle: Draft, created through the submit boundary. The importer is an adapter, not a back door.
This page covers the user-facing import features: supported sources, preview, link conversion, conflict handling. For the write-path mechanics — how the adapter constructs each WorldWrite, how conflicts become deviation records, how bulk-canonicalize works against the run — see architecture/data-flow/import.
Imports land as drafts
Section titled “Imports land as drafts”Every imported page lands as lifecycle: Draft regardless of source. The user’s import click is permission to ingest, not to canonicalize. Canonicalization is a separate, explicit act.
| Field | Value on import | Why |
|---|---|---|
origin | Imported | The content came from outside the workspace. Permanent; does not shift as the content is later edited. See provenance §origin. |
lifecycle | Draft | The user imported; they did not endorse. See provenance §lifecycle and ADR-018. |
origin_source_id | Import run id + source file path | Ties every Draft page back to the run that produced it. Used for undo, bulk-canonicalize, and re-import reconciliation. |
Source “authoritativeness” (an Obsidian vault the user has been tending for years, an AnyType export from another tool) does not alter the default. A user who wants to accept their import wholesale uses the bulk-canonicalize affordance described below.
This default gives the author two things:
- A clean landing zone. Drafts are reviewable. Canonical content is what the author has endorsed.
- A safe default for re-imports. An overwriting re-import of the same source produces new drafts, not silent canonical overwrites.
The agent treats drafts differently from canonical content in context assembly and reasoning: drafts are workspace-present but not author-endorsed, so their weight in agent responses scales accordingly. See provenance §standing.
Import flow
Section titled “Import flow”Two stages, same pattern as every other workspace write surface:
- Analysis — read-only scan of the source, produces an
ImportAnalysiswith file counts, detected source type, issues, and a folder preview. No workspace mutation. - Execution — per-file conversion (frontmatter, links, slug) followed by
WorldWritesubmission. Each file is one submit-boundary call. Errors on one file do not abort the run; they accumulate as warnings.
The write-path mechanics (what the boundary does with each WorldWrite, what side effects fire, what the per-row transaction looks like) are unified in data-flow/write-path and specialized for import in data-flow/import.
Key design decisions
Section titled “Key design decisions”Copy, not move
Section titled “Copy, not move”Import copies files into the workspace. Original files remain untouched. Re-import is safe; the source is always intact for reference or reconciliation.
Preserve full structure
Section titled “Preserve full structure”Hierarchy is preserved as-is with no depth limit. Deep nesting (5+ levels) triggers an informational warning in the preview but does not modify the import. Authors who want a flatter workspace can apply a flatten strategy at configure time.
Wiki-link conversion
Section titled “Wiki-link conversion”Obsidian-style [[wiki links]] and the equivalents from other tools are converted to Inklings’ standard markdown link form at import time. See §link conversion rules.
Preview before commit
Section titled “Preview before commit”The user sees the full preview — file count, folder structure, detected issues, estimated time — before the import runs. This is deliberate: an import is a workspace mutation (many WorldWrite calls in sequence), and the author gets the same chance to back out that they would get for any bulk edit.
Supported sources
Section titled “Supported sources”Markdown folder
Section titled “Markdown folder”Direct import of any folder containing .md files:
- Recursively scan for
.mdfiles - Preserve relative structure
- Preserve frontmatter (pass-through)
- Generate IDs for pages without them
Obsidian vault
Section titled “Obsidian vault”Specialized handling for Obsidian vaults — Obsidian is the primary migration source for Inklings, so this path gets the most polish:
- Convert
[[wiki links]]and[[Page|Display]]aliases to Inklings standard markdown links - Handle
![[embeds]](convert to link or inline, per embed target type) - Map Obsidian frontmatter to Inklings page-type fields where a mapping is known; pass through unknown fields
- Ignore
.obsidian/config folder (themes, plugins, view state — not content)
AnyType export
Section titled “AnyType export”AnyType’s native markdown export has a predictable shape:
- Scan
objects/directory for content; skip metadata directories - Extract object types from YAML frontmatter
- Convert AnyType object links to Inklings links
- Convert frontmatter relations to wiki-links
- Optional AnyType-type → Inklings-page-type mapping at configure time
Notion export (preview)
Section titled “Notion export (preview)”Notion’s markdown and CSV exports have idiosyncrasies:
- Detect via 32-character hex UUID suffixes on filenames; strip suffixes to produce clean slugs
- Convert URL-encoded relative internal links to wiki-links
- Convert
<aside>callout blocks to blockquotes - Convert
<details>/<summary>toggle blocks to bold headings - Parse CSV sidecar files for database property extraction
- Extract inline
Property: Valuelines from database row pages - Optional database-name → Inklings-page-type mapping
Conflict handling
Section titled “Conflict handling”When a source page’s slug collides with existing workspace content, the author picks a conflict strategy at configure time:
| Strategy | Behavior |
|---|---|
skip | The imported page is not submitted. The run records it as skipped. |
rename | The imported page gets a collision-resolved slug (name-1, name-2, …). Both pages coexist. |
overwrite | The imported page submits as a draft that conflicts with the canonical target. The boundary produces a deviation record. |
The overwrite case is the important one. Prior to the submit-boundary rewrite, overwrite silently replaced canonical content. It no longer does. The imported page submits as origin: Imported, lifecycle: Draft, and the collision with existing canonical content produces a deviation record visible in the author’s triage inbox. No canonical content is silently displaced. The author resolves, dismisses, or rolls back from the inbox — same surface every other deviation flows through. See data-flow/import §conflict strategies.
Bulk-canonicalize after import
Section titled “Bulk-canonicalize after import”Because every imported page lands as Draft, a large import does not immediately change what is canonical in the workspace. The import-complete summary offers a bulk-canonicalize affordance so an author importing content they want to endorse can elevate the whole run (or a filtered subset) in one step.
Bulk-canonicalize is an ordinary lifecycle transition set executed through the normal write path:
- One
WorldWriteper page transitioningDraft → Canonical - Respects domain rule 3 (author-controlled transitions)
- Produces retroactive-revision flags on pages whose derivation graph was affected while the imports were Draft — see data-flow/import §bulk-canonicalize
The affordance is scoped: the author can canonicalize the whole run, a folder subtree of the run, or a type-filtered subset (all characters/ pages, all pages with a specific tag). The rest remain Draft and continue to appear in the Drafts surface for review.
Submit-boundary routing
Section titled “Submit-boundary routing”Every imported page is a WorldWrite. The import adapter is the one caller-side responsibility: construct the WorldWrite with the right fields and hand it to the boundary. The boundary does the rest.
The adapter-constructed fields:
| Field | Value |
|---|---|
origin | Imported (fixed by adapter; not caller-controlled) |
lifecycle | Draft (fixed by adapter; elevating is a separate act via bulk-canonicalize) |
origin_source_id | {run_id}/{relative_source_path} — stable across re-imports of the same run |
derivation_sources | Empty for imports (import is not derived from workspace content); may become non-empty on re-import reconciliation — see data-flow/import |
content | The converted page body (frontmatter parsed, links rewritten, slug derived) |
The boundary then validates, applies, produces deviations if the write conflicts with canon, and fires downstream side effects via WriteEffectCoordinator — the event log gets an entry with event_source = 'import' (see event-log-system), the embedding queue receives the new page, sync replicates the write to other devices.
The importer does not call CreatePageUseCase or any other use case directly. It submits WorldWrite values. This uniformity is the point of the submit boundary: the page system does not need to know anything about imports, and the importer does not need to know anything about write-path side effects.
API surface
Section titled “API surface”Tauri commands
Section titled “Tauri commands”// Analyze source for import preview (read-only)analyze_import(path: string): Promise<ImportAnalysis>
// Execute import — produces WorldWrites through the submit boundaryexecute_import(params: ImportParams): Promise<ImportResult>
// Progress polling for large importsget_import_progress(): Promise<ImportProgress>
// Cancel an in-progress import (pages already submitted are retired, not hard-deleted)cancel_import(): Promise<void>
// Elevate a run (or subset) from Draft to Canonicalbulk_canonicalize(params: BulkCanonicalizeParams): Promise<BulkCanonicalizeResult>TypeScript types
Section titled “TypeScript types”interface ImportAnalysis { source_type: 'Markdown' | 'Obsidian' | 'AnyType' | 'Notion'; file_count: number; folder_count: number; issues: ImportIssue[]; preview: FolderPreview;}
interface ImportIssue { type: 'deep_nesting' | 'name_conflict' | 'invalid_frontmatter'; path: string; message: string; suggestion: string;}
interface ImportParams { source_path: string; destination_path?: string; // default: workspace root flatten_strategy: 'preserve' | 'flatten_deep'; conflict_strategy: 'skip' | 'rename' | 'overwrite';}
interface ImportResult { imported_count: number; skipped_count: number; deviation_count: number; // pages whose submission produced a deviation record warnings: string[]; run_id: string; // stable id for the run; input to bulk_canonicalize}
interface ImportProgress { total: number; completed: number; current_file: string; phase: 'analyzing' | 'copying' | 'converting' | 'submitting';}
interface BulkCanonicalizeParams { run_id: string; subset?: | { kind: 'all' } | { kind: 'folder', path: string } | { kind: 'tag', tag: string } | { kind: 'type', page_type: string };}Link conversion rules
Section titled “Link conversion rules”Obsidian / AnyType / Notion → Inklings
Section titled “Obsidian / AnyType / Notion → Inklings”| Source | Inklings |
|---|---|
[[Page]] | [Page](./page.md) |
[[Page|Display]] | [Display](./page.md) |
[[Folder/Page]] | [Page](./folder/page.md) |
![[Embed]] | [Embed](./embed.md) (or inline, per target) |
[[#Heading]] | [Heading](#heading) |
| Notion URL-encoded link | Wiki-link, UUID suffix stripped |
Path resolution
Section titled “Path resolution”Links are resolved relative to the page containing them:
- Same folder:
./sibling.md - Child folder:
./child/page.md - Parent folder:
../sibling.md
Unresolvable links are preserved as plain text with a warning in the run result so the author can decide whether to chase them.
Error handling
Section titled “Error handling”| Error | Handling |
|---|---|
| Unreadable file | Skip; record in warnings |
| Invalid frontmatter | Import as plain markdown; record in warnings |
| Submit rejected | Record in warnings with the domain error; other files in the run continue |
| Circular links | Convert to plain text |
| Disk full | Stop the run; report what was submitted; partial progress preserved (submitted pages remain as Drafts) |
Cancel mid-run does not hard-delete submitted pages. Compensating writes transition them to Retired, preserving the event-log trail (see event-log-system).
Testing strategy
Section titled “Testing strategy”- Unit — link conversion, path resolution, frontmatter parsing
- Application — import analysis, conflict detection, bulk-canonicalize scoping
- Infrastructure — file operations against temp directories
- E2E — full import flow with sample vaults for each supported source
Related
Section titled “Related”- architecture/data-flow/import — write-path mechanics specific to imports
- architecture/data-flow/write-path — the shared write path every caller traverses
- Submit boundary — the domain invariant
- Provenance — origin and lifecycle semantics
- Deviation records — what overwrite-mode conflicts produce
- Retroactive revision — what happens when a Draft that downstream content was derived from is later canonicalized
- Event log system — where import events surface, tagged
event_source = 'import' - ADR-017: Submit Boundary Invariant
- ADR-018: Provenance Model
Was this page helpful?
Thanks for your feedback!