|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## What this app is |
| 6 | + |
| 7 | +CASA is a Rails app used by CASA (Court Appointed Special Advocate) chapters to track volunteer work with foster youth. The domain shape: a **CasaCase** (a youth) is assigned to a **Volunteer**, who logs **CaseContacts**; **Supervisors** oversee volunteers; **CasaAdmins** run the chapter. The app is **multi-tenant** — every record belongs to a `CasaOrg`, and cross-org data leaks are the most important class of bug to avoid. |
| 8 | + |
| 9 | +## Common commands |
| 10 | + |
| 11 | +| Task | Command | |
| 12 | +|---|---| |
| 13 | +| One-time setup | `bin/setup` | |
| 14 | +| Run app (web + JS/CSS watchers) | `bin/dev` then visit http://localhost:3000 | |
| 15 | +| Run full RSpec suite | `bin/rails spec` | |
| 16 | +| Run a single spec file | `bundle exec rspec spec/path/to/file_spec.rb` | |
| 17 | +| Run a single example | `bundle exec rspec spec/path/to/file_spec.rb:LINE` | |
| 18 | +| Run JS tests | `npm run test` | |
| 19 | +| Run all linters w/ autofix | `bin/lint` (standardrb + erb_lint + standardjs + factory_bot:lint) | |
| 20 | +| Update env after pulling main | `bin/update` (migrate, bundle, npm, after_party) | |
| 21 | +| Run post-deploy tasks | `bundle exec rake after_party:run` | |
| 22 | +| Local mailer previews | http://localhost:3000/rails/mailers | |
| 23 | + |
| 24 | +Seed login credentials (password `12345678` for all): |
| 25 | +- `volunteer1@example.com`, `supervisor1@example.com`, `casa_admin1@example.com` at `/users/sign_in` |
| 26 | +- `allcasaadmin@example.com` at `/all_casa_admins/sign_in` |
| 27 | + |
| 28 | +## Tech stack |
| 29 | + |
| 30 | +Rails 7.2 on Ruby (`.ruby-version`), PostgreSQL, Hotwire (Turbo + Stimulus), Bootstrap 5, ESBuild, Devise + Devise-Invitable, Pundit, Draper, ViewComponent, Delayed Job, Flipper, Paranoia (soft deletes), Strong Migrations. Linting: Standard.rb + standard-rails, StandardJS, erb-lint. Testing: RSpec + Capybara (system tests via Selenium/Chrome), Jest. There is **no UI sign-up** — users are admin-invited only (ADR 0002). |
| 31 | + |
| 32 | +## Architecture: things that require reading multiple files to understand |
| 33 | + |
| 34 | +### Two user tables (ADR 0003) |
| 35 | +There are **two separate Devise models**: `User` (with subclasses `Volunteer`, `Supervisor`, `CasaAdmin` — these are real STI subclasses, each with its own model file) and `AllCasaAdmin` (a separate top-level model and table for super-admins who span orgs). Devise is configured for both in `config/routes.rb`, and there is a parallel `app/controllers/all_casa_admins/` namespace + `app/models/all_casa_admins/` for the all-casa flows. When touching auth, check both sides. |
| 36 | + |
| 37 | +### Multi-tenancy is enforced in three places |
| 38 | +1. Models include `ByOrganizationScope` (`app/models/concerns/by_organization_scope.rb`) which gives `.by_organization(casa_org)`. |
| 39 | +2. The `Organizational` controller concern (`app/controllers/concerns/organizational.rb`) provides `current_organization` from the signed-in user. |
| 40 | +3. Pundit policy scopes filter by org: `scope.by_organization(user.casa_org)`. |
| 41 | + |
| 42 | +Every controller index action should call `policy_scope`; every show/update/destroy should call `authorize`. Controllers typically use `after_action :verify_authorized` (with explicit `except:`). When adding a new model with org-scoped data, all three places must be wired up or queries will leak across orgs. |
| 43 | + |
| 44 | +### Authorization is policy-based (Pundit) |
| 45 | +Policies live in `app/policies/`. They define both predicate methods (`update?`, `destroy?`) and `permitted_attributes` (role-based field allowlists for strong params). When adding a model attribute that admins/supervisors/volunteers should be able to set, update `permitted_attributes` in the policy — not just the controller. |
| 46 | + |
| 47 | +### Layering: where logic goes |
| 48 | +- **Controllers** stay thin. Standard pattern: `set_record` before_action, `authorize`, save/render with `:unprocessable_content` on failure. |
| 49 | +- **Service objects** (`app/services/`) — `ServiceName.new(args).perform`. Used for CSV exports, SMS reminders, complex multi-record operations. |
| 50 | +- **Decorators** (`app/decorators/`, Draper) — presentation logic. Access view helpers via `h.helper_method`. Called as `model.decorate`. Date formatting, conditional display strings, etc. belong here, NOT in models or ERB. |
| 51 | +- **ViewComponents** (`app/components/`) — reusable UI primitives (badges, modals, sidebar, dropdown menus, form bits). |
| 52 | +- **Parameter objects** (`app/values/`) — builder-pattern wrappers for non-trivial strong-params logic, e.g. `VolunteerParameters.new(params).with_password(pw).without_active`. |
| 53 | +- **Concerns** (`app/{models,controllers}/concerns/`) — shared behavior via `extend ActiveSupport::Concern`. Notable model concerns: `ByOrganizationScope`, `Roles`, `Api`, and a `CasaCase/` directory of concern modules used by the `CasaCase` model. |
| 54 | + |
| 55 | +### Frontend |
| 56 | +Hotwire-first: Turbo for navigation/forms, Stimulus controllers in `app/javascript/controllers/`. There is an [in-progress migration](https://github.com/rubyforgood/casa/issues/5016) from inline JS / jQuery to Stimulus — new code should be Stimulus, but legacy jQuery is not flagged. JS is bundled via ESBuild (`bin/asset_bundling_scripts/build_js.js`); SCSS via the `sass` CLI. Both have watchers in `Procfile.dev`. Email views require **inline CSS** for client compatibility (ADR 0007). |
| 57 | + |
| 58 | +### Notable conventions |
| 59 | +- **Soft deletes** via Paranoia: `destroy` marks deleted, doesn't hard-delete. Be deliberate when reasoning about uniqueness or "where is this record." |
| 60 | +- **Strong Migrations** is enabled and will block unsafe DDL (column removal without `safety_assured`, non-concurrent index adds on large tables, type changes). Reversibility is required. |
| 61 | +- **Soft-deletion-aware uniqueness**: `validate_uniqueness_of(...).scoped_to(:casa_org_id)` is the typical pattern — uniqueness is per-org. |
| 62 | +- **Enums** use prefix syntax: `enum :status, {active: 0, inactive: 1}, prefix: :status`. |
| 63 | +- **Post-deployment tasks** run via After Party (`bundle exec rake after_party:run`), invoked automatically by `bin/update` and on Heroku release. |
| 64 | + |
| 65 | +### Testing conventions |
| 66 | +- **System tests are preferred** over controller tests (ADR 0006 — `app/controllers/concerns/users/` and `spec/controllers/` exist but are minimal). New UI behavior should land in `spec/system/`. |
| 67 | +- Factories use **traits** for variants: `create(:casa_case, :active)`. `bin/lint` runs `factory_bot:lint` to catch invalid factories. |
| 68 | +- Use `build` for unit tests; `create` only when persistence is required. `let` for lazy, `let!` when the record must exist before the example. |
| 69 | +- shoulda-matchers handles association/validation tests. |
| 70 | +- No `sleep` in tests — rely on Capybara waiting. |
| 71 | +- Flaky tests are disabled with `xit` + a tracking issue, never deleted. |
| 72 | + |
| 73 | +## Style |
| 74 | +This project uses **Standard.rb** (not vanilla RuboCop). Don't fight it on spacing/quotes/trailing commas. There is also a `.standard_todo.yml` of grandfathered violations — leave older files alone unless touching them substantively. Older migration files (2020–2024) are intentionally excluded from linting. |
| 75 | + |
| 76 | +## Where to look for more |
| 77 | +- `.github/instructions/ruby.instructions.md` and `.github/instructions/copilot-review.instructions.md` — the project's own review checklist; the source of truth for "what is a bug-shaped change here." |
| 78 | +- `doc/architecture-decisions/` — ADRs (especially 0002 no-UI-signup, 0003 two-user-tables, 0006 few-controller-tests, 0007 inline-email-CSS). |
| 79 | +- `doc/productsense.md` — product philosophy. |
| 80 | +- `db/schema.rb` — paste into [dbdiagram.io](https://dbdiagram.io/d) for an ERD. |
0 commit comments