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
42 changes: 37 additions & 5 deletions .claude/commands/commit.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,48 @@ type(scope): imperative subject

BREAKING CHANGE: <only if applicable>

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the PR description you claim drop the robot attribution line from commit messages.

I may consider removing the Co-Authored-By line too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i wanted to keep it for transparancy's sake, we can discuss later if we want to get rid of it completely

```

- **type**: one of `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`. `commitlint.config.js` extends [`@commitlint/config-conventional`](https://www.npmjs.com/package/@commitlint/config-conventional), which defines the allowed set — pick the type that genuinely matches the change (`feat`/`fix` only for actual features/bug fixes).
- **scope**: full package name (`ui-button`, `ui-select`). Comma-separate for a few, use `many` for several, omit for repo-wide.
- **subject**: imperative ("add loading state", not "added"). Must start with a lowercase letter (commitlint's `subject-case` rejects sentence/Start/PascalCase). No trailing period.
- **Body lines: hard-wrap at 100 characters.** Commitlint (`body-max-line-length: 100`) runs in CI and will reject longer lines. The footer lines (Claude Code attribution, Co-Authored-By) are exempt.
- **subject**: imperative ("add loading state", not "added"). Must start with a lowercase letter (commitlint's `subject-case` rejects sentence/Start/PascalCase). No trailing period. **Hard limit 100 characters** (`subject-max-length`), but aim for ~70: the median subject in this repo is 50. If you're pushing the limit, you're listing everything the change touches instead of naming the change.
- **Breaking changes**: add a `BREAKING CHANGE:` line in the body describing what breaks. See CLAUDE.md for what counts as breaking.
- **Attribution**: no `🤖 Generated with` line in commit messages — that belongs in PR bodies (`/pr` handles it).

### Body

**Omit the body when the subject says it all.** When you do write one, it explains **why** — the constraint, the cause, the thing the diff cannot show. Never restate what changed.

@joyenjoyer joyenjoyer Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure that any model is smart enough to understand the first sentence, how do they interpret "the subject says it all"? I think this is too abstract.

I would add two real life examples that help Claude's pattern recognition:

  1. if the commit message title is straightforward
  2. if it does not and therefore further explanation is needed in commit body

Claude derives patterns from examples easier than from general statements.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added some examples


```
✅ the subject says it all — no body
docs(ui-table): fix the caption prop description

✅ the subject can't carry the reason — the body explains why
fix(ui-link): derive the icon layout from props instead of makeStyles

The flex layout for icons was only applied after mount, so the server markup
differed from the mounted one and the page jumped during hydration.
```

- **Hard-wrap at 100 characters** (`body-max-line-length`). Trailers are exempt.
- **Never turn the body into a changelog.** No grouping headings (`Configuration:`, `Build Tooling:`), no numbered sections, no bullet list of the files you touched — the diff already lists them.
- Naming a specific file is fine when the file _is_ the point.

```
❌ a changelog of the diff
Configuration:
- Add pnpm-workspace.yaml
- Add .npmrc with hoisted node linker
Build Tooling:
- Update scripts/bootstrap.js

✅ the reason the diff cannot show
regression-test stays on npm so it keeps installing @instructure/ui
the way an external consumer would.
```

Writing about _before_ and _after_ is encouraged — "Previously the placeholder only showed on hover" is exactly right in a commit message, which is permanently anchored to its own diff.

## Steps

Expand All @@ -31,7 +63,7 @@ Co-Authored-By: Claude <noreply@anthropic.com>
- If you're on a feature branch, glance at its name. If it looks **unrelated** to the change you're about to commit, flag it and offer to branch off (so you don't pile an unrelated commit onto someone else's WIP); otherwise proceed.
2. Stage the files that belong in this commit — be specific, don't `git add -A`.
3. Propose a type(scope) and subject based on the diff, then **ask the user to confirm or override the commit type** before writing the message — don't assume `fix`/`feat` silently; **`feat`/`fix` types are used for non-test/tooling code in our public packages.**
4. Commit normally — let the git hooks run. The interactive Commitizen prompt is **no longer** a hook (it now lives behind `pnpm run commit` for humans), so a non-interactive `-m` commit works while `pre-commit` (lint-staged + TS references check) and `commit-msg` (commitlint) still fire:
4. Commit normally — let the git hooks run. Commitizen's interactive prompt is not a hook; it lives behind `pnpm run commit` for humans. A non-interactive `-m` commit therefore works, while `pre-commit` (lint-staged + TS references check) and `commit-msg` (commitlint) still fire:

```bash
git commit -m "$(cat <<'EOF'
Expand Down
1 change: 0 additions & 1 deletion .claude/reviewers.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
# Refresh manually as the team changes. Lines starting with # are ignored;
# a leading @ is optional.
matyasf
ToMESSKa
joyenjoyer
balzss
git-nandor
Expand Down
29 changes: 27 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,31 @@ External docs (preferred over guessing component APIs): https://instructure.desi
- **New components: functional + hooks only.** Class components exist in legacy code — don't extend that pattern.
- Styling is Emotion CSS-in-JS via `theme.ts` files co-located with each component.

