Systeric / Docs Open App →

Branching & PRs

We work on main, in short bursts. Branch off it, do one small thing, open a PR, merge back the same day or two. Long-lived branches drift and turn every merge into a negotiation. Never commit straight to main.

main feat/roadmap-releases squash fix/request-back-button squash branch off small commits PR merges as 1

Each branch is one unit of work that starts from main and returns to it as a single squashed commit. Two branches in flight at once is normal; a branch open for a week is a smell.


Naming a branch

type/short-description, kebab-case, so the branch says what it is at a glance.

TypeForExample
featA new capabilityfeat/roadmap-releases
fixA defect in shipped behaviorfix/request-back-button
docsDocs onlydocs/pd-onboarding
designDesign or visual workdesign/glide-request-keyboard
choreTooling, deps, configchore/bump-drizzle

Keep the description a few words. The issue or context lives in the PR, not the branch name.


Commits

We write Conventional Commits: type(scope): subject. The scope is the area touched, the subject is the change in the imperative.

feat(roadmap): add release notes field to initiatives
fix(web): keep the back button on the request detail page
docs(glide): make Above-the-fork per-attribute

Types in use: feat, fix, docs, test, chore, content. Two rules that matter more than the format:

  • Small and atomic. One commit is one self-contained change that builds and passes on its own. If you cannot describe it in one line without “and”, it is two commits.
  • Green at every commit. You should be able to check out any commit on the branch and have it work. This is what makes a revert clean.

When AI wrote a meaningful part of the change, add the trailer:

Co-Authored-By: Claude <noreply@anthropic.com>

Opening a PR

Base is main. Title is a one-liner in the same type: description shape as a commit. The description follows one format so a reviewer can read any PR the same way:

  • What changed · one line.
  • Why · one line, the problem it solves.
  • Before / After · the observable difference, with a number when you have one.
  • Risks & Mitigation · even low risk. What could break, and the lever that catches it (a flag, a test, a revert).
  • Testing · what you ran and what you checked by hand.
  • Rollback · for anything non-trivial, the exact way back (flip the flag, revert the commit).

Here is that format filled in for a small, flag-guarded feature:

EXAMPLE PR feat(roadmap): add release notes field to initiatives
What changed
Initiatives now carry a release notes draft, editable inline on the roadmap. Shipped behind initiative-release-notes-v1, default off.
Why
DRIs were drafting release notes in scratch docs and pasting them at Launch, where they were often stale. Keeping the draft on the initiative means it travels with the work.
Before / After
Before · notes lived in ad-hoc docs, easy to lose and stale by Launch.
After · one field on the initiative, one click from the roadmap, versioned with it.
Risks & Mitigation
Risk Impact Mitigation
Draft shows before the feature is readyLowGated by the flag, off in prod
Long notes bloat the roadmap payloadLowLength-capped and validated in the shared Zod schema
Testing
  • Unit: the initiative entity validates and stores the draft
  • Integration: save then reload round-trips the value
  • Manual: staging with the flag on, edited and reloaded
Rollback
Flip initiative-release-notes-v1 off. The column is additive and nullable, so no schema rollback is needed.

Notice what makes it good: it is short, the risk is named even though it is low, and the way back is one line a stressed on-call can follow. The same template lives in the repo’s CLAUDE.md. It exists so risk is stated out loud, not so the PR is long.


What a good PR looks like

Small is the whole game. A PR a reviewer can hold in their head gets read carefully and merged fast. A large one gets skimmed and rubber-stamped, which is where bugs walk in. If a PR touches many files for many reasons, split it. Ship the refactor, then the feature, in that order, as separate PRs.

  • One reason to exist. A PR does one thing. “Rename this and also add that” is two PRs.
  • Reviewable in one sitting. If you cannot review it in ten minutes, neither can anyone else.
  • Self-reviewed first. Read your own diff before you request a review. You will catch the debug log, the stray file, the half-named variable.
  • CI green before review. The PR checks run lint, typecheck, build, and tests on the affected packages. Do not hand a red PR to a person.
  • AI review addressed. When an AI reviewer comments, resolve the real issues and say why you skipped the rest.

Merge with squash, so the branch collapses to one clean commit on main, and delete the branch. Whether a change needs another engineer’s eyes before merge is a separate question, answered in Code Review: most changes are reversible and do not.


Related: Code Review, Feature Flags, Testing & TDD, Releasing & Rollback