# Contributing to YellowJacket This is the contributor's half of the [README](README.md): how to build it, how to check a change, and how a change gets in. [`CLAUDE.md`](CLAUDE.md) is the deep reference — the architecture, and the reasons behind the shape of it — and is worth reading before a change of any size, because most of this codebase's traps are written down there and nowhere else. ## Building from source YellowJacket is [Go](https://go.dev/) with a [Lit](https://lit.dev/)/TypeScript frontend, bridged by [Wails v3](https://wails.io/). | Tool | Version | |------|---------| | Go | 1.25+ | | Node.js | 22+ | | pnpm | 10+ | | Wails CLI | v3 — vendored, no install needed (`go tool wails3`) | The Wails v3 CLI resolves from the `tool` block in `go.mod`, so there is nothing to install globally; `make setup` fetches it with the rest of the tooling. On Linux, install the system libraries Wails needs. v3 builds against GTK4 + WebKitGTK 6.0 by default: ```bash sudo apt-get install libasound2-dev libgtk-4-dev libwebkitgtk-6.0-dev # Debian/Ubuntu sudo pacman -S alsa-lib gtk4 webkitgtk-6.0 # Arch ``` A machine without `webkitgtk-6.0` can still build with `-tags gtk3` against the older WebKit2GTK 4.1 stack, but that is an escape hatch, not what CI or a release builds. macOS and Windows need no extra system packages. Run `go tool wails3 doctor` to check your environment. ```bash make setup # install tooling, frontend packages and the git hooks make dev # run with hot-reload make build-dev # debug build with symbols make build-prod # production build (stripped and trimmed) make android # the arm64 APK, into bin/ ``` The `Makefile` is the front door and carries a one-line description against each target; `Taskfile.yml` and `build//Taskfile.yml` are the build implementation behind it and are not called directly. ## Generated code Two generators run from `go generate ./...`, which `make generate` wraps: **sqlc** turns `backend/database/sql/queries/` into Go in `backend/database/sql/sqlcgen/`, and **templ** turns `.templ` files into `*_templ.go` beside them. Never edit either output by hand — run `make generate` after touching a `.sql` or a `.templ` file. The TypeScript bindings in `frontend/bindings/` are generated by `wails3` rather than by `go generate`, so they are a separate step: `make bindings` regenerates them and `make bindings-check` fails if they are stale. `frontend/src/events.ts` is generated too, from `backend/events/events.go`. A pre-commit hook checks that all of this is fresh, so the usual way to meet it is a failing commit rather than a bug. ## Checking a change Run the tier the change actually demands, not the cheapest one. | Change | Command | |---|---| | Go | `make lint` and `make test` — both cover all three build configurations | | A frontend component or store | `make ui-test` (Vitest in a real Chromium, no backend) | | A user-visible flow | `make e2e`, against a running `make dev-headless` | | CSS | `make css-check` — see the Chrome 113 note below | | Anything cosmetic | look at a screenshot; several bugs here were invisible to every assertion and obvious in an image | `make test` needs the fixture library, which is generated rather than committed — it runs `make testdata` itself (about a second). The end-to-end tier drives the real app with no display at all: `make dev-headless` starts it in the background on `:34115` (add `SEED=` for a seeded library, built by `make sandbox-seed NAME=`), `make dev-logs` tails it and `make dev-stop` stops it. **Check the port before starting one** — if `:34115` is already answering, someone else's app is there, and a green result about their build is worse than no result. Two smaller checks exist because the failure they catch is silent: `make bindings-check` (stale generated bindings) and `make css-check`, which is two passes — one fails on a `css` literal ended early by a backtick inside a comment, the other on a nested CSS rule that begins with a bare element selector. Chrome 113 is what the reference Android device renders with, and it drops such a rule without a word. `make vulncheck` runs govulncheck over the module. ## The issue tracker is the source of truth Work is described by issues before it is described by branches, and the tracker is shared with people who cannot see your terminal. - **Search before starting**, closed issues included: `./scripts/issue.sh search `. "That was fixed three weeks ago" is the cheapest possible answer. - **Claim before the first edit**, not before the commit: `./scripts/issue.sh claim ` sets the assignee, applies `Status/In Progress` and comments with the branch, so the work is visibly taken *while it is being done*. It refuses if somebody else holds it — talk to them rather than working around it. - **If no issue covers the work, open one first** (`./scripts/issue.sh new`). - **Findings get filed.** A bug tripped over on the way to something else is an issue with a reproduction, not a wider diff and not a sentence in a chat log. - **#73 is the roadmap** and states the order the backlog should be worked in. `scripts/issue.sh` is the whole interface (`list`, `mine`, `search`, `show`, `new`, `claim`, `unclaim`, `comment`, `close`, `label`, `depends`, `labels`) and wants a `GITEA_TOKEN` with `write:issue`. The labels are a taxonomy rather than tags: `Kind/*`, `Area/*`, `Priority/*`, `Platform/*`, plus `Reviewed/*` and `Status/*`, of which the last two are exclusive scopes. ## Commits and pull requests `main` is protected, so a branch and a PR are the only way in. Branch from `origin/main`, and name the branch after the issue (`fix/140-…`, `feat/25-…`). Commit subjects are [Conventional Commits](https://www.conventionalcommits.org/) — `type(scope): subject`, imperative, ≤72 characters — and are enforced by a `commit-msg` hook and by CI (`make commit-check`). This is load-bearing rather than decorative: semantic-release reads the **type** to decide the next version, so a CI-only change is `ci:` and never `fix(ci):`, which would ship a patch release. `make release-dry` prints what a release would cut right now. **The closing keyword goes in the commit body**, one issue per line, because Gitea parses commit messages that reach `main` and does not parse the PR body: ``` docs: rewrite the README as a landing page Closes #50 ``` A PR body carries a commit-to-issue table, the verification you actually ran (with results), and a `Closes` list for whoever reads it. ## Style - **Go** — golangci-lint v2, strict: `err113` (static errors), `nlreturn`, `wsl_v5`, `godot`, `sloglint`, `perfsprint`, and imports grouped stdlib → third-party → `yellowjacket/…` by gci. - **TypeScript** — strict mode, no implicit `any`, no unused locals or parameters. - Match the surrounding code. Where `CLAUDE.md` explains why something is shaped the way it is, that shape is load-bearing and there is usually a test pinning it. Hooks do most of the enforcing (`lefthook.yml`, installed by `make setup`): pre-commit runs vet, lint, the codegen checks, the frontend typecheck and the two CSS checks in parallel; pre-push runs the Go suite and the UI tier, deliberately one after the other rather than together. ## Where the rest of the documentation is - [`CLAUDE.md`](CLAUDE.md) — architecture and constraints, in depth. - [`docs/PROFILING.md`](docs/PROFILING.md) — Go pprof and frontend profiling. - [`docs/android-release.md`](docs/android-release.md) — the APK, its signing key, and what the release workflow checks. - [`docs/index-cache.md`](docs/index-cache.md) — the search-index build cache and why it has a snapshot. - [`packaging/arch/README.md`](packaging/arch/README.md), [`packaging/homebrew/README.md`](packaging/homebrew/README.md) — the two package channels. - `.planning/` — design documents and measured history, not a queue. The queue is the tracker.