Project 8: An Upstream Contribution
2–4 weeks · ●●●●○ · touches everything, plus code review by strangers
The only project here whose acceptance criterion is decided by someone else. That is the point.
1. The Problem
You have built a terminal, and you understand the domain. That understanding is now worth something to projects with real users — but a private repository helps nobody, and code that has never been reviewed by a maintainer has never been tested against the standard that matters.
2. Why It Is Hard
Not technically. Socially and procedurally.
| Problem | Detail |
|---|---|
| Finding the right issue | Too small is trivial; too large will not be merged from a stranger |
| Their code, not yours | You must match a style, an architecture, and a set of constraints you did not choose |
| Maintainers are busy | A PR that requires them to teach you the codebase will sit |
| Terminals are conservative | A compatibility regression affects thousands of people; the bar is correctly high |
| Review is uncomfortable | Someone will tell you your approach is wrong, in public, and they may be right |
| Latency | Weeks between rounds is normal. Plan for it rather than being surprised. |
3. Choosing the Contribution
The strong candidates
| Project | Language | Good contributions | Why it is approachable |
|---|---|---|---|
vte (Alacritty's parser) | Rust | Parser edge cases, spec conformance, tests | Small, focused, well-tested, and you just wrote one |
alacritty_terminal | Rust | Screen-model bugs, escape-sequence support | Separated from the app; changes are contained |
| zellij | Rust | Layouts, plugins, bug fixes | Young, active, welcoming to newcomers |
WezTerm (termwiz, portable-pty) | Rust | Terminal library work, PTY edge cases | Well-scoped sub-crates |
| foot | C | Performance, Wayland, protocol support | Small and readable; a responsive maintainer |
| Ghostty | Zig | libghostty API surface, protocol support | The core/platform split needs API work |
unicode-width | Rust | Width-table corrections | Tiny diffs, enormous blast radius — everything depends on it |
| terminfo / ncurses | C + data | Database corrections | Unglamorous, genuinely valuable, and undersupplied |
What makes a good first contribution
✓ A bug YOU hit, with a reproduction you already have
✓ A conformance gap your Project 7 suite found
✓ A missing escape sequence with a clear specification
✓ A test for existing untested behavior
✓ Documentation of something that confused you — while it is still fresh
✓ A performance fix WITH a benchmark
✗ A large refactor
✗ A new feature nobody asked for
✗ Reformatting
✗ "I rewrote your parser"
✗ Anything touching a compatibility guarantee without discussion first
Tip: The best source of first contributions is your own Milestone 14 compatibility matrix and your Project 7 conformance findings. You already have reproductions, you already know the expected behavior, and you already know why it matters. That is 80% of a good bug report, and a good bug report is often more valuable to a maintainer than a patch.
4. Milestones
| # | Goal | Output |
|---|---|---|
| 1 | Choose a project; read its CONTRIBUTING, its issue tracker, and its last 20 merged PRs | A note on what their PRs look like |
| 2 | Build it from source, run its tests, make a trivial local change | You can iterate |
| 3 | Choose an issue and comment on it before writing code | Maintainer acknowledgement, or a redirect |
| 4 | A minimal reproduction, in their test framework | A failing test |
| 5 | The fix, plus the test, plus a changelog entry if they keep one | A PR |
| 6 | Respond to review; iterate | Merged, or a clear "not now" with a reason |
Milestone 3 is the one people skip, and it is the highest-value one. A comment saying "I hit this, here is my reproduction, I intend to fix it by X — does that approach sound right?" saves you from writing the wrong patch and tells the maintainer you are worth engaging with.
5. The Standard Your PR Must Meet
Terminal projects are conservative because a regression breaks people's daily tools. Assume the bar is high.
□ ONE logical change. Not a bug fix plus a refactor plus a rename.
□ A test that FAILS before and PASSES after.
□ Their style. Run their formatter, their linter, their full test suite.
□ A commit message that explains WHY, not just what.
□ A description with: the symptom, the reproduction, the root cause, the fix,
and how you tested it.
□ A note on compatibility risk, explicitly. Even if it is "none."
□ No unrelated whitespace changes. None.
□ Their changelog/DCO/sign-off conventions, if they have them.
The commit message
parser: handle CSI intermediates before the private marker
The parser accepted `CSI ? 1 h` but rejected `CSI ! ? p`, because the
private-marker check ran before the intermediate check. Per ECMA-48 §5.4,
intermediate bytes (0x20-0x2F) may precede the final byte in any CSI
sequence, and xterm accepts this ordering.
Observed with <program>, which emits `CSI ! p` (DECSTR) after a private
sequence; the soft reset was silently dropped.
Adds a regression test covering both orderings.
Fixes #1234
Note what it contains: the symptom, the rule with a citation, the real-world trigger, the test, and the issue reference. Not "fix parser bug."
6. Working With Review
The part that is genuinely uncomfortable, and the part with the most transferable value.
| Feedback | The wrong response | The right response |
|---|---|---|
| "This should be done differently" | Defend the original at length | Ask what constraint you missed, then decide |
| "Add a test for X" | "It is covered by Y" | Add the test. They know their regression history. |
| "This breaks compatibility with Z" | "Z is obsolete" | They maintain it; take the constraint seriously |
| Silence for two weeks | Bump every day | One polite ping after ~2 weeks. Maintainers are volunteers. |
| "We do not want this feature" | Argue | Ask what would be wanted. Often something adjacent is. |
| A style nit | Ignore it | Fix it. It costs nothing and signals you are easy to work with. |
Note: "Not now" is a normal and legitimate outcome. Terminal maintainers say it a lot, usually for good reasons — compatibility risk, maintenance burden, or a plan you cannot see. A rejected PR that taught you their constraints is still a successful project for your purposes. Record the reason in your write-up; it is more interesting than a merge.
7. If You Cannot Find an Issue: Make One
A high-quality bug report is a legitimate deliverable for this project, and often more useful than a patch.
## Summary
`CSI 38:2::255:0:0m` (colon-form truecolor with an empty colour-space id) is
parsed as SGR 38 followed by SGR 2 (dim), producing dim default-colour text
instead of red.
## Reproduction
printf '\033[38:2::255:0:0mred\033[0m\n'
Expected: red text (matches xterm, kitty, foot, WezTerm).
Actual: dim default-coloured text.
Recording attached (asciinema v2), plus the raw byte stream.
## Root cause
`csi_param()` treats ':' as equivalent to ';' (src/parser.rs, the parameter
accumulator), so sub-parameters are flattened into separate parameters. The SGR
handler then sees [38, 2, 255, 0, 0] as five independent parameters rather than
one parameter with five sub-parameters.
Per ITU-T T.416 and xterm's ctlseqs, ':' introduces sub-parameters and the empty
field is the (omitted) colour-space id.
## Impact
Any program emitting the standard colon form renders wrongly. This includes
<list>. The semicolon form works, which is why it has gone unnoticed.
## Suggested fix
Track sub-parameter boundaries in the parameter accumulator. I have a patch and
can open a PR if the approach sounds right.
## Environment
<version>, <os>, TERM=xterm-256color
That report contains: a minimal reproduction, the expected/actual behavior with cross-terminal evidence, the root cause with a file reference, a specification citation, an impact statement, and an offer. A maintainer can act on it in ten minutes.
8. Known Traps
| Trap | Detail |
|---|---|
| Writing the patch before commenting on the issue | You may build the wrong thing, or duplicate someone's work |
| A PR that mixes concerns | Reviewers will ask you to split it; do it yourself first |
| Not running their full test suite | The CI failure is a bad first impression |
Ignoring CONTRIBUTING.md | It exists because they got tired of asking |
| Assuming your architecture is theirs | Your crate boundaries are yours. Match theirs. |
| Taking review personally | It is about the code, and they are protecting users |
| Disappearing after review comments | The single most common way a good PR dies |
| Over-explaining in the PR description | Symptom, cause, fix, test. Four paragraphs. |
| Reformatting "while I was in there" | Makes the diff unreviewable |
Deliverables
- A project chosen, with a written note on why and what its PRs look like.
- Built from source; its tests run locally.
- An issue identified, commented on before coding.
- A minimal reproduction, in their framework.
- A PR meeting all eight checklist items above.
- Review responded to, with iterations.
- A write-up including the reviewer's comments verbatim and what you learned from them.
- If not merged: an honest account of why, and whether you agree.
Why This Is the Last Project
Everything else in this curriculum you could grade yourself. The rubric is honest, but you are still marking your own work.
A maintainer reviewing your patch is the first time your understanding meets a standard you did not set — and that is the actual transition this whole curriculum is aimed at. The code you wrote is practice. The review is the exam.
Return to the portfolio, or to the capstone.