Development Setup and Guide
This comprehensive guide covers everything you need to know about developing Mimir, from initial setup to advanced development workflows.
Quick Links
Section titled “Quick Links”- Developer Documentation Home - Overview of developer docs
- Contributing Guide - How to contribute to the project
- Architecture - Technical architecture overview
- GitHub Repository
Table of Contents
Section titled “Table of Contents”- Environment Setup
- Building and Running
- Project Architecture
- Development Workflows
- Testing Strategy
- Debugging Techniques
- Database Management
- Frontend Development
- Tauri-Specific Considerations
- Common Tasks
Environment Setup
Section titled “Environment Setup”System Requirements
Section titled “System Requirements”- macOS: 10.15 (Catalina) or later
- Windows: Windows 10 or later
- Linux: Any modern distribution with glibc 2.31+
Required Tools
Section titled “Required Tools”Rust Toolchain
Section titled “Rust Toolchain”# Install rustup (Rust version manager)curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Verify installationrustc --versioncargo --versionNode.js and npm
Section titled “Node.js and npm”# macOS (via Homebrew)brew install node
# Windows (via installer)# Download from https://nodejs.org
# Linux (via package manager)curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -sudo apt-get install -y nodejs
# Verify installationnode --version # Should be v18 or highernpm --versionPlatform-Specific Dependencies
Section titled “Platform-Specific Dependencies”macOS Setup
Section titled “macOS Setup”# Install Xcode Command Line Toolsxcode-select --install
# Install Homebrew (if not already installed)/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Optional: Install additional development toolsbrew install git ghLinux Setup (Ubuntu/Debian)
Section titled “Linux Setup (Ubuntu/Debian)”# Update package listssudo apt-get update
# Install required dependenciessudo apt-get install -y \ libwebkit2gtk-4.1-dev \ build-essential \ curl \ wget \ file \ libssl-dev \ libgtk-3-dev \ libayatana-appindicator3-dev \ librsvg2-dev \ patchelf
# Optional: Install git and ghsudo apt-get install -y git ghLinux Setup (Fedora)
Section titled “Linux Setup (Fedora)”sudo dnf install \ webkit2gtk4.1-devel \ openssl-devel \ curl \ wget \ file \ libappindicator-gtk3-devel \ librsvg2-devel \ patchelf
sudo dnf groupinstall "C Development Tools and Libraries"Windows Setup
Section titled “Windows Setup”- Install Visual Studio Build Tools
- Install Git for Windows
- Install Node.js
- Install Rust via rustup-init.exe
Angreal (Recommended Workflow)
Section titled “Angreal (Recommended Workflow)”Angreal is the project’s task runner and the recommended way to run, test, and document Mimir. It requires Python and pip:
pip install 'angreal>=2'Key tasks (run from the project root):
angreal dev launch # Start Vite dev server + Tauri app (builds sidecar if missing)angreal dev reset # Delete the dev database and dev assets (never touches production)angreal test unit # Run Rust tests for core crates (excludes the Tauri app crate)angreal test coverage # Coverage via cargo-tarpaulin (config: tarpaulin.toml)angreal docs serve # Serve the docs site locally with live reloadThe full angreal docs task list: build, serve, clean (see .angreal/task_docs.py). The docs site is Astro Starlight in docs/; it needs Node 22 or later.
Optional Development Tools
Section titled “Optional Development Tools”# Tauri CLI (faster startup for dev mode)cargo install tauri-cli
# Diesel CLI (database migrations)cargo install diesel_cli --no-default-features --features sqlite
# GitHub CLI (for PR management)brew install gh # macOS# or download from https://cli.github.com/IDE Setup
Section titled “IDE Setup”VS Code (Recommended)
Section titled “VS Code (Recommended)”Install these extensions:
- rust-analyzer - Rust language support
- Tauri - Tauri development tools
- Volar - Vue 3 language support
- ESLint - JavaScript/TypeScript linting
- Error Lens - Inline error messages
Settings:
{ "rust-analyzer.checkOnSave.command": "clippy", "editor.formatOnSave": true, "[rust]": { "editor.defaultFormatter": "rust-lang.rust-analyzer" }, "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[vue]": { "editor.defaultFormatter": "Vue.volar" }}IntelliJ IDEA / CLion
Section titled “IntelliJ IDEA / CLion”- Install Rust plugin
- Install Vue.js plugin
- Enable Tauri support
Building and Running
Section titled “Building and Running”Initial Build
Section titled “Initial Build”# Clone repositorygit clone https://github.com/mimir-dm/mimir.gitcd mimir
# Build the MCP sidecar FIRST — tauri.conf.json declares it as externalBin,# so building the mimir crate fails if the binary is missingbash scripts/build-sidecar.sh # on Windows: .\scripts\build-sidecar.ps1
# Build Rust workspacecargo build
# Install frontend dependenciescd crates/mimir/frontendnpm installcd ../../..Development Mode
Section titled “Development Mode”The recommended way to run in development mode is via the angreal task:
angreal dev launchThis handles the Vite dev server and Tauri app startup correctly.
Manual Development Mode
Section titled “Manual Development Mode”Known issue:
cargo tauri devhas a bug where it runsnpm run devfromcrates/instead ofcrates/mimir/frontend, causing the Vite dev server to fail. Use the workaround below.
# Terminal 1: Start Vite dev server manuallycd crates/mimir/frontendnpm run dev
# Terminal 2: Run Rust backend (without Tauri's frontend handling)cargo run -p mimir --no-default-featuresSidecar Binary
Section titled “Sidecar Binary”The MCP sidecar binary must exist before building the mimir crate (required by externalBin in tauri.conf.json):
# Build the sidecar (on Windows: .\scripts\build-sidecar.ps1)./scripts/build-sidecar.sh
# Output: crates/mimir/binaries/mimir-mcp-{target-triple}Build performance: the image-processing dependencies (
image,png,fdeflate,zune-jpeg) are pinned toopt-level = 3in the dev profile (workspaceCargo.toml) because their pixel loops are 10-50x slower unoptimized, which would make map extraction unusable in debug builds.
Production Build
Section titled “Production Build”# Build optimized releasecd crates/mimircargo tauri build
# Output locations:# macOS: target/release/bundle/dmg/# Windows: target/release/bundle/msi/# Linux: target/release/bundle/deb/ and target/release/bundle/appimage/Project Architecture
Section titled “Project Architecture”Workspace Structure
Section titled “Workspace Structure”mimir/├── Cargo.toml # Workspace configuration├── crates/│ ├── mimir/ # Main application│ ├── mimir-core/ # Business logic│ ├── mimir-mcp/ # MCP server for Claude Code│ ├── mimir-mapgen/ # Procedural map generation│ └── mimir-print/ # PDF generation├── docs/ # Documentation├── data/ # D&D reference data└── .metis/ # Project managementArchitecture Layers
Section titled “Architecture Layers”Frontend Layer (Vue 3 + TypeScript)
- Views: Page-level components
- Components: Reusable UI components
- Stores: Pinia state management
- Services: API wrappers for Tauri commands
Application Layer (Tauri + Rust)
- Commands: IPC handlers
- Services: Application-level business logic
- State: Application state management
Domain Layer (mimir-core)
- Models: Domain entities
- Services: Business logic
- DAL: Data access layer
- Migrations: Database schema
Infrastructure Layer
- Database: SQLite with Diesel ORM
- File System: Campaign data storage
Data Flow
Section titled “Data Flow”User Interaction ↓Vue Component ↓Pinia Store / API Service ↓Tauri Command (IPC) ↓Command Handler ↓Core Service ↓Repository (DAL) ↓DatabaseDevelopment Workflows
Section titled “Development Workflows”Feature Development
Section titled “Feature Development”- Create Feature Branch
git checkout -b feature/my-feature- Implement Feature
- Add backend logic in
mimir-core - Add Tauri command in
mimir/src/commands - Add frontend UI in
mimir/frontend/src
- Test Locally
angreal test unitcd crates/mimir/frontend && npm test- Commit and Push
git add .git commit -m "Add feature description"git push origin feature/my-featureBug Fix Workflow
Section titled “Bug Fix Workflow”- Reproduce the Bug
- Create a failing test
- Document reproduction steps
- Fix the Issue
- Implement fix
- Verify test passes
- Test Edge Cases
- Add additional tests
- Manual testing
- Submit PR
Adding a New Page
Section titled “Adding a New Page”- Create Vue Component
# In crates/mimir/frontend/src/views/touch MyNewView.vue- Add Route
// In crates/mimir/frontend/src/app/router/index.ts{ path: '/my-new-page', name: 'MyNewPage', component: () => import('@/views/MyNewView.vue')}- Add Navigation
<!-- In layout component --><router-link to="/my-new-page">My New Page</router-link>Adding a New Tauri Command
Section titled “Adding a New Tauri Command”- Create Command Function
Commands take State<'_, AppState> and open an on-demand connection with state.connect(), returning an ApiResponse (mirroring crates/mimir/src/commands/campaign.rs):
// In crates/mimir/src/commands/my_commands.rsuse tauri::State;
use crate::state::AppState;use super::{to_api_response, ApiResponse};
#[tauri::command]pub fn my_new_command(state: State<'_, AppState>, param: String) -> ApiResponse<String> { let mut db = match state.connect() { Ok(db) => db, Err(e) => return ApiResponse::err(e), };
// Business logic here, e.g. via a mimir-core service: // let result = CampaignService::new(&mut db).list(false); // to_api_response(result) ApiResponse::ok(format!("Processed: {}", param))}- Register Command
// In crates/mimir/src/main.rs.invoke_handler(tauri::generate_handler![ commands::my_new_command, // ... other commands])- Call from Frontend
// In frontend serviceimport { invoke } from '@tauri-apps/api/core';
export async function myNewCommand(param: string): Promise<string> { return await invoke<string>('my_new_command', { param });}Testing Strategy
Section titled “Testing Strategy”Rust Testing
Section titled “Rust Testing”Unit Tests
Section titled “Unit Tests”// In same file as implementation#[cfg(test)]mod tests { use super::*;
#[test] fn test_feature() { let result = my_function(); assert_eq!(result, expected); }}Integration Tests
Section titled “Integration Tests”Integration tests live in each crate’s tests/ directory. The real targets are:
mimir-core:catalog_import,srd_smoke_testmimir-mapgen:cli_integration,format_roundtrip,polygon_snapshots
SRD test fixtures are generated by scripts/extract-srd-fixtures.py (SRD/OGL-safe content only) into crates/mimir/frontend/__tests__/fixtures and crates/mimir-core/tests/fixtures.
// In crates/mimir-core/tests/catalog_import.rs (for example)use mimir_core::*;
#[test]fn test_database_workflow() { let mut conn = create_connection(":memory:").unwrap(); run_migrations(&mut conn).unwrap();
// Test full workflow}Running Rust Tests
Section titled “Running Rust Tests”The recommended entry point is angreal:
# Rust tests for core crates (recommended)angreal test unit
# With coverage (cargo-tarpaulin, config in tarpaulin.toml)angreal test coverageangreal test unit runs the real underlying command:
cargo test --workspace --exclude mimir -- --test-threads=1The mimir (Tauri) crate is excluded because it cannot build without the sidecar binary, and --test-threads=1 is required to avoid SQLite locking issues. Avoid bare cargo test --workspace.
Targeted runs:
# Specific cratecargo test -p mimir-core -- --test-threads=1
# Specific integration test targetcargo test -p mimir-core --test catalog_import -- --test-threads=1cargo test -p mimir-mapgen --test cli_integration
# Specific test by namecargo test -p mimir-core test_campaign_creation -- --test-threads=1
# With outputcargo test -p mimir-core -- --test-threads=1 --nocaptureFrontend Testing
Section titled “Frontend Testing”Component Tests
Section titled “Component Tests”// In crates/mimir/frontend/src/components/__tests__/import { describe, it, expect } from 'vitest';import { mount } from '@vue/test-utils';import MyComponent from '../MyComponent.vue';
describe('MyComponent', () => { it('renders correctly', () => { const wrapper = mount(MyComponent, { props: { title: 'Test' } }); expect(wrapper.text()).toContain('Test'); });});Running Frontend Tests
Section titled “Running Frontend Tests”cd crates/mimir/frontend
# Run testsnpm test
# Watch modenpm test -- --watch
# Coveragenpm run test:coverage
# UI modenpm run test:uiDebugging Techniques
Section titled “Debugging Techniques”Rust Debugging
Section titled “Rust Debugging”Print Debugging
Section titled “Print Debugging”use tracing::{info, debug, warn, error};
debug!("Variable value: {:?}", my_var);info!("Operation started");warn!("Unusual condition: {}", condition);error!("Operation failed: {}", err);VS Code Debugging
Section titled “VS Code Debugging”- Install CodeLLDB extension
- Add
.vscode/launch.json:
{ "version": "0.2.0", "configurations": [ { "type": "lldb", "request": "launch", "name": "Debug Tauri", "cargo": { "args": ["build", "--manifest-path=crates/mimir/Cargo.toml"] }, "cwd": "${workspaceFolder}" } ]}Frontend Debugging
Section titled “Frontend Debugging”Browser DevTools
Section titled “Browser DevTools”- Right-click in app → “Inspect Element”
- Console tab for logs
- Network tab for Tauri commands
- Vue DevTools for component inspection
Console Logging
Section titled “Console Logging”console.log('Debug:', variable);console.warn('Warning:', condition);console.error('Error:', error);Database Debugging
Section titled “Database Debugging”# Open development database (macOS)sqlite3 ~/Library/Application\ Support/com.mimir.app/dev/data/mimir.db
# Common commands.tables # List tables.schema campaigns # Show table structureSELECT * FROM campaigns; # Query data.quit # ExitDatabase Management
Section titled “Database Management”Migrations
Section titled “Migrations”Creating a Migration
Section titled “Creating a Migration”cd crates/mimir-corediesel migration generate add_new_feature
# Edit migrations/YYYY-MM-DD-HHMMSS_add_new_feature/up.sql# Edit migrations/YYYY-MM-DD-HHMMSS_add_new_feature/down.sqlRunning Migrations
Section titled “Running Migrations”# Apply migrationsdiesel migration run
# Rollback last migrationdiesel migration revert
# Test migration cyclediesel migration redoDevelopment Database Location
Section titled “Development Database Location”- macOS:
~/Library/Application Support/com.mimir.app/dev/data/mimir.db - Linux:
~/.local/share/com.mimir.app/dev/data/mimir.db - Windows:
%APPDATA%\com.mimir.app\dev\data\mimir.db
Resetting Development Database
Section titled “Resetting Development Database”angreal dev resetThis deletes ONLY the dev database (.../com.mimir.app/dev/data/mimir.db) and clears dev assets — it never touches the production database (see .angreal/task_dev.py). Restart the app (e.g. angreal dev launch) to recreate a fresh database.
Frontend Development
Section titled “Frontend Development”Hot Reload
Section titled “Hot Reload”Changes to Vue components, TypeScript, and CSS hot reload automatically:
- Save file
- Browser refreshes automatically
- State may reset depending on change
State Management (Pinia)
Section titled “State Management (Pinia)”// Define storeimport { defineStore } from 'pinia';
export const useCampaignStore = defineStore('campaign', () => { const campaigns = ref<Campaign[]>([]);
async function loadCampaigns() { campaigns.value = await invoke('list_campaigns'); }
return { campaigns, loadCampaigns };});
// Use in componentconst campaignStore = useCampaignStore();await campaignStore.loadCampaigns();Calling Tauri Commands
Section titled “Calling Tauri Commands”import { invoke } from '@tauri-apps/api/core';
// Basic invocationconst result = await invoke<string>('command_name', { param: value });
// With error handlingtry { const data = await invoke<Campaign>('get_campaign', { id: campaignId });} catch (error) { console.error('Failed to get campaign:', error);}Tauri-Specific Considerations
Section titled “Tauri-Specific Considerations”IPC Communication
Section titled “IPC Communication”Commands run in separate thread from UI:
- Always async
- Data must be serializable
- Large data may have performance impact
File System Access
Section titled “File System Access”Tauri v2 resolves paths via the app handle’s path resolver (see crates/mimir/src/main.rs):
// Inside .setup(|app| { ... })let data_dir = app .path() .app_data_dir() .expect("Failed to get app data directory");Window Management
Section titled “Window Management”Frontend uses @tauri-apps/api v2:
import { getCurrentWindow } from '@tauri-apps/api/window';
const appWindow = getCurrentWindow();await appWindow.setTitle('New Title');await appWindow.maximize();For creating secondary windows, see crates/mimir/frontend/src/utils/windows.ts, which uses WebviewWindow from @tauri-apps/api/webviewWindow.
Common Tasks
Section titled “Common Tasks”Updating Dependencies
Section titled “Updating Dependencies”# Rust dependenciescargo update
# Frontend dependenciescd crates/mimir/frontendnpm updateFormatting Code
Section titled “Formatting Code”# Rustcargo fmt
# TypeScript/Vuecd crates/mimir/frontendnpm run lintRunning Clippy
Section titled “Running Clippy”cargo clippy --all-targets --all-features
# Fix auto-fixable issuescargo clippy --fixType Checking Frontend
Section titled “Type Checking Frontend”cd crates/mimir/frontendnpm run type-checkPerformance Optimization
Section titled “Performance Optimization”Profiling Rust Code
Section titled “Profiling Rust Code”Build in release mode and use an external profiler:
cargo build --release
# Example: samply (cargo install samply)samply record ./target/release/mimir-mapgen generate --preset forest --output /tmp/map.dungeondraft_map
# On macOS, Instruments (Xcode) also works against the release binaryFrontend Performance
Section titled “Frontend Performance”- Use Vue DevTools performance tab
- Check bundle size:
npm run build -- --analyze - Lazy load routes and heavy components
Additional Resources
Section titled “Additional Resources”Getting Help
Section titled “Getting Help”- Check CONTRIBUTING.md for contribution guidelines
- Search GitHub Issues
- Review crate READMEs for detailed architecture info
- Ask questions in pull request comments