Contributing to Focust
January 29, 2026 · View on GitHub
Thank you for your interest in contributing to Focust! Whether you're fixing bugs, adding features, improving documentation, or helping with translations, your contributions are warmly welcomed.
Table of Contents
- Getting Started
- Development Environment
- Project Structure
- Development Workflow
- Coding Standards
- Testing Guidelines
- Commit Messages
- Pull Request Process
- Areas Needing Help
- Getting Help
Getting Started
Prerequisites
Before you start contributing, make sure you have:
- Git - Download Git
- Node.js (v18+) or Bun (recommended) - Download Bun
- Rust (latest stable) - Install Rustup
- Just (optional but recommended) -
cargo install just - Platform-specific dependencies - See README.md
Fork and Clone
- Fork the repository on GitHub
- Clone your fork:
git clone https://github.com/YOUR_USERNAME/Focust.git cd Focust - Add upstream remote:
git remote add upstream https://github.com/pilgrimlyieu/Focust.git
Initial Setup
# Install dependencies
bun install
# Setup Rust dependencies
cd src-tauri
cargo check
cd ..
# Or use Just
just setup
Development Environment
Recommended IDE
Visual Studio Code with the following extensions:
- rust-analyzer - Rust language support
- Tauri - Tauri development tools
- Vue - Official - Vue 3 support
- Biome - Formatting and linting
Running the Development Server
# Start with hot-reload
bun run tauri dev
# Or use Just
just dev
The settings window should be opened in tray tab. To test break windows:
- In the settings UI, adjust break intervals to short durations (e.g., 30 seconds)
- Wait for the break to trigger, or use the "Trigger Break" test command in development
Project Structure
Focust/
├── src/ # Frontend (Vue 3 + TypeScript)
│ ├── components/ # Vue components (settings, UI, icons)
│ ├── stores/ # Pinia state management
│ ├── views/ # Main application views
│ ├── i18n/ # Internationalization
│ ├── types/ # Type definitions (including generated/)
│ └── utils/ # Utility functions
│
├── src-tauri/ # Backend (Rust + Tauri)
│ ├── src/
│ │ ├── cmd/ # Tauri command handlers
│ │ ├── config/ # Configuration system
│ │ ├── core/ # Business logic (audio, schedule, theme, etc.)
│ │ ├── scheduler/ # Break scheduling engine
│ │ ├── monitors/ # Environment monitoring (idle, DND, etc.)
│ │ ├── platform/ # Platform integrations (tray, hotkeys, notifications)
│ │ └── utils/ # Utility functions
│ └── assets/sounds/ # Built-in audio files
│
├── docs/ # Documentation
│ ├── ARCHITECTURE.md # System architecture
│ ├── CONFIGURATION.md # Configuration reference
│ └── QUICKSTART.md # Quick start guide
│
├── justfile # Command definitions
└── README.md # Project readme
For detailed architecture information, see docs/ARCHITECTURE.md.
Development Workflow
Creating a New Branch
Always create a new branch for your work:
# Update your fork
git checkout main
git pull upstream main
# Create a feature branch
git checkout -b feature/your-feature-name
# or
git checkout -b fix/issue-number-description
Syncing with Upstream
Regularly sync your fork with the upstream repository:
git fetch upstream
git checkout main
git merge upstream/main
git push origin main
Running Tests
Before submitting changes, run all tests:
# Run all tests
just test-all
# Run frontend tests only
just test-front-all
# Run backend tests only
just test-back-all
# Run specific test
just test-back scheduler_test
Code Quality Checks
Before committing, ensure your code meets quality standards:
# Format code
just format
# Run linters
just lint
# Type checking
just check
# Or run all at once
just pre-commit
Coding Standards
General Principles
- Write clean, readable code: Prioritize clarity over cleverness
- Comment complex logic: Explain the "why", not just the "what"
- Keep functions small: Each function should do one thing well
- Use meaningful names: Variables, functions, and types should be self-documenting
- Test your code: Add tests for new features and bug fixes
Language Standards
Rust Code
Style:
- Follow Rust API Guidelines
- Use
cargo fmtfor formatting - Use
cargo clippyfor linting
Naming Conventions:
// Types: PascalCase
struct AppConfig { }
enum AudioSource { }
// Functions and variables: snake_case
fn load_config() -> Result<AppConfig> { }
let user_name = "Alice";
// Constants: SCREAMING_SNAKE_CASE
const MAX_RETRIES: u32 = 3;
// Private fields: prefix with underscore if unused
struct Internal {
_unused_field: String,
}
Error Handling:
// Use Result<T, E> for recoverable errors
fn parse_config(path: &str) -> Result<AppConfig, ConfigError> {
// ...
}
// Use ? operator for error propagation
let config = load_file(path)?;
// Avoid unwrap() in library code
// In tests and examples, unwrap() is acceptable
#[cfg(test)]
fn parsing_works() {
let config = parse_config("test.toml").unwrap();
}
Async Code:
// Use async/await for asynchronous operations
async fn fetch_data() -> Result<Data> {
// Implementation
}
Type Exports for TypeScript:
use serde::{Deserialize, Serialize};
use ts_rs::TS;
#[derive(Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")] // JavaScript convention
#[ts(export, rename_all = "camelCase")]
pub struct AppConfig {
pub autostart: bool,
pub theme_mode: String,
}
Documentation:
/// Loads the application configuration from disk.
///
/// Loads from platform-specific config directory, or creates default if not found.
///
/// # Arguments
/// * `app_handle` - Handle to the Tauri application
///
/// # Returns
/// The loaded or default configuration
pub async fn load_config(app_handle: &AppHandle) -> AppConfig {
// Implementation
}
TypeScript/Vue Code
Style:
- Use Biome for formatting and linting (configured in
biome.json) - Use TypeScript strict mode
Naming Conventions:
// Types and Interfaces: PascalCase
interface UserConfig {
userName: string;
}
type AudioSource = "builtin" | "file";
// Functions and variables: camelCase
function loadConfig(): UserConfig { }
const userName = "Alice";
// Constants: SCREAMING_SNAKE_CASE
const MAX_RETRIES = 3;
// Components: PascalCase (Vue SFC files)
// SettingsPanel.vue, BreakWindow.vue
Vue 3 Composition API:
<script setup lang="ts">
import { ref, computed } from "vue";
import type { AppConfig } from "@/types";
// Define props with TypeScript
interface Props {
config: AppConfig;
readonly?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
readonly: false,
});
// Reactive state and computed values
const count = ref(0);
const doubled = computed(() => count.value * 2);
</script>
<template>
<div>
<p>{{ count }} × 2 = {{ doubled }}</p>
</div>
</template>
State Management (Pinia):
import { defineStore } from "pinia";
import { ref, computed } from "vue";
export const useConfigStore = defineStore("config", () => {
// State
const config = ref<AppConfig | null>(null);
// Getters (computed values)
const isDarkMode = computed(() => config.value?.themeMode === "dark");
// Actions (functions that modify state)
async function loadConfig() {
config.value = await invoke<AppConfig>("get_config");
}
return { config, isDarkMode, loadConfig };
});
Error Handling:
// Handle errors gracefully with try-catch
try {
await invoke("save_config", { config });
showToast("success", "Settings saved");
} catch (error) {
console.error("Failed to save config:", error);
showToast("error", "Failed to save settings");
}
// Use type guards for runtime type checking
import { isPromptPayload } from "@/types";
if (isPromptPayload(data)) {
startBreak(data); // TypeScript knows data type
}
Documentation:
/**
* Loads the user configuration from the backend.
*
* @returns A promise that resolves to the user configuration
* @throws Error if configuration cannot be loaded
*/
export async function loadUserConfig(): Promise<UserConfig> {
return await invoke<UserConfig>("get_user_config");
}
Testing Standards
Key Points:
- Add tests for new features and bug fixes
- Test edge cases and error handling
- Use descriptive test names
- Keep tests simple and focused
- Check existing tests for patterns
Rust Tests
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic_basic_functionality_works() {
let result = calculate(5);
assert_eq!(result, expected_value);
}
// For async tests, use tokio::test
#[tokio::test]
async fn async_function_works() {
let data = fetch_data().await.unwrap();
assert!(!data.is_empty());
}
}
TypeScript/Vue Tests
import { describe, it, expect } from "vitest";
import { mount } from "@vue/test-utils";
import MyComponent from "@/components/MyComponent.vue";
describe("MyComponent", () => {
it("renders with props", () => {
const wrapper = mount(MyComponent, { props: { title: "Test" } });
expect(wrapper.text()).toContain("Test");
});
it("emits events on interaction", async () => {
const wrapper = mount(MyComponent);
await wrapper.find("button").trigger("click");
expect(wrapper.emitted("submit")).toBeTruthy();
});
});
Commit Messages
We follow the Conventional Commits specification:
<type>(<scope>): <subject>
<body>
<footer>
Types
feat: New featurefix: Bug fixdocs: Documentation changesstyle: Code formatting (no logic changes)refactor: Code refactoringperf: Performance improvementstest: Test additions or modificationschore: Build process, dependencies, tooling
Scope
Optional, indicates the area of change:
config: Configuration systemscheduler: Break schedulerui: User interfaceaudio: Audio systemtray: System trayi18n: Internationalization
Examples
feat(scheduler): add postpone break functionality
Implement the ability to postpone breaks by a configurable duration.
Users can now use the postpone button or global hotkey to delay
the next break.
Closes #42
---
fix(tray): fix tray menu not updating on pause
The tray menu was not reflecting the paused state correctly.
Fixed by adding proper state synchronization.
Fixes #38
---
docs(architecture): update backend architecture documentation
Added details about the event-driven scheduler and improved
the module organization diagrams.
---
chore(deps): update tauri to 2.9.2
Updated Tauri and related plugins to the latest versions.
Pull Request Process
Before Submitting
- Update your branch with the latest upstream changes
- Run all tests and ensure they pass
- Format and lint your code
- Update documentation if needed
- Add tests for new features or bug fixes
Submitting a PR
-
Push your branch to your fork:
git push origin feature/your-feature -
Open a Pull Request on GitHub:
- Use a clear, descriptive title
- Reference related issues (e.g., "Closes #123", "Fixes #456")
- Describe your changes in detail
- Add screenshots for UI changes
- List any breaking changes
-
Fill out the PR template:
- The PR template will automatically appear when you create a pull request
- Complete all relevant sections (description, type of change, testing, checklist)
- Check all applicable boxes in the checklist before requesting review
- See
.github/pull_request_template.mdfor the full template
Review Process
- Automated Checks: CI/CD will run tests and linting
- Code Review: Maintainers will review your code
- Feedback: Address any requested changes
- Approval: Once approved, your PR will be merged
After Merging
-
Delete your branch (optional):
git branch -d feature/your-feature git push origin --delete feature/your-feature -
Update your local main:
git checkout main git pull upstream main
Areas Needing Help
High Priority
- Platform Testing: Test on macOS and Linux, report platform-specific issues
- Bug Fixes: Check open issues labeled
bug - Documentation: Improve existing docs, add missing documentation
Medium Priority
- Translations: Add support for new languages
- UI/UX Improvements: Design enhancements, accessibility improvements
- Performance: Profile and optimize slow operations
- Test Coverage: Add tests for untested code paths
Feature Requests
Check open issues labeled enhancement or feature-request. Feel free to propose new features via discussions.
Getting Help
Resources
- Documentation: docs/ARCHITECTURE.md, docs/CONFIGURATION.md
- API Reference: Generated TypeScript types in
src/types/generated/ - Examples: Check existing code for similar implementations
Communication
- GitHub Issues: Report bugs or request features
- GitHub Discussions: Ask questions, share ideas
- Code Comments: Check inline comments for context
- PR Comments: Ask questions during code review
Tips for New Contributors
- Start small: Fix typos, improve documentation, add tests
- Read existing code: Understand the patterns and conventions
- Ask questions: Don't hesitate to ask for help
- Be patient: Code review takes time
- Learn from feedback: Use reviews as learning opportunities
Development Tips
Quick Commands (Just)
# Pre-commit checks (format, lint, check)
just pre-commit-checks
# Watch mode for tests
bun run test:ui # Frontend tests with UI
cargo watch -x test # Backend tests (requires cargo-watch)
# Clean and rebuild
just clean
just build
Debugging
Rust Backend:
// Add debug logs
tracing::debug!("Variable value: {value:?}");
tracing::info!("Processing started");
tracing::warn!("Potential issue detected");
tracing::error!("Operation failed: {error}");
Frontend:
// Console debugging
console.log("[Component] State:", state);
console.warn("[Store] Invalid data:", data);
// Vue Devtools
// Install Vue Devtools browser extension for reactive debugging
Common Issues
Issue: Types out of sync
# Regenerate TypeScript types from Rust
cd src-tauri
cargo test export_bindings
Issue: Build fails after pulling changes
# Clean and reinstall dependencies
just clean
bun install
cargo clean
just setup
Issue: Hot reload not working
# Restart dev server
# Kill any running tauri dev processes
just dev
License
By contributing to Focust, you agree that your contributions will be licensed under the same license as the project (GPL-3.0 License).
Thank you for contributing to Focust! Your efforts help make break reminders better for everyone. 🎉