From 889c5d0a4304f1383c5014a27eed1cef23fc8b8b Mon Sep 17 00:00:00 2001 From: Rob Snow Date: Mon, 20 Jul 2026 14:26:01 +1000 Subject: [PATCH 1/5] chore: add a Claude.md file to help with contributions --- CLAUDE.md | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000000..4a6ec69e0db --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,59 @@ +# CLAUDE.md + +Guidance for working in the react-spectrum monorepo. + +## Repo layout + +The repo is layered. Changes flow up from the lowest level: + +- **`@internationalized/*` and `@react-stately/*`** — the two lowest levels (i18n utilities and state management). +- **`@react-aria/*`** — behavior and accessibility hooks built on the above. +- **`react-aria-components` (RAC)** and some **React Spectrum v3 (RSP)** — component layer built on the hooks. +- **RSP S2 (`@react-spectrum/s2`)** — the Spectrum 2 design system, the highest level. + +Test suites are split by type: + +- **Jest tests** — `yarn test` +- **SSR tests** — `yarn test:ssr` +- **Browser tests** — `yarn test:browser` +- **Visual regression tests (VRT)** — `yarn chromatic` +- **High-contrast-mode VRT** — `yarn chromatic:forced-colors` + +Maintainers run the Chromatic VRT suites themselves — don't run `yarn chromatic` / `yarn chromatic:forced-colors`. You can still start the VRT Storybooks locally to verify visual state: `yarn start:chromatic` and `yarn start:chromatic-fc`. + +All commonly used commands live in the root `package.json` scripts. + +Tests are **not** co-located with source — each package keeps them in a sibling `test/` directory. The file suffix routes the test to a runner: `*.ssr.test.*` → `yarn test:ssr`, `*.browser.test.*` → `yarn test:browser`, plain `*.test.*` → `yarn test` (Jest). Shared test helpers live in `@react-aria/test-utils` / `@react-spectrum/test-utils` (the `User` event abstraction and per-component testers). + +## Tooling + +This repo does **not** use the conventional JS toolchain — reach for these, and don't hand-format code or swap in defaults: + +- **Format** — `oxfmt` (`yarn format`), not Prettier. The style is opinionated (single quotes, no bracket spacing → `{foo}`, no trailing commas). Always run the tool rather than formatting by hand. +- **Lint** — `oxlint` plus repo-local rules, not ESLint. `yarn lint` bundles format-check, type-check, `oxlint`, and Yarn `constraints` (which enforce cross-package dependency versions). +- **Type-check** — `tsgo` (`yarn check-types`), the native TypeScript compiler — not `tsc`. A `tsc` fallback exists as `yarn check-types:tsc`. +- **Build** — Parcel driven by `make` (`yarn build`), not plain `tsc`/rollup. +- **Yarn 4 workspaces** monorepo; use `yarn workspaces foreach` for cross-package operations. + +## Writing tests + +- **Run the full suite before committing.** Do not write PR descriptions that list a subset of specific passing tests — run everything (`yarn test`, and `yarn test:browser` when relevant). +- **Run lint and formatting before committing** (`yarn lint`, `yarn format`). +- **Test at the right level.** For any change at the RAC level or below (including hooks), write the test at the RAC level ideally. If the change lives at a higher level, test at that level. +- **Move to browser tests when needed.** If a test requires mocking specific browser behavior, consider moving it to the browser run (`yarn test:browser`). +- **Cover the reported issue.** When fixing a reported issue, add a test that reproduces the specific example given in the issue. +- **Check whether the test already exists.** Find a home for it near other similar tests. +- **Check code coverage** to decide whether a new test adds value — this is subjective. +- **In unit tests, prefer** fake timers, our test utils, and user event. +- **Keep comments minimal** — let the code speak for itself. +- **Combine tests** that share the same setup before an assertion. +- **Ground test titles in the goal**, not the implementation — double-check they are accurate. + +## Contributing + +- **Match the surrounding code** — follow the naming, structure, and patterns of neighboring files. +- **Commit format** — use conventional-commit prefixes (`fix:`, `feat:`, `chore:`, `docs:`) as seen in the git history. +- **Storybook** is the main way to develop and view components: `yarn start` (v3) and `yarn start:s2` (S2). +- **S2 styling** — style with the `style` macro (`import {style} from '../style' with {type: 'macro'};` — the `with {type: 'macro'}` attribute is load-bearing). Pass typed style objects to it; don't write CSS files or hand-rolled className strings for S2. +- **User-facing strings** — add the key to the package's `intl/en-US.json` (ICU MessageFormat) and read it via the localized string hook. Never hardcode UI text, and don't hand-edit the other locale files (translators own those). +- **Generated code** — icon components are generated (`yarn build:icons`), not hand-written, and `postinstall` runs `patch-package`, so run install on a fresh clone. From 55ac19ce58c254621be26f55428cd796e467ae96 Mon Sep 17 00:00:00 2001 From: Rob Snow Date: Tue, 28 Jul 2026 15:23:17 +1000 Subject: [PATCH 2/5] improving AI contribution expectations --- .github/PULL_REQUEST_TEMPLATE.md | 3 +++ CLAUDE.md | 13 ++++----- CONTRIBUTING.md | 46 ++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 85fa50515bf..3c0ae90bdb2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,3 +1,4 @@ + Closes @@ -8,6 +9,8 @@ Closes - [ ] Filled out test instructions. - [ ] Updated documentation (if it already exists for this component). - [ ] Looked at the Accessibility Practices for this feature - [Aria Practices](https://www.w3.org/WAI/ARIA/apg/) +- [ ] I understand every change in this PR and can explain why it's there. +- [ ] If AI-assisted, I followed our [AI contribution guidance](https://github.com/adobe/react-spectrum/blob/main/CONTRIBUTING.md#ai-assisted-contributions) and pointed my assistant at [CLAUDE.md](https://github.com/adobe/react-spectrum/blob/main/CLAUDE.md). ## 📝 Test Instructions: diff --git a/CLAUDE.md b/CLAUDE.md index 4a6ec69e0db..0bfb77c6ff7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -43,17 +43,18 @@ This repo does **not** use the conventional JS toolchain — reach for these, an - **Move to browser tests when needed.** If a test requires mocking specific browser behavior, consider moving it to the browser run (`yarn test:browser`). - **Cover the reported issue.** When fixing a reported issue, add a test that reproduces the specific example given in the issue. - **Check whether the test already exists.** Find a home for it near other similar tests. -- **Check code coverage** to decide whether a new test adds value — this is subjective. -- **In unit tests, prefer** fake timers, our test utils, and user event. -- **Keep comments minimal** — let the code speak for itself. +- **Check code coverage** to help decide whether a new test adds value — this is subjective. +- **In unit tests, prefer** fake timers, our test utils, and user event. Aside from those, prefer not mocking other modules, instead, move the test to a higher level. - **Combine tests** that share the same setup before an assertion. - **Ground test titles in the goal**, not the implementation — double-check they are accurate. ## Contributing - **Match the surrounding code** — follow the naming, structure, and patterns of neighboring files. +- **Limit comments** — let the code speak for itself. Provide a holistic summary of how the changes work and why this approach was used in the description. If a particular section of code is complex, then prefer a higher level description of multiple lines over explaining a single line. - **Commit format** — use conventional-commit prefixes (`fix:`, `feat:`, `chore:`, `docs:`) as seen in the git history. -- **Storybook** is the main way to develop and view components: `yarn start` (v3) and `yarn start:s2` (S2). -- **S2 styling** — style with the `style` macro (`import {style} from '../style' with {type: 'macro'};` — the `with {type: 'macro'}` attribute is load-bearing). Pass typed style objects to it; don't write CSS files or hand-rolled className strings for S2. +- **Opening a PR** — use `.github/PULL_REQUEST_TEMPLATE.md` as the PR body (e.g. `gh pr create --body-file .github/PULL_REQUEST_TEMPLATE.md`), don't hand-write a body. Fill out the checklist honestly and disclose AI use. +- **Storybook** is the main way to develop and view components: `yarn start` (v3/RAC) and `yarn start:s2` (S2). +- **S2 styling** — style with the `style` macro (`import {style} from '../style' with {type: 'macro'};` — the `with {type: 'macro'}` attribute is required). Pass typed style objects to it; don't write CSS files or hand-rolled className strings for S2. - **User-facing strings** — add the key to the package's `intl/en-US.json` (ICU MessageFormat) and read it via the localized string hook. Never hardcode UI text, and don't hand-edit the other locale files (translators own those). -- **Generated code** — icon components are generated (`yarn build:icons`), not hand-written, and `postinstall` runs `patch-package`, so run install on a fresh clone. +- **Generated code** — v3 icon components are generated (`yarn build:icons`) and s2 are handled through a parcel transformer, not hand-written, and `postinstall` runs `patch-package`, so run install on a fresh clone. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ff52e57539a..94db3a9e4bd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,6 +44,52 @@ Read [GitHub's pull request documentation](https://help.github.com/articles/abou Lastly, please follow the pull request template when submitting a pull request! + +## AI-assisted contributions +Setting expectations: the AI doesn't contribute to React Spectrum or Quarry, you do. The AI is a tool, but you are still the author, and you own every line, every decision, and every explanation. + +If you use an AI assistant, point it at our [CLAUDE.md](CLAUDE.md), which captures the repo conventions we expect it to follow. + +### Aligning on a solution + +Open an issue or discussion, or at minimum, describe the problem and intended solution in the PR description. Otherwise, we have to reverse-engineer both the problem and the intended solution before we can even begin reviewing. An issue with: "here's what I'm seeing, here's what I plan to do, does that sound right?" really helps. + +### Give us the intent, not just the fix + +Tell us what you want and why, separately from how you did it. Then if we need to make a change to the PR, we can be confident that we're satisfying the goal you had. This framing helps when prompting the AI as well. + +### Tell us what you tested + +* mouse / touch / keyboard / screen reader +* LTR / RTL +* light / dark / high-contrast +* disabled / loading / error / empty +* narrow / wide / truncated / very long / wrapping text +* component sizes and zoom levels + +Even if you haven't tested all of these, it's hugely helpful to us to know where to focus efforts. + +### Beware false confidence + +One of the biggest issues we face with the rise of AI contributions is the wrong root cause but with a lot of details asserting why it is, in fact, the issue. Press the AI, and ask both it and yourself "is this the root cause, or a symptom?" and "what else could cause this?" before committing to a solution. + +### Keep a human in the loop of the conversation + +When you iterate between reviews, be sure you can say what changed and why, one sentence is enough. A PR that transforms completely between every review without explanation is exhausting to follow and makes us feel like we're arguing with a machine instead of collaborating with a person. + + +#### Requirements of front end code + +These can be useful constraints or reminders as AI is not inherently good at these things. + +* **Small** — everything you add ships across the network to the client, so more code means slower load times. +* **Fast** — must run well on constrained CPU and memory, not just the latest MacBook Pro. Many users are on years-old, low-end Android devices. +* **Mindful of the shared environment** — keep the global namespace clean, don't hog resources or throw uncaught errors, avoid CSS that leaks across boundaries, and keep ids unique. +* **Stable** — RSP and Quarry are libraries with many downstream dependents who upgrade on their own schedule, so avoid breaking them. +* **Accessible** — accessibility is still a relatively new web requirement, so strong examples are scarce and bad ones are common. use other examples in the repo or the APG examples first. +* **Cross-environment** — works across browsers, assistive technologies, and devices. + + ### Contributor License Agreement All third-party contributions to this project must be accompanied by a signed contributor license agreement. This gives Adobe permission to redistribute your contributions as part of the project. [Sign our CLA](https://opensource.adobe.com/cla.html). You only need to submit an Adobe CLA one time, so if you have submitted one previously, you are good to go! From 1f2908319fea4a85aa6a16cced1291bf81ccaf9d Mon Sep 17 00:00:00 2001 From: Rob Snow Date: Tue, 4 Aug 2026 10:21:59 +1000 Subject: [PATCH 3/5] divvy up claude.md to task specific files --- AGENTS.md | 1 + CLAUDE.md | 56 +++++++++--------------------- CONTRIBUTING.md | 4 +-- docs/contributing/codegen.md | 3 ++ docs/contributing/i18n-strings.md | 3 ++ docs/contributing/pull-requests.md | 11 ++++++ docs/contributing/s2-styling.md | 3 ++ docs/contributing/testing.md | 26 ++++++++++++++ docs/contributing/tooling.md | 13 +++++++ 9 files changed, 78 insertions(+), 42 deletions(-) create mode 120000 AGENTS.md create mode 100644 docs/contributing/codegen.md create mode 100644 docs/contributing/i18n-strings.md create mode 100644 docs/contributing/pull-requests.md create mode 100644 docs/contributing/s2-styling.md create mode 100644 docs/contributing/testing.md create mode 100644 docs/contributing/tooling.md diff --git a/AGENTS.md b/AGENTS.md new file mode 120000 index 00000000000..681311eb9cf --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +CLAUDE.md \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 0bfb77c6ff7..d12a9d54e38 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,50 +11,26 @@ The repo is layered. Changes flow up from the lowest level: - **`react-aria-components` (RAC)** and some **React Spectrum v3 (RSP)** — component layer built on the hooks. - **RSP S2 (`@react-spectrum/s2`)** — the Spectrum 2 design system, the highest level. -Test suites are split by type: +## Toolchain guardrails -- **Jest tests** — `yarn test` -- **SSR tests** — `yarn test:ssr` -- **Browser tests** — `yarn test:browser` -- **Visual regression tests (VRT)** — `yarn chromatic` -- **High-contrast-mode VRT** — `yarn chromatic:forced-colors` +This repo does **not** use the conventional JS toolchain — use these, don't swap in defaults: -Maintainers run the Chromatic VRT suites themselves — don't run `yarn chromatic` / `yarn chromatic:forced-colors`. You can still start the VRT Storybooks locally to verify visual state: `yarn start:chromatic` and `yarn start:chromatic-fc`. - -All commonly used commands live in the root `package.json` scripts. - -Tests are **not** co-located with source — each package keeps them in a sibling `test/` directory. The file suffix routes the test to a runner: `*.ssr.test.*` → `yarn test:ssr`, `*.browser.test.*` → `yarn test:browser`, plain `*.test.*` → `yarn test` (Jest). Shared test helpers live in `@react-aria/test-utils` / `@react-spectrum/test-utils` (the `User` event abstraction and per-component testers). - -## Tooling - -This repo does **not** use the conventional JS toolchain — reach for these, and don't hand-format code or swap in defaults: - -- **Format** — `oxfmt` (`yarn format`), not Prettier. The style is opinionated (single quotes, no bracket spacing → `{foo}`, no trailing commas). Always run the tool rather than formatting by hand. -- **Lint** — `oxlint` plus repo-local rules, not ESLint. `yarn lint` bundles format-check, type-check, `oxlint`, and Yarn `constraints` (which enforce cross-package dependency versions). -- **Type-check** — `tsgo` (`yarn check-types`), the native TypeScript compiler — not `tsc`. A `tsc` fallback exists as `yarn check-types:tsc`. -- **Build** — Parcel driven by `make` (`yarn build`), not plain `tsc`/rollup. -- **Yarn 4 workspaces** monorepo; use `yarn workspaces foreach` for cross-package operations. - -## Writing tests - -- **Run the full suite before committing.** Do not write PR descriptions that list a subset of specific passing tests — run everything (`yarn test`, and `yarn test:browser` when relevant). -- **Run lint and formatting before committing** (`yarn lint`, `yarn format`). -- **Test at the right level.** For any change at the RAC level or below (including hooks), write the test at the RAC level ideally. If the change lives at a higher level, test at that level. -- **Move to browser tests when needed.** If a test requires mocking specific browser behavior, consider moving it to the browser run (`yarn test:browser`). -- **Cover the reported issue.** When fixing a reported issue, add a test that reproduces the specific example given in the issue. -- **Check whether the test already exists.** Find a home for it near other similar tests. -- **Check code coverage** to help decide whether a new test adds value — this is subjective. -- **In unit tests, prefer** fake timers, our test utils, and user event. Aside from those, prefer not mocking other modules, instead, move the test to a higher level. -- **Combine tests** that share the same setup before an assertion. -- **Ground test titles in the goal**, not the implementation — double-check they are accurate. +- Format with `yarn format` (oxfmt), **not** Prettier. Lint with `yarn lint` (oxlint), **not** ESLint. Type-check with `yarn check-types` (tsgo), **not** tsc. Build with `yarn build` (Parcel), **not** rollup/tsc. +- **Don't run `yarn chromatic` / `yarn chromatic:forced-colors`** — maintainers run the VRT suites. +- All commonly used commands live in the root `package.json` scripts. ## Contributing - **Match the surrounding code** — follow the naming, structure, and patterns of neighboring files. -- **Limit comments** — let the code speak for itself. Provide a holistic summary of how the changes work and why this approach was used in the description. If a particular section of code is complex, then prefer a higher level description of multiple lines over explaining a single line. - **Commit format** — use conventional-commit prefixes (`fix:`, `feat:`, `chore:`, `docs:`) as seen in the git history. -- **Opening a PR** — use `.github/PULL_REQUEST_TEMPLATE.md` as the PR body (e.g. `gh pr create --body-file .github/PULL_REQUEST_TEMPLATE.md`), don't hand-write a body. Fill out the checklist honestly and disclose AI use. -- **Storybook** is the main way to develop and view components: `yarn start` (v3/RAC) and `yarn start:s2` (S2). -- **S2 styling** — style with the `style` macro (`import {style} from '../style' with {type: 'macro'};` — the `with {type: 'macro'}` attribute is required). Pass typed style objects to it; don't write CSS files or hand-rolled className strings for S2. -- **User-facing strings** — add the key to the package's `intl/en-US.json` (ICU MessageFormat) and read it via the localized string hook. Never hardcode UI text, and don't hand-edit the other locale files (translators own those). -- **Generated code** — v3 icon components are generated (`yarn build:icons`) and s2 are handled through a parcel transformer, not hand-written, and `postinstall` runs `patch-package`, so run install on a fresh clone. + +## Task-specific workflows + +Read the relevant file before starting that kind of work (other agents: read the file directly; Claude will surface it): + +- Writing or running tests → [`docs/contributing/testing.md`](docs/contributing/testing.md) +- Tooling details (format/lint/type-check/build, Storybook, workspaces) → [`docs/contributing/tooling.md`](docs/contributing/tooling.md) +- Styling S2 components → [`docs/contributing/s2-styling.md`](docs/contributing/s2-styling.md) +- Adding user-facing strings → [`docs/contributing/i18n-strings.md`](docs/contributing/i18n-strings.md) +- Touching generated code (icons) → [`docs/contributing/codegen.md`](docs/contributing/codegen.md) +- Comments and opening a PR → [`docs/contributing/pull-requests.md`](docs/contributing/pull-requests.md) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 94db3a9e4bd..8553be3bf2f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -48,7 +48,7 @@ Lastly, please follow the pull request template when submitting a pull request! ## AI-assisted contributions Setting expectations: the AI doesn't contribute to React Spectrum or Quarry, you do. The AI is a tool, but you are still the author, and you own every line, every decision, and every explanation. -If you use an AI assistant, point it at our [CLAUDE.md](CLAUDE.md), which captures the repo conventions we expect it to follow. +If you use an AI assistant, point it at our [CLAUDE.md](CLAUDE.md), which captures the repo conventions we expect it to follow. The detailed conventions are split across [`docs/contributing/`](docs/contributing/) — see testing, tooling, s2-styling, i18n-strings, codegen, and pull-requests. ### Aligning on a solution @@ -80,7 +80,7 @@ When you iterate between reviews, be sure you can say what changed and why, one #### Requirements of front end code -These can be useful constraints or reminders as AI is not inherently good at these things. +These can be useful constraints or reminders as AI is not inherently good at these things. For the toolchain that enforces some of this, see [`docs/contributing/tooling.md`](docs/contributing/tooling.md). * **Small** — everything you add ships across the network to the client, so more code means slower load times. * **Fast** — must run well on constrained CPU and memory, not just the latest MacBook Pro. Many users are on years-old, low-end Android devices. diff --git a/docs/contributing/codegen.md b/docs/contributing/codegen.md new file mode 100644 index 00000000000..d4bbc00bf73 --- /dev/null +++ b/docs/contributing/codegen.md @@ -0,0 +1,3 @@ +# Generated code + +v3 icon components are generated (`yarn build:icons`) and s2 are handled through a parcel transformer, not hand-written, and `postinstall` runs `patch-package`, so run install on a fresh clone. diff --git a/docs/contributing/i18n-strings.md b/docs/contributing/i18n-strings.md new file mode 100644 index 00000000000..bea91a05b9a --- /dev/null +++ b/docs/contributing/i18n-strings.md @@ -0,0 +1,3 @@ +# User-facing strings (i18n) + +Add the key to the package's `intl/en-US.json` (ICU MessageFormat) and read it via the localized string hook. Never hardcode UI text, and don't hand-edit the other locale files (translators own those). diff --git a/docs/contributing/pull-requests.md b/docs/contributing/pull-requests.md new file mode 100644 index 00000000000..961b90db254 --- /dev/null +++ b/docs/contributing/pull-requests.md @@ -0,0 +1,11 @@ +# Pull requests + +## Commenting + +Comments while developing are fine. Before presenting code for review, trim verbose comments so the diff reads cleanly. When a genuinely complex section still warrants a comment, prefer a higher-level explanation of the whole section over annotating individual lines. + +## Opening a PR + +Start from `.github/PULL_REQUEST_TEMPLATE.md` (e.g. `gh pr create --body-file .github/PULL_REQUEST_TEMPLATE.md`) rather than writing a body from scratch: fill in every section, complete the checklist honestly, and disclose AI use. Above the checklist, add a holistic summary of how the changes work and why this approach was chosen — give the intent separately from the implementation. + +See also `CONTRIBUTING.md` (AI-assisted contributions). diff --git a/docs/contributing/s2-styling.md b/docs/contributing/s2-styling.md new file mode 100644 index 00000000000..4a4fc5f3d82 --- /dev/null +++ b/docs/contributing/s2-styling.md @@ -0,0 +1,3 @@ +# S2 styling + +Style with the `style` macro (`import {style} from '../style' with {type: 'macro'};` — the `with {type: 'macro'}` attribute is required). Pass typed style objects to it; don't write CSS files or hand-rolled className strings for S2. diff --git a/docs/contributing/testing.md b/docs/contributing/testing.md new file mode 100644 index 00000000000..c5c16b7cbb0 --- /dev/null +++ b/docs/contributing/testing.md @@ -0,0 +1,26 @@ +# Testing + +Test suites are split by type: + +- **Jest tests** — `yarn test` +- **SSR tests** — `yarn test:ssr` +- **Browser tests** — `yarn test:browser` +- **Visual regression tests (VRT)** — `yarn chromatic` +- **High-contrast-mode VRT** — `yarn chromatic:forced-colors` + +Maintainers run the Chromatic VRT suites themselves — don't run `yarn chromatic` / `yarn chromatic:forced-colors`. You can still start the VRT Storybooks locally to verify visual state: `yarn start:chromatic` and `yarn start:chromatic-fc`. + +Tests are **not** co-located with source — each package keeps them in a sibling `test/` directory. The file suffix routes the test to a runner: `*.ssr.test.*` → `yarn test:ssr`, `*.browser.test.*` → `yarn test:browser`, plain `*.test.*` → `yarn test` (Jest). Shared test helpers live in `@react-aria/test-utils` / `@react-spectrum/test-utils` (the `User` event abstraction and per-component testers). + +## Writing tests + +- **Run the full suite before committing.** Do not write PR descriptions that list a subset of specific passing tests — run `yarn test`, `yarn test:ssr` and (when relevant) `yarn test:browser` — do not run the Chromatic VRT suites (see above). +- **Run lint and formatting before committing** (`yarn lint`, `yarn format`). +- **Test at the right level.** For any change at the RAC level or below (including hooks), write the test at the RAC level ideally. If the change lives at a higher level, test at that level. +- **Move to browser tests when needed.** If a test requires mocking specific browser behavior, consider moving it to the browser run (`yarn test:browser`). +- **Cover the reported issue.** When fixing a reported issue, add a test that reproduces the specific example given in the issue. +- **Check whether the test already exists.** Find a home for it near other similar tests. +- **Check code coverage** to help decide whether a new test adds value — this is subjective. +- **In unit tests, prefer** fake timers, our test utils, and user event. Aside from those, prefer not mocking other modules, instead, move the test to a higher level. +- **Combine tests** that share the same setup before an assertion. +- **Ground test titles in the goal**, not the implementation — double-check they are accurate. diff --git a/docs/contributing/tooling.md b/docs/contributing/tooling.md new file mode 100644 index 00000000000..5eeca4509ae --- /dev/null +++ b/docs/contributing/tooling.md @@ -0,0 +1,13 @@ +# Tooling + +This repo does **not** use the conventional JS toolchain — reach for these, and don't hand-format code or swap in defaults: + +- **Format** — `oxfmt` (`yarn format`), not Prettier. The style is opinionated (single quotes, no bracket spacing → `{foo}`, no trailing commas). Always run the tool rather than formatting by hand. +- **Lint** — `oxlint` plus repo-local rules, not ESLint. `yarn lint` bundles format-check, type-check, `oxlint`, and Yarn `constraints` (which enforce cross-package dependency versions). +- **Type-check** — `tsgo` (`yarn check-types`), the native TypeScript compiler — not `tsc`. A `tsc` fallback exists as `yarn check-types:tsc`. +- **Build** — Parcel driven by `make` (`yarn build`), not plain `tsc`/rollup. +- **Yarn 4 workspaces** monorepo; use `yarn workspaces foreach` for cross-package operations. + +## Storybook + +Storybook is the main way to develop and view components: `yarn start` (v3/RAC) and `yarn start:s2` (S2). From a731ebba949de705ce04cebb06d86c7202c95b07 Mon Sep 17 00:00:00 2001 From: Rob Snow Date: Wed, 5 Aug 2026 17:49:56 +1000 Subject: [PATCH 4/5] Add skills to make dev on hard code spaces easier and hopefully take less tokens --- .claude/skills/collections/SKILL.md | 220 ++++++++++++++++++++++++++ .claude/skills/drag-and-drop/SKILL.md | 144 +++++++++++++++++ .claude/skills/virtualizer/SKILL.md | 181 +++++++++++++++++++++ 3 files changed, 545 insertions(+) create mode 100644 .claude/skills/collections/SKILL.md create mode 100644 .claude/skills/drag-and-drop/SKILL.md create mode 100644 .claude/skills/virtualizer/SKILL.md diff --git a/.claude/skills/collections/SKILL.md b/.claude/skills/collections/SKILL.md new file mode 100644 index 00000000000..304385b14a8 --- /dev/null +++ b/.claude/skills/collections/SKILL.md @@ -0,0 +1,220 @@ +--- +description: Use when answering questions about or modifying the Collections system used by react-aria-components (RAC) and Spectrum 2 (S2) — the two-pass render, the fake DOM / Document, CollectionBuilder, BaseCollection, CollectionNode, useListState/useTreeState, createLeafComponent/createBranchComponent, Section/Item nodes, SSR of collections, or the difference between the new and old (RSP v3) collection builders. +--- + +# Collections (new RAC/S2 system) + +Guidance for the collection architecture behind RAC components (`ListBox`, `Menu`, `Table`, `Tree`, +`GridList`, `TagGroup`, `Tabs`, `Breadcrumbs`) and S2 (which re-exports/wraps RAC). + +Source of truth lives in **`packages/react-aria/src/collections/`** (re-exported publicly as +`@react-aria/collections` / the `react-aria/private/collections/*` aliases). Do not confuse it with +the *old* builder in `packages/react-stately/src/collections/`. + +## The core idea: two-pass render + +Collection children use natural JSX (``), but that +JSX is **not** what ends up in the browser DOM. Rendering happens in two passes: + +1. **Pass 1 — build the Collection.** The collection JSX is rendered by React into a *fake DOM* (a + lightweight in-memory document model, not `document`). This produces an immutable `BaseCollection` + — a `Map` with sibling/parent/child links. Because React does this rendering, + we keep JSX syntax *and* composition/context, and we learn each item's index, level, parent, + sibling keys, and the total item count before rendering anything real. +2. **Pass 2 — render the real DOM.** The `BaseCollection` is fed into state (`useListState` / + `useTreeState`), and a renderer walks the collection and calls each node's stored `render` function + to emit the actual DOM (supporting virtualization / rendering a subset). + +Rationale is documented inline at `packages/react-aria/src/collections/Document.ts:18-30`. + +``` +{item => } + │ + ▼ CollectionBuilder renders content into portal + ┌─────────────────────────── PASS 1 (fake DOM) ───────────────────────────┐ + │ Collection → CollectionRoot → createPortal(children, Document) │ + │ each Item/Section is a createLeafComponent/createBranchComponent → │ + │ renders host elements → React reconciler mutates fake DOM │ + │ Document.getCollection() finalizes an immutable BaseCollection │ + └──────────────────────────────────────────────────────────────────────────┘ + │ collection (BaseCollection) + ▼ + ┌─────────────────────────── PASS 2 (real DOM) ───────────────────────────┐ + │ useListState(collection) → SelectionManager, keyboard delegates │ + │ CollectionRoot walks collection, calls node.render(node) → real
s │ + └──────────────────────────────────────────────────────────────────────────┘ +``` + +## The fake DOM / document model + +React can render into any host environment given a host-config; here the host is a hand-written mock +DOM in `packages/react-aria/src/collections/Document.ts`. **No custom reconciler** is written — instead +`react-dom`'s `createPortal` targets a fake `Document` object that duck-types the DOM API React calls +(`createElement`, `appendChild`, `insertBefore`, `removeChild`, `style`, `setAttribute`, …). + +Key classes (all in `Document.ts`): + +| Class | Role | +|---|---| +| `BaseNode` (`Document.ts:36`) | Base mutable fake-DOM node: `firstChild`/`lastChild`/`nextSibling`/`parentNode` getters+setters that call `ownerDocument.markDirty`. Implements `appendChild`/`insertBefore`/`removeChild` (`Document.ts:126-220`). | +| `ElementNode` (`Document.ts:262`) | A mutable fake element. `nodeType = 8` (COMMENT_NODE — deliberately not ELEMENT_NODE so React DevTools doesn't try to measure it, `Document.ts:263`). Owns one immutable `CollectionNode`. Has `setProps` (`:337`), `updateNode` (`:309`), a fake `style` getter for Suspense `display:none` handling (`:379`), and no-op `setAttribute`/`hasAttribute`. | +| `Document` (`Document.ts:428`) | The portal target. `nodeType = 11` (DOCUMENT_FRAGMENT_NODE). Owns the current immutable `collection`, a `nextCollection` (copy-on-write), a `dirtyNodes` set, and the `useSyncExternalStore` subscription plumbing. | + +How nodes get created & the collection is built: + +- React calls `document.createElement(type)` → `new ElementNode(type, this)` (`Document.ts:452`). +- React sets children via `appendChild`/`insertBefore`; each setter calls `markDirty` and, when + connected, `queueUpdate()` (`Document.ts:148-151`, `:180-182`). +- The `ref` callback on the host element calls `element.setProps(...)`, which lazily constructs (or + copy-on-write clones) the immutable `CollectionNode`, copying `props`, `rendered`, `render`, `value`, + `textValue`, `id` (`Document.ts:337-377`). **`id` is immutable** — changing it throws (`:366-368`). +- `Document.updateCollection()` (`:509`) is the finalize step: removes disconnected/hidden nodes, + recomputes indices, calls `ElementNode.updateNode()` to recompute `index`/`level`/`parentKey`/ + `prevKey`/`nextKey`/`firstChildKey`/`lastChildKey`/`colIndex` (`:309-335`), adds surviving nodes to + `nextCollection`, then `collection.commit(...)` **freezes** it (`:538-548`). +- `getCollection()` (`:495`) runs the finalize and returns the frozen collection to React via + `useSyncExternalStore`. `queueUpdate()` clones the collection so React notices a new snapshot and + schedules the second render (`:551-576`). + +**Mutable fake node vs immutable collection node.** Each `ElementNode` (mutable, stable identity that +React holds onto) owns one `CollectionNode` (immutable, copy-on-write). `getMutableNode()` clones the +`CollectionNode` on first write per update cycle (`Document.ts:295-307`); unchanged nodes are shared, +so updates are cheap. + +`` (`packages/react-aria/src/collections/Hidden.tsx:66`): during SSR there are no portals, so +the hidden collection tree is rendered into a `