4.5 KiB
Contributing to QBZ
This project is actively evolving. Contributions are welcome, but we have a few rules to keep releases stable and avoid regressions (especially around audio output).
Where the code lives
The live app is the Rust workspace under crates/ — a single native process
with a Slint UI. UI code is in crates/qbz-ui (.slint + generated bindings)
and crates/qbz (the binary). The old Svelte src/ and Tauri src-tauri/
trees were deleted in 2.0.2; they survive only at the git tag
legacy-tauri-svelte for reference. PRs against those paths cannot be merged —
port the change to crates/ instead.
Quick rules
- Write clear, concise English (no emojis in code, comments, or commit messages).
- Keep PRs focused and small when possible.
- Do not change app branding or legal disclaimers without discussing it first.
- Do not modify protected audio-backend behavior unless explicitly requested by the maintainer.
Branch naming
We use a consistent branch naming scheme:
<type>/<origin>/<branch_name>
type:feature|bugfix|hotfix|refactor|release|chore|docsorigin:internal: created/owned by maintainersexternal: branches/commits authored by third-party contributors (PRs)
Examples:
feature/internal/offline-cache-encryptionbugfix/internal/login-footer-alignmentdocs/internal/contributing-processfeature/external/add-album-to-playlist
Branch workflow
We use a pre-release integration branch to keep main stable and release-ready at all times.
feature/xyz ──┐
bugfix/abc ──┼──> pre-release ──> main (tagged release)
hotfix/123 ──┘
Branch hierarchy
main- Releases ONLY. Protected branch. Merging here triggers a tagged release.pre-release- Integration branch. All features and fixes merge here first.feature/*,bugfix/*, etc. - Individual work branches.
For contributors
All PRs must target pre-release, not main.
PRs targeting main will be closed and asked to retarget to pre-release.
Procedure (maintainer)
- Triage
- Confirm scope and that it does not touch protected areas (audio routing/backends, credential storage, etc.) unless requested.
- Verify PR targets
pre-release(notmain).
- Check out the PR
gh pr checkout <PR_NUMBER>
- Rename the checked-out branch (local)
- Use an
externalbranch name so it's obvious these commits are third-party authored: git branch -m <type>/external/<topic>
- Use an
- Merge to pre-release
git checkout pre-releasegit merge --no-ff <type>/external/<topic>
- Run checks
- Build/validate a touched core crate:
cargo check -p <crate>(run fromcrates/). The full UI (qbz/qbz-ui) is a ~20–30 GB compile — see the README "Building from Source" section before attempting it.
- Build/validate a touched core crate:
- Push pre-release
git push origin pre-release
- Close the PR with a comment explaining it was merged to
pre-release.
Releasing to main
When ready to release:
git checkout main
git merge pre-release
git push origin main
git tag vX.Y.Z
git push origin vX.Y.Z
This is done exclusively by maintainers.
Merge strategy note (to preserve “external” authorship)
If you want the git history to clearly show third-party authored commits, avoid “squash merge”. Prefer:
- Create a merge commit, or
- Rebase and merge (preserves individual commits/authors)
What to include in PRs
- A short description of the problem and solution.
- Screenshots for UI changes when possible.
- Notes about any breaking changes or migrations.
What not to include
- Large refactors mixed with feature work.
- Changes that reintroduce removed UI/UX patterns (for example, exporting offline cache files).
Internationalization (i18n)
QBZ ships 8 locales (en es de fr pt ru ja nl) as gettext .po files, bundled
via the qbz-i18n crate. Rules:
- No hardcoded UI strings in
.slint— every string goes through@tr("..."). - Adding or changing a string means updating all locale
.pofiles, not just English. @trproperty defaults are not reactive — re-seed from Rust on language change (seecrates/qbz-i18nandselect_bundled_translation(), called afterAppWindow::new()).
Checklist for PRs with UI Text
- No hardcoded strings in
.slint— all text via@tr - Every new/changed string updated across all 8
.polocales - Reused an existing string where one already fit