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
2 changes: 1 addition & 1 deletion .claude/commands/create-branch.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ You are running `/create-branch`. Cut a fresh branch off the latest `main`, name

1. **Sync.** Run `git fetch origin --prune`.
2. **Read the change (if any).** If the working tree already has changes, run `git status` and `git diff` and use them to infer the branch `type` and a slug. If the tree is clean, derive both from `$ARGUMENTS` (ask for a 2–4 word description if none was given).
3. **Type.** Pick from the same enum the repo's commit convention uses: `feat` (default — new component/prop/event/export), `fix`, `hotfix`, `chore`, `docs`, `style`, `refactor`, `perf`, `test`, `ci`, `revert`.
3. **Type.** Pick from the same enum the repo's commit convention uses: `feat` (default — new component/prop/event/export), `fix`, `chore`, `docs`, `style`, `refactor`, `perf`, `test`, `ci`, `revert`. Only `feat` / `fix` (or a breaking `!`) actually release (see [`release-types.md`](../rules/release-types.md)).
4. **Related issue (optional).** Ask: "Which issue does this relate to? (`ENG-1234`, or leave blank for `NO-ISSUE`)". A blank answer is fine — treat it as no issue. Do not block on it.
5. **Create.**
- With an issue: `git checkout -b <type>/<ISSUE>-<slug> origin/main`
Expand Down
10 changes: 5 additions & 5 deletions .claude/commands/open-pr.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
description: Open a PR to `main`. Analyzes the diff to pick the Conventional Commit type/scope (drives the semantic-release bump), prompts for the related issue (optional), commits by context, pushes, and opens the PR.
description: Open a PR to `main`. Analyzes the diff to pick the Conventional Commit type/scope (drives the release-please bump), prompts for the related issue (optional), commits by context, pushes, and opens the PR.
argument-hint: [PR title or focus]
---

Expand All @@ -9,8 +9,8 @@ You are running `/open-pr`. Land the current working changes as a pull request a

## Source of truth

- Commit format and type→bump: [`CONTRIBUTING.md`](../../CONTRIBUTING.md) § Commit convention and [`commitlint.config.js`](../../commitlint.config.js).
- Release rules: `packages/<pkg>/.releaserc` (semantic-release; merges to `main` cut the version).
- Commit format and type→release mapping: [`CONTRIBUTING.md`](../../CONTRIBUTING.md) § Commit convention, [`commitlint.config.js`](../../commitlint.config.js), and [`release-types.md`](../rules/release-types.md).
- Release automation: [`release-please-config.json`](../../release-please-config.json) — merges to `main` update the pending Release PR; merging the Release PR cuts the version. Stock release-please: only `feat` / `fix` / breaking release.

## Steps

Expand All @@ -19,7 +19,7 @@ You are running `/open-pr`. Land the current working changes as a pull request a

### 1. Analyze the diff (drives the version bump)
Run `git diff` (staged + unstaged) and determine, from the change itself:
- **type** → `feat` (new component / prop / event / slot / public export), `fix` / `hotfix` (bug, a11y, visual regression), `refactor`, `perf`, `docs`, `style`, `chore`, `test`, `ci`, `revert`. This sets the bump: `feat`→minor; `fix`/`hotfix`/`chore`/`docs`/`style`/`refactor`/`perf`→patch; `test`/`ci`/`revert`→no release.
- **type** → `feat` (new component / prop / event / slot / public export), `fix` (bug, a11y, visual regression — anything that must ship), `refactor`, `perf`, `docs`, `style`, `chore`, `test`, `ci`, `revert`. Release effect (release-please): `feat`→minor; `fix`→patch; breaking→major; **every other type → no release on its own** (urgent production fixes are `fix` — there is no `hotfix` type).
- **scope** → the package without its namespace (`webkit`, `theme`, `icons`), inferred from the changed paths.
- **breaking?** → a removed or renamed public prop, event, slot, or export ⇒ major. Mark it with `!` and a `BREAKING CHANGE:` footer. **Confirm with the user before marking a change breaking.**

