Technical architecture and design decisions for Mindwtr.
Mindwtr is a cross-platform GTD application with:
Shared core — TypeScript business logic package
┌─────────────────────────────────────────────────────────┐
│ User Interface │
├─────────────────────────────┬───────────────────────────┤
│ Desktop (Tauri) │ Mobile (Expo) │
│ React + Vite + Tailwind │ React Native + NativeWind│
├─────────────────────────────┴───────────────────────────┤
│ @mindwtr/core │
│ Zustand Store · Types · i18n · Utils · Sync │
├─────────────────────────────┬───────────────────────────┤
│ Tauri FS (Rust) │ SQLite + JSON backup │
│ SQLite + JSON backup │ App storage │
└──────────────┬──────────────┴───────────────────────────┘
│
┌──────────────▼──────────────┐
│ Cloud / Sync │
│ WebDAV / Local / Server │
└─────────────────────────────┘
flowchart LR
Desktop["Desktop App<br/>Tauri + React"] --> Core["@mindwtr/core"]
Mobile["Mobile App<br/>Expo + RN"] --> Core
Core --> LocalDB[("SQLite")]
Core --> JSON[("data.json")]
Core --> Sync["Sync Backends"]
Sync --> WebDAV["WebDAV"]
Sync --> File["File Sync"]
Sync --> Cloud["Self-hosted Cloud"]
The project uses a monorepo with Bun workspaces:
Mindwtr/
├── apps/
│ ├── cloud/ # Sync server (Bun)
│ ├── desktop/ # Tauri app
│ └── mobile/ # Expo app
├── packages/
│ └── core/ # Shared business logic
└── package.json # Workspace root
@mindwtr/core)The core package contains all shared business logic:
| Module | Purpose |
|---|---|
store.ts |
Zustand state store with all actions |
types.ts |
TypeScript interfaces (Task, Project, etc.) |
i18n.ts |
Translation strings and loading |
contexts.ts |
Preset contexts and tags |
quick-add.ts |
Natural language task parser |
recurrence.ts |
Recurring task logic (RFC 5545 partial) |
sync.ts |
Data merge utilities (LWW + Tombstones) |
date.ts |
Safe date parsing utilities |
ai/ |
AI integration (Gemini/OpenAI/Anthropic) |
sqlite-adapter.ts |
Local storage adapter interface |
webdav.ts |
WebDAV sync client |
all tasks/projects).| Feature | Tauri | Electron |
|---|---|---|
| Binary size | ~5 MB | ~150 MB |
| Memory usage | ~50 MB | ~300 MB |
| Backend | Rust | Node.js |
| Webview | System | Bundled Chromium |
apps/desktop/
├── src/ # React frontend
│ ├── App.tsx # Root component
│ ├── components/ # UI components
│ │ ├── Layout.tsx # Sidebar + content
│ │ ├── TaskItem.tsx # Task component
│ │ └── views/ # View components
│ ├── contexts/ # React contexts
│ ├── store/ # UI-specific state (filters, focus mode)
│ └── lib/ # Utilities
│
├── src-tauri/ # Rust backend
│ ├── src/main.rs # Entry point
│ ├── Cargo.toml # Rust dependencies
│ └── tauri.conf.json # Tauri config
│
└── package.json
User Action → React Component → Zustand Store (@mindwtr/core) → Storage Adapter → SQLite + data.json
The Rust backend exposes commands for:
apps/mobile/
├── app/ # Expo Router pages
│ ├── (drawer)/ # Drawer navigation
│ │ ├── (tabs)/ # Tab navigation
│ │ │ ├── inbox.tsx
│ │ │ ├── next.tsx
│ │ │ └── ...
│ │ ├── projects-screen.tsx
│ │ └── settings.tsx
│ └── _layout.tsx # Root layout
│
├── components/ # Shared components
├── contexts/ # Theme, Language
├── lib/ # Storage, sync utilities
└── package.json
Drawer/Stack Layout
├── Tab Navigator
│ ├── Inbox
│ ├── Agenda
│ ├── Next Actions
│ ├── Projects
│ └── Menu (links to other views)
├── Other Screens (Stack)
│ ├── Board
│ ├── Calendar
│ ├── Review
│ ├── Contexts
│ ├── Waiting For
│ ├── Someday/Maybe
│ ├── Archived
│ └── Settings
The central store (@mindwtr/core/src/store.ts) manages all application state:
interface TaskStore {
tasks: Task[];
projects: Project[];
areas: Area[];
settings: AppData['settings'];
// Actions
fetchData: () => Promise<void>;
addTask: (title: string, props?: Partial<Task>) => Promise<void>;
updateTask: (id: string, updates: Partial<Task>) => Promise<void>;
deleteTask: (id: string) => Promise<void>;
// ... projects, areas, and settings actions
}
The store uses injected storage adapters:
// Desktop: Tauri file system
setStorageAdapter(tauriStorage);
// Mobile: SQLite (with JSON backup fallback)
setStorageAdapter(mobileStorage);
deletedAt for syncinterface Task {
id: string;
title: string;
status: TaskStatus; // inbox | next | waiting | someday | done | archived
priority?: TaskPriority; // low | medium | high | urgent
startTime?: string;
dueDate?: string;
recurrence?: Recurrence | RecurrenceRule;
tags: string[];
contexts: string[];
checklist?: ChecklistItem[];
description?: string;
attachments?: Attachment[];
location?: string;
projectId?: string;
isFocusedToday?: boolean;
timeEstimate?: TimeEstimate;
reviewAt?: string; // Tickler/review date
completedAt?: string;
createdAt: string;
updatedAt: string;
deletedAt?: string; // Tombstone for sync
pushCount?: number;
orderNum?: number;
}
interface Project {
id: string;
title: string;
status: 'active' | 'someday' | 'waiting' | 'archived';
color: string;
areaId?: string;
tagIds: string[];
isSequential?: boolean;
isFocused?: boolean;
supportNotes?: string;
attachments?: Attachment[];
reviewAt?: string;
createdAt: string;
updatedAt: string;
deletedAt?: string;
}
interface Area {
id: string;
name: string;
color?: string;
icon?: string;
order: number;
createdAt?: string;
updatedAt?: string;
}
interface Attachment {
id: string;
kind: 'file' | 'link';
title: string;
uri: string;
createdAt: string;
updatedAt: string;
deletedAt?: string;
}
Data synchronization relies on merging local and remote datasets based on timestamps (updatedAt).
updatedAt.deletedAt set.1. Read Local Data
2. Read Remote Data (Cloud/WebDAV/File)
3. Merge (Memory) -> Generate Stats (conflicts, updates)
4. Write Local (if changed)
5. Write Remote (if changed)
Translations are in packages/core/src/i18n.ts and i18n-translations.ts:
export const translations: Record<Language, Record<string, string>> = {
en: { 'nav.inbox': 'Inbox', ... },
zh: { 'nav.inbox': '收集箱', ... },
// ... other languages
};
Each app has a language context that provides a t() function.
Wiki: Contributing
Wiki: Core API
Wiki: Developer Guide
Wiki: Home
Wiki: Performance Guide
Wiki: Testing Strategy
Wiki: _Sidebar