High-level architectural overview and mental models for this app.
- Clarity over Cleverness - Predictable patterns over magic
- AI-Friendly Architecture - Clear patterns that AI agents can follow
- Performance by Design - Patterns that prevent common performance pitfalls
- Security First - Built-in security patterns for file system operations
- Extensible Foundation - Easy to add new features without refactoring
State management follows a three-layer hierarchy:
┌─────────────────────────────────────┐
│ useState │ ← Component UI State
│ ┌─────────────────────────────────┐│
│ │ Zustand ││ ← Global UI State
│ │ ┌─────────────────────────────┐││
│ │ │ TanStack Query │││ ← Persistent Data
│ │ └─────────────────────────────┘││
│ └─────────────────────────────────┘│
└─────────────────────────────────────┘
Decision Tree:
Is this data needed across multiple components?
├─ No → useState
└─ Yes → Does this data persist between app sessions?
├─ No → Zustand
└─ Yes → TanStack Query
See state-management.md for implementation details.
Rust and React communicate through events for loose coupling:
Rust Menu Click → Event Emission → React Listener → Command Execution → State Update
Keyboard Shortcut → Event Handler → Command Execution → State Update
Command Palette → Command Selection → Command Execution → State Update
This ensures the same actions work consistently across all interaction methods.
All user actions flow through a centralized command system:
- Commands are pure objects with
execute()functions - Context provides all state and actions commands need
- Registration merges commands from different domains at runtime
This decouples UI triggers from implementations and enables consistent behavior.
Understanding how patterns work together:
Command System
├── Depends on: State Management (context)
├── Integrates with: Keyboard Shortcuts, Menus
└── Enables: Consistent behavior across UI
State Management
├── Enables: Performance (getState pattern)
├── Supports: Data Persistence, UI State
└── Foundation for: All other systems
Event-Driven Bridge
├── Enables: Rust-React communication
├── Supports: Security (validation in Rust)
└── Foundation for: Menus, Updates, Notifications
| System | Documentation |
|---|---|
| Command System | command-system.md |
| Keyboard Shortcuts | keyboard-shortcuts.md |
| Native Menus | menus.md |
| Quick Panes | quick-panes.md |
| Data Persistence | data-persistence.md |
| Internationalization | i18n-patterns.md |
| Cross-Platform | cross-platform.md |
MainWindow (Top-level orchestrator)
├── TitleBar (Window controls + toolbar)
├── LeftSidebar (Collapsible panel)
├── MainWindowContent (Primary content area)
├── RightSidebar (Collapsible panel)
└── Global Overlays
├── PreferencesDialog (Settings)
├── CommandPalette (Cmd+K)
└── Toaster (Notifications)
locales/ # Translation JSON files
src/
├── components/
│ ├── layout/ # Layout components (MainWindow, sidebars)
│ ├── command-palette/ # Command palette system
│ ├── preferences/ # Preferences dialog system
│ └── ui/ # Shadcn UI components
├── hooks/ # Custom React hooks
├── i18n/ # Internationalization config
├── lib/
│ ├── commands/ # Command system implementation
│ └── menu.ts # Native menu builder with i18n
├── services/ # TanStack Query + Tauri integration
├── store/ # Zustand stores
└── types/ # Shared TypeScript types
Tauri applications can have multiple windows, each running a separate JavaScript context. Windows cannot share React state directly.
Key patterns:
- Separate entry points - Each window has its own HTML file and React root
- Event-based communication - Use Tauri events to communicate between windows
- Window reuse - Create windows once at startup, then show/hide as needed
- Theme synchronization - Emit theme changes so all windows stay in sync
// Window A: emit event
await emit('data-updated', { value: 'new data' })
// Window B: listen and react
listen('data-updated', ({ payload }) => {
setData(payload.value)
})See quick-panes.md for a complete implementation example.
Tauri v2 uses a permission-based capabilities system. Each window only gets the permissions it needs.
Location: src-tauri/capabilities/default.json
{
"identifier": "main-capability",
"windows": ["main"],
"permissions": ["core:window:allow-minimize", "fs:default"]
}Key rules:
- Use specific window labels, not
["*"] - Only add permissions actually needed
- Remote content (if any) should have minimal permissions
CSP prevents XSS attacks. Configuration is in src-tauri/tauri.conf.json.
Rules:
- Never load scripts from CDNs - bundle everything locally
- Avoid
'unsafe-eval'unless absolutely necessary - Images: restrict to specific domains when possible
| Data Type | Storage | Security Level |
|---|---|---|
| API tokens/keys | OS keychain (keyring crate) |
High |
| App preferences | App data directory (JSON) | Medium |
| User content | App data directory/SQLite | Medium |
Never store sensitive tokens in tauri-plugin-store (plain JSON on disk). See external-apis.md for keychain patterns.
All file operations happen in Rust with built-in validation:
fn is_blocked_directory(path: &Path) -> bool {
let blocked_patterns = ["/System/", "/usr/", "/etc/", "/.ssh/"];
blocked_patterns.iter().any(|pattern| path.starts_with(pattern))
}pub fn sanitize_filename(filename: &str) -> String {
filename.chars()
.filter(|c| !['/', '\\', ':', '*', '?', '"', '<', '>', '|'].contains(c))
.collect()
}All disk writes use atomic operations to prevent corruption:
// Write to temp file, then rename (atomic)
std::fs::write(&temp_path, content)?;
std::fs::rename(&temp_path, &final_path)?;See Tauri Security Documentation for detailed guidance.
All Tauri commands use tauri-specta for type safety:
// ✅ GOOD: Type-safe with autocomplete
import { commands } from '@/lib/tauri-bindings'
const result = await commands.loadPreferences()
if (result.status === 'ok') {
console.log(result.data.theme)
}
// ❌ BAD: String-based invoke (no type safety)
const prefs = await invoke<AppPreferences>('load_preferences')See tauri-commands.md for adding new commands.
Before any changes are committed:
npm run check:allSee static-analysis.md for all tools included.
| Anti-Pattern | Why It's Bad | Do This Instead |
|---|---|---|
| State in wrong layer | Confuses ownership, breaks patterns | Follow the onion model |
| Direct Rust-React coupling | Tight coupling, hard to maintain | Use command system and events |
| Store subscription in callbacks | Causes render cascades | Use getState() pattern |
| Skipping input validation | Security vulnerabilities | Always validate in Rust |
| Magic/implicit patterns | Hard for AI and humans to follow | Prefer explicit, clear code |
- Commands - Add to appropriate command group file
- State - Choose appropriate layer (useState/Zustand/TanStack Query)
- UI - Follow component architecture
- Persistence - Use established data-persistence.md patterns
- Testing - Add tests following testing.md patterns
- Documentation - Update relevant docs