# Contributing Thanks for taking an interest. Bug reports and small focused PRs are the most useful things you can send. ## Before you write code - **Bug fix** — just open a PR. Link the issue if there is one. - **New feature** — open an issue first so we can agree on the shape before you spend an evening on it. Check [the roadmap](docs/wiki/Roadmap.md); the built-in player is already planned, and a half-built version of a planned feature is harder to merge than nothing. - **Issues are labelled automatically** — a type and an `area/*` label, plus `needs-info` or `possible-duplicate` when they fit. A maintainer reads every issue and corrects the labels where needed. - **New integration** (indexer, download client, media server) — welcome. Copy the shape of an existing one in `internal/indexer/`, `internal/download/` (e.g. `deluge.go`) or `internal/mediaserver/`. ## Setup Requires Go >= 1.27.3 (the `go` directive in `go.mod`), Node >= 24.22.2, [pnpm](https://pnpm.io) 12 (the `packageManager` in `package.json`) and [Task](https://taskfile.dev). Nix users get all of it, at exactly those versions, from the flake devshell (`nix develop`, or direnv). ```bash git clone https://github.com/datahearth/streamline.git cd streamline pnpm install --frozen-lockfile task # builds frontend assets + the binary ``` The frontend is `//go:embed`-ed into the binary, so `task build:go` depends on `task build:js` and `task build:css`. Always build through Task — raw `go build` / `go test` / `pnpm exec` skips steps that CI does not. For development: ```bash streamline config init --output ./tmp/config.yaml # or ./streamline config init … task dev # live reload via air, serves http://localhost:8080 ``` ## The loop ```bash task lint # golangci-lint + biome + svelte-check task fmt # golangci-lint fmt + biome format --write + nixfmt task test # all suites task test:unit -- ./internal/metadata/... # one package ``` CI runs `task test` and all three linters: `task lint:go`, `task lint:frontend` and `task lint:ts` (svelte-check, with warnings counted as failures, since nothing else type-checks the Svelte app). If `task lint` and `task test` pass locally, the PR should be green. ## Code generation Generated files are committed. Regenerate and include the result when you change an input: | Changed | Run | | ------------------------------ | -------------------------------------------- | | `api/openapi.yaml` | `go tool oapi-codegen --config api/oapi-codegen.yaml api/openapi.yaml` | | `ent/schema/` | `go generate ./ent`, then `task migrate:diff -- ` | | a mocked interface | `go tool mockery` | | any of the above | `task generate` does all three | `api/openapi.yaml` is the source of truth for the REST API — write the spec first, then implement the generated handler interface. ## README screenshots The screenshots in `docs/assets/` come from one command: ```bash task docs:screenshots # all twenty task docs:screenshots -- -only movies,books # some of them task docs:screenshots -- -keep # leave the instance up afterwards ``` It boots a throwaway instance under `$TMPDIR/streamline-demo`, adds a demo library of public-domain films, television and books and Creative Commons music, grabs it through a fake indexer and a fake qBittorrent, imports the finished releases from files ffmpeg makes, files the requests and a library import, and shoots each page. It needs `ffmpeg`, a Chromium (`CHROME_PATH` or on `PATH`) and the TMDB, TVDB and Hardcover keys as `STREAMLINE_METADATA__TMDB_API_KEY`, `STREAMLINE_METADATA__TVDB_API_KEY` and `STREAMLINE_METADATA__HARDCOVER_API_KEY`. A run takes about fifteen minutes, most of it MusicBrainz's one request a second; the first also encodes the video clips it reuses. Retake them when a change shows on any of the pages, and read every image before committing them. What the demo holds and the two values it stages are described in `e2e/demoshots/main.go`. ## Conventions The full house style lives in [CLAUDE.md](CLAUDE.md) — it is written for AI agents but it is an accurate description of how the codebase is put together (logging, observability, config-backed resources, auth, frontend). Worth skimming before a larger change. The short version: - **Go**: tabs, `slog.XContext(ctx, …)` for logging (no logger plumbing), a span per service operation via the package `tracer`, no named returns, no ignored errors. - **Tests**: Ginkgo + Gomega. One `_test.go` per source file, in the same package. Every top-level `Describe` needs a `Label("unit"|"integration"|"e2e")`, or `task test:unit`, `test:integration` and `test:e2e` silently skip it. - **Frontend**: Svelte 5 + TypeScript (`