Skip to main content
Inkdown is architected from the ground up to be truly cross-platform, capable of running on Desktop (via Tauri), Web browsers, and React Native mobile applications with a single shared codebase for business logic and plugins.

Core Principles

The cross-platform architecture is built on three fundamental principles:

Platform-Agnostic Core

All business logic and plugin APIs are implemented without platform-specific dependencies

Bridge Pattern

Platform-specific functionality is accessed through abstract interfaces

Dependency Injection

Platform implementations are registered at runtime

The Bridge Pattern

Inkdown uses the Bridge Pattern to decouple platform-agnostic core logic from platform-specific implementations. This allows the same code to run on different platforms by swapping out the implementation at runtime.

How Bridges Work

The bridge acts as a singleton that holds a reference to the platform-specific implementation. Core code calls methods on the bridge, which delegates to the registered implementation.

Native Bridge

The NativeBridge provides access to platform-specific native capabilities:

Bridge Architecture

NativeBridge.ts
import { native } from '@inkdown/core';

// Platform implementations register themselves at startup
native.registerModule('fs', new TauriFileSystem());
native.registerModule('dialog', new TauriDialog());
native.registerModule('clipboard', new TauriClipboard());

// Core code uses the bridge without knowing the platform
const content = await native.fs.readFile('/path/to/file.md');

Available Modules

Platform-agnostic file system operations:
// Read file
const content = await native.fs.readFile(path);

// Write file
await native.fs.writeFile(path, content);

// List directory
const entries = await native.fs.listDirectory(path);

// Watch directory
const unwatch = await native.fs.watchDirectory(path, (events) => {
    console.log('File system events:', events);
});
Desktop (Tauri): Direct OS file system access Web: IndexedDB virtual file system Mobile: React Native File System API
Native dialog prompts:
// Save dialog
const result = await native.dialog?.showSaveDialog({
    defaultPath: 'export.md',
    filters: [{ name: 'Markdown', extensions: ['md'] }]
});

// Open dialog
const files = await native.dialog?.showOpenDialog({
    multiple: true,
    filters: [{ name: 'Markdown', extensions: ['md'] }]
});
Desktop (Tauri): Native OS dialogs Web: Browser file picker API Mobile: React Native document picker
System clipboard access:
// Write to clipboard
await native.clipboard?.writeText('Hello, World!');

// Read from clipboard
const text = await native.clipboard?.readText();
Desktop (Tauri): System clipboard Web: Clipboard API Mobile: React Native Clipboard
Platform information and capabilities:
// Get platform info
const info = native.platform.info;
console.log(info.type);      // 'desktop' | 'web' | 'mobile'
console.log(info.os);        // 'windows' | 'macos' | 'linux' | 'ios' | 'android'
console.log(info.version);   // OS version

// Window controls (desktop only)
await native.platform.window?.minimize();
await native.platform.window?.maximize();
await native.platform.window?.close();
File export capabilities:
// Export to PDF
await native.export.toPDF(content, options);

// Export to HTML
await native.export.toHTML(content, options);
Configuration storage:
// Load config
const config = await native.config.load('app');

// Save config
await native.config.save('app', config);
Desktop (Tauri): JSON files in config directory Web: LocalStorage Mobile: AsyncStorage
System font access:
// List system fonts
const fonts = await native.font?.listSystemFonts();
Desktop (Tauri): Native font enumeration Web: CSS font API Mobile: Limited/fallback fonts

Feature Detection

Not all platforms support all features. Use feature detection:
// Check if a module is available
if (native.supportsModule('dialog')) {
    await native.dialog?.showSaveDialog(options);
} else {
    // Fallback behavior
    showCustomDialog(options);
}

// Check if a platform feature is supported
if (native.supports('nativeDialog')) {
    // Use native dialog
} else if (native.supports('fileSystemAccess')) {
    // Use browser file system API
} else {
    // Use fallback
}
Always check for optional modules and features before using them to ensure graceful degradation across platforms.

Storage Bridge

The StorageBridge provides platform-agnostic storage for caching and persistence:
StorageBridge.ts
import { StorageBridge } from '@inkdown/core';

const storage = StorageBridge.getInstance();

// Key-Value storage
await storage.kv.set('key', 'value');
const value = await storage.kv.get('key');
await storage.kv.delete('key');

// Document storage (for complex objects)
await storage.doc.create('tabs', { id: '1', filePath: 'note.md' });
const tab = await storage.doc.read('tabs', '1');
await storage.doc.update('tabs', '1', { filePath: 'updated.md' });
await storage.doc.delete('tabs', '1');

Storage Implementations

import { IndexedDBStorage } from '@inkdown/storage-tauri';

// Uses IndexedDB for structured storage
StorageBridge.getInstance().registerStorageProviders(
    new IndexedDBKVStorage(),
    new IndexedDBDocumentStorage()
);

UI Bridge

The UIBridge abstracts UI rendering for cross-platform compatibility:
import { UIBridge } from '@inkdown/core';

