Thanks for working on Webkit. This document covers how to propose changes that pass review and ship cleanly.
- Read the README for repo layout and the consumer-facing API.
- Skim
.claude/rules/— these are non-negotiable:dependencies.md— no external positioning or animation libs.styling.md— classes on the root, no JS class presets.migration.md— never inherit, always rewrite.no-invention.md— the spec is the contract.
# Node >= 22.18.0, pnpm 10.x (corepack will install the pinned version)
pnpm install
pnpm storybook:devEvery new component starts as a spec at .specs/<name>.md. The spec is the contract; the .vue, story, and exports are derived from it.
- Draft the spec —
/spec-create <name>writes.specs/<name>.mdwithstatus: draft. Review and flip tostatus: approved. - Scaffold —
/component-create <name>writes the.vue, thepackages/webkit/package.json#exportsentry, and a minimal.stories.js. It will refuse to add props, events, or slots that are not in the spec. - Verify —
/component-verify <name>re-runs spec compliance and validators without touching files.
The spec template lives at .specs/_template.md. The Constraints block is verbatim by design — do not edit it.
- Update the spec first;
statusmust remainapprovedand thechecksumwill be recomputed byspec-validate. - Re-run
/component-verify <name>and the relevantpnpm webkit:*gates.
Components in .claude/hooks/_lib/legacy-components.json predate the pipeline. When migrating one under enforcement, rewrite the spec from scratch (see migration.md) — do not paste from the legacy file.
Before opening a PR, all of these must pass:
pnpm webkit:lint # ESLint, max-warnings 0
pnpm webkit:lint:style # Stylelint
pnpm webkit:type-check # vue-tsc --noEmit
pnpm webkit:type-coverage # type-coverage >= 95%
pnpm webkit:format:check # Prettier
pnpm storybook:build # Catches SFC compile errors invisible to vue-tscOr the aggregate:
pnpm governance # lint + type-check + format:check + security:auditThe governance workflow runs on every push to main — its status is the badge at the top of the README.
We use Conventional Commits. semantic-release parses commit messages to compute version bumps and changelogs, so the scope and type matter.
| Type | When | Bump |
|---|---|---|
feat |
New component, new prop/event/slot, new public export | minor |
fix |
Bug fix, accessibility correction, visual regression | patch |
hotfix |
Urgent production fix | patch |
chore |
Tooling, dependency bumps, internal cleanup | patch |
docs |
README, spec body, JSDoc | patch |
style |
Formatting / whitespace only | patch |
refactor |
Internal restructure with no API change | patch |
perf |
Performance improvement | patch |
test |
Test additions or changes | none |
ci |
CI/CD pipeline changes | none |
revert |
Reverting a prior commit | none |
! after type or BREAKING CHANGE: footer |
Removed/renamed prop, event, slot, or export | major |
The commit-analyzer regex in every .releaserc accepts these forms:
[NO-ISSUE] fix(webkit): commit message
[ENG-1231] fix(webkit): 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. - Scope is the package name without the namespace:
webkit,theme,icons. - Breaking changes use either the
!marker (feat(webkit)!: …) or aBREAKING CHANGE:footer.
Examples:
[ENG-1231] feat(webkit): add Dropdown component[NO-ISSUE] fix(theme): correct --ring-color for dark modechore(icons): regenerate after source updatefeat(webkit)!: drop deprecated tone prop on Button
Stay scoped: one package per commit when possible. Mixed-scope commits should use the broadest affected scope.
Note: the analyzer also gates by file path. A commit must touch files under
packages/<scope>/for that package'ssemantic-releaseworkflow to consider it. Afix(webkit): …commit that only edits theme files will not trigger a webkit release.
A husky commit-msg hook runs @commitlint/cli against commitlint.config.js, which mirrors the regex in every .releaserc. A malformed message is rejected at commit time with a pointer to the failing rule — you cannot accidentally land a commit that the release analyzer would silently drop.
The config also enforces:
typemust be one of:feat,fix,hotfix,chore,docs,style,refactor,perf,test,ci,revert. Every type is enumerated in each.releasercreleaseRules: the first eight produce a release (feat→ minor, the rest → patch);test,ci,revertcarryrelease: falsefor hygiene and produce no version bump. Breaking changes use the!marker orBREAKING CHANGE:footer and produce amajorrelease.typeandscopemust be lower-case.subjectcannot be empty.- Header (full first line) cannot exceed 100 characters.
- Title mirrors the lead commit (Conventional Commits).
- Body explains the why — screenshots for visual changes, before/after for behavior changes.
- Link the spec (
.specs/<name>.md) for component PRs. - One feature per PR. Refactors and cleanups go in their own PRs.
- All CI checks green before requesting review.
Reviewers will look for:
- Spec compliance. Every prop/event/slot in the
.vueis in the spec. Nothing extra. - Token usage. No HEX literals, no raw Tailwind palette (
bg-blue-500), no inline@keyframes. Tokens only — see.claude/rules/styling.md. - No invented dependencies. Imports resolve; no
floating-ui,popper,gsap,framer-motion, etc. - Accessibility. Keyboard paths, focus rings,
motion-reduce:*fallbacks for any motion. - Storybook coverage. The states listed in the spec each have a story.
Open an issue with:
- Affected package and version (
@aziontech/webkit@x.y.z). - Vue version and bundler (Vite/webpack/etc.).
- Minimal reproduction — a Storybook story or a CodeSandbox is ideal.
- Expected vs. actual behavior.
Releases are automated by semantic-release on merge to main. There is no manual release step. Three workflows publish independently:
package-icons.yml→@aziontech/iconspackage-theme.yml→@aziontech/themepackage-webkit.yml→@aziontech/webkit
If you need a merge that does not produce a release, use chore: or docs: (no version bump).
Open a draft PR or a GitHub issue; tag a CODEOWNER from .github/CODEOWNERS.