# AGENTS.md Instructions for AI coding agents working on the React on Rails codebase. React on Rails is a Ruby gem + npm package that integrates React with Ruby on Rails, providing server-side rendering (SSR) via Node.js or ExecJS. This is a monorepo: the open-source gem lives at `react_on_rails/`, the npm package at `packages/react-on-rails/`, and the Pro package at `react_on_rails_pro/`. ## Reusable Workflows - `AGENTS.md`: canonical entry point for agent instructions and workflow discovery - `.claude/commands/`: Claude Code slash commands - `.agents/workflows/`: shared prompt templates and reusable workflows for Codex, GPT, and other non-Claude tools - When the user asks to address PR review comments outside Claude slash commands, follow `.agents/workflows/address-review.md` ## Canonical Agent Policy `AGENTS.md` is the canonical source for repository-wide agent rules: - Commands and test/lint workflow - Code style and formatting expectations - Git/PR boundaries and safety rules - Directory and documentation boundaries Other agent-facing docs (for example `CLAUDE.md`) should contain only tool-specific workflow notes and link back here. If there is a conflict, `AGENTS.md` wins. ## Commands ```bash # Install dependencies bundle && pnpm install # Build TypeScript → JavaScript pnpm run build # Lint (MANDATORY before every commit) bundle exec rubocop # Ruby — must pass with zero offenses pnpm run lint # JS/TS via ESLint pnpm start format.listDifferent # Check Prettier formatting rake lint # All linting (Ruby + JS + formatting) # Auto-fix formatting rake autofix # Preferred for all formatting # Run tests rake run_rspec:gem # Ruby unit tests (gem code) rake run_rspec:dummy # Ruby integration tests (dummy Rails app) pnpm run test # JavaScript/TypeScript tests rake # Full suite (lint + all tests except examples) # Type checking pnpm run type-check # TypeScript bundle exec rake rbs:validate # RBS signatures # Additional test subsets rake run_rspec # All Ruby tests rake all_but_examples # All tests except generated examples rake run_rspec:shakapacker_examples_basic # Single example test # Full initial setup bundle && pnpm install && rake shakapacker_examples:gen_all && rake node_package && rake # CI/workflow linting actionlint # GitHub Actions lint yamllint .github/ # YAML lint (do NOT run RuboCop on .yml files) # Dependency version updates rake shakapacker:update_version[9.6.1] # Update shakapacker across the monorepo ``` ### Updating Shakapacker Use `rake shakapacker:update_version[VERSION]` to update shakapacker across the entire monorepo. This single command updates all Gemfiles, package.json files, Gemfile.lock files, and pnpm-lock.yaml. Do **not** manually edit individual version references — always use the rake task to keep everything in sync. The task handles Ruby version switching for apps that require a different Ruby version (set `RUBY_VERSION_MANAGER` to `rvm`, `rbenv`, `asdf`, or `mise` if needed; defaults to `rvm`). It continues gracefully if a single lock file update fails (e.g., due to a missing Ruby version). ## Testing - **Prefer local testing over CI iteration** — don't push "hopeful" fixes. Apply the **15-minute rule**: if 15 more minutes of local testing would catch the issue before CI does, spend the 15 minutes. - **Never claim a test is "fixed" without running it locally first.** Use "This SHOULD fix..." or "Proposed fix (UNTESTED)" for unverified changes. - **Automated tests passing is necessary but not sufficient.** If your changes affect how the app starts, builds, or serves, you must also verify the dev environment manually. See [Manual Dev Environment Testing](.claude/docs/manual-dev-environment-testing.md) for the full checklist. - **Ruby**: RSpec. Unit tests in `react_on_rails/spec/react_on_rails/`, integration tests via a dummy Rails app in `react_on_rails/spec/dummy/`. - **JavaScript/TypeScript**: Jest. Tests in `packages/react-on-rails/tests/`. - **E2E**: Playwright. Tests in `react_on_rails/spec/dummy/e2e/playwright/e2e/`. Run with `cd react_on_rails/spec/dummy && pnpm test:e2e`. - **The dummy app** (`react_on_rails/spec/dummy/`) is a full Rails application used for integration testing. Many tests require it. Run specific test files: ```bash bundle exec rspec react_on_rails/spec/react_on_rails/path/to/spec.rb cd react_on_rails/spec/dummy && bundle exec rspec spec/path/to/spec.rb ``` ## Project Structure | Directory | Purpose | | ------------------------------------------------ | ---------------------------------------------------------------------------------------- | | `react_on_rails/lib/react_on_rails/` | Ruby gem source — helpers, configuration, SSR pool, engine | | `react_on_rails/lib/generators/` | Rails generators for `react_on_rails:install` | | `react_on_rails/spec/` | RSpec tests (unit + integration via dummy app) | | `react_on_rails/spec/dummy/` | Full Rails app for integration testing and E2E | | `packages/react-on-rails/src/` | TypeScript source — client-side React integration | | `packages/react-on-rails/tests/` | Jest tests for the npm package | | `react_on_rails_pro/` | Pro package (separate gem + npm, own lint config) | | `rakelib/` | Rake task definitions | | `docs/oss/` | OSS documentation — published to the [ShakaCode website](https://reactonrails.com/docs/) | | `docs/pro/` | Pro documentation — installation, configuration, RSC, node renderer, caching | | `internal/contributor-info/` | Internal contributor docs (not published to the website) | | `internal/planning/` | Internal planning docs, drafts, and historical analysis | | `internal/react_on_rails_pro/contributors-info/` | Internal Pro contributor docs (not published to the website) | | `analysis/` | Investigation and analysis documents (kebab-case `.md` files) | ## Code Style ### Ruby (RuboCop) Line length max 120 characters. Run `bundle exec rubocop [file]` to check. **Line length — break long chains:** ```ruby # Bad content = pack_content.gsub(/import.*from.*['"];/, "").gsub(/ReactOnRails\.register.*/, "") # Good content = pack_content.gsub(/import.*from.*['"];/, "") .gsub(/ReactOnRails\.register.*/, "") ``` **Named subjects in RSpec:** ```ruby # Bad subject { instance.method_name(arg) } # Good subject(:method_result) { instance.method_name(arg) } ``` **Security violations — scope disable comments tightly:** ```ruby # rubocop:disable Security/Eval expect { evaluate(sanitized_content) }.not_to raise_error # rubocop:enable Security/Eval ``` ### JavaScript/TypeScript Prettier handles all formatting. Never manually format — run `rake autofix` instead. ## Git Workflow **Branch naming**: `type/descriptive-name` (e.g., `fix/ssr-hydration-mismatch`) **Commit messages**: Explain why, not what. One logical change per commit. **PR creation**: Use `gh pr create` with a clear title, summary, and test plan. ## Review Workflow For small, focused PRs (roughly 5 files changed or fewer and one clear purpose): - Use at most one AI reviewer that leaves inline comments. Additional AI tools should be summary-only or used manually. - Wait for the first full review pass to finish before pushing follow-up commits. - Batch review fixes into one follow-up push when practical. Do not create a new commit for each minor comment. - Treat as blocking only: correctness bugs, failing tests, regressions, and clear inconsistencies with adjacent code. Nits and style suggestions are optional unless a maintainer asks for them. - Verify language, runtime, and library claims locally before changing code in response to AI review comments. - Deduplicate repeated bot comments before acting on them. Fix the underlying issue once, then resolve the duplicates. - Rebase or merge `main` once, near the end of the review cycle. For `CHANGELOG.md` conflicts, prefer resolving them as the final step before merge. - When asking an agent to address review comments, instruct it to classify comments into `blocking`, `optional`, and `noise`, then apply only the `blocking` items plus any explicitly selected optional items. ## Boundaries ### Always - Run `bundle exec rubocop` before committing — CI will reject violations - Use `pnpm` for all JS operations — never `npm` or `yarn` - Use `bundle exec` for Ruby commands - Ensure all files end with a newline - Let Prettier and RuboCop handle formatting — never format manually - When adding docs under `docs/oss/` or `docs/pro/`, also add the doc ID to `docs/sidebars.ts` — CI will fail otherwise. To intentionally exclude a doc from the sidebar, add its ID to `docs/.sidebar-exclusions` with a reason comment. ### Ask First - Destructive git operations (force push, reset --hard, branch deletion) - Changes to CI workflows (`.github/workflows/`) - Changes to build configuration (`package.json` scripts, webpack config) - Modifying the Pro package (`react_on_rails_pro/`) ### Never - Skip pre-commit hooks (`--no-verify`) - Commit secrets, credentials, or `.env` files - Commit `package-lock.json`, `yarn.lock`, or other non-pnpm lock files - Add files to the `docs/` root — OSS docs go in `docs/oss/` subdirectories (`getting-started/`, `core-concepts/`, `building-features/`, `configuration/`, `api-reference/`, `deployment/`, `migrating/`, `upgrading/`, `misc/`); Pro docs go in `docs/pro/` - Force push to `main` or `master` ## Key Concept: File Suffixes vs. RSC Directive React on Rails has two **independent** systems that both use "client" and "server" terminology. Do not confuse them. ### 1. Bundle Placement (`.client.` / `.server.` file suffixes) A React on Rails auto-bundling feature that controls which webpack bundle imports a file. This exists independently of React Server Components and is used with or without RSC: - `Component.client.jsx` → imported only in the **client bundle** (browser) - `Component.server.jsx` → imported only in the **server bundle** (and RSC bundle when RSC enabled) - `Component.jsx` (no suffix) → imported in **both** bundles This controls where the source file is loaded, nothing more. A `.server.jsx` file is NOT a React Server Component — it is simply a file that webpack includes in the server bundle (and the RSC bundle when RSC is enabled). These suffixes only make sense for client components, as server components exist only in the RSC bundle. ### 2. RSC Classification (`'use client'` directive) The `'use client'` directive is part of the React Server Components architecture. It marks a component as a React Client Component. Components without it are treated as React Server Components. When auto-bundling is enabled with RSC support (Pro feature), React on Rails uses this directive to control: - **Registration**: `'use client'` → `ReactOnRails.register()`, no `'use client'` → `registerServerComponent()` - **RSC bundling**: The RSC webpack loader uses this directive to decide whether a component is included in the RSC bundle or replaced with a client reference in that bundle The `client_entrypoint?` method in `packs_generator.rb` checks for this directive. ### They Are Orthogonal A `.client.jsx` file can be a React Server Component (if it lacks `'use client'`), and a `.server.jsx` file can be a React Client Component (if it has `'use client'`). In practice, paired `.client.`/`.server.` files should have consistent `'use client'` status because the client and server must agree on the component's RSC role for hydration to work. ## Changelog Update `/CHANGELOG.md` for **user-visible changes only** (features, bug fixes, breaking changes, deprecations, performance improvements). Do **not** add entries for linting, formatting, refactoring, tests, or doc fixes. - **Format**: `[PR 1818](https://github.com/shakacode/react_on_rails/pull/1818) by [username](https://github.com/username)` (no hash before PR number) - **Pro-only changes** use an inline `**[Pro]**` tag prefix within the standard category sections (e.g., `- **[Pro]** **Feature name**: Description...`); do NOT create separate `#### Pro` subsections