Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -421,13 +421,23 @@ reviews:
"Open gaps".
- A row names a behaviour with no verification method, or a method with no observed
outcome.
- A `unit` or `e2e` row does not name the test or spec it rests on. A `manual` row has no
test to name and passes without one.
- A row claims `red on main` or `mutation` without a command or a mutated `file:line` a
reviewer could rerun.
- A `unit` or `e2e` row names no test or spec. The name may sit in the row or in the
`Rerun:` line above the table, which carries it once for every row — but a bare suite
command such as `pnpm test` names nothing, so those rows name their own. A `manual` row
has no test to name and passes without one.
- A row claims `red on main` and gives nothing to rerun: neither the `Rerun:` line nor a
command of its own where that differs.
- A row claims `mutation` and does not name the mutated `file:line` a reviewer would edit
to turn it red.
- The diff changes rendered UI and the description attaches no screenshot or screencast.
- The "Open gaps" subsection is absent. An explicit "none" passes.

Content inside a `<details>` fold counts as present. The template caps the description at
350 words outside folds and folds the overflow rather than dropping it — long media,
command logs, and Coverage rows past the sixth — while the Coverage table, Open gaps and
Breaking changes stay visible themselves. A collapsed fold is evidence, not a gap: do not
ask for a longer description, ask for the missing behaviour.

PASS if every behaviour the diff changes is either covered by a row stating how it was
verified and what was observed, or listed under "Open gaps" with a reason.

Expand Down
103 changes: 50 additions & 53 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,41 @@
<!-- PR title must follow Conventional Commits: https://www.conventionalcommits.org/en/v1.0.0/#summary -->

<!-- Keep it skimmable: bullets over paragraphs, and `<details>` folds for anything a reviewer only
opens on demand (background, file audits, setup steps). Never fold the Coverage table, Open gaps or
Breaking changes — long media inside Coverage may still be folded. -->

<!-- AI agents: no promotional footers, don't advertise yourself or any tool/service (e.g. "Generated
by …"). The agent note at the bottom is the one exception. -->

<!-- Complete the line below: `Fixes ENG-<id>`. A bare URL does NOT link the PR — the magic word has
to come first. Use `Ref ENG-<id>` instead if this PR only partly addresses the ticket, so merging
doesn't close it. No ticket? Delete the line and say why. Closing a GitHub issue? Add `Fixes #<number>`.
This line is the only place a magic word belongs: `Fixes ENG-…` written anywhere else in the
description links and closes that ticket too, backticks included.
<!-- Title: Conventional Commits — https://www.conventionalcommits.org/en/v1.0.0/#summary -->

<!-- BUDGET: ≤350 words outside `<details>` folds — one screen. Lists are ≤3 bullets of ≤20 words,
one idea each; the Coverage table is ≤6 rows. Short sentences, plain words, present tense, written
for a colleague who has not read the ticket: user-visible effect first, mechanism second. Overflow
folds rather than being dropped. Coverage, Open gaps and Breaking changes stay visible; only long
media, command logs and Coverage rows past the sixth fold. -->

<!-- NEVER WRITE — each of these reads as diligence and costs the reviewer a paragraph: blame
archaeology (which commit introduced it, who touched what); a defence of a choice nobody questioned,
or of what you deliberately did not do; commentary on how strong your own tests are; a restatement
of the ticket, or of what CI reports (lint, typecheck, tests, build, Sonar); a path the `Rerun:`
line already carries; bold on more than a phrase or two per section. No promotional footers and no
advertising of yourself or any tool — the agent note at the bottom is the one exception. -->

<!-- Complete the line below: `Fixes ENG-<id>`, or `Ref ENG-<id>` if this PR only partly addresses
the ticket, so merging doesn't close it. The magic word comes first; a bare URL links nothing.
This is the only line it may sit on — `Fixes ENG-…` anywhere else, backticks included, closes that
ticket too. No ticket? Delete the line and say why. GitHub issue? `Fixes #<number>`.
More: https://linear.app/docs/github -->

Fixes ENG-

## What & why

<!-- Problem first, then the solution, grounded in the diff rather than commit titles. Short enough
to grasp without opening files. -->
<!-- One sentence each, read off the diff rather than the commit titles. Up to 3 bullets after
them, only for what they cannot carry. -->

**Was:** `<what was broken, or how it behaved before>`

**Now:** `<what happens after this PR>`

## Where to look

