# Contributing to JellyTau

Thanks for looking. This file is the short version of how the project is built
and what has to be true before a change lands. The long version lives in
[CLAUDE.md](CLAUDE.md) and [docs/architecture/](docs/architecture/README.md),
which are maintained rather than decorative — read them before a structural
change.

## Getting set up

Package manager is **bun**. You will also need a Rust toolchain (the exact
version is pinned in [src-tauri/rust-toolchain.toml](src-tauri/rust-toolchain.toml)
— rustup honours it automatically) and the Tauri Linux dependencies.

```bash
bun install
bun run hooks:install    # do this once: it enables the pre-commit gates
bun run tauri dev
```

`hooks:install` points `core.hooksPath` at [scripts/hooks/](scripts/hooks/), so
hook updates arrive with a `git pull` instead of needing a re-install.

## What has to pass

Everything below runs in CI, and the fast half runs in the pre-commit hook. None
of it is advisory:

```bash
bun run check          # svelte-check — 0 errors
bun run test           # vitest
bun run format:check   # prettier
bun run lint           # eslint — 0 errors; the warning count is a ratchet
bun run check:boundary # no Jellyfin taxonomy in the frontend
bun run test:rust      # cargo test
cd src-tauri && cargo fmt --all && cargo clippy --all-targets -- -D warnings
cd src-tauri && cargo deny check          # advisories, licences, bans, sources
```

`bun run test:all` runs the whole set.

Several of these are **ratchets** — a number that only ever moves in the
improving direction:

| Ratchet | Where | Rule |
|---|---|---|
| eslint `--max-warnings` | [.gitea/workflows/build-and-test.yml](.gitea/workflows/build-and-test.yml) | only goes down |
| Coverage thresholds | [vitest.config.ts](vitest.config.ts) | only go up |
| Traceability coverage | [.gitea/workflows/traceability-check.yml](.gitea/workflows/traceability-check.yml) | only goes up |

Never relax one to make a build pass. Fix the thing it caught.

## The two rules that surprise people

**1. Bug fixes start with a failing test.** Write a test that reproduces the bug
and *watch it fail* before you touch the fix. A test written against
already-fixed code can pass for the wrong reason and guards nothing. If the logic
is trapped in a component, extract the pure part into a plain `.ts` module and
test that — see `episodeStrip.ts` or `TrackList.logic.ts` for the pattern.

**2. Domain vocabulary lives in Rust.** The frontend is presentation-only. It
must not encode Jellyfin's *taxonomy* — for example, the set of item types that
makes up a category like "Music". Send an opaque scope across the IPC boundary
and let the backend expand it. `bun run check:boundary` is a tripwire, not a
proof: it only flags item-type array literals, so a green run does not mean you
are clear. [docs/specs/scoped-search-boundary.md](docs/specs/scoped-search-boundary.md)
describes the leak that made this a rule.

## Traceability

Code that implements a requirement carries a `TRACES:` comment naming the
requirement IDs, and a tool builds the matrix from those comments:

```rust
/// TRACES: UR-005 | DR-001
```

Every ID must exist as a row in [docs/requirements.md](docs/requirements.md) —
`bun run traces:validate` fails on a typo or a stale rename. Internal helpers and
requirement-less code stay untraced; do not sprinkle IDs to raise the number.

If you add a requirement, add its row. If you implement one, tag the code.

## Commits and pull requests

- Conventional-commit subjects: `fix(player): …`, `feat(updater): …`, `ci: …`.
- Explain **why** in the body, not what the diff already shows. The commit log
  is the main record of why things are the way they are here, and it is used to
  draft release notes.
- One concern per commit. A formatting sweep and a behaviour change in the same
  commit is unreviewable.
- Rebase rather than merge-commit onto `master`.

## Specs

New features start from [docs/specs/SPEC-TEMPLATE.md](docs/specs/SPEC-TEMPLATE.md).
Its "Layer assignment" section is the point: each piece of logic gets placed in
Rust or the frontend *with a reason*. Review against
[docs/specs/SPEC-REVIEW-CHECKLIST.md](docs/specs/SPEC-REVIEW-CHECKLIST.md). Do
not frame a spec around "no Rust changes required" — correct placement is the
goal, not minimal backend churn.

## CI

CI is **Gitea Actions** (`.gitea/workflows/`), not GitHub.

🔴 **CI installs no system tools.** Every build, test and packaging tool must
already be in the Docker builder image. If a job needs a tool the image lacks,
add it to [Dockerfile.builder](Dockerfile.builder), rebuild and push the image,
and pin the new tag — do not `apt-get` it at job time. Details in
[docs/build/ci-operations.md](docs/build/ci-operations.md).

Fetching the project's own declared dependencies (`bun install`, cargo crates,
an advisory database) is not a toolchain install and is fine.

## Reporting bugs

Use the issue templates. For anything involving playback, include what the
platform was, whether the media was streaming or downloaded, and whether it was
transcoding — those three answers determine which of several code paths you were
actually on.

Security issues go to [SECURITY.md](SECURITY.md), not the tracker.