## Code comments

**A comment must make sense to someone reading the file in a year who never saw the change that introduced it** — no access to the PR, the ticket, or the conversation. The same standard applies to commit messages; `/commit` has the specifics.

- Explain **why**, never **what**. If the code already says it, delete the comment.
- **One line.** Two or three only when the reason genuinely needs them.
- **Never reference the change itself.** `now`, `new`, `previously`, `used to`, `this change`, `the fix`, `as discussed`, `per review`, `we decided`, `recently` — these only mean something next to the diff. Name the constraint instead.
- **Never narrate the diff** (`// added onKeyDown handler`, `// updated to support X`) — that's what `git log` is for.
- No commented-out code, no banner or separator comments, and **don't add comments to code you didn't change**.
- Lowercase `//` on its own line above what it explains, never trailing. Ticket ids only on a real external blocker, and only `INSTUI-` keys — never another product's board: `// TODO INSTUI-1234: <what unblocks it>`.
- Leave the MIT license header alone — `notice/notice` in `.oxlintrc.json` enforces it.

```ts
// ❌ verbose, references the change, restates the code
// We now memoize this because we found a performance issue during testing
// where the component re-rendered too often. Previously computed inline.
const styles = useMemo(...)

// ✅ names the constraint, reads standalone
// getCSSStyleDeclaration costs ~100ms per call
const styles = useMemo(...)
```

Prop docs are a JSDoc block with **one prose sentence** and no `@param`/`@type` — types come from TypeScript and `react-docgen`.

## Component versioning (v1/v2)

Some components ship in two versions during a migration period — a legacy **v1** and a newer **v2** (e.g. `DateInput`). v2 is the preferred implementation for new work; v1 is deprecated and gets removed in a later major release. Don't assume a component has only one version: check its README and the package exports to see which versions exist and which is current before using or changing one.
Expand All @@ -42,6 +67,6 @@ Avoid them unless the user explicitly asks. Breaking = removing/renaming a prop,
## Workflow

- Use `/commit` and `/pr` — they follow InstUI conventions (Conventional Commits with package-name scopes, PR body with an `INSTUI-` Jira ref). Husky `pre-commit` runs lint-staged + a TS references check and `commit-msg` runs commitlint; both fire on a normal `git commit`. The interactive Commitizen prompt is **not** a git hook — run `pnpm run commit` for the guided flow. Don't use `HUSKY=0` to bypass failing hooks; fix the cause.
- Branch from `master`. PRs are squash-merged.
- **Integrate `master` by rebasing, not merging** — use `git rebase master` (or `git pull --rebase`) to update a branch. Don't create merge commits; keep branch history linear since PRs are squash-merged anyway.
- Branch from `master`. PRs are rebase-merged — squash and merge commits are both disabled, so every commit on the branch lands on `master` as its own commit. Make each one stand on its own; fold review fixes into the commit they belong to with `git commit --fixup` instead of appending them.
- **Integrate `master` by rebasing, not merging** — use `git rebase master` (or `git pull --rebase`) to update a branch. Don't create merge commits; branch history is what `master` gets.
- **Docs structure:** the site is generated from source code (JSDoc + `react-docgen` for prop types) plus `.md` files. Markdown docs use fenced code blocks with a gray-matter `type:` header that controls rendering: `type: code` (syntax-highlighted, not executed), `type: embed` (renders the JSX live into the page), and `type: example` (interactive, editable playground). Full reference: `/docs/contributing/writing-docs.md`.
8 changes: 3 additions & 5 deletions commitlint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,12 @@ function getAllPackages() {

module.exports = {
extends: ['@commitlint/config-conventional'],
parserOpts: {
headerPattern: /^(\w*)\((\w*)\)-(\w*)\s(.*)$/,
headerCorrespondence: ['type', 'scope', 'subject']
},
// https://commitlint.js.org/reference/rules.html
rules: {
// The header is unbounded because multi-package scopes are long, e.g.
// `fix(ui-drawer-layout,ui-a11y-utils):`. The subject itself is capped.
'header-max-length': [0, 'always', 150], // 0 === rule is disabled
'subject-max-length': [2, 'always', 150]
'subject-max-length': [2, 'always', 100]
},

// https://cz-git.qbb.sh/config/
Expand Down
12 changes: 12 additions & 0 deletions docs/contributing/contributing-getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,18 @@ Please update the documentation and examples with any changes.
- Write documentation inline in code comment blocks. The code and docs should
always be in sync.

### Code Comments

Write comments for someone reading the file a year from now, with no access to
the pull request or ticket that introduced the change.

- Explain _why_, not _what_. If the code already says it, leave the comment out.
- Keep it to one line where you can.
- Don't refer to the change itself ("we now…", "previously…", "this fix…") or
narrate the diff ("added onKeyDown handler") - `git log` covers that.
- Document props with a JSDoc block containing one prose sentence. Types are
parsed from TypeScript, so `@param` and `@type` tags aren't needed.

### Commit Guidelines

Run `git commit` to commit your changes and follow our commit message format.
Expand Down
Loading