Files
yellowjacket/CONTRIBUTING.md
T
yonluandClaude Opus 5.5 792c2d9fbc
CI / check (push) Skipped
CI / e2e (push) Skipped
CI / check (pull_request) Successful in 8m21s
CI / e2e (pull_request) Successful in 15m56s
build: require Go 1.26 everywhere at once
The newest go-json-experiment/json, which wails/v3's application
package imports, declares go 1.26, so taking it raises our go
directive with it.  Every other place that names a Go version moves in
the same commit: GO_VERSION in ci, desktop-assets and android-apk, the
Arch makedepends, and CONTRIBUTING's table.

index-artifact's container image moves too, and it is the one that
matters most.  The official golang images set GOTOOLCHAIN=local, so a
golang:1.25 container refuses a go 1.26 module outright rather than
fetching a newer toolchain, and that job owns the ~205 GB checkpoint.

Closes #265

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017HJiuc3ZZhxsPXz3ozTirT
2026-09-26 17:00:47 -04:00

7.9 KiB

Contributing to YellowJacket

This is the contributor's half of the README: how to build it, how to check a change, and how a change gets in. 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 with a Lit/TypeScript frontend, bridged by Wails v3.

Tool Version
Go 1.26+
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:

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.

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/<platform>/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=<name> for a seeded library, built by make sandbox-seed NAME=<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 <terms>. "That was fixed three weeks ago" is the cheapest possible answer.
  • Claim before the first edit, not before the commit: ./scripts/issue.sh claim <n> 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 — 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

<why>

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