diff --git a/.claude/commands/commit.md b/.claude/commands/commit.md index 604f806122..221f7d4ceb 100644 --- a/.claude/commands/commit.md +++ b/.claude/commands/commit.md @@ -2,63 +2,41 @@ description: Create a commit following InstUI conventions with HUSKY=0 --- -Create a commit following the Instructure UI commit conventions: +Commit staged + relevant unstaged changes using Conventional Commits. -## Format Requirements - -Use Conventional Commits format: +## Format ``` -type(scope): subject - -[optional body] -Document any breaking changes here with BREAKING CHANGE: prefix - -[optional footer] -``` - -**Types**: feat, fix, docs, style, refactor, test, chore +type(scope): imperative subject -**Scope**: Full package name as-is (e.g., ui-button, ui-select). Use comma-separated for multiple packages, `many` for many packages, or omit for repo-wide changes. + -**Subject**: Brief imperative description (e.g., "add loading state", not "adds" or "added") +BREAKING CHANGE: -## Breaking Changes - -Mark breaking changes with an exclamation mark after scope and document in body: - -``` -feat(ui-select)!: remove deprecated onOpen prop +🤖 Generated with [Claude Code](https://claude.com/claude-code) -BREAKING CHANGE: The onOpen prop has been removed. Use onShowOptions instead. +Co-Authored-By: Claude ``` -Breaking changes include: - -- Removing/renaming props or components -- Changing prop types or behavior -- Changing defaults that affect behavior -- Removing theme variables or exports - -## Commit Footer - -Always include: +- **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"). 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. +- **Breaking changes**: add a `BREAKING CHANGE:` line in the body describing what breaks. See CLAUDE.md for what counts as breaking. -``` -🤖 Generated with [Claude Code](https://claude.com/claude-code) +## Steps -Co-Authored-By: Claude -``` +1. `git status` + `git diff` (and `git diff --staged` if anything's staged). +2. Stage the files that belong in this commit — be specific, don't `git add -A`. +3. Commit with `HUSKY=0` to skip the interactive husky prompt: -## Process + ```bash + HUSKY=0 git commit -m "$(cat <<'EOF' + + EOF + )" + ``` -1. Run `git status` and `git diff` to see changes -2. Analyze the changes and draft appropriate commit message -3. Add files to staging if needed: `git add ` -4. Create commit with `HUSKY=0 git commit -m "$(cat <<'EOF' -[commit message here with proper footer] -EOF -)"` -5. Run `git status` after to verify +4. `git status` to confirm. -**Important**: Use HUSKY=0 prefix to skip interactive prompt since AI can't interact with it. +If the husky hook fails, fix the underlying issue and create a **new** commit — never `--amend` after a failed hook. diff --git a/.claude/commands/pr.md b/.claude/commands/pr.md index df907553e1..422db353b5 100644 --- a/.claude/commands/pr.md +++ b/.claude/commands/pr.md @@ -2,55 +2,50 @@ description: Create a pull request following InstUI conventions --- -Create a pull request following Instructure UI conventions: +Open a PR for the current branch. -## PR Requirements +## Steps -All PRs must include: +1. `git status` — confirm branch + remote tracking. +2. `git log master..HEAD` and `git diff master...HEAD` — review **all** commits in the branch, not just the latest. +3. If not pushed: `git push -u origin `. +4. Create the PR (see invocation below). +5. Return the PR URL. -1. **Summary**: Brief description of changes (1-3 bullet points) -2. **Test Plan**: Clear steps for reviewers to test the changes -3. **Jira Reference**: Include relevant Jira ticket number if applicable (e.g., `Fixes INST-1234`) -4. **AI Disclosure**: Must clearly indicate this was created with AI assistance +If the branch name or any commit references a Jira ticket (e.g. `INSTUI-1234`), include it. If you can't find one, ask the user once before opening — don't invent one. -## Draft vs Ready +## gh invocation -- Open as **draft** when work is in progress or not ready for review -- Mark as **ready for review** only when complete and all requirements met +Use `--body-file -` with a heredoc on stdin. This avoids shell-quoting issues and is the form supported by current `gh` versions. If unsure about flags, run `gh pr create --help` first — do **not** fall back to older forms like `gh pr create -t ... -b ...` with inline `-b`. -## Process - -1. Run `git status` to check current branch and remote tracking -2. Run `git log master..HEAD` to see all commits that will be in the PR -3. Run `git diff master...HEAD` to see full diff from base branch -4. Analyze changes across ALL commits (not just latest) -5. Draft PR summary covering all changes -6. **If Jira ticket number is unknown, ask the user for it before creating the PR** -7. Push to remote if needed: `git push -u origin ` -8. Create PR with `gh pr create --title "title" --body "$(cat <<'EOF' +```bash +gh pr create --title "" --body-file - <<'EOF' +<body> +EOF +``` -## Summary +Open as draft (`--draft`) if the work is in progress. -- Bullet point 1 -- Bullet point 2 +## Body format -## Test Plan +Keep it **short**. No preamble, no restating the title, no "this PR does X" filler. -- [ ] Step 1 -- [ ] Step 2 +``` +## Summary +- <one line per logically distinct change; 1–4 bullets total> -## Jira Reference +## Test Plan +- <only manual / non-CI checks a reviewer should do> -Fixes INST-XXXX (or omit this section if not applicable) +Fixes INSTUI-XXXX 🤖 Generated with [Claude Code](https://claude.com/claude-code) -EOF -)"` 9. Return the PR URL +``` -**Important**: +Rules: -- Base branch is usually `master` (not main) -- Analyze ALL commits in the branch, not just the latest one -- Use markdown checklists for test plan -- Include AI attribution footer -- Always confirm Jira ticket number with user if not found in commits or branch name +- **Summary**: terse bullets. Each bullet is one change, not a paragraph. Skip context the diff already shows. +- **Test Plan**: only what CI **doesn't** cover — manual UI checks, RTL, a11y spot-checks, visual regression cases to look at, edge cases worth poking. Never include "tests pass", "lint passes", "types check", "build succeeds" — CI runs those. +- If there's genuinely nothing to manually verify (e.g. pure refactor with full test coverage), write `- No manual verification needed; covered by existing tests.` and move on. +- Omit the `Fixes` line entirely if no ticket applies. Don't write `Fixes INSTUI-XXXX (or omit if not applicable)`. +- Keep the Claude Code footer. diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000000..1ab1850258 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,39 @@ +{ + "permissions": { + "allow": [ + "Bash(git status:*)", + "Bash(git diff:*)", + "Bash(git log:*)", + "Bash(git show:*)", + "Bash(git branch:*)", + "Bash(git blame:*)", + "Bash(git stash list:*)", + "Bash(pnpm run lint)", + "Bash(pnpm run lint:changes)", + "Bash(pnpm run lint:commits)", + "Bash(pnpm run ts:check)", + "Bash(pnpm run test:vitest)", + "Bash(pnpm run test:vitest *)", + "Bash(pnpm list:*)", + "Bash(pnpm why:*)", + "Bash(ls:*)", + "Bash(find:*)", + "Bash(grep:*)", + "Bash(rg:*)", + "Bash(tree:*)", + "Bash(wc:*)", + "Bash(pwd)", + "Bash(node --version)", + "Bash(pnpm --version)", + "Bash(gh pr view:*)", + "Bash(gh pr list:*)", + "Bash(gh pr diff:*)", + "Bash(gh pr checks:*)", + "Bash(gh issue view:*)", + "Bash(gh issue list:*)", + "Bash(gh api:*)", + "Bash(gh --help)", + "Bash(gh * --help)" + ] + } +} diff --git a/.gitignore b/.gitignore index 6d8b537589..6168045152 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ tsconfig.node.build.tsbuildinfo .claude/settings.local.json .claude/cache/ .claude/*.local.* +CLAUDE.local.md # Ignore all commands except shared ones .claude/commands/* diff --git a/CLAUDE.md b/CLAUDE.md index 0145b3f9a8..2d583a1e9d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,201 +1,47 @@ -# Instructure UI - Claude Code Documentation +# Instructure UI -## Project Overview +React component library and design system. Lerna 8 + pnpm monorepo, ~100 packages under `/packages/ui-*`. -InstUI is a React component library and design system. **Lerna 8 monorepo** with 100+ packages. +External docs (preferred over guessing component APIs): https://instructure.design/llms.txt — per-component markdown at `https://instructure.design/markdowns/<Component>.md`. -**Key Resources:** +## Commands -- Website: https://instructure.design -- AI docs: https://instructure.design/llms.txt -- Component docs: https://instructure.design/markdowns/[ComponentName].md - - Example: https://instructure.design/markdowns/Alert.md - - Example: https://instructure.design/markdowns/Button.md +- `pnpm run bootstrap` — required first time and after `pnpm run clean`. Builds icons, compiles, generates tokens. Plain `pnpm install` is **not** enough. +- `pnpm run dev` — docs app at http://localhost:9090 (hot-reloads component changes). +- `pnpm run test:vitest <pkg>` — run a single package's unit tests, e.g. `pnpm run test:vitest ui-radio-input`. Without an argument it runs everything (slow). +- `pnpm run cy:component` — Cypress component tests. +- Visual regression lives in `/regression-test` (separate Next.js app, port 3000). See `/regression-test/README.md`. -**Tech Stack:** Node.js >=22, pnpm, React 18.3.1+, TypeScript 5.8.3, Emotion (CSS-in-JS), Vitest, Cypress, Chromatic +## Code conventions -## Repository Structure +- **Never hardcode user-facing strings.** All UI text must come from props for i18n. This is the most common review comment. +- **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. -``` -/packages # 100+ packages - /ui-* # Component packages (ui-button, ui-select, etc.) - /canvas-theme # Theme packages - /__docs__ # Documentation app -/docs # Documentation source files -/regression-test # Visual & a11y testing (Next.js 15 app) -/cypress # Component tests -/scripts # Build scripts -``` +## Component versioning (v1/v2) -**Important file locations:** +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. -- Component source: `/packages/ui-*/src/` -- Component tests: Co-located with components -- Theme types: `/packages/shared-types/src/ComponentThemeVariables.ts` -- Visual/a11y tests: `/regression-test/` -- Testing docs: `/docs/testing/testing-overview.md` +## Testing gotchas -## Quick Start +- Prefer `vitest` fake timers in unit tests. +- If `fireEvent.click` doesn't trigger handlers, pass `{ button: 0, detail: 1 }`. `FocusRegion.ts` inspects `event.detail`/`event.button` to distinguish real mouse clicks from synthetic events. +- Every component needs: unit tests (`*.test.tsx`, co-located), a page under `/regression-test/src/app/<name>/page.tsx`, a Cypress entry in `/regression-test/cypress/e2e/spec.cy.ts`, WCAG 2.1 AA compliance, and RTL support. -```bash -pnpm run bootstrap # First time setup (clean, build icons, compile, generate tokens) -pnpm run dev # Start dev server at http://localhost:9090 -``` +## Finding component info -Most changes hot-reload automatically when dev server is running. +1. **Start with the component README**: `/packages/<pkg>/src/<Component>/README.md` — this is the source for the published docs and has examples + prop descriptions. +2. Props: `props.ts` next to the component. Theme variables: `theme.ts` next to the component. +3. Shared theme types: `/packages/shared-types/src/ComponentThemeVariables.ts`. -## Essential Commands +If you add/change a prop, update the component's README in the same change — that file is what ships to instructure.design. -```bash -# Development -pnpm run dev # Dev server (http://localhost:9090) -pnpm run build:types # Build TypeScript declarations +## Breaking changes -# Testing -pnpm run test:vitest # Unit tests -pnpm run test:vitest ui-radio-input # Run tests for a single package -pnpm run cy:component # Cypress component tests +Avoid them unless the user explicitly asks. Breaking = removing/renaming a prop, component, theme variable, or exported util; changing a prop's type or a default that alters behavior. Adding optional props, new components, or new theme variables is fine. When a break is intentional, put `BREAKING CHANGE:` in the commit body — `/commit` handles the format. -# Linting -pnpm run lint:fix # Auto-fix linting issues -pnpm run ts:check # TypeScript references check +## Workflow -# Troubleshooting -pnpm run clean && pnpm run bootstrap # Fix build issues -pnpm run clean-node && pnpm install # Nuclear option (removes all node_modules) -``` - -## Code Style - -**IMPORTANT: Always use functional components with React hooks for new code.** - -- ✅ Functional components with hooks (useState, useEffect, etc.) -- ❌ No class components for new code (legacy codebase has them) -- ❌ **Never hardcode text** - all user-facing text must come from props (for i18n) -- ✅ Support accessibility (WCAG 2.1 AA), RTL languages -- ✅ Use TypeScript for all new code -- ✅ Use Emotion for styling (CSS-in-JS) - -## Finding Component Information - -**To find available components and their packages:** - -- Check `/packages/__docs__/src/components.ts` - lists all components with their package locations - -**Each component has two READMEs:** - -1. **Component README**: `/packages/[package]/src/[Component]/README.md` ⭐ - - - What the component does and usage examples - - **Check this first** - it has the detailed information - - These READMEs are also used to render the component documentation pages on instructure.design - -2. **Package README**: `/packages/[package]/README.md` - - Package overview, installation, exports - -**For complete API details of a component:** - -- Props: Check `props.ts` files in component source -- Theme variables: Check `theme.ts` files in component source - -## Component Development Workflow - -1. Find component in `/packages/ui-[name]/src/[Component]/` -2. Check Component README for API details -3. Make changes -4. Run tests: `pnpm run test:vitest` -5. Update README if the functionality has changed (e.g., a new prop was added) -6. Use `/commit` to create commit - -## Breaking Changes - -**IMPORTANT: Avoid breaking changes unless explicitly requested by the user.** - -A change is **breaking** if it requires consumers to modify their code: - -**Breaking changes (avoid):** - -- ❌ Removing or renaming a prop -- ❌ Changing a prop's type or behavior -- ❌ Removing or renaming a component -- ❌ Changing default values that affect behavior -- ❌ Removing or renaming theme variables -- ❌ Removing exported utilities or functions - -**Not breaking changes (preferred):** - -- ✅ Adding new optional props -- ✅ Adding new components -- ✅ Bug fixes that restore intended behavior -- ✅ Internal refactoring without API changes -- ✅ Adding new theme variables -- ✅ Deprecating features with warnings (without removing) -- ✅ Documentation updates - -When a breaking change is explicitly requested, document it clearly in the commit message with `BREAKING CHANGE:` in the body. - -## Testing Requirements - -All components **MUST**: - -1. Have unit tests (Vitest + React Testing Library in `*.test.tsx`) -2. Have visual regression tests in `/regression-test` -3. Pass accessibility audits (WCAG 2.1 AA) -4. Support RTL languages - -### Writing tests - -- Try to use `vitest`'s fake timers in unit tests -- When simulating mouse events (e.g., `fireEvent.click`), use `{ button: 0, detail: 1 }` if the click events don't seem to work. This is needed because `FocusRegion.ts/handleDocumentClick` uses `event.detail` and `event.button` to determine if the event was a mouse/touch click. - -### Running Tests - -```bash -pnpm run test:vitest # Unit tests -pnpm run cy:component # Cypress tests -pnpm run test:vitest ui-radio-input # Run tests for a single package - -# Visual regression tests (in regression-test directory) -cd regression-test -pnpm run dev # Start at localhost:3000 -pnpm run cypress-chrome # Run with GUI -``` - -### Adding Visual Regression Test - -1. Create test page: `/regression-test/src/app/[component-name]/page.tsx` -2. Add Cypress test: `/regression-test/cypress/e2e/spec.cy.ts` - -See `/regression-test/README.md` for detailed instructions. - -## Writing Documentation - -InstUI uses custom markdown with special code blocks (using [gray-matter](https://github.com/jonschlinkert/gray-matter) YAML syntax) for interactive examples. - -**Code block types:** - -- `type: code` - Syntax highlighting only -- `type: embed` - Rendered component only -- `type: example` - Interactive (rendered + editable) ⭐ Most common - -**IMPORTANT:** - -- Always write functional component examples with hooks -- All InstUI components are available without imports in examples - -See `/docs/contributor-docs/writing-docs.md` for complete guidelines and syntax. - -## Naming Conventions - -- **Packages**: `ui-[component-name]` (kebab-case) -- **Components**: PascalCase (Button, Select) -- **Props**: camelCase with prefixes (onClick, isDisabled) -- **Theme variables**: camelCase - -## Committing & PRs - -Use `/commit` and `/pr` slash commands - they follow InstUI conventions automatically. - -**Branch naming:** Create feature branches from `master` - -**PR merging:** PRs are typically squashed when merged +- 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; `/commit` sets `HUSKY=0` to skip the interactive prompt. +- Branch from `master`. PRs are squash-merged. +- **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/contributor-docs/writing-docs.md`.