Files
yellowjacket/CONTRIBUTING.md
logan 1c08d8db90
CI / check (push) Skipped
CI / e2e (push) Skipped
CI / check (pull_request) Successful in 2m58s
CI / e2e (pull_request) Successful in 10m7s
docs: split the README into a landing page and CONTRIBUTING
The README was two documents in one, and neither reader was served by
the other's half. It opened on a feature list, then spent its second
half on Go versions, WebKitGTK packages and `make` targets — while its
install table named a `darwin-universal.app.zip` and a
`windows-amd64.exe` that nothing has ever produced, and its header
claimed Windows and never mentioned Android, which is the one platform
with a published, self-updating channel.

So this is a correctness pass as much as a friendliness one. The README
now answers a user's questions only: what the app is, three screenshots
from the seeded fixture library so anyone can retake them, the four
formats, one install section per channel that names what is actually
published, first run, where the data lives, and pointers out. The
version-restart note is linked to the two documents that own it rather
than copied, because a copy is a second thing to keep true.

CONTRIBUTING.md takes the technical half: prerequisites, the system
libraries, the build and codegen commands, which verification tier a
change demands, the tracker workflow, the commit grammar and the style
rules. CLAUDE.md is unchanged apart from one paragraph naming the split
— it was already the deep reference both of the others point at, and
stays the only one of the three that explains why a shape is what it is.

Closes #50
2026-08-26 03:37:13 -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.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:

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 Commitstype(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