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 @@ -16,7 +16,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`, `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.
4. **Related issue (optional).** Ask: "Which issue does this relate to? (`ENG-1234`, or leave blank if none)". A blank answer is fine — treat it as no issue (no tag anywhere; never `[NO-ISSUE]`). Do not block on it.
5. **Create.**
- With an issue: `git checkout -b <type>/<ISSUE>-<slug> origin/main`
- Without: `git checkout -b <type>/<slug> origin/main`
Expand Down
9 changes: 5 additions & 4 deletions .claude/commands/open-pr.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,17 @@ Run `git diff` (staged + unstaged) and determine, from the change itself:
If the diff spans multiple packages, prefer **one commit per scope** (CONTRIBUTING: "one package per commit when possible").

### 2. Related issue (optional)
- Ask: "Which issue does this relate to? (`ENG-1234`, or leave blank for `NO-ISSUE`)". Blank → use `[NO-ISSUE]`.
- Ask: "Which issue does this relate to? (`ENG-1234`, or leave blank if none)". With an issue, the tag goes **in the subject, after the colon** (`fix(webkit): [ENG-1234] …`) — never before the type. **No issue → no tag** — never write `[NO-ISSUE]` (dead characters; commitlint rejects it).

### 3. Ensure a `main`-based feature branch
- If on `main` (or not on a `main`-based feature branch), run the `/create-branch` flow (type from step 1, issue from step 2), carrying the working changes over.
- **Never** open a PR directly from `main`.

### 4. Commit by context
- **Split shared docs/rules from code.** If the diff mixes code with shared docs/rules/templates (`.claude/rules/*`, `.claude/skills/*`, `.specs/_template.md`, …), tell the user those belong in a **separate PR** and offer to split them out. A component's own new `.specs/<name>.md` stays with the component.
- Header (commitlint-valid, ≤100 chars): `[<ISSUE|NO-ISSUE>] <type>(<scope>): <subject>`
- Breaking: `[…] <type>(<scope>)!: <subject>` plus a `BREAKING CHANGE: <what + migration>` footer.
- Header (commitlint-valid, ≤100 chars): `<type>(<scope>): [<ISSUE>] <subject>` — the `[<ISSUE>]` tag only when an issue exists; with none, plain `<type>(<scope>): <subject>`.
- Breaking: `<type>(<scope>)!: [<ISSUE>] <subject>` plus a `BREAKING CHANGE: <what + migration>` footer.
- The header **starts with the bare type**. A leading ticket tag (`[ENG-1234] fix: …`) is unparseable by release-please — the merge would release **nothing** — and commitlint now rejects it (as it rejects `[NO-ISSUE]` anywhere).
- **Never** add a `Co-Authored-By` trailer or any "Generated with Claude" / attribution footer.
- Let the hooks run — `commit-msg` runs commitlint and the header must pass. **Do not** use `--no-verify` to skip commitlint. If `pre-commit` (lint-staged) fails for an environmental reason, report it and ask the user before retrying.