Expand All @@ -45,7 +45,7 @@ If the diff spans multiple packages, prefer **one commit per scope** (CONTRIBUTI
### 6. Push and open the PR
- `git push -u origin <branch>`.
- If a PR already exists for the branch, update it; otherwise `gh pr create --base main --head <branch>`.
- **Title:** the conventional commit header (or `$ARGUMENTS`). **Body:** `## Summary` (what + why) and `## Notes`; call out any new dependency and any breaking change; reference the issue. No Figma links, no attribution footer.
- **Title:** the conventional commit header (or `$ARGUMENTS`). Merges are **squash merges**, so the PR title is the commit release-please parses — keep it commitlint-valid, and put `!` in the title for a breaking change. **Body:** `## Summary` (what + why) and `## Notes`; call out any new dependency and any breaking change; reference the issue. No Figma links, no attribution footer.
- Report the PR URL.

## Rules
Expand Down
2 changes: 1 addition & 1 deletion .claude/rules/deprecation.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ For a whole component:
## Enforcement

- **`webkit/no-deprecated-component`** (ESLint, `error` in `recommended`) flags a consumer importing a component marked deprecated in the catalog.
- **`.releaserc`** (`releaseRules`) maps the bump; a `BREAKING CHANGE:` footer / `!` produces the `major` (see [`release-types.md`](./release-types.md)).
- **release-please** maps the bump; a `BREAKING CHANGE:` footer / `!` produces the `major` (see [`release-types.md`](./release-types.md)).
- **`catalog.json`** records `deprecated` per part; `build-catalog.mjs` stamps it and the MCP surfaces it.

## Why this rule exists
Expand Down
2 changes: 1 addition & 1 deletion .claude/rules/git-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ These commands are the **source of truth** for the process. This rule exists onl
## Conventions (already embedded in the flows — repeated here because they are non-negotiable)

- **Always base on `main`.** The branch comes off `origin/main`, the PR targets `main`.
- **Branch name:** kebab-case `<type>/<ISSUE>-<slug>` (or `<type>/<slug>` without an issue). `type` comes from the same Conventional Commits enum ([`CONTRIBUTING.md`](../../CONTRIBUTING.md) § Commit convention / [`commitlint.config.js`](../../commitlint.config.js)). That enum must match every `packages/*/.releaserc` — see [`release-types.md`](./release-types.md).
- **Branch name:** kebab-case `<type>/<ISSUE>-<slug>` (or `<type>/<slug>` without an issue). `type` comes from the same Conventional Commits enum ([`CONTRIBUTING.md`](../../CONTRIBUTING.md) § Commit convention / [`commitlint.config.js`](../../commitlint.config.js)). That enum must match the release-please mapping (only `feat`/`fix`/breaking release) — see [`release-types.md`](./release-types.md).
- **Commit:** Conventional Commits, commitlint-valid header. **Never** add `Co-Authored-By` or an attribution footer ("Generated with Claude"). **Never** `--no-verify` to skip commitlint.
- **Commit/push only as part of `/open-pr`** — running the command is the authorization. Do not commit unrelated changes.
- **Shared docs/rules in a separate PR from code.** If the diff mixes code with `.claude/rules/*`, `.claude/skills/*`, `.specs/_template.md`, etc., split them into another PR. A component's own `.specs/<name>.md` stays with the component.
Expand Down
57 changes: 29 additions & 28 deletions .claude/rules/release-types.md
Original file line number Diff line number Diff line change
@@ -1,52 +1,53 @@
# Rule: release types — one single set of types across commitlint, `.releaserc`, CONTRIBUTING and commands
# Rule: release types — one single set of types across commitlint, release-please, CONTRIBUTING and commands

The set of Conventional Commit `type`s and the **version bump** of each one are versioned in four places. They **must be identical**. When they diverge, commitlint accepts a commit that `semantic-release` silently ignores (or vice versa), and the `/open-pr` / `/create-branch` flows start offering a type that does not produce the expected release. This rule is what the reviewer enforces in a PR: _"should match release and commit lint"_.
The set of Conventional Commit `type`s and the **release effect** of each one are versioned in four places. They **must be identical**. When they diverge, commitlint accepts a commit that the release automation silently ignores (or the docs promise a bump that never happens), and the `/open-pr` / `/create-branch` flows start offering a type that does not produce the expected release. This rule was originally written for `semantic-release` (`.releaserc` `releaseRules`); **#798 replaced semantic-release with release-please**, whose bump semantics are **not configurable per type** — so the four surfaces now align on release-please's stock behavior instead of a custom table.

## The rule (invariant)

The list of accepted types and the type→bump must be **identical** at these four points:
The list of accepted types and the type→release-effect must be **identical** at these four points:

| File | What it defines |
|---|---|
| [`commitlint.config.js`](../../commitlint.config.js) (`type-enum`) | which types pass `commit-msg` |
| `packages/*/.releaserc` (`releaseRules`) | the bump of each type — in **all** packages: [`webkit`](../../packages/webkit/.releaserc), [`theme`](../../packages/theme/.releaserc), [`icons`](../../packages/icons/.releaserc) |
| [`CONTRIBUTING.md`](../../CONTRIBUTING.md) § Commit convention | the type → bump table, documented for humans |
| [`release-please-config.json`](../../release-please-config.json) (stock `release-type: node`, **no** bump customization) | which types actually release, per release-please semantics |
| [`CONTRIBUTING.md`](../../CONTRIBUTING.md) § Commit convention | the type → release-effect table, documented for humans |
| [`open-pr.md`](../commands/open-pr.md) + [`create-branch.md`](../commands/create-branch.md) | the list of types the flows infer/offer |

## Canonical set (today)
## Canonical set (today — release-please semantics)

- `feat` → **minor**
- `fix` / `hotfix` / `chore` / `docs` / `style` / `refactor` / `perf` → **patch**
- `test` / `ci` / `revert` → `release: false` (no bump; allowed for hygiene)
- `!` after the type or a `BREAKING CHANGE:` footer → **major**
- `fix` → **patch**
- `!` after the type or a `BREAKING CHANGE:` footer → **major** (on any type)
- `chore` / `docs` / `style` / `refactor` / `perf` / `test` / `ci` / `revert` → **no release on their own**. They are accepted for hygiene and ride along in the next release cut by a `feat`/`fix`; release-please does not count them as releasable units.

Each type is listed **explicitly** in the `releaseRules` of every `.releaserc` — do not rely on the implicit default of the `conventionalcommits` preset. A type without an explicit rule is a divergence waiting to happen.
> **`hotfix` was removed from the enum (2026-07-26).** Its name implied a release, but release-please does not recognize the type — a silent trap for exactly the most urgent commits. It had zero uses in the repo's history, so it was dropped: commitlint now rejects it loudly, and urgent production fixes are committed as `fix`.

## When adding, removing, or re-mapping a type

Make the four edits **in the same PR**:
## release-please details that matter

1. `commitlint.config.js` → `type-enum`.
2. **Every** `packages/*/.releaserc` → `releaseRules` (webkit, theme, icons). Don't forget any package.
3. `CONTRIBUTING.md` → the type → bump table and the enforcement note.
4. `open-pr.md` + `create-branch.md` → the list of types and the bump mapping.
- **Releasable units.** Stock release-please opens/updates a Release PR only for `feat`, `fix` and `deps` commits — plus anything marked breaking. Other types never trigger or bump a release by themselves. (`deps` is not in our commitlint enum; dependency bumps here land as `ci`/`chore` and therefore do not release.)
- **No per-type bump config.** Unlike semantic-release's `releaseRules`, release-please cannot map `chore` → patch. If a change must ship, give it a releasing type (`fix`/`feat`) — do not invent config that does not exist.
- **Squash-merge means the PR title is the commit.** Merges to `main` are squash merges (#795), so release-please parses the **PR title** as the conventional commit. The title must be commitlint-valid and carry the intended type; a breaking change belongs in the title as `!` — a `BREAKING CHANGE:` footer buried in the squashed body's bullet list is easy to lose.
- **Path filtering per package.** A commit counts toward a package's release only if it touches files under that package's path ([`release-please-config.json`](../../release-please-config.json)`#packages`). A `fix(webkit)` that only edits theme files bumps nothing for webkit.
- **`Release-As:` footer** overrides the computed version when an explicit version is required.
- **The Release PR is the release.** Merging a `feat`/`fix` updates the pending Release PR; merging the Release PR bumps versions, tags, and triggers the `package-*.yml` publish workflows via `release: published`.

No release? Use `{ "type": "<x>", "release": false }` in `releaseRules` (do not omit the type).
## When adding, removing, or re-mapping a type

## `@semantic-release/commit-analyzer` details that matter
Make the edits **in the same PR**:

- The custom `releaseRules` is evaluated **before** `DEFAULT_RELEASE_RULES`; the default only kicks in when **no** custom rule matches (`isUndefined`).
- A custom rule with `release: false` returns `false` (not `undefined`), so it **suppresses** the default. This is how `revert` ends up with no release, instead of falling into the default `{ revert: true } → patch`.
- The rule `{ "breaking": true, "release": "major" }` is **always last** in the array. Because of `compareReleaseTypes`, an earlier `release: false` rule does not downgrade a breaking commit — `major` still wins.
- The analyzer also filters by path: a commit only counts toward a package's release if it touches files under `packages/<scope>/`.
1. `commitlint.config.js` → `type-enum`.
2. `CONTRIBUTING.md` → the type table and the enforcement note.
3. `open-pr.md` + `create-branch.md` → the list of types and the release mapping.
4. If the intent is for a type to start releasing: that is **not** a config edit — release-please cannot express it. The change is "use `fix`/`feat` instead", and this rule + the docs must say so.

## What not to do

- Don't add a type to the command or to commitlint without adding it to the three `.releaserc` files.
- Don't touch only `webkit`'s `.releaserc` and forget `theme` / `icons`.
- Don't rely on the preset default for a type — list it explicitly.
- Don't let `CONTRIBUTING.md` describe a different bump than the one `releaseRules` produces.
- Don't add a type to the commands or to commitlint without updating CONTRIBUTING and the other command in the same PR.
- Don't document a bump release-please won't produce (the old `chore`/`docs`/`style`/`refactor`/`perf` → patch table died with semantic-release).
- Don't re-add `hotfix` (or any alias of `fix`) — a type whose name implies a release but never produces one is a trap; urgent fixes are `fix`.
- Don't reference `.releaserc` files — they were deleted with #798.
- Don't put a breaking change only in a commit footer of a multi-commit PR — put `!` in the PR title (squash merge).

## Why this rule exists

`/open-pr` and `/create-branch` listed `perf` / `test` / `ci` / `revert`, and `commitlint` accepted them, but the `.releaserc` files only enumerated seven types + breaking. `perf` only got a patch from the preset default and `test`/`ci`/`revert` did not appear — a divergence that earned a `CHANGES_REQUESTED`. Making the four points identical and explicit eliminates the entire class of bug.
`/open-pr` and `/create-branch` listed `perf` / `test` / `ci` / `revert`, and `commitlint` accepted them, but the (then) `.releaserc` files only enumerated seven types + breaking — a divergence that earned a `CHANGES_REQUESTED`. The same class of bug reappeared when #798 swapped semantic-release for release-please: this rule, CONTRIBUTING and the commands kept promising `hotfix`/`chore`/`docs`/`style`/`refactor`/`perf` → patch while stock release-please releases only on `feat`/`fix`/breaking. Rewritten 2026-07-26 to make release-please's real semantics the single documented truth across all four surfaces.
Loading
Loading