Thank you for taking the time to contribute. Below you'll find everything needed to get set up, make changes, and cut a release.
- Code of Conduct
- Filing a Bug or Feature
- Development Setup
- Project Structure
- Making Changes
- Releasing
Be kind and respectful to the members of the community. Take time to educate others who are seeking help. Harassment of any kind will not be tolerated.
- Check existing issues before opening a new one.
- Bug reports — include steps to reproduce, Pomotroid version, OS, and what you expected vs. what happened.
- Feature requests — open an issue with a clear title and description of the feature and why it would be useful.
| Tool | Version |
|---|---|
| Node.js | 22+ |
| Rust | stable (via rustup) |
| npm | bundled with Node.js |
Linux — install system dependencies:
sudo apt-get install -y \
libwebkit2gtk-4.1-dev libssl-dev \
libayatana-appindicator3-dev librsvg2-dev \
patchelf libasound2-devmacOS / Windows — no extra system dependencies required.
git clone https://github.com/Splode/pomotroid
cd pomotroid
npm install
npm run tauri dev# TypeScript + Svelte type checking
npm run check
# Rust unit tests
cargo test --manifest-path src-tauri/Cargo.toml
# Rust linting (must pass with zero warnings)
cargo clippy --manifest-path src-tauri/Cargo.toml -- -D warningsAll three must pass before submitting a pull request. The CI pipeline runs them automatically.
To populate the statistics views with realistic historical data, use the seed script (requires Python 3, no extra dependencies):
# Preview what would be inserted — no database changes
python3 scripts/seed-db.py --dry-run
# Seed ~2 years of session history (default)
python3 scripts/seed-db.py
# Seed 1 year and wipe any existing sessions first
python3 scripts/seed-db.py --days 365 --clearThe database must exist before seeding — launch Pomotroid at least once to create it. The script resolves the platform-specific app-data path automatically; pass --db PATH to override.
pomotroid/
├── src/ # SvelteKit frontend (Svelte 5 runes)
│ ├── routes/ # Page components (main timer, settings, stats)
│ └── lib/
│ ├── components/ # Reusable UI components
│ ├── ipc/ # Frontend wrappers for Tauri commands
│ └── stores/ # Svelte stores (settings, theme)
├── src-tauri/ # Rust backend (Tauri 2)
│ └── src/
│ ├── commands.rs # IPC command handlers
│ ├── timer/ # Timer engine
│ ├── audio/ # Audio playback (rodio)
│ ├── db/ # SQLite persistence (rusqlite)
│ └── settings/ # Settings load/save
├── static/themes/ # Built-in JSON theme files
└── scripts/ # Maintainer scripts
- Fork the repository and create a feature branch from
main. - Make your changes. Keep commits focused and use conventional commit prefixes (
feat:,fix:,chore:, etc.). - Ensure all checks pass (
npm run check,cargo test,cargo clippy). - Open a pull request against
mainwith a clear description of what changed and why.
Built-in themes are embedded into the binary at compile time, so adding one requires a small code change in addition to the JSON file.
-
Create the theme JSON in
static/themes/your-theme-name.json. SeeTHEMES.mdfor the required format and color keys. -
Register it in
src-tauri/src/themes/mod.rs— add aninclude_str!()entry to theBUNDLED_JSONarray:include_str!("../../../static/themes/your-theme-name.json"),
-
Update the bundled theme count in the test assertion in the same file (e.g.
assert_eq!(themes.len(), 38, ...)).
Custom user themes (placed in the app data themes/ folder) are discovered at runtime and require no code changes.
The app uses Inlang + Paraglide for translations. Adding a language requires two files to be touched, then a rebuild.
-
Create the message file at
src/messages/{locale}.json. Copysrc/messages/en.jsonas a starting point and translate all values — do not change any keys. -
Register the locale by adding it to the
localesarray inproject.inlang/settings.json:"locales": ["en", "zh", "pt", "your-locale"]
-
Rebuild (
npm run build) — Paraglide auto-generates the typed JS modules insrc/paraglide/messages/. No further code changes are needed; the language will appear in Settings automatically.
Settings are stored as key/value strings in SQLite. Adding a new setting involves:
- Adding the DB key and default value in
src-tauri/src/settings/defaults.rs - Adding the typed field to the
Settingsstruct insrc-tauri/src/settings/mod.rs - Updating the frontend
Settingstype insrc/lib/types.ts - Exposing a toggle or input in the relevant settings section under
src/lib/components/settings/sections/
Releases are managed by maintainers. The process is intentionally minimal: one script, one push.
- You are on the
mainbranch with a clean working tree. [Unreleased]inCHANGELOG.mdcontains the changes for this release.
1. Fill in the changelog
Open CHANGELOG.md and make sure [Unreleased] accurately describes everything going into the release. The release workflow uses this section as the GitHub Release body.
2. Run the bump script
./scripts/bump-version.sh <version>
# e.g.
./scripts/bump-version.sh 1.1.0This will:
- Update the version in
tauri.conf.json,Cargo.toml, andpackage.json - Rename
[Unreleased]inCHANGELOG.mdto[v1.1.0] - YYYY-MM-DD - Commit all changes and create an annotated
v1.1.0tag
3. Push
git push origin main --follow-tagsPushing the tag triggers the release workflow, which:
- Builds Linux (
.deb,.AppImage), macOS (universal.dmg), and Windows (.exeinstaller) in parallel - Creates a draft GitHub Release with all artifacts attached and the changelog section as the release body
- Commits
latest.json(auto-updater manifest) andpomotroid.json(Scoop manifest) tomain
4. Publish the draft
Go to the Releases page, review the draft, and click Publish release.
5. Add a new [Unreleased] section
After publishing, add a fresh [Unreleased] block at the top of CHANGELOG.md to start collecting the next release's changes.
Pomotroid follows Semantic Versioning:
| Change | Version bump |
|---|---|
| Breaking changes or major rewrites | X.0.0 |
| New features, backward-compatible | X.Y.0 |
| Bug fixes only | X.Y.Z |