Expand All @@ -45,7 +46,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`). 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.
- **Title:** the conventional commit header (normalize `$ARGUMENTS` into that form when given). Merges are **squash merges**, so the PR title is the commit release-please parses — it must **start with the bare type**, with the ticket tag after the colon only when an issue exists (`<type>(<scope>): [<ISSUE>] <subject>`), 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
15 changes: 8 additions & 7 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,20 +118,20 @@ Types marked **none** never release on their own — they ride along in the next
The commit parser accepts these forms:

```text
[NO-ISSUE] fix(webkit): commit message
[ENG-1231] fix(webkit): commit message
fix(webkit): [ENG-1231] commit message
fix(webkit): commit message
fix: commit message
```

- **Ticket prefix** is optional. Use `[NO-ISSUE]` when there is no tracking ticket, or `[<PROJECT-NUMBER>]` (e.g. `[ENG-1231]`) otherwise.
- **The header starts with the bare type.** A leading ticket tag (`[NO-ISSUE] fix(webkit): …` — the pre-2026-07 convention) is unparseable by release-please, so the merge would silently release **nothing**; commitlint rejects it (`header-no-leading-ticket`).
- **Ticket tag** — only when a tracking ticket exists: `[<PROJECT>-<NUMBER>]` (e.g. `[ENG-1231]`) at the start of the **subject**, right after the colon. **No ticket → no tag** — never write `[NO-ISSUE]`; it is dead characters in the 100-char budget and in changelogs, and commitlint rejects it (`subject-ticket-tag`, which also rejects malformed tags like `[eng-123]`). The tag is part of the subject, so it appears in changelogs.
- **Scope** is the package name without the namespace: `webkit`, `theme`, `icons`.
- **Breaking changes** use either the `!` marker (`feat(webkit)!: …`) or a `BREAKING CHANGE:` footer.

Examples:

- `[ENG-1231] feat(webkit): add Dropdown component`
- `[NO-ISSUE] fix(theme): correct --ring-color for dark mode`
- `feat(webkit): [ENG-1231] add Dropdown component`
- `fix(theme): correct --ring-color for dark mode`
- `chore(icons): regenerate after source update`
- `feat(webkit)!: drop deprecated tone prop on Button`

Expand All @@ -145,6 +145,7 @@ A husky `commit-msg` hook runs `@commitlint/cli` against [`commitlint.config.js`

The config also enforces:

- The header **starts with the bare type** — its pattern now matches release-please's parser exactly. A leading `[TICKET]` tag is rejected with a pointer to the new form (`header-no-leading-ticket`), and a subject-leading tag must be a well-formed real ticket (`[ABC-123]` + space + text); `[NO-ISSUE]` is rejected — omit the tag instead (`subject-ticket-tag`).
- `type` must be one of: `feat`, `fix`, `chore`, `docs`, `style`, `refactor`, `perf`, `test`, `ci`, `revert`. Only `feat` (minor) and `fix` (patch) produce a release; every other type is accepted for hygiene and produces **no** version bump on its own. Breaking changes use the `!` marker or `BREAKING CHANGE:` footer and produce a `major` release on any type. (`hotfix` was removed 2026-07-26 — release-please never released it; use `fix`.)
- `type` and `scope` must be lower-case.
- `subject` cannot be empty.
Expand Down Expand Up @@ -179,13 +180,13 @@ Open an [issue](https://github.com/aziontech/webkit/issues/new) with:

## Releasing

Releases are automated by `semantic-release` on merge to `main`. There is no manual release step. Three workflows publish independently:
Releases are automated by [release-please](https://github.com/googleapis/release-please) ([`release-please.yml`](./.github/workflows/release-please.yml)) — the Release PR model. Merging `feat` / `fix` / breaking commits to `main` creates or updates a pending **Release PR** (version bumps + changelogs); merging that Release PR creates the per-package tags and GitHub Releases, which trigger the publish workflows via `release: published`:

- [`package-icons.yml`](./.github/workflows/package-icons.yml) → `@aziontech/icons`
- [`package-theme.yml`](./.github/workflows/package-theme.yml) → `@aziontech/theme`
- [`package-webkit.yml`](./.github/workflows/package-webkit.yml) → `@aziontech/webkit`

If you need a merge that does not produce a release, use `chore:` or `docs:` (no version bump).
There is no manual release step. If you need a merge that does not produce a release, use `chore:` or `docs:` (no version bump).

## Questions

Expand Down
53 changes: 47 additions & 6 deletions commitlint.config.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
/**
* Commitlint config — the commit-message gate for the repo.
* Merges to `main` are squash merges: release-please parses the squashed
* commit (the PR title), so keep PR titles commitlint-valid too.
* commit header (the PR title), and its conventional-commit parser is NOT
* configurable — the header must START with the bare type. A leading ticket
* tag (`[NO-ISSUE] fix: …`) is unparseable there and silently produces no
* release, so that form is rejected here; the ticket tag lives in the
* subject instead, right after the colon — and only when a real ticket
* exists. No ticket → no tag (never write "[NO-ISSUE]" — dead characters).
*
* Accepted forms:
* [NO-ISSUE] fix(webkit): commit message
* [ENG-1231] fix(webkit): commit message
* fix(webkit): commit message
* fix(theme): [ENG-1234] move vue-tsc to devDependencies
* feat(webkit)!: [ENG-999] drop tone prop
* fix(webkit): commit message ← no ticket → no tag
* fix: commit message
*
* Rejected:
* [NO-ISSUE] fix(webkit): commit message ← pre-2026-07 form — invisible to release-please
* fix(webkit): [NO-ISSUE] commit message ← no ticket → omit the tag entirely
*
* Breaking changes (produce a major release):
* feat(webkit)!: drop tone prop ← `!` after type/scope
* feat(webkit): add x\n\nBREAKING CHANGE: drops y ← footer form
Expand All @@ -24,11 +33,42 @@
export default {
parserPreset: {
parserOpts: {
headerPattern: /^(\[[\w-]+\]\s+)?(\w+)(?:\(([\w-]+)\))?!?:\s(.*)$/,
headerCorrespondence: ['ticket', 'type', 'scope', 'subject']
headerPattern: /^(\w+)(?:\(([\w-]+)\))?!?:\s(.*)$/,
headerCorrespondence: ['type', 'scope', 'subject']
}
},
plugins: [
{
rules: {
// Migration guard: the old `[TICKET] type: …` header parses as no
// type at all, so without this rule the only feedback would be a
// cryptic "type may not be empty". Point at the new form instead.
'header-no-leading-ticket': (parsed) => {
const header = parsed.header ?? ''
return [
!/^\[[^\]]*\]/.test(header),
'the ticket tag moved: write "type(scope): [TICKET] subject" — a leading "[TICKET] type: …" header is invisible to release-please and releases nothing'
]
},
// When the subject opens with a bracket tag, it must be a real,
// well-formed ticket ([ABC-123]) followed by a space and the subject
// text. No ticket → no tag ("[NO-ISSUE]" is dead characters).
'subject-ticket-tag': (parsed) => {
const subject = parsed.subject ?? ''
if (!subject.startsWith('[')) return [true]
if (/^\[NO-ISSUE\]/i.test(subject)) {
return [false, 'no tracking ticket → omit the tag entirely (do not write "[NO-ISSUE]")']
}
return [
/^\[[A-Z]+-\d+\] \S/.test(subject),
'subject ticket tag must be "[ABC-123]" followed by a space and the subject text'
]
}
}
}
],
rules: {
'header-no-leading-ticket': [2, 'always'],
'type-empty': [2, 'never'],
'type-case': [2, 'always', 'lower-case'],
'type-enum': [
Expand All @@ -50,6 +90,7 @@ export default {
'scope-case': [2, 'always', 'lower-case'],
'subject-empty': [2, 'never'],
'subject-case': [0],
'subject-ticket-tag': [2, 'always'],
'header-max-length': [2, 'always', 100]
}
}
19 changes: 11 additions & 8 deletions packages/webkit/docs/DOC_LINTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -1402,24 +1402,27 @@ Run `pnpm webkit:type-coverage` — `--detail` lists every offending expression.

## 10. commitlint — commit messages

Commits **drive releases**: release-please parses the same header — on `main`, the squashed **PR title** — to compute version bumps ([`release-types.md`](../../../.claude/rules/release-types.md)). Header anatomy:
Commits **drive releases**: release-please parses the same header — on `main`, the squashed **PR title** — to compute version bumps ([`release-types.md`](../../../.claude/rules/release-types.md)). Its parser is **not configurable**: the header must start with the bare type, so a ticket tag lives at the start of the **subject** — and only when a real ticket exists (no ticket → no tag). Header anatomy:

```
[ENG-1231] feat(webkit)!: add table export
└───┬────┘ └─┬┘└──┬───┘│ └──────┬───────┘
ticket type scope breaking subject
(optional) (opt.) (opt.)
feat(webkit)!: [ENG-1231] add table export
└─┬┘└──┬───┘│ └───┬────┘ └──────┬───────┘
type scope breaking ticket subject
(opt.) (opt.) (optional)
```

**❌ Wrong**

```text
Update button styles → no type
[NO-ISSUE] fix(webkit): focus ring → leading ticket tag releases NOTHING (header-no-leading-ticket)
fix(webkit): [NO-ISSUE] focus ring → no ticket → omit the tag entirely (subject-ticket-tag)
ENG-1231 fix(webkit): focus ring → header must start with the type: fix(webkit): [ENG-1231] focus ring
fix(webkit): [eng-123] focus ring → malformed tag; use [ABC-123] (subject-ticket-tag)
Feat(webkit): add table export → type must be lower-case
feature(webkit): add table export → "feature" is not in the enum; use feat
feat(Webkit): add table export → scope must be lower-case
fix(webkit): → subject is required
ENG-1231 fix(webkit): focus ring → ticket must be bracketed: [ENG-1231]
fix(webkit): correct the paginator focus ring so that it stays visible in dark mode and in high-contrast themes
→ header over 100 chars (111)
```
Expand All @@ -1428,8 +1431,8 @@ fix(webkit): correct the paginator focus ring so that it stays visible in dark m

```text
fix(webkit): correct paginator focus ring
[ENG-1231] feat(webkit): add table export
[NO-ISSUE] chore: bump tooling
feat(webkit): [ENG-1231] add table export
chore: bump tooling
docs: describe compound API exports
feat(webkit)!: drop tone prop
```
Expand Down
Loading