Changes & governance
The token editor is govern-first: opening a project always lands you in read-only browse mode. Every edit — big or small — happens inside a named Change, so nothing gets modified by accident and every edit carries a reason from the moment it's made.
What is a Change?#
A Change is the primary editing object in the Studio. When you open a project, the whole token system is visible but read-only — you can browse every token, but the detail form stays locked until you deliberately start a Change.
Click Start a change in the topbar, or from the read-only inspector card on any token. You'll be asked for three things:
| Field | What it is |
|---|---|
| Name | A kebab-case identifier, e.g. darken-brand-ramp. Must be unique in the project. |
| Intent | The "why" behind the change. Stored as the working branch’s description. |
| Base branch | Where the change forks from. Defaults to main. |
Submitting the form creates a working branch and switches you into it — the detail form unlocks and editing begins. Under the hood, a Change is 1:1 with a branch: starting a change always creates a new branch, and the change's name and intent are exactly the branch's name and description.
Branch names are unique per project. If you reuse a name, the form validates before submitting — the request would otherwise fail with HTTP 409 BRANCH_NAME_TAKEN.
The Change Bar#
While a change is active, a Change Bar docks at the bottom of the editor. It keeps the two things you need mid-edit — what changed, and whether it's safe to ship — always in view:
| Element | What it shows |
|---|---|
| Change name | The name you gave the change when you started it. |
| Edit count / diff | A live count of tokens touched so far. Expand it for the full diff — added, modified, removed, and renamed tokens versus main. |
| Accessibility gate | An ambient badge showing whether every contrast pair still passes at the project’s enforcement level, evaluated against this change’s current token values. |
| Discard | Ends the change. The branch itself is kept — delete it separately from Branches if you don’t need it. |
| Request review | Opens the token-request form to send the change for review. Disabled while the accessibility gate fails. |
The gate is read from GET /v1/projects/:id/branches/:branchId/a11y-gate, which evaluates every tagged foreground/background contrast pair in the project against the branch's current token values — at WCAG AA, or AAA if the project's accessibility enforcement level is set to AAA. It runs even when enforcement is set to off: the gate is an always-on signal on the Change Bar, independent of whether the organisation has turned on blocking enforcement elsewhere.
{
"passed": false,
"totalPairs": 12,
"failingPairs": [
{
"pairId": "018fde70-…",
"foregroundTokenId": "018fde70-…",
"backgroundTokenId": "018fde70-…",
"foregroundName": "color/text-muted",
"backgroundName": "color/surface",
"ratio": 3.1,
"required": 4.5
}
]
}A failing gate only blocks review — editing stays free. Fix the failing pairs, or point them at compliant tokens, and the badge clears on its own.
How it composes with branches#
Clicking Request review opens the token-request form prefilled with the change's name as the request title, and its intent shown alongside as read-only context for the reviewer. Submitting ends the change and returns the editor to main — from there, review and merge follow the same token-request lifecycle described in Branches & reviews.
A change survives reloads: it's persisted client-side, so reopening the project drops you straight back into whatever change you were working on, on the same branch, with the Change Bar already docked.
A branch created outside of Start a change (for example, forked from the branch selector or the CLI) behaves the same way once you open it in the editor — the editor still requires an active change to unlock editing.
Governance controls#
Beyond the editor's browse-mode gate, governance is enforced server-side per project. Everything below lives in the project's Protection settings (org admins only) and applies to every client — Studio, CLI, Figma plugin, and API alike.
| Control | What it enforces |
|---|---|
| Protected main | Direct token writes to main are rejected (403 MAIN_PROTECTED) — including bulk paths: applied imports, CLI push, Figma push, Make Scalable, restructure, cutover, and reverts. Org-admin writes go through but are audited as hotfix_write events. New projects on Team and Business plans start protected; Free and Pro start unprotected, and admins can toggle it any time. |
| Required approvals | The review quorum (1–5). A token request only flips to approved once that many distinct reviewers approve. Self-approval is banned while main is protected. |
| Owner rules | CODEOWNERS-style routing: pattern + optional tier + owners. Requests touching matching tokens additionally need an approval from a listed owner. An org-admin approval can override unsatisfiable ownership — audited as ownership_override. |
| Token policy | Write-time rules on token shape: naming pattern (with per-tier overrides), required description, semantic-must-alias, and allowed units for dimension tokens. Violations block editor writes with 422 POLICY_VIOLATION. Imports are never blocked — the import diff reports non-blocking policyWarnings instead. |
The one exemption: the first import into an empty main is always allowed, so onboarding can seed a brand-new protected project before any governance is relevant.
The audit trail#
Every governance-relevant action is recorded as an event and readable at GET /v1/projects/:id/governance/events?page=&pageSize= — newest first, with the actor, branch, and detail attached. The same feed is shown in the Studio under the project rail's Activity page.
| Event | Recorded when |
|---|---|
| hotfix_write | An org admin writes directly to a protected main. |
| gate_block_merge | A merge into main was blocked by the accessibility gate. |
| gate_block_release | A release publish was blocked by the accessibility gate. |
| unreviewed_merge | A branch merged into an unprotected main with no approved request. |
| ownership_override | An org admin’s approval overrode unsatisfiable owner-rule routing. |