diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b6e1e06 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,163 @@ +# Contributing to DarkRoom + +There is a lot of documentation here — 14 documents and 177 numbered +requirements — and almost all of it is written for someone who has already +decided to work on this. This file is the other thing: how to get a first +change landed without reading any of it. + +## The shortest useful contribution + +**A develop operation is one file.** Not one file plus a registration, plus a +shader edit, plus a control in the UI — one file: + +``` +core/dr-pipeline/ops/split_toning.yaml +``` + +`build.rs` finds it with `read_dir`, compiles it into Rust implementing +`Operation`, and from there it is indistinguishable from a hand-written node. +It arrives with controls built from its declared parameter kinds, a place in +the chain from `order:`, a place in the panel from `attributes:`, sidecar +persistence, and its own tests — which are declared in the same file and run +under `cargo test`. + +Read [`core/dr-pipeline/ops/README.md`](core/dr-pipeline/ops/README.md) and +copy [`exposure.yaml`](core/dr-pipeline/ops/exposure.yaml). Split toning, +colour zones, selective colour and channel-mixer variants are all pure point +operations, which means all of them are declarations rather than code. + +If you want to understand one thing about the architecture before starting, +make it this: **the core describes its capabilities and the interface composes +them.** No code in `ui/` names an operation, and a test enforces that +(`ui/dr-ui/tests/ui_names_no_operation.rs`). It is why your node needs no UI +change. + +## Getting it to build + +**Git LFS is required.** Model weights are stored in LFS, and a clone made +without it leaves a ~130-byte text pointer where an 11 MB model should be: + +```bash +git lfs install && git lfs pull +``` + +Forget this and `dr-segment`'s build script stops with an instruction rather +than embedding the pointer and failing at inference time — but it is easier to +run the two commands now. + +**The toolchain pins itself.** `rust-toolchain.toml` selects 1.92.0 and rustup +fetches it on first use. Do not override it; `cargo fmt` and `clippy` are both +version-sensitive and CI runs exactly this version. + +**System packages.** Slint and winit need these at build time. On Debian or +Ubuntu: + +```bash +sudo apt-get install pkg-config libfontconfig1-dev libxkbcommon-dev +``` + +**Then:** + +```bash +cargo run -p darkroom-desktop +``` + +The first build resolves 826 crates and takes a while — on a laptop, long +enough to look like a hang. It is not one. + +Android is a containerised toolchain and is not needed for most work; see +[`docker/android/README.md`](docker/android/README.md) if you get there. + +## What CI will check + +All four of these run on every push, so run them before you send anything: + +```bash +cargo fmt --all -- --check +cargo clippy --workspace --all-targets -- -D warnings +cargo test --workspace +cargo build --workspace --release +``` + +GPU tests skip themselves where there is no adapter rather than failing — a +test that cannot run is not evidence either way — so a green run on a machine +without a GPU is expected, and does not mean the GPU paths were exercised. + +## Requirements and traceability + +[`requirements.md`](docs/requirements.md) is the register of record. +[`traceability.md`](docs/traceability.md) is generated from `TRACES:` tags in +the source and must never be hand-edited: + +```rust +// TRACES: FR-DEV-3a | FR-DEV-3c +``` + +Tags are read from `.rs`, `.slint`, `.wgsl` and `.yaml` — the last so a +declared operation can record the requirement it satisfies, since the Rust it +generates lands in `OUT_DIR` and is not scanned. + +A pre-commit hook regenerates the matrix and stages it whenever you touch +something that can carry a tag, so you should not have to think about it. If +you do need to run it by hand: + +```bash +cargo run -p traceability -- report +``` + +Note that it tracks line numbers, so a change that only moves code still moves +the matrix. Never regenerate it with a stale prebuilt binary. + +**One convention that the tooling cannot enforce.** A tag proves that a tag +exists, not that the code under it does the thing — `docs/code-health.md` +CH-4 has the details, and two requirements currently read as covered on the +strength of plumbing a future feature would use. So: **close a requirement +with a test that would fail if the behaviour were removed.** Coverage that +moves slowly and means something beats coverage that moves quickly. + +## Two invariants the build defends + +Worth knowing before you trip one, because both failures name a requirement +rather than a line: + +- **No operation may be named in `ui/`** (FR-DEV-3a). Special-casing one + operation in the panel to fix a layout problem is how a generated interface + stops being generated. If a node needs presentation the panel cannot give it, + the answer is a `presentation:` hint in the declaration and a `WidgetKind`, + not a branch in `develop.rs`. +- **The operation schema rejects ambiguity at build time**: a duplicate + `order:`, a filename disagreeing with its `id:`, a default outside its own + range, an expression naming something that is not a parameter. Each error + names the key you got wrong and exits rather than panicking. + +## Commit messages + +Imperative subject describing the change from the reader's side — "Offer the +merge when two people turn out to share a name", not "fix: merge dialog". No +conventional-commits prefixes. + +The body is where the reasoning goes, and it is expected to be substantial when +the change is. This codebase records *why* far more than most, in commits and +in comments alike, and that is the single habit most worth adopting: the +constraint you worked around is invisible to whoever reads the diff next. + +One commit per change. If you fixed two things, that is two commits. + +## Where to read next, in order + +| Document | Read it when | +|---|---| +| [`core/dr-pipeline/ops/README.md`](core/dr-pipeline/ops/README.md) | Adding or changing a develop operation — start here regardless | +| [`docs/architecture.md`](docs/architecture.md) | Anything touching the render path, catalog or sync | +| [`docs/code-health.md`](docs/code-health.md) | Deciding what to work on; grades each seam by what it costs | +| [`docs/technical-debt.md`](docs/technical-debt.md) | Something looks wrong — check it was not chosen | +| [`docs/requirements.md`](docs/requirements.md) | Reference, not reading | + +`technical-debt.md` is the one to check before "fixing" anything surprising. +It records compromises that were deliberate, each with the reasoning and a +falsifiable condition for when it stops being one — the point being that you +can tell a constraint from an accident without asking. + +## Licence + +GPL-3.0-or-later. By contributing you agree your work is licensed the same way. diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..82aa8c7 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,23 @@ +# Pinned so CI, the Android image and a contributor's laptop agree. +# +# `build-and-test.yml` already installs exactly 1.92.0 and says why: a floating +# toolchain turns an unrelated push into a mystery failure. That reasoning +# applies with more force locally than in CI, because the two checks most +# sensitive to the compiler version are the two that gate every push. +# +# `cargo fmt --all -- --check` is the sharp one. rustfmt's output changes +# between releases, so a contributor on a newer stable can reformat a file in a +# way this CI rejects — a diff nobody wrote, on a line nobody touched. +# `clippy -D warnings` is the same story with new lints. +# +# `rust-version = "1.92"` in the workspace manifest is a *minimum* and stays +# where it is; it produces a clear error on an older toolchain rather than a +# confusing one. This file is the other half: an upper bound as well as a lower +# one, so everyone compiles with the same compiler rather than merely a +# sufficient one. +# +# rustup reads this automatically and will fetch the toolchain on first use. + +[toolchain] +channel = "1.92.0" +components = ["rustfmt", "clippy"]