<!-- 1–3 links to the lines carrying the risk, so a reviewer can spot-check the code without
reading all of it: link plus ≤10 words, no prose. Nothing risky? Say so in a line. -->

-

## Breaking changes

Expand Down Expand Up @@ -67,68 +83,49 @@ None

## Migrations & env

<!-- New or changed env vars, DB migrations and cutover steps — including non-breaking ones, since
this is what a deployer or self-hoster acts on. Write "none". -->
<!-- New or changed env vars, DB migrations, cutover steps — including non-breaking ones, since a
deployer acts on them. Write "none". -->

- none

## How this was tested

<!-- REQUIRED. All QA for this change happens here, before review — there is no separate release QA
pass. Don't restate the checks below (lint, typecheck, unit tests, build, Sonar); give the reviewer
what those cannot show. -->

**Coverage**
pass. `Rerun:` carries the command once, so no row below repeats it. -->

<!-- One row per behaviour the diff changes: for an automated row name the test or spec (not a count),
how strong that check is, and what you observed. Name the account, plan or flag state where an outcome
depends on it. For UI work, attach the screenshot or screencast that proves it — fold it if long. -->
Rerun: `<one command covering the table below>`

<!-- "How" uses one of: `unit (red on main)` — fails against the old code, so it proves the bug
existed; `unit (mutation)` — only fails if you break the fix, because the code under test is new;
`unit (guard)` — passes either way, protecting against future regressions; plus `e2e` and `manual`.
Any red-on-main or mutation row must carry the command or the mutated `file:line`, so a reviewer can
rerun it instead of taking the claim on trust. -->
**Coverage**

<!-- Pick the cheapest level that can fail on the behaviour (AGENTS.md "Testing Guidelines"): `unit`
for logic and invariants, a route test for authorization/response shape/scoping, `e2e` only for a
feature's happy path or a journey across several surfaces, and `manual` — with the screenshot — for
UI detail inside a feature that already has an e2e. A `manual` row is a complete answer; a new e2e
spec is paid on every PR forever, so "this row had no automated test" is not a reason to add one. -->
<!-- ≤6 rows, one per behaviour the diff changes; Outcome ≤15 words of what you observed, naming the
account, plan or flag state where it matters. `How` is one of `unit (red on main)`,
`unit (mutation)`, `unit (guard)`, `e2e`, `manual` — AGENTS.md defines them and says how to pick the
cheapest level that can fail. Every `unit` and `e2e` row names the test or spec it rests on, in the
row or in a `Rerun:` line that names it. A `red on main` row names its own command where that
differs from `Rerun:`; a `mutation` row names the mutated `file:line`. The table stays visible —
fold long media and command logs, and rows past the sixth. -->

| Behaviour | How | Outcome |
| --- | --- | --- |
| | unit (red on main) / unit (mutation) / unit (guard) / e2e / manual | |
| | | |

**Open gaps**

<!-- Anything you could not verify, and why. Write "none" if there are none — the reviewer's job is
to challenge this list, so an empty one is a claim rather than a formality. -->
<!-- ≤3 bullets: what you could not verify, and why. The reviewer's job is to challenge this list,
so "none" is a claim rather than a formality. -->

- none

**Risks**

<!-- What nearby behaviour could break, and what to re-check if it does. -->
<!-- ≤3 bullets: what nearby behaviour could break, and what to re-check if it does. -->

- none

---

<!-- Fill in if an AI agent wrote code or this description; delete the note if none did.

`<model>` is the exact model id the vendor serves — `claude-opus-5`, `gpt-5.1-codex` — not the
product it runs in: Claude Code, Codex CLI and Cursor are harnesses, not models. Name the harness
and its version in parentheses when it adds something: `claude-opus-5 (Claude Code 2.1.237)`.

`<level>` is whatever reasoning knob that vendor exposes, in that vendor's own units: an effort
level (`max`, `high`), a thinking budget (`32k tokens`), or `n/a` where there is no such setting.

Read both values out of the tool rather than from memory. Claude Code reports them in `/status`,
or as the session's `model` and `effort_level`; Codex CLI in `/model` or the line it prints at
startup; other tools, whatever they report about themselves. A value you cannot look up is
`unknown` — never a plausible-looking guess, and never the harness name standing in for the
model. -->
<!-- Delete if no agent was involved. Read both values out of the tool, never from memory; a value the
tool does not expose is `unknown`, never a guess. AGENTS.md says where each tool reports them. -->