// Show modal (works on Desktop, Web, Mobile)
UIBridge.showModal({
    title: 'Settings',
    component: SettingsModal,
    props: { settings }
});

// Show notice
UIBridge.showNotice('File saved!', 3000);

// Create setting UI
const setting = UIBridge.createSetting()
    .setName('Enable feature')
    .setDesc('Toggle this feature')
    .addToggle(toggle => {
        toggle.setValue(enabled);
        toggle.onChange(value => { /* ... */ });
    });
Plugins should use UIBridge or high-level abstractions (Modal, Setting, Notice) instead of direct DOM manipulation to ensure mobile compatibility.

Platform Initialization

Each platform registers its implementations at startup:
import { App } from '@inkdown/core';
import { native } from '@inkdown/core';
import { StorageBridge } from '@inkdown/core';
import {
    TauriFileSystem,
    TauriDialog,
    TauriClipboard,
    TauriPlatform,
    TauriConfig,
    TauriExport
} from '@inkdown/native-tauri';
import {
    IndexedDBKVStorage,
    IndexedDBDocumentStorage
} from '@inkdown/storage-tauri';

// Register native modules
native.registerAll({
    fs: new TauriFileSystem(),
    dialog: new TauriDialog(),
    clipboard: new TauriClipboard(),
    platform: new TauriPlatform(),
    config: new TauriConfig(),
    export: new TauriExport()
});

// Register storage
StorageBridge.getInstance().registerStorageProviders(
    new IndexedDBKVStorage(),
    new IndexedDBDocumentStorage()
);

// Create and initialize app
const app = new App(builtInPlugins);
await app.init();

Plugin Compatibility

The bridge pattern ensures plugins remain platform-agnostic:
MyPlugin.ts
import { Plugin, native } from '@inkdown/core';

export default class MyPlugin extends Plugin {
    async onload() {
        this.addCommand({
            id: 'export-note',
            name: 'Export Note',
            callback: async () => {
                const content = this.getActiveContent();
                
                // This works on ALL platforms!
                if (native.dialog) {
                    const result = await native.dialog.showSaveDialog({
                        defaultPath: 'note.md'
                    });
                    
                    if (result.filePath) {
                        await native.fs.writeFile(result.filePath, content);
                        this.showNotice('Note exported!');
                    }
                } else {
                    // Fallback for platforms without dialog
                    await this.downloadFile('note.md', content);
                }
            }
        });
    }
}
This plugin works unchanged on Desktop (Tauri), Web, and Mobile because it uses the bridge APIs instead of platform-specific code.

Benefits of Bridge Pattern

Single Codebase

Write business logic and plugins once, run everywhere

Type Safety

TypeScript interfaces ensure compile-time checking

Testability

Easy to mock implementations for unit testing

Flexibility

Add new platforms by implementing interfaces

Performance

Zero runtime overhead - direct function calls

Maintainability

Clear separation between core and platform code

Adding a New Platform

To add support for a new platform:
1

Create Platform Package

Create a new package like @inkdown/native-[platform]:
mkdir packages/native-myplatform
cd packages/native-myplatform
npm init
2

Implement Interfaces

Implement all required native interfaces:
import type { IFileSystem } from '@inkdown/core';

export class MyPlatformFileSystem implements IFileSystem {
    async readFile(path: string): Promise<string> {
        // Platform-specific implementation
    }
    
    async writeFile(path: string, content: string): Promise<void> {
        // Platform-specific implementation
    }
    
    // ... implement other methods
}
3

Create App Entry Point

Create a platform-specific app that registers implementations:
import { App, native, StorageBridge } from '@inkdown/core';
import { MyPlatformFileSystem } from './MyPlatformFileSystem';

native.registerAll({
    fs: new MyPlatformFileSystem(),
    // ... other modules
});

const app = new App(builtInPlugins);
await app.init();
4

Test and Deploy

Test that existing plugins work without modification:
// All existing plugins should work immediately
const content = await native.fs.readFile('test.md');

Best Practices

Never use platform-specific APIs directly in core or plugin code:
// ❌ Bad - platform-specific
import { readFile } from '@tauri-apps/api/fs';
const content = await readFile('note.md');

// ✅ Good - platform-agnostic
import { native } from '@inkdown/core';
const content = await native.fs.readFile('note.md');
Use feature detection for optional capabilities:
if (native.supports('nativeDialog')) {
    await native.dialog?.showSaveDialog(options);
} else {
    // Provide fallback
}
Design UX to work well on all platforms:
// Desktop: Use native dialogs
// Web: Use browser download
// Mobile: Use share sheet
if (native.platform.info.type === 'mobile') {
    await this.shareFile(content);
} else {
    await this.saveFile(content);
}
Ensure your code works on all target platforms:
  • Test on Desktop (Tauri)
  • Test in Web browsers
  • Test on iOS and Android (if applicable)
  • Use feature detection tests

Architecture

Learn about Inkdown’s overall architecture

Plugin System

Build cross-platform plugins

Native API Reference

Complete native bridge API documentation

Build a Plugin

Create your first cross-platform plugin