Contributing
We welcome contributions to tomat! Here’s a guide to help you get started.
Quick Contribution Checklist
Before submitting any changes:
- Formatting:
cargo fmt -- --check(MUST exit with code 0) - Linting:
cargo clippy --all-targets --all-features -- -D warnings(MUST exit with code 0, no warnings allowed) - Compilation:
cargo check(MUST pass) - Tests:
cargo test(all integration tests must pass)
Getting Started
-
Fork the repository.
-
Clone your fork:
git clone <your-fork-url> -
Install prerequisites:
# Rust toolchain (specified in rust-toolchain.toml) curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # ALSA development libraries (for audio) sudo apt-get install libasound2-dev # Ubuntu/Debian sudo dnf install alsa-lib-devel # Fedora/RHEL sudo pacman -S alsa-lib # Arch Linux -
You might also want to install task for easier task management. The following
taskcommands assume you have it installed.
Development Workflow
Essential Build Commands
Always run commands from the repository root.
# Quick development check (recommended)
task dev
# Individual commands
cargo check # Check compilation without building
cargo test # Run all tests (19 integration tests)
cargo clippy --all-targets --all-features -- -D warnings # Lint
cargo fmt # Format code
cargo fmt -- --check # Check formatting
# Build commands
cargo build # Development build
cargo build --release # Release build
Testing Your Changes
# Build and start daemon for testing
cargo build
./target/debug/tomat daemon start
# Test with short durations for fast feedback
./target/debug/tomat start --work 0.1 --break 0.05 # 6s work, 3s break
./target/debug/tomat status
./target/debug/tomat toggle
# Stop daemon when done
./target/debug/tomat daemon stop
Code Quality Standards
Mandatory Requirements
All code changes MUST pass these checks before commit:
- Zero clippy warnings:
cargo clippy --all-targets --all-features -- -D warnings - Proper formatting:
cargo fmt -- --check - All tests pass:
cargo test - Compilation success:
cargo check
Code Style
- Error handling: Uses
Box<dyn std::error::Error>for simplicity - Comments: Only add comments when they match existing style or explain complex logic
- Dependencies: Use existing libraries when possible, avoid adding new dependencies unless absolutely necessary
- Commit style: Use
Conventional Commits (
feat:,fix:,docs:,test:,refactor:)
Architecture Overview
Tomat is designed as a small, focused Rust project with a client-server architecture.
Module Structure
src/main.rsMain entry point, command parsing, high-level flowsrc/cli.rsCLI argument parsing withclapsrc/config.rsConfiguration system with timer, sound, and notification settingssrc/server.rsUnix socket server, daemon lifecycle, client request handling, PID file managementsrc/timer.rsTimer state machine, phase transitions, status output formatting, desktop notificationssrc/audio.rsAudio playback utilitiestests/integration tests
Communication Flow
Client CLI Commands
↓
Unix Socket ($XDG_RUNTIME_DIR/tomat.sock)
↓
Daemon Process (background)
↓
TimerState (Work/Break/LongBreak phases)
↓
JSON Status Output (optimized for waybar)
Key Design Decisions
Client-Server Architecture:
- Single binary with subcommands
- Daemon listens on Unix socket at
$XDG_RUNTIME_DIR/tomat.sock - PID file tracking at
$XDG_RUNTIME_DIR/tomat.pidwith exclusive file locking - Line-delimited JSON protocol for communication
Timer State Machine:
- Phases: Work → Break → Work → … → LongBreak (after N sessions)
- Two modes controlled by
--auto-advanceflag:false(default): Timer transitions to next phase but pauses, requiring manual resumetrue: Timer automatically continues through all phases
- Timer starts in paused work state
Testing Infrastructure
Integration Test Pattern
All tests use the TestDaemon helper struct for isolated testing:
#![allow(unused)]
fn main() {
// Start isolated test daemon with temporary socket
let daemon = TestDaemon::start()?;
// Send commands
daemon.send_command(&["start", "--work", "0.05", "--break", "0.05"])?;
// Get status
let status = daemon.get_status()?;
assert_eq!(status["class"], "work");
// Wait for timer completion
daemon.wait_for_completion(10)?;
}
Key testing features:
- Isolated environments: Each test uses
tempfile::tempdir()with customXDG_RUNTIME_DIR - Fast execution: Fractional minutes (0.05 = 3 seconds) for rapid testing
- Notification suppression:
TOMAT_TESTING=1env var disables desktop notifications - Automatic cleanup:
TestDaemonDrop impl kills daemon process
Test Categories
- Auto-advance behavior: Verify
auto_advance=falsepauses after transitions,auto_advance=truecontinues automatically - Timer control: Toggle pause/resume, stop/start
- Daemon lifecycle: Start, stop, status, duplicate detection
- Edge cases: Manual skip, fractional minutes
- Configuration: Timer, sound, and notification configuration parsing
- Icon management: Embedded icon caching and different icon modes
Adding New Features
Adding a New Command
- Add enum variant to
Commandsinsrc/main.rs - Add command handling in
handle_client()insrc/server.rs - Add match arm in
main()insrc/main.rs - Write integration tests in
tests/usingTestDaemon
Modifying Timer Behavior
- Update
TimerStatestruct insrc/timer.rsif new fields needed - Modify state machine logic in
next_phase(),start_work(), etc. - Update status output in
get_status_output() - Test both
auto_advance=trueandauto_advance=falsemodes
Adding Configuration Options
- Update appropriate config struct in
src/config.rs - Add default value functions
- Update
Defaultimplementation - Add comprehensive tests for new configuration options
- Update documentation and examples
Technical Implementation Details
Process Management
- Daemon lifecycle: SIGTERM with 5-second timeout, then SIGKILL
- PID file locking: Uses
fs2::FileExt::try_lock_exclusive()to prevent race conditions - Socket cleanup: Automatic cleanup of socket and PID files on graceful shutdown
Notification System
- Desktop notifications: Via
notify-rustwith embedded icon system - Icon caching: Embedded icon automatically cached to
~/.cache/tomat/icon.png - Mako compatibility: Default “auto” icon mode works with mako out of the box
Configuration System
- TOML-based: Configuration loaded from
~/.config/tomat/config.toml - Hierarchical: Built-in defaults → config file → CLI arguments
Debugging Tips
# Run daemon in foreground (see output directly)
cargo build && ./target/debug/tomat daemon run
# Test output (see println! statements)
cargo test -- --nocapture
# Check socket status
ss -lx | grep tomat
# Inspect PID file
cat $XDG_RUNTIME_DIR/tomat.pid && ps -p <PID>
# Check logs (if using systemd)
journalctl --user -u tomat.service -f
Backward Compatibility
When contributing, ensure:
- No breaking changes: Existing waybar configurations must continue to work
- CLI stability: Existing command-line interfaces are preserved
- Configuration compatibility: Existing config files remain valid
- API consistency: JSON output format remains stable for waybar integration
Release Process
The project uses automated semantic versioning:
- Conventional Commits: Commit messages determine version bumps
- Automated CI: GitHub Actions handle testing and releases
- Semantic Versioning:
feat:→ minor,fix:→ patch,BREAKING CHANGE:→ major