Contributing to Mimir
Welcome to the Mimir contributor guide! This comprehensive guide covers everything you need to know about contributing to the project.
Quick Links
- Development Setup - Detailed development environment setup
- Architecture - Technical architecture overview
- GitHub Repository
- Issue Tracker
Table of Contents
- Getting Started
- Development Workflow
- Code Style and Standards
- Testing Guidelines
- Pull Request Process
- Architecture Overview
- Design Principles
- CI and Releases
- Release Process
Getting Started
First-Time Contributors
Welcome! Here’s how to make your first contribution:
- Find an issue - Look for issues labeled
good first issueorhelp wanted - Comment on the issue - Let us know you’re working on it
- Fork and clone - Create your own fork and clone it locally
- Set up your environment - Follow the Development Setup guide
- Make your changes - Implement your fix or feature
- Submit a PR - Create a pull request with your changes
Finding Work
- Good First Issues - Tagged issues suitable for newcomers
- Help Wanted - Issues where we’d appreciate community help
- Bug Reports - Always welcome to fix reported bugs
- Feature Requests - Check if a feature request needs implementation
- Documentation - Help improve our docs
Development Workflow
Branch Strategy
main- Stable release branch- Feature branches -
feature/descriptionorfix/description - Always branch from
main
git checkout main
git pull origin main
git checkout -b feature/my-new-feature
Making Changes
- Write code following our style guidelines
- Test your changes locally
- Commit frequently with clear messages
- Push to your fork regularly
- Keep your branch updated with main
# Keep your branch up to date
git fetch origin
git rebase origin/main
# Or merge if you prefer
git merge origin/main
Before Submitting
Checklist before creating a pull request:
- All tests pass locally
- Code follows style guidelines
- New tests added for new functionality
- Documentation updated if needed
- Commit messages follow conventions
- No merge conflicts with main
- Changes are focused and minimal
Code Style and Standards
Rust Code Style
Formatting:
# Format all Rust code
cargo fmt
# Check formatting without changing
cargo fmt -- --check
Linting:
# Run clippy
cargo clippy --all-targets --all-features
# Fix auto-fixable warnings
cargo clippy --fix
Best Practices:
- Use meaningful variable and function names
- Write doc comments for public APIs
- Keep functions focused and reasonably sized
- Prefer explicit error handling over unwrap()
- Use
Result<T, E>for operations that can fail - Follow the Rust API Guidelines
Example:
Campaign IDs are TEXT UUIDs (String), not integers:
#![allow(unused)]
fn main() {
/// Retrieves a campaign by ID from the database.
///
/// # Arguments
/// * `conn` - Database connection
/// * `id` - Campaign ID (UUID string) to retrieve
///
/// # Returns
/// * `Ok(Campaign)` - The campaign if found
/// * `Err(String)` - Error message if not found
pub fn get_campaign(conn: &mut SqliteConnection, id: &str) -> Result<Campaign, String> {
campaigns::table
.find(id)
.first(conn)
.map_err(|e| format!("Failed to get campaign: {}", e))
}
}
TypeScript/Vue Code Style
Type Safety:
- Always use TypeScript, not JavaScript
- Define interfaces for all data structures
- Avoid
anytype unless absolutely necessary - Use strict mode in tsconfig.json
Vue Best Practices:
- Use Composition API with
<script setup> - Keep components focused and single-purpose
- Props should have types and validators
- Emit events with proper typing
- Use composables for reusable logic
Example:
<script setup lang="ts">
import { ref, computed } from 'vue';
import type { Campaign } from '@/types/campaigns';
interface Props {
campaign: Campaign;
editable?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
editable: false
});
const emit = defineEmits<{
save: [campaign: Campaign];
cancel: [];
}>();
const localCampaign = ref({ ...props.campaign });
const hasChanges = computed(() => {
return JSON.stringify(localCampaign.value) !== JSON.stringify(props.campaign);
});
function handleSave() {
emit('save', localCampaign.value);
}
</script>
General Conventions
Naming:
- Rust:
snake_casefor functions/variables,PascalCasefor types - TypeScript:
camelCasefor variables/functions,PascalCasefor classes/interfaces - Files: Match the primary export name
Comments:
- Write self-documenting code
- Add comments for complex logic
- Doc comments for public APIs
- No commented-out code in commits
Prohibited:
- No emojis in code, comments, or commit messages
- No references to Claude or Anthropic in commit messages
- No hardcoded secrets or credentials
- No console.log in production code (use logging frameworks)
Testing Guidelines
Rust Testing
Unit Tests:
New campaigns are built with NewCampaign::new(id, name) — the ID is a caller-supplied UUID string:
#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_campaign_creation() {
let campaign = NewCampaign::new("test-id", "Test Campaign")
.with_description("A test campaign");
assert_eq!(campaign.id, "test-id");
assert_eq!(campaign.name, "Test Campaign");
}
}
}
Integration Tests:
Place in the crate’s tests/ directory. Existing targets: catalog_import and srd_smoke_test in mimir-core; cli_integration, format_roundtrip, and polygon_snapshots in mimir-mapgen.
#![allow(unused)]
fn main() {
use mimir_core::{create_connection, run_migrations};
#[test]
fn test_database_integration() {
let mut conn = create_connection(":memory:").unwrap();
run_migrations(&mut conn).unwrap();
// Test database operations
}
}
Frontend Testing
Component Tests:
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import CampaignCard from '@/components/CampaignCard.vue';
describe('CampaignCard', () => {
it('renders campaign name', () => {
const wrapper = mount(CampaignCard, {
props: {
campaign: { id: 1, name: 'Test Campaign' }
}
});
expect(wrapper.text()).toContain('Test Campaign');
});
});
Running Tests
# Rust tests (recommended)
angreal test unit
# Underlying command:
# cargo test --workspace --exclude mimir -- --test-threads=1
# (excludes the Tauri crate, which needs the sidecar binary to build;
# --test-threads=1 avoids SQLite locking issues)
# Frontend tests
cd crates/mimir/frontend && npm test
# With coverage
angreal test coverage # Rust (cargo-tarpaulin)
npm run test:coverage # Frontend
# Specific integration test target
cargo test -p mimir-core --test catalog_import -- --test-threads=1
# Specific test by name
cargo test -p mimir-core test_campaign_creation -- --test-threads=1
Pull Request Process
1. Prepare Your PR
- Ensure all tests pass
- Update documentation
- Write a clear PR description
- Reference related issues
2. PR Description Template
## Description
Brief description of what this PR does
## Related Issues
Fixes #123
Related to #456
## Changes Made
- Added feature X
- Fixed bug Y
- Updated documentation for Z
## Testing
- [ ] Unit tests added/updated
- [ ] Integration tests added/updated
- [ ] Manually tested on macOS/Windows/Linux
## Checklist
- [ ] Code follows style guidelines
- [ ] Self-review completed
- [ ] Documentation updated
- [ ] No new warnings
- [ ] Tests pass locally
3. Review Process
- Maintainers will review your PR
- Address feedback and requested changes
- Keep discussion focused and professional
- Be patient - reviews may take a few days
4. After Approval
- PR will be merged to main
- Your contribution will be included in the next release
- Thank you for contributing!
Architecture Overview
System Architecture
Mimir follows a clean architecture pattern with clear separation of concerns:
┌─────────────────────────────────────────┐
│ Frontend (Vue 3 + TS) │
│ ┌──────────────────────────────────┐ │
│ │ Components, Views, Stores │ │
│ └──────────────────────────────────┘ │
└─────────────────┬───────────────────────┘
│ Tauri IPC
┌─────────────────▼───────────────────────┐
│ Tauri Desktop Shell (Rust) │
│ ┌──────────────────────────────────┐ │
│ │ Commands, Services, State │ │
│ └──────────────┬───────────────────┘ │
└─────────────────┼───────────────────────┘
│
┌─────────────────▼───────────────────────┐
│ mimir-core (Business Logic) │
│ ┌──────────────────────────────────┐ │
│ │ Services, DAL, Domain Models │ │
│ └──────────────┬───────────────────┘ │
└─────────────────┼───────────────────────┘
│
┌─────────────────▼───────────────────────┐
│ Database (SQLite + Diesel) │
└─────────────────────────────────────────┘
Key Crates
- mimir - Tauri app shell and command handlers
- mimir-core - Business logic, domain models, database
- mimir-mcp - MCP server for Claude Code integration
- mimir-mapgen - Procedural map generation (library + CLI)
- mimir-print - PDF generation via Typst
Design Principles
- Local-First - All data stored locally, no cloud dependencies
- Privacy-First - No telemetry, no tracking, user data stays local
- Type Safety - Strong typing in both Rust and TypeScript
- Separation of Concerns - Clear boundaries between layers
- Testability - Write testable code with good test coverage
- Domain-Driven Design - Model the D&D campaign management domain
- Progressive Enhancement - Core features work without LLM
- Cross-Platform - Native experience on Windows, macOS, Linux
CI and Releases
Continuous Integration
CI (.github/workflows/ci.yml) runs on pushes and pull requests to main:
- Build matrix: macOS (aarch64 and x86_64), Ubuntu 22.04, and Windows — each builds the MCP sidecar then the full Tauri app
- Frontend tests: vitest on Ubuntu
- Core crate tests:
angreal test unit --coreon Ubuntu - SRD smoke test (heavy integration,
cargo test -p mimir-core --test srd_smoke_test): runs only on pushes tomain, not on pull requests - Coverage: cargo-tarpaulin on
mimir-core, run with explicit flags andcontinue-on-error: true, so CI reports coverage but does not fail on it. The 50% floor (fail-under = 50intarpaulin.toml) is enforced only by the localangreal test coveragetask
Releases
Releases (.github/workflows/release.yml) are triggered by pushing a tag matching v*.*.*:
- The same four-platform matrix builds the sidecar, the
mimir-mapgenbinary, and the Tauri app bundles tauri-actioncreates a draft GitHub release taggedapp-v<version>- A follow-up job attaches the
mimir-mcpsidecar andmimir-mapgenbinaries for every target to that release
Documentation Deploys
Pushes to main that touch docs/** trigger .github/workflows/docs.yml, which builds the mdBook and deploys it to the external repository mimir-dm/mimir-dm.github.io (GitHub Pages).
Release Process
Version Numbering
We follow Semantic Versioning:
- MAJOR.MINOR.PATCH (e.g., 0.1.0)
- MAJOR: Breaking changes
- MINOR: New features, backwards compatible
- PATCH: Bug fixes, backwards compatible
Release Steps
- Update version in all
Cargo.tomlfiles - Update version in
package.json - Update version in
tauri.conf.json - Create git tag:
git tag vX.Y.Z - Push tag:
git push origin vX.Y.Z - GitHub Actions builds the matrix and creates a draft release
app-vX.Y.Zwith sidecar and mapgen binaries attached
Questions and Help
Getting Help
- Documentation - Check this guide and DEVELOPMENT.md
- Issues - Search existing issues or create a new one
- Discussions - Use GitHub Discussions for questions
- Pull Requests - Ask questions in PR comments
Maintainer Response Times
- Issues: Usually within 1-3 days
- PRs: Usually within 3-7 days
- Critical bugs: Within 24 hours when possible
Recognition
Contributors are recognized in:
- Git commit history
- Release notes
- Project documentation
- GitHub contributors page
Thank you for contributing to Mimir!