Lesson 24 — Review: propose, approve, and the protected merge
Goal: by the end of this lesson you can protect a branch with a required-approvals rule, watch it refuse an unreviewed merge, and walk a change through the propose → approve cycle until it may land.
Concepts: required approvals on the merge target, the 📤 propose / ✅ approve cycle, stale-approval dismissal, self-approval, review comments (anchored and general), suggested changes, and why a merged branch is permanent.
Prefer to be shown? This lesson exists as a guided in-editor tour: open the demo with the tour running (no sign-up), or pick Lesson 24 under Interactive tutorial in the editor's account menu.
This lesson builds on Lesson 23 — fork, edit, diff, merge. Here the missing half: making a merge conditional on review.
The rule lives on the target
On GitHub you protect main and require approving reviews; graphden
works the same way, and the knobs sit under ⋯ → ⚙ Protection…
on the branch-popover row of the branch being merged into:
- Required approvals (0–3) — a merge into this branch is refused
(409, "requires N approval(s)…") until the proposal has N valid
approvals. It is a one-tap
0…3segmented control — pick the number and the rule is saved immediately. - Push only via merge — no direct writes at all; the only way in is a merge (lesson 23 covers this one).
- Count the author's own approval — on by default, so a solo user is never locked out; untick it for genuine four-eyes review.
Approvals are target-bound and content-aware: an approval is recorded for a merge into the proposal's base branch, and editing the proposed branch afterwards dismisses the now-stale approvals — like GitHub dismissing stale reviews on a new push.
The cycle: 📤 → ✅ → ⇢
- 📤 Propose marks a branch as submitted for review into its base. Proposed branches are the reviewer's to-do list — the popover header counts them.
- ✅ Approve records your approval; the row's badge shows
n/Nand turns green when the requirement is met. Both buttons toggle: 📤 on a proposed branch withdraws the proposal, and a pressed ✅ (your approval applies to the current proposal) withdraws your approval. After the source changes, the old approval remains recorded but the button is unpressed: click it to approve the changed content. - ⇢ Merge now lands. Before the requirement is met it answers 409 with the shortfall.
- The Review dialog opens with Verified on
— the proposal's own tests (passed / failed / never ran) and its newest runs, so a reviewer reads what the author actually verified before reading the diff. - Every proposal carries a comment thread in its 💬 Review & comments dialog (the row's ⋯ menu, or the Δ chip's cockpit) — alongside a collapsible "What changed" list and the suggestions.
- A comment can be anchored to one element: click 💬 on a change row in the dialog — or right in the inspector's diff panel while comparing — and the note pins to exactly that fn / arg / list item, GitHub-line-comment style. Anchored threads render inline under their row; if the element later drops out of the diff, the thread falls back to the general conversation with a context chip.
- The Review dialog also lists Suggestions — branches forked
off the proposal itself and proposed back into it. A reviewer clicks
+ Suggest a change, lands on a fresh
suggest-…branch, edits with the full editor, and proposes it (⋯→ 📤). The author then sees it in the proposal's Review dialog — with a collapsible Δ preview of what it changes — and applies it with one click (⇢ apply— an ordinary merge into the proposal, which also dismisses now-stale approvals). No new machinery: a suggestion is just the branch/propose/merge cycle one level down.
Try it
- Create
tutorial-releasefrommainfirst. Extendconstintoreview-demoon that owned branch and bind its:valueto1. - In its branch-popover
⋯menu choose ⚙ Protection… and set Required approvals to1. - Create
tutorial-featurefrom the release branch. Change the value to2, return to the release and try ⇢. The target refuses the unreviewed merge. - Choose 📤 Propose for review, then Δ to read the change. In the Inspector, open the function's 💬 thread, save a comment, then click 💬 again to reopen its answer field and save an answer in the same thread. A draft is not a saved reply.
- Exit comparison and approve: the badge reads
1/1. - Return to the feature and change the value to
3. On the release, the earlier approval is stale and the badge reads0/1. - Compare again, read the changed value and the preserved conversation,
then approve the current content. The badge returns to
1/1. - ⇢ would now land the reviewed value. Finish this guided lesson
without merging, return to
mainand delete its owned child branches.
Why the tour stops before the merge
A merge in graphden is by-reference: no rows are copied — the
target simply starts reading the source branch's version rows. That
makes merges cheap, and it has a consequence worth knowing: a merged
branch has become part of its target's history and can no longer be
deleted while the target exists (deleting it would silently revert
the target's merged-in content — the server refuses). In real work
that permanence is the point: merged branches are the record, like
merged commits. In a tutorial sandbox it means the guided tour stops
one click short of ⇢, so its cleanup can still remove both branches.
Lesson 23 performs an actual merge and remerge between sibling sandbox
branches: removing the target releases its merge reference before the
source and common base are deleted.
Where this shows up next
- Lesson 23 — protected branches ("push only via merge"), conflicts, and branch-local fns that never merge.
- VERSIONING.md — the branch model underneath.