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
Website Hosting
Cloudflare Workers serves the mdBook documentation at https://tomat.cc/.
The www.tomat.cc domain redirects to the apex domain, preserving paths and
query strings. GitHub Pages hosts browser redirects for the old documentation
URLs and retains the installer at /tomat/install for existing shell commands.
The website tools use Node.js 24 and pnpm 11 or 12; CI pins pnpm 12.9.0. From the repository root, run:
pnpm install --frozen-lockfile
pnpm docs:build
pnpm docs:test
pnpm docs:deploy:check
pnpm docs:preview
On NixOS, devenv sets MINIFLARE_WORKERD_PATH to run the project’s Workers
runtime with the Nix dynamic loader.
The build uses mdBook and mdbook-mermaid, copies the installer into the site,
and generates the legacy Pages redirects in target/legacy-pages. It uses
the committed CLI reference generated from src/cli.rs; regenerate it with
Cargo when changing the CLI.
After building and checking the site, pnpm docs:deploy uploads the Worker and
assets together. Both deployment commands verify the installer first, since a
later Cargo build can regenerate mdBook’s output without it. Authenticate
locally with pnpm exec wrangler login, or set
CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID in your environment. Never
commit credentials. wrangler.jsonc defines both custom domains, which
Cloudflare connects to the Worker with managed DNS and HTTPS certificates.
The Documentation workflow deploys on published releases and manual runs.
Release runs build the released tag; manual runs build the selected ref. It
verifies the new site before publishing legacy redirects. The repository needs
the GitHub Actions secrets CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID.
The token must permit deployment to tomat-web and Workers Routes Write for
the tomat.cc zone. Creating the Worker initially requires Workers Admin at
the product scope; subsequent deployments can use a Worker-scoped Editor.
See the Cloudflare permissions documentation.
Check the deployed site with node scripts/docs-verify.mjs. Run the same checks
against a local Worker with node scripts/docs-verify.mjs http://localhost:8787.
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