Skip to content
Documentation GitHub
Reference

Tauri Commands

Codebase: apps/desktop/src-tauri/src/commands/ Shared Error Types: crates/commands/ State: apps/desktop/src-tauri/src/state.rs


Diagram


Overview

Tauri commands are the IPC bridge between the React frontend and the Rust backend. Each command is a thin adapter that validates input at the system boundary, constructs the appropriate use case, executes it, and returns a typed result. Commands contain no business logic — they delegate everything to the application layer.

Command Organization

Commands are organized in src/commands/mod.rs by domain area:

ModulePurposeKey Commands
workspaceWorkspace CRUD and initializationinitialize_workspace, open_workspace, create_workspace
pagePage CRUD, block content, tree operationscreate_page, get_page, save_block_content, list_page_tree
attachmentFile attachments (upload, retrieve, delete)upload_attachment, get_attachment_file, delete_attachment
tagTag and tag group managementcreate_tag, set_page_tags, merge_tags
type_defType definitions + type-property linkingcreate_type, add_property_to_type
propertyProperty definition CRUDcreate_property, update_property
property_valueGet/set property values on pagesget_property_values, set_property_value
layoutLayout definition CRUD + page layout opscreate_layout, apply_layout, assign_block_area
collection_viewBrowse pages filtered by typeget_collection_view
container_ruleAutomatic type assignment rulescreate_container_rule
filtered_treeSidebar tree filtered by typeget_filtered_tree
event_logEvent log and timeline queriesquery_page_events, query_page_timeline, query_timeline
bookmarkNamed history bookmarkscreate_bookmark, list_bookmarks, delete_bookmark
searchFull-text and semantic searchsearch_pages
importExternal markdown import (Obsidian, etc.)import_markdown
settingsApplication settingsget_settings, update_setting
recent_workspacesRecent workspace listlist_recent_workspaces, add_to_recent_workspaces
mcpMCP server controlget_mcp_status, set_mcp_enabled, regenerate_mcp_token
syncSync engine lifecyclestart_sync, stop_sync, get_workspace_sync_status
embeddingEmbedding pipeline statusget_embedding_status
templateTemplate catalog browsinglist_templates, apply_template
agentAgent harness lifecyclestart_agent, stop_agent, get_agent_status
ollamaOllama local model managementcheck_ollama_health, list_ollama_models, pull_ollama_model
openrouterOpenRouter OAuth + model accessstart_openrouter_auth, list_openrouter_models
presenceUser/agent presence trackingget_presence, update_presence
proactiveProactive assistant suggestionsget_proactive_suggestions, dismiss_suggestion
task_runnerBackground task statusget_task_runner_status
authAuthentication (login, logout, session)login, logout, get_session
memoryAgent memory CRUDsave_memory, get_memories, delete_memory, search_memories
skillAgent skill managementlist_skills, get_skill, execute_skill
model_downloadEmbedding model download controlstart_model_download, get_download_status, cancel_download
loggingFrontend log forwardinglog_frontend_event

The Adapter Pattern

Every command follows the same structure:

#[tauri::command]
#[specta::specta]
pub fn create_page(
state: State<AppState>, // 1. Inject shared state
title: String, // 2. Accept frontend parameters
parent_slug: Option<String>,
) -> Result<CreatePageResult, CommandError> {
let _span = command_span("create_page").entered(); // 3. Tracing span
// 4. Validate at system boundary
validate_title(&title)?;
if let Some(ref parent) = parent_slug {
validate_slug_input(parent, "parent slug")?;
}
// 5. Get workspace (most commands need this)
let workspace = state.require_workspace()?;
// 6. Resolve permissions
let guard = state.resolve_owner_guard()?;
// 7. Construct and execute use case
let use_case = CreatePageUseCase::new(state.page_repo());
let response = use_case
.execute(&guard, &workspace.path, CreatePageRequest { title, parent_slug, content: None })
.map_err(|e| sanitize_error(e, "create_page"))?;
// 8. Trigger side effects (embedding, sync queue)
WriteEffectCoordinator::on_page_created(&state);
// 9. Return typed result
Ok(CreatePageResult { slug: response.slug, ... })
}

Key characteristics:

  • #[tauri::command] — Makes the function callable from the frontend via invoke()
  • #[specta::specta] — Generates TypeScript types for parameters and return values
  • No business logic — Commands never make decisions about data; they only wire inputs to use cases
  • Validation at boundary — Input validation (validate_title, validate_slug_input, validate_uuid) happens before entering the use case layer
  • Error sanitization — All errors pass through sanitize_error() or sanitize_validation_error() to strip technical details before reaching the frontend

State Injection

Commands receive AppState via Tauri’s dependency injection (State<AppState>). The state struct at src/state.rs is the composition root — it holds all repositories, use cases, and lifecycle managers:

Repository Access

Repositories are accessed via helper methods that clone stateless unit structs:

let use_case = CreatePageUseCase::new(state.page_repo());
let use_case = CreateTagUseCase::new(state.tag_repo());

Most SQLite repositories are stateless unit structs (they open the per-workspace database on demand using the workspace path). Arc-wrapped repositories (e.g., page_repository) are shared when multiple components need the same instance.

Workspace Resolution

Most commands need the active workspace. Two convenience methods:

// Returns Workspace or CommandError::NoWorkspace
let workspace = state.require_workspace()?;
// Returns PermissionGuard or CommandError::Permission
let guard = state.resolve_owner_guard()?;

Lifecycle Managers

State also holds lifecycle managers for background services:

ManagerPurpose
embedding_managerBackground embedding pipeline
sync_managerCloud sync engine
mcp_serverMCP server handle
analytics_managerBackground analytics flush

Error Handling

Shared Error Types (crates/commands/)

The commands crate provides transport-agnostic error types shared between Tauri IPC and the HTTP bridge (MCP server):

#[serde(tag = "type", content = "data")]
pub enum CommandError {
NotFound { entity: String, id: String },
Validation { message: String },
Internal { message: String },
NoWorkspace,
Permission { message: String },
Unauthenticated { message: String },
}

This serializes to { type: "Validation", data: { message: "..." } } on the frontend. The parseCommandError() utility in lib/errors.ts handles all variants.

Error Sanitization

The UserFacingError trait and sanitize_error() / sanitize_validation_error() functions ensure that technical details (file paths, SQL errors, Rust stack traces) never reach the frontend:

  • Validation errors (user-correctable) map to CommandError::Validation
  • System errors (infrastructure failures) map to CommandError::Internal with a generic message
  • Expected errors (NotFound, AlreadyExists) are logged at warn! level
  • Unexpected errors (IO, database) are logged at error! level with full technical details

Validation Functions

The commands crate provides boundary validation:

FunctionPurpose
validate_title()Non-empty, max length, no control characters
validate_slug_input()Valid slug format, no path traversal
validate_uuid()Valid UUID v4 format
validate_content_size()Content within size limits

Type Generation

Commands annotated with #[specta::specta] automatically generate TypeScript types in packages/contracts/generated/. Run pnpm generate:types after modifying command signatures or return types.

The generated types use snake_case (Specta preserves Rust naming conventions), so the frontend accesses fields as result.storage_path, not result.storagePath.

Adding a New Command

  1. Create the use case in crates/application/src/{domain}/
  2. Add the command function in src/commands/{module}.rs
  3. Annotate with #[tauri::command] and #[specta::specta]
  4. Register in main.rs (add to the invoke_handler macro)
  5. Run pnpm generate:types to update TypeScript bindings
  6. Call from the frontend via invoke<ReturnType>("command_name", { params })

Was this page helpful?