Skip to content

Your First Contribution

Lshika edited this page Aug 14, 2026 · 1 revision

Your First Contribution

This page is different from the other two that mention "getting started":

  • Getting Started is for using tuicc — installing it, configuring it, no code involved.
  • CONTRIBUTING.md is the rules once you're ready to open a PR — how I review things, what a good test looks like, ground rules the codebase already follows.

Neither of those actually walks you through doing something in this codebase for the first time. This page does. If you've never touched tuicc's code before — human or an AI coding agent working on your behalf — start here.

By the end of this page you'll have made one small, real change, run it, and seen it work. That's the whole goal — not to teach you the whole codebase, just to get you past the "where do I even start" wall.

The shape of this codebase, in one minute

Three things make almost everything else make sense:

  1. Everything that talks to your window manager goes through one Provider interface (providers/sway.py, providers/i3.py). Nothing else in the codebase knows or cares which WM you're running. If you're not touching WM-specific code, you'll never need to think about this.
  2. Every UI module (sidebar, connectivity, media, control, ...) follows one contract: it owns its own draw() and nav_items(), and gets wired in via a registry dict (MODULES in render.py) instead of the core code needing to know about it. Adding a module is "add a registry line," not editing dispatch logic.
  3. State lives in small, plain @dataclasses, mutated by plain functions that take the instance — not classes with methods. You'll see this pattern everywhere (ResizeState, LauncherState, PendingMovesQueue, ...). It's deliberate, not an accident — keep new code consistent with it.

That's enough to start. Architecture goes much deeper, whenever you actually need it — you probably don't yet.

Walk through a real example first

Before writing anything yourself, look at something that already works. Open src/tuicc/defaults/config.toml and find the commented-out [[control.toggle]] examples (search for "Night Light"). This block is real, already-shipped, already-tested code — just switched off by default:

# [[control.toggle]]
# label = "Night Light"
# shell_true = false
#
#   [[control.toggle.state]]
#   name = "on"
#   status_command = "pgrep -f gammastep"
#   command = "gammastep -O 4500"
#
#   [[control.toggle.state]]
#   name = "off"
#   command = "pkill -f gammastep"

Read it next to Config Reference: [[control.toggle]] — that page explains exactly what every field means and why (status_command's exit code decides which state you're in, checked in order; the last state can skip status_command since it's whatever's left by elimination). Notice the comment above this block in the real file explains why it's written the way it is (-O 4500 instead of needing location setup, -f instead of -x because gammastep's own process name doesn't match its own binary name) — that's this codebase's documentation style: comments explain why, not just what, and you're expected to write yours the same way.

Now make it real, in your own ~/.config/tuicc/config.toml (not the packaged default — that file is only ever regenerated from, never edited directly):

  1. If you have gammastep installed, copy the block above into your config's [[control.toggle]] section, uncommented.
  2. Don't have gammastep? Even better for this exercise — copy it in anyway, run tuicc, and select it. You'll see it fail loudly (an [ERROR] state, the actual command's stderr shown in the preview panel) instead of silently doing nothing. That's not a bug you found — that's a deliberate project principle (see VISION.md's "no-silent-failure") working exactly as intended. Confirming you understand why it fails that way, instead of just shrugging at an error, is worth more at this stage than the toggle actually working.
  3. Restart tuicc, navigate to Control, select Night Light, press Enter. Watch it switch state (or fail loudly, per step 2).

You just exercised the whole config-driven toggle system without writing a line of Python.

Now write your own

Pick something on your own machine with a real on/off (or multi-state) shell command — a VPN, a mute toggle, whatever you actually use. Write your own [[control.toggle]] block from scratch, following the same shape. Test it the same way: restart tuicc, try it, confirm it does what you expect (and fails loudly if it doesn't).

This part doesn't need to become a real PR — packaged examples are curated deliberately (see VISION.md's own reasoning for why only six ship, and why a "Dark Mode" example was tried and rejected as unreliable across GTK/Qt). Treat this as a practice rep for the pattern, not an attempt to add a seventh default.

What a real first PR looks like

Once the pattern above feels natural, a genuinely useful, low-risk first PR is usually one of:

  • A docs fix — something on this wiki or in CLAUDE/NOTES/*.md that's out of date or unclear. No code risk at all, and it teaches you to navigate the docs while you're at it.
  • A small, self-contained module or a fix to an existing one, following the draw()/nav_items() contract — see Writing a Module once you're ready for this.
  • A new WM provider — bigger, see Writing a WM Provider and open an issue first per CONTRIBUTING.md, so we can talk through the shape together before you sink real time into it.

Whatever it is, CONTRIBUTING.md is what happens next — how PRs get reviewed, what tests need to look like, the ground rules. Read that before opening anything.

Stuck anywhere on this page? Open an issue — genuinely, no question is too basic.

Clone this wiki locally