0 m · surface

Your first contribution

From a fresh clone to a fixed bug in the engine in about ten minutes. No prior knowledge of the codebase, no system packages, nothing to configure.

35 m · sunlit zone

Why start with a failing test?

The hardest part of a first contribution is often finding something to work on. We skip that step by using the web-platform-tests (WPT): the conformance suite that all browser vendors share. Every failing test in it is a specific, already-agreed statement about something our engine gets wrong. You don't have to invent a task, and you don't have to guess whether the work is wanted.

All you need is Rust and git.

1. Set up the engine and the tests

Clone the engine, then clone the part of WPT we test against, pinned to the commit the engine is measured against. The WPT checkout is sparse, so it's a few hundred MB rather than several GB.

# The engine
git clone https://github.com/gosub-io/gosub-engine.git
cd gosub-engine

# A sparse wpt checkout (the wpt/ directory is gitignored)
git clone --filter=blob:none --sparse \
    https://github.com/web-platform-tests/wpt.git wpt
git -C wpt sparse-checkout set \
    resources common css/css-syntax css/css-values css/support css/reference
git -C wpt checkout "$(cat tests/wpt/wpt-commit.txt)"
export WPT_ROOT="$PWD/wpt"

# Run a component. The first build takes about a minute, the run about 30 seconds.
cargo run --release -p gosub-wpt -- "$WPT_ROOT" css/css-values

If the last command prints a table of directories with bars next to them, you're ready. The pass rates will be low. That's expected, and it's exactly why there's so much to pick from.

  css/css-values                 █░░░░░░░░░  192/4516    4.3%
  css/css-values/calc-size       ███░░░░░░░    38/120   31.7%
  css/css-values/urls            ███░░░░░░░    39/126   31.0%

The test runner, gosub-wpt, parses each test with our real HTML5 parser and runs its scripts against our real CSS engine. It isn't a full browser: there's no network and no window, which is what keeps it fast.

2. Find something to fix

Ask the engine what's worth picking up right now:

cargo run --release -p gosub-wpt -- "$WPT_ROOT" css/css-values --shortlist
  Partly passing, nearest to working first:
     94.3%  css/css-values/progress-invalid.html            33/35, 2 left
     71.4%  css/css-values/random-item-invalid.html         10/14, 4 left
     53.2%  css/css-values/tree-counting/calc-sibling...    25/47, 22 left

Take one from the top. A partly passing test means the engine already understands the feature and gets a detail wrong. That's an afternoon of work, not a project. If the list opens with a Crashes section, start there: a crash is a bug that a real web page could trigger.

Then run just that one file to see every failing assertion, and what the engine returned instead:

cargo run --release -p gosub-wpt -- "$WPT_ROOT" <the-file-you-picked>

3. A worked example

This is a real bug from September 2026. It has probably been fixed by now, but the method is the same every time.

The file css/css-values/viewport-units-parsing.html had 9 subtests passing and 15 failing:

FAIL e.style['width'] = "1svh" should set the property value
     - assert_not_equals: property should be set got disallowed value ""
FAIL e.style['width'] = "1lvmax" should set the property value
     - assert_not_equals: property should be set got disallowed value ""

Compare what passes with what fails. 1svw was accepted, but 1svh wasn't. So viewport units weren't missing altogether. Somewhere, a list included some of the spellings and left out others. Reading the results this way is the key step, and it works for almost every test.

Search for a value that works next to one that doesn't:

rg '"svw"' crates/gosub_css3/src/

That search leads to LENGTH_UNITS in crates/gosub_css3/src/matcher/syntax_matcher.rs. The list had svw, lvw and dvw, but none of the matching h, i, b, min and max units, even though the code that computes the final values already supported them. Adding the missing units fixed the file: 24 of 24 passing.

One rule: fix the behaviour in the engine itself. The gosub_domjs crate only exists so the tests can talk to the engine. Hard-coding an answer there would turn the test green without fixing anything.

4. Prove it

Run your file again. It should pass now. Then compare the whole suite against the committed baseline:

cargo run --release -p gosub-wpt -- "$WPT_ROOT" --all --expect tests/wpt/expectations-css.txt

This run fails, and that's intended. You'll see an UNEXPECTED PASS line for each subtest you fixed. The baseline file records exactly which tests pass, so every improvement to the engine has to update it. Regenerate the baseline:

cargo run --release -p gosub-wpt -- "$WPT_ROOT" css/css-syntax css/css-values \
    --write-expectations > tests/wpt/expectations-css.txt

Commit your fix together with the regenerated baseline. The change in the baseline shows that your fix works.

5. Open a pull request

make test          # fmt, clippy, smoke and unit tests

A few things we ask of every pull request:

  • Signed commits. Every commit must be signed before it can be merged. GitHub has a guide on setting this up.
  • Keep it small. Small PRs get reviewed and merged much faster. One to three commits is ideal, so squash where it makes sense.
  • Explain what you did. Add a short summary of what the PR changes and why. Nobody on the team knows every part of the codebase.

Where to go next

Run the shortlist again and take the next one. Once you're comfortable, the engine repository has more reading material:

Not into CSS parsers? That's fine too. See the contribute page for everything else we need help with, from documentation to security research.

80 m · open water

Stuck, or found something better to work on?

Tell us in the Zulip chat. We're happy to help you find your way around the engine.

- seafloor reached · resurface at ↑ 0 m -