> [!NOTE]
> **AI model used** — `<model>`, reasoning effort `<level>`.
13 changes: 13 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,15 @@ where the behavior only exists once browser, survey bundle, and server are wired
The spec filenames in `apps/web/playwright/` are the inventory of covered areas — check there before
concluding an area has no spec.

The PR's Coverage table names each row's level with one of five words, and the first three are claims a
reviewer can check: `unit (red on main)` fails against the old code, so it proves the bug existed;
`unit (mutation)` only fails if you break the fix, because the code under test is new; `unit (guard)`
passes either way, protecting against a future regression; `e2e` and `manual` say where the check ran. Every
`unit` and `e2e` row names the test or spec it rests on, in the row or in a `Rerun:` line that names
it — a bare `pnpm test` names nothing. A `unit (red on main)` row is rerunnable from the
`Rerun:` command, or names its own where that differs; a `unit (mutation)` row names the mutated
`file:line` a reviewer edits to turn it red.

This raises a floor as well as lowering a ceiling. Every feature area ships a happy-path E2E, and an area
with none is a gap rather than a saving (Dashboards and Workflows are the current examples — ENG-2314). A
bug fix inside a feature that already has one almost never needs a second spec — the level still follows
Expand Down Expand Up @@ -298,6 +307,10 @@ Commits follow a lightweight Conventional Commit format (`fix:`, `chore:`, `feat

Every PR must use `.github/pull_request_template.md` and follow its inline guidance — the template is the source of truth for PR structure. The ticket line at the top is the only place a magic word (`Fixes`, `Ref`, `Closes`) may sit next to a ticket id: Linear and GitHub scan the whole body, so the same pair written in prose — inside backticks too — links and closes that ticket as well. When you need to name the convention in prose, write it without a resolvable id. All QA for a change happens on its own PR before review: the creator shows that every behaviour the diff changes is covered, and lists what is not under `Open gaps`; the reviewer challenges that list and asks for the missing coverage. There is no separate release QA pass per PR — release review only looks for problems arising from the interplay of several changes. Fill every section from the actual diff on PR open, and re-update it in the same turn on every change (new commits, scope or review fixes) so it never drifts — treat a stale section as a bug.

**A PR description is read, not filed.** Keep the whole thing under 350 words outside `<details>` folds — one screen — with lists of at most three bullets of at most twenty words and a Coverage table of at most six rows. Open `## What & why` with a `**Was:**` / `**Now:**` pair: one plain sentence for how it behaved before, one for what happens now. User-visible effect first, mechanism second, written for a colleague who has not read the ticket. Under `## Where to look`, link the one to three places that carry the risk so a reviewer can spot-check the code without reading all of it. Detail that does not fit goes into a fold rather than being dropped — the evidence stays in the PR, out of the reviewer's way. Four things never belong at any length: blame archaeology, a defence of a choice nobody questioned or of what you deliberately did not do, commentary on how strong your own tests are, and anything the `Rerun:` line already carries.

The agent note names the exact model id the vendor serves — `claude-opus-5`, `gpt-5.1-codex` — not the harness it runs in; Claude Code, Codex CLI and Cursor are harnesses, so name one in parentheses only when it adds something (`claude-opus-5 (Claude Code 2.1.237)`). The reasoning level is whatever knob that vendor exposes, in its own units: an effort level (`max`, `high`), a thinking budget (`32k tokens`), or `n/a`. Read both out of the tool, never from memory — Claude Code reports them in `/status` or as the session's `model` and `effort_level`, Codex CLI in `/model` or its startup line. A value you cannot look up is `unknown`, never a guess and never the harness name standing in for the model.

The checkbox under `## Breaking changes` is a decision you own, not a formality: judge the diff against the template's list of breaking changes and tick it (`- [x]`) when one applies, leave it unticked when none does. The template also lists what is **not** breaking — purely additive changes, and anything internal to this repo that no external consumer reaches — and an uncertain call is an unticked box with a line of reasoning, never a defensive tick. It is the only input to the `breaking-change` label, which feeds the release notes and the self-hoster migration guide, so a wrong answer either invents a migration entry or hides one. Re-check it whenever the diff grows. `pr-label-sync.yml` reads nothing but the tick, so the prose below the checkbox cannot change the label — but it is not free-form either: the CodeRabbit `Breaking changes match the diff` check compares the tick against the diff and expects a ticked box to document each breaking change, so explain your answer there in whatever shape fits (table or prose).

## Next.js Documentation
Expand Down
Loading
Loading