Contributing
Thanks for helping out! This page covers the mechanics of getting a change merged. For how the code is organised, read the Architecture overview first.
Branching
Section titled “Branching”- Branch off
master. - Keep branches focused — one logical change per PR is easier to review.
- CI runs on every PR (see below); the Linux release-profile build and the server tests run too.
Before you open a PR
Section titled “Before you open a PR”Run the full local check suite. CI fails on any clippy warning, so don’t skip the clippy step.
# Backend (from tauri/src-tauri)cargo checkcargo clippy --all-targets -- -D warningscargo test
# Frontend (from tauri/)bun run checkbun run lintbun run testWhat CI runs
Section titled “What CI runs”GitHub Actions workflows live in .github/workflows/:
ci.yml—build-windows(backend build + clippy/check/test + frontend checks),build-linux(release-profile smoke build, push-only),e2e-linux(WebDriver suite under Xvfb), andserver(sync-server tests).release.yml— tag-triggered; builds the Windows NSIS installer and the Linux AppImage and publishes a GitHub Release plus alatest.jsonupdater manifest.docs.yml— builds and deploys this documentation site to GitHub Pages on pushes tomasterthat touchdocs-site/.decky.yml— builds, verifies, and packages the Spool Backup Decky Loader plugin into an installable zip on pushes affectingdecky/or the plugin server backend.bump-release.yml— manual workflow kickoff that increments version components (major, minor, patch), tags the commit, and triggersrelease.yml.server-publish.yml— on version tags, builds and publishes the sync-server Docker image to GHCR (only whenserver/changes).
Conventions worth knowing
Section titled “Conventions worth knowing”- JSON shape compatibility: the
library.jsonandconfig.jsonstructs carry a container-level#[serde(default)], so missing keys fall back to the struct’sDefaultand older files load without migration — keep it that way when adding fields. Apply the attribute at the struct (container) level, not per-field: a per-field#[serde(default)]shadows the struct’s customDefaultvalues with the field-type default. Fields the app no longer uses are removed, not retained for legacy round-trip. - Lock discipline: never hold a
std::sync::Mutexguard across.await. Snapshot what you need, drop the guard, then await. - Add a command, add a wrapper: when you add a Rust
#[tauri::command], register it in thegenerate_handler!list and add its typed wrapper totauri/src/lib/api.ts. Keeptauri/src/lib/types.tsin sync with the Rust serde structs it mirrors. - Event names are colon-namespaced (
library:changed,run:phase) — Tauri 2 rejects.in event names at runtime.
Editing these docs
Section titled “Editing these docs”The docs site is a standalone Astro project in docs-site/.
cd docs-sitebun installbun run dev # local preview with hot reloadbun run build # production build into docs-site/dist/Content lives in docs-site/src/content/docs/ as Markdown / MDX. The sidebar is
configured in docs-site/astro.config.mjs. Every page has an “Edit page” link
that points straight at the source file on GitHub.