# Contributing to Punktfunk

Thanks for your interest in contributing!

## Licensing of contributions (inbound = outbound)

Punktfunk is dual-licensed under **MIT OR Apache-2.0**.

> Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in
> the work by you, as defined in the Apache-2.0 license, shall be dual licensed as **MIT OR
> Apache-2.0**, without any additional terms or conditions.

By opening a pull request you agree to license your contribution under these terms. This is the
standard Rust-ecosystem "inbound = outbound" model; it keeps the project's licensing unambiguous
(including the Apache-2.0 §5 contributor patent grant) and any future relicensing clean. You retain
the copyright to your contributions.

### Do not paste copyleft (or otherwise incompatibly-licensed) code

The single thing that could poison the permissive license is **copied source from a copyleft
project**. Several adjacent projects (Sunshine, Apollo, Moonlight) are GPL-3.0. You may study them
and reimplement a *technique*, protocol, or wire format — those are not copyrightable — but **never
paste their code**, and do not translate a GPL implementation line-by-line. When a comment credits
prior art, make clear it is an independent reimplementation, not a copy. The same applies to any
third party's code under a license incompatible with MIT/Apache.

If you add a new third-party dependency, it must be permissive (MIT / Apache-2.0 / BSD / ISC / Zlib /
Unicode-3.0 / etc.). `about.toml` holds the accepted-license allow-list; regenerate the attribution
file with `scripts/gen-third-party-notices.sh` when the dependency tree changes.

## Prerequisites

The Rust toolchain is **pinned exactly** in `rust-toolchain.toml`; rustup installs it for you the
first time you build, so don't override it — a different rustc reformats files nobody touched.

The workspace links real system libraries, so a bare `cargo build --workspace` fails on a stock
machine. The authoritative list is what CI installs, in `ci/rust-ci.Dockerfile` — on **Ubuntu 26.04**,
which is what gets you FFmpeg 8:

```sh
sudo apt install build-essential clang libclang-dev pkg-config cmake \
  libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavfilter-dev libavdevice-dev \
  libpipewire-0.3-dev libopus-dev libwayland-dev libxkbcommon-dev \
  libgl-dev libegl-dev libgbm-dev \
  libgtk-4-dev libadwaita-1-dev libsdl3-dev \
  libvulkan-dev
```

(The last two groups are the Linux client shell and the Vulkan session presenter; skip them only
if you never build those crates. `libvulkan-dev` is for the LOADER's pkg-config/soname — ash
dlopens it, and the client links no FFmpeg at all, so no libav*-dev appears here.
`scripts/bootstrap-ubuntu.sh` sets up an Ubuntu **capture-test host** — NVIDIA, Sway, PipeWire —
and is not a substitute for the list above.)

## Before you push

Enable the repo git hooks once per clone — they run the exact rustfmt gates CI runs (main
workspace + the UMDF driver workspace) on every commit and push, so a push can never fail CI
on formatting alone:

```sh
git config core.hooksPath scripts/git-hooks
```

Then the usual full pass. Use `--locked` as CI does — otherwise a silent `Cargo.lock` update can pass
locally and fail CI:

```sh
cargo fmt --all --check
cargo clippy --workspace --all-targets --locked -- -D warnings
cargo test  --workspace --locked
```

Two more gates that only apply to some changes:

- **Touched `web/` or `docs-site/`?** CI builds and typechecks both. Run, in that directory:
  ```sh
  bun install && bun run build && bun run lint
  ```
  Build first — it generates the API client / MDX typegen that the typecheck imports.
- **Touched Windows- or Linux-gated code from another OS?** `scripts/xcheck.sh windows` (or
  `linux`) type-checks and lints that platform's `#[cfg(target_os = …)]` code in about a second,
  instead of waiting for the CI job that compiles it.

Generated artifacts are checked in. `include/punktfunk_core.h` (cbindgen) is regenerated by the build
and CI fails if the committed copy drifts. `api/openapi.json` is gated the same way: the `rust` job
regenerates the spec and diffs it against the committed file, and the `docs-drift` job checks that
`docs-site/public/openapi.json` — the snapshot the docs site serves — is a byte-for-byte copy of it.
Touch the management API and CI stays red until you regenerate and re-copy:

```sh
cargo run -p punktfunk-host -- openapi > api/openapi.json
cp api/openapi.json docs-site/public/openapi.json
```

## Where facts live (docs vs READMEs vs website)

Every user-facing fact has exactly one canonical home; everything else links to it. Duplicated
walkthroughs are how the docs drifted before — don't add new ones.

| Surface | Owns | Never contains |
|---|---|---|
| [docs-site](https://docs.punktfunk.unom.io) (`docs-site/content/`) | All user-facing facts: install, config, features, troubleshooting | Design rationale |
| READMEs (root, `packaging/*`, `scripts/*`) | Dev/packager rationale and pointers into the docs | User walkthroughs duplicated from docs-site |
| [punktfunk.unom.io](https://punktfunk.unom.io) (separate repo) | Marketing, downloads, blog | Instructions — it deep-links the docs instead |
| punktfunk-planning (private) | Design rationale, RFCs, plans | Anything user-facing |

Docs pages are written for one of two audiences, not both at once: the **get-started track**
(quickstart, install, pairing — short, one task per page, happy path only) assumes no Linux
expertise; the **reference track** (configuration, CLI, API, per-compositor pages) is allowed to be
dense. When a change touches a user-facing fact, update the docs-site page that owns it in the same
PR.

CI enforces the cheap half of this (`scripts/ci/check-docs-drift.sh` and `check-docs-links.sh`):
the OpenAPI snapshot must match `api/openapi.json`, the docs-site copy of `data/platforms.json` must
match the canonical one, `scripts/install.sh` must carry the file's install lines verbatim, every `PUNKTFUNK_*` variable the docs mention
must still exist in the tree, the counts of undocumented `PUNKTFUNK_*` variables and undocumented
`punktfunk-host` subcommands may never grow (document the new knob, or consciously raise the
baseline in the script), and internal docs links must resolve.

Match the surrounding code's comment density and naming. Commit messages end with the
`Co-Authored-By` trailer (see `git log`).

See the [README's Build & test section](README.md#build--test-from-source) for the extra dev
commands (the FEC loss harness, the standalone C-ABI proof) and
[Design invariants](README.md#design-invariants) for the rules a change is expected to hold to, and
the [docs site](https://docs.punktfunk.unom.io) for architecture and per-platform guides.
