Thanks for your interest in contributing! This document explains how to participate effectively.
This project follows the Contributor Covenant. By participating, you agree to abide by its terms.
| Contribution | Where |
|---|---|
| Report a bug | Issues → Bug report |
| Suggest a feature | Issues → Feature request |
| Add a new printer model | Issues → Plugin request — see also docs/plugin-development.md |
| Ask a question | Discussions |
| Improve documentation | PR directly |
| Security issue | See SECURITY.md (do not open a public issue) |
gh repo fork strausmann/label-printer-hub --clone
cd label-printer-hubBackend (Python):
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pre-commit installFrontend: see docs/architecture.md once finalised.
Branch naming follows Conventional Commits prefix:
| Prefix | Use for |
|---|---|
feat/ |
New feature |
fix/ |
Bug fix |
docs/ |
Documentation only |
refactor/ |
Code change without behaviour change |
test/ |
Test additions/changes |
chore/ |
Tooling, deps, CI |
perf/ |
Performance |
ci/ |
CI changes only |
Example: git checkout -b feat/ptouch-tape-detection.
This project follows Test-Driven Development. Every behaviour change requires:
- Write a failing test first
- Make the test pass with minimal code
- Refactor if needed
- Commit (Conventional Commits)
Run tests:
pytest # all
pytest tests/unit -v # unit only
pytest tests/integration -v # integration
pytest tests/hardware -v -m hw # hardware-in-the-loop (skipped by default)This is mandatory. We use semantic-release which generates version numbers and changelogs automatically from commit messages.
Format:
<type>(<scope>): <subject>
<body>
<footer>
Examples:
feat(printer-models): add Brother PT-D610BT plugin
Implements PrinterModel protocol for the PT-D610BT desktop label printer.
Tested with TZe-12mm and TZe-24mm tapes.
Closes #42
fix(queue): retry job no longer duplicates on race
Adds idempotency check before enqueueing the retry copy.
feat!: rename PrinterService.print() to submit_job()
BREAKING CHANGE: API consumers must update method calls. The new method
returns immediately with a JobId instead of blocking until print is done.
| Type | Effect on version | When to use |
|---|---|---|
feat |
minor (1.x.0) | New feature |
fix |
patch (1.0.x) | Bug fix |
perf |
patch | Performance improvement |
docs |
none (no release) | Docs only |
test, refactor, chore, ci, style |
none | Internal |
feat! or BREAKING CHANGE: footer |
major (x.0.0) | Breaking API change |
Scopes we use: printer-models, queue, status, api, ui, webhook, docker, ci, examples.
- Use the PR template — fill in all sections
- Link the issue with
Closes #<num>orRefs #<num> - Wait for CI to pass
- Address review comments
We squash-merge by default. The squash commit message must follow Conventional Commits — semantic-release will use it.
This project takes AI-assisted code review seriously as part of the standard PR workflow:
| Reviewer | What they look at | How to read their feedback |
|---|---|---|
| Maintainer (human) | Architecture fit, ADR compliance, hardware-specific behaviour, judgement calls | Authoritative — addresses or explains why not |
| Gemini Code Assist | Style, common pitfalls, security smells, language idiom — configured in .gemini/styleguide.md |
Take seriously — false positives exist but the signal is good. Reply inline if disagreeing |
| GitHub Copilot Chat | (When invoked on a PR) similar to Gemini, plus deep code context. Following .github/copilot-instructions.md |
Same — read every comment, reply or fix |
Workflow:
- Open the PR (draft is fine — push triggers reviews)
- Wait for CI green + AI reviews to land (usually within 1-2 minutes)
- Read every AI comment. Even when you disagree, write a brief reply explaining why — that builds the project's institutional memory of "we don't do X because Y"
- Address legitimate findings with a follow-up commit; the reviewers re-run on push
- Mark the PR ready for review when AI feedback is addressed
- The maintainer reviews next; squash-merge follows their approval
A PR is not ready to merge if:
- AI reviewers found issues that haven't been addressed or explicitly dismissed (with reasoning)
- CI is red
- The PR title doesn't follow Conventional Commits
- The PR description is missing the linked issue or hardware-impact note (where relevant)
One feature = one branch = one PR. Don't bundle unrelated changes — that defeats the review value. If you discover an unrelated bug while working on a feature, file a separate issue and a separate PR.
Before opening a PR, skim docs/learnings/code-review-patterns.md. It collects recurring findings from previous AI reviews so you can self-correct in advance. When an AI review surfaces a new recurring pattern that's likely to bite future PRs, add it to that file in the same follow-up commit.
Wait for the AI reviewers, even on small PRs. Gemini and Copilot post within ~1-2 minutes. Merging a small "trivial" PR with --admin before they've commented forfeits the review value and makes follow-up comments awkward to address. If a PR is truly time-critical (security fix, broken main), call it out explicitly in the PR description and own that you're skipping the review window — and address any post-merge comments in a follow-up PR.
Side-effects must be in the PR description. If a PR primarily fixes A but also changes B, list both in the description. Reviewers shouldn't have to discover changes by reading the diff line by line.
Want to add support for a new Brother model (or even a non-Brother printer)? Excellent.
- Open a Plugin request issue so others know it's in progress
- Read
docs/plugin-development.md - Implement
app/printer_models/<your_model>.pyagainst thePrinterModelprotocol - Add tests in
tests/unit/printer_models/test_<your_model>.py - If you have hardware: add an integration test under
tests/hardware/ - Open a PR — title
feat(printer-models): add <Model> plugin
We try to verify each plugin against real hardware before merging when feasible. If you're contributing a plugin for hardware we don't have access to, we'll merge based on tests + your hardware confirmation.
Before opening a PR, run from backend/:
ruff check . # linting (blocking in CI)
ruff format --check . # formatting (blocking in CI)
mypy app/ # type checking (blocking in CI — strict mode)
pytest # tests (blocking in CI)The CI workflow runs the same. All four are hard gates — no warn-only:
ruff checkandruff format --checkreject style/lint issuesmypyruns in strict mode (configured inpyproject.toml[tool.mypy] strict = true); type-check failures fail the buildpytestrequires all tests pass; coverage floor is 80% (configured inpyproject.toml)
If you want to bypass a gate locally during exploration, use mypy --no-strict-optional etc., but the merged code must pass strict checks.
PRs that fail any check won't be reviewed until green.
Releases are scheduled, not on every merge. Merging to main does NOT publish a release — it only bundles changes for the next scheduled release window.
| Trigger | When |
|---|---|
cron: '0 4 * * *' |
Nightly at 04:00 UTC. semantic-release analyses commits since the last tag and publishes if there's anything releasable, otherwise skips silently |
workflow_dispatch |
Manual button in the Actions tab. Optional dry-run input lets a maintainer preview what would be released without publishing |
Behind the scenes, semantic-release reads the Conventional Commit messages on main since the previous tag, decides the next semver version, generates the CHANGELOG.md entry, creates the Git tag and GitHub Release, and triggers the Docker image push to GHCR and Docker Hub.
A scheduled run only skips publishing when there are no releasable commits since the last tag. semantic-release evaluates the entire history since the previous release, not just "today's" commits. If a feat, fix, or perf commit is already on main from an earlier merge, the next scheduled run will publish — adding a chore or docs commit on top doesn't suppress that. To genuinely skip a release window, the only options are: rely on there being no releasable commits, or postpone merges that would bump the version.
Maintainers do not manually tag releases. If a hotfix needs to ship before the next scheduled window, use the Run workflow button on the Release workflow in the Actions tab. The dry-run input lets a maintainer preview which version would be published before committing.
Please remember Brother trademarks are the property of Brother Industries, Ltd. (see README). Don't add wording that suggests endorsement, partnership, or affiliation with Brother Industries, Ltd.