Contributing
This page is short. The repository holds the technical depth, and duplicating it here means one of the two goes stale.
Where things are
labdash is built and maintained by Giancarlos Isasi.
Building it
Go 1.26 or newer. No CGO, no code generation step, and no other tool required. make on its own lists the targets.
The fastest way to see whether a change to the appearance worked:
Running the tests
Every feature carries an identifier, and its test rows are written before the code that satisfies them. A test function's name embeds the identifier, so a failure names the feature: TestTHM07_T2_EveryGlyphIsOneSafeCell. Grepping for THM-07 finds the feature, its tests, its screen and its page.
Golden tests cover each screen in four states: populated, empty, error and narrow, and every screen is recorded at each colour tier and in both font modes.
Golden files
A golden file is a screen, recorded byte for byte, colour included. It lives at testdata/golden/<name>.golden beside the package that draws it.
Three rules make this worth having:
- A golden diff is reviewed like code. It is a screen changing. If you cannot say what changed and why, do not accept it.
- CI never passes
-update. A golden that no longer matches fails the build; it does not rewrite itself. A workflow that gains the flag fails its own check. - Every golden test fixes its width. A test that reads the real terminal's size is a flake waiting for a different CI runner.
Colour is inside the file on purpose. A colour regression is the hardest kind to spot by eye and the easiest to catch by diff, and the failure message escapes the sequences so the diff is readable.
Determinism
Nothing calls time.Now directly. A Clock is threaded through, and tests inject a fixed one, which is what lets a frame containing a timestamp live in a golden file at all. Anything else that would vary between runs has to be injected the same way — a -update producing a large diff for no reason is the signal that something was not.
The fake GitLab
No unit test needs a real instance. internal/testsupport/fakegitlab is an in-process server that answers the GraphQL endpoint, the four REST endpoints labdash uses, and GET /version:
It is a scenario builder rather than a cassette player, because latency, failure, rate limiting and pagination are what most of the tests are about, and none of the four is expressible in a recorded response body. Bodies still come from fixtures, for shape fidelity.
The credential guard
Every package's TestMain runs two suite-wide checks:
goleak, so a goroutine that outlives its test fails the package that leaked it.- The credential-shape scan, over everything the test binary wrote to stdout and stderr and over every file under
testdata. It looks for GitLab's prefixed tokens, 64-character hex,Bearercredentials and thePRIVATE-TOKENheader, and it fails the package that produced one.
The guard reports the location and the length, never the value, because a failing build log is public. It exists because a test leaked a real token into its own output once already.
The documentation site
The site is Rspress v2 in labdash/website/:
pnpm run build passing is the primary success criterion. It catches broken links, missing navigation entries, invalid frontmatter and failed MDX imports, which are the four ways a documentation site rots.
Two conventions worth knowing:
- Second person, present tense, active voice. "Press r to refresh this section", never "the section can be refreshed by the user".
- Numbers, not adjectives. "one request for a whole group tree, rather than one per project", never "blazing fast".
VOICE.md in that directory holds the rest, and it is binding on every page.
Two things on this site are generated from the Go source: the keyboard reference, from labdash keys --markdown, and the per-instance key table on instances. Do not hand-edit either; CI regenerates them and fails on a diff. Every other page is written by a person.
Review checklist
Each of these is a bug that has already happened somewhere:
- No colour literal outside
internal/tui/theme. CI enforces it. - Every state has a glyph, a colour and a word. Colour alone fails for colour-blind readers, at
NO_COLOR, and in every screenshot. - Unicode and ASCII renderings have identical column widths. Every glyph is one cell, every fallback is one character, and no glyph is East Asian Wide or from a private-use area.
- Contrast is computed, not eyeballed.
- No token in a log, an error string, or a URL. Tests print token lengths, never values.
- Every subprocess sends stdout and stderr to
io.Discard. Output leaking into a full-screen application corrupts the frame. - The documentation ships with the change. The pages a change touches are part of the same change.
No real project name, username, hostname, group path or token appears anywhere in the code, the tests, the demo data or these pages. Every example on this site uses fictional projects, and demo data comes from a fixture generator.
Proposing a change
Open an issue that names the behaviour you want and the screen it belongs on. A proposal that can name the feature identifiers it touches is easier to review, and labdash keys --list prints every built-in action name.
Three kinds of help are worth more than code:
- Testing on hardware nobody here has. macOS and Linux credential paths, and any self-managed instance.
- Terminal reports. The support matrix is marked tested only where somebody ran it.
detailedMergeStatusvalues in the wild. A blocker that renders as a title-cased raw enum is a mapping gap, and the exact value is what fixes it.
Rules on this site have their reason written next to them. If one looks wrong, argue with the reason.