Two files, one repository
we-promise/sure ships 4 formats across 13 indexed files. The question worth asking is whether the second one says anything the first does not.
| Dimension | Shared | Only in A | Only in B | Overlap |
|---|---|---|---|---|
| Sections | 3 | 12 | 44 | 5% |
| Commands | 2 | 0 | 4 | 33% |
| Section tags | 9 | 2 | 6 | 53% |
What each file covers
Sections
3 shared · 12 only in A · 44 only in B- − Repository Guidelines
- − Project Structure & Module Organization
- − Build, Test, and Development Commands
- − Coding Style & Naming Conventions
- − Testing Guidelines
- − Commit & Pull Request Guidelines
- − Security & Configuration Tips
- − Post-commit API consistency (LLM checklist)
- − Design System Hygiene (UI PRs)
- − Securities Providers
- − Providers: Pending Transactions and FX Metadata (SimpleFIN/Plaid/Lunchflow)
- − Provider support notes
- + CLAUDE.md
- + Common Development Commands
- + Development Server
- + Testing
- + Linting & Formatting
- + Database
- + Setup
- + Pre-Pull Request CI Workflow
- + General Development Rules
- + Authentication Context
- + Development Guidelines
- + High-Level Architecture
- + Application Modes
- + Core Domain Model
- + API Architecture
- + Sync & Import System
- + Provider Integrations: Pending Transactions and FX (SimpleFIN/Plaid)
- + Background Processing
- + Frontend Architecture
- + Internationalization (i18n) Guidelines
- + Multi-Currency Support
- + Security & Authentication
- + Testing Philosophy
- + Performance Considerations
- + Development Workflow
- + Project Conventions
- + Convention 1: Minimize Dependencies
- + Convention 2: Skinny Controllers, Fat Models
- + Convention 3: Hotwire-First Frontend
- + Convention 4: Optimize for Simplicity
- + Convention 5: Database vs ActiveRecord Validations
- + TailwindCSS Design System
- + Design System Rules
- + Component Architecture
- + ViewComponent vs Partials Decision Making
- + Stimulus Controller Guidelines
- + General Testing Rules
- + Test Quality Guidelines
- + Testing Examples
- + GOOD - Testing critical domain business logic
- + BAD - Testing ActiveRecord functionality
- + Stubs and Mocks
- + spec/requests/api/v1/widgets_spec.rb
- + Post-commit API consistency (issue #944)
- API Development Guidelines
- OpenAPI Documentation (MANDATORY)
- Debug Logging for Provider Syncs
Commands
2 shared · 0 only in A · 4 only in B- + npm run lint:fix
- + bundle exec erb_lint ./app/**/*.erb -a
- + rails server
- + rails credentials
- npm run lint
- npm run format
Section tags
9 shared · 2 only in A · 6 only in B- − build
- − architecture
- + setup
- + testing-strategy
- + dependencies
- + database
- + performance
- + agent-behaviour
- test
- lint-format
- code-style
- git-pr
- security
- api
- ui
- do-not
- docs
Line diff
we-promise/sure · AGENTS.md
@@ −1 @@
1# Repository Guidelines
2
3## Project Structure & Module Organization
4- Code: `app/` (Rails MVC, services, jobs, mailers, components), JS in `app/javascript/`, styles/assets in `app/assets/` (Tailwind, images, fonts).
5- Config: `config/`, environment examples in `.env.local.example` and `.env.test.example`.
6- Data: `db/` (migrations, seeds), fixtures in `test/fixtures/`.
7- Tests: `test/` mirroring `app/` (e.g., `test/models/*_test.rb`).
8- Tooling: `bin/` (project scripts), `docs/` (guides), `public/` (static), `lib/` (shared libs).
9
10## Build, Test, and Development Commands
11- Setup: `cp .env.local.example .env.local && bin/setup` — install deps, set DB, prepare app.
12- Run app: `bin/dev` — starts Rails server and asset/watchers via `Procfile.dev`.
13- Test suite: `bin/rails test` — run all Minitest tests; add `TEST=test/models/user_test.rb` to target a file.
14- Lint Ruby: `bin/rubocop` — style checks; add `-A` to auto-correct safe cops.
15- Lint/format JS/CSS: `npm run lint` and `npm run format` — uses Biome.
16- Security scan: `bin/brakeman` — static analysis for common Rails issues.
17
18## Coding Style & Naming Conventions
19- Ruby: 2-space indent, `snake_case` for methods/vars, `CamelCase` for classes/modules. Follow Rails conventions for folders and file names.
20- Views: ERB checked by `erb-lint` (see `.erb_lint.yml`). Avoid heavy logic in views; prefer helpers/components.
21- JavaScript: `lowerCamelCase` for vars/functions, `PascalCase` for classes/components. Let Biome format code.
22- Commit small, cohesive changes; keep diffs focused.
23
24## Testing Guidelines
25- Framework: Minitest (Rails). Name files `*_test.rb` and mirror `app/` structure.
26- Run: `bin/rails test` locally and ensure green before pushing.
27- Fixtures/VCR: Use `test/fixtures` and existing VCR cassettes for HTTP. Prefer unit tests plus focused integration tests.
28
29## Commit & Pull Request Guidelines
30- Commits: Imperative subject ≤ 72 chars (e.g., "Add account balance validation"). Include rationale in body and reference issues (`#123`).
31- PRs: Clear description, linked issues, screenshots for UI changes, and migration notes if applicable. Ensure CI passes, tests added/updated, and `rubocop`/Biome are clean.
32
33## Security & Configuration Tips
34- Never commit secrets. Start from `.env.local.example`; use `.env.local` for development only.
35- Run `bin/brakeman` before major PRs. Prefer environment variables over hard-coded values.
36
37## API Development Guidelines
38
39### OpenAPI Documentation (MANDATORY)
40When adding or modifying API endpoints in `app/controllers/api/v1/`, you **MUST** create or update corresponding OpenAPI request specs for **DOCUMENTATION ONLY**:
41
421. **Location**: `spec/requests/api/v1/{resource}_spec.rb`
432. **Framework**: RSpec with rswag for OpenAPI generation
443. **Schemas**: Define reusable schemas in `spec/swagger_helper.rb`
454. **Generated Docs**: `docs/api/openapi.yaml`
465. **Regenerate**: Run `RAILS_ENV=test bundle exec rake rswag:specs:swaggerize` after changes
47
48### Post-commit API consistency (LLM checklist)
49After every API endpoint commit, ensure: (1) **Minitest** behavioral coverage in `test/controllers/api/v1/{resource}_controller_test.rb` (no behavioral assertions in rswag); (2) **rswag** remains docs-only (no `expect`/`assert_*` in `spec/requests/api/v1/`); (3) **rswag auth** uses the same API key pattern everywhere (`X-Api-Key`, not OAuth/Bearer). Full checklist: [.cursor/rules/api-endpoint-consistency.mdc](.cursor/rules/api-endpoint-consistency.mdc).
50
51## Design System Hygiene (UI PRs)
52
53When a PR touches `.erb`, view components, or `.css`:
54
551. **Tokens, not palette.** Use functional tokens from `app/assets/tailwind/sure-design-system.css` (`bg-warning/10`, `text-destructive`, `bg-container`, `text-primary`, `border-primary`). No raw Tailwind palette (`bg-blue-50`, `text-red-500`, hex literals).
562. **Reach for `DS::*` first.** Check `app/components/DS/` (`DS::Alert`, `DS::Button`, `DS::Disclosure`, `DS::Dialog`, `DS::Menu`, etc.) before writing an alert, badge, button, disclosure, dialog, or input shape.
573. **Two copies → lift to DS.** Same hand-rolled shape ≥2× in a diff with no DS equivalent → propose a new `DS::*` primitive before the second copy lands.
584. **Conventions.** Use the `icon` helper (never `lucide_icon` directly), no raw SVG outside DS primitives, user-facing strings via `t()`, avoid arbitrary `*-[Npx]` values when a scale token fits.
59
60Reviewers escalate violations of (2)–(3) to close/rewrite; (1) and (4) are request-changes.
61
62## Securities Providers
63
64If you need to add a new securities price provider (Tiingo, EODHD, Binance-style crypto, etc.), see [adding-a-securities-provider.md](./docs/llm-guides/adding-a-securities-provider.md) for the full walkthrough — provider class, registry wiring, MIC handling, settings UI, locales, and tests.
65
66## Debug Logging for Provider Syncs
67
68When a provider sync/import path hits a recoverable error or suspicious partial response that support may need to inspect later, prefer `DebugLogEntry.capture(...)` over `Rails.logger.*`.
69
70- Record support-relevant diagnostics in the debug log so they surface in the super-admin-friendly `/settings/debug` UI.
71- Include `category`, `level`, `message`, `source`, `provider_key`, and useful structured `metadata`.
72- Attach `family` and `account_provider` when available so support can filter and trace the affected connection.
73- Reserve raw Rails logging for low-value local noise; anything operators may need should go to the debug log.
74
75## Providers: Pending Transactions and FX Metadata (SimpleFIN/Plaid/Lunchflow)
76
77- Pending detection
78 - SimpleFIN: pending when provider sends `pending: true`, or when `posted` is blank/0 and `transacted_at` is present.
79 - Plaid: pending when Plaid sends `pending: true` (stored at `transaction.extra["plaid"]["pending"]` for bank/credit transactions imported via `PlaidEntry::Processor`).
80 - Lunchflow: pending when API returns `isPending: true` in transaction response (stored at `transaction.extra["lunchflow"]["pending"]`).
81- Storage (extras)
82 - Provider metadata lives on `Transaction#extra`, namespaced (e.g., `extra["simplefin"]["pending"]`).
83 - SimpleFIN FX: `extra["simplefin"]["fx_from"]`, `extra["simplefin"]["fx_date"]`.
84- UI
85 - Shows a small “Pending” badge when `transaction.pending?` is true.
86- Variability
87 - Some providers don’t expose pendings; in that case nothing is shown.
88- Configuration (default-off)
89 - SimpleFIN runtime toggles live in `config/initializers/simplefin.rb` via `Rails.configuration.x.simplefin.*`.
90 - Lunchflow runtime toggles live in `config/initializers/lunchflow.rb` via `Rails.configuration.x.lunchflow.*`.
91 - ENV-backed keys:
92 - `SIMPLEFIN_INCLUDE_PENDING=1` (forces `pending=1` on SimpleFIN fetches when caller didn’t specify a `pending:` arg)
93 - `SIMPLEFIN_DEBUG_RAW=1` (logs raw payload returned by SimpleFIN)
94 - `LUNCHFLOW_INCLUDE_PENDING=1` (forces `include_pending=true` on Lunchflow API requests)
95 - `LUNCHFLOW_DEBUG_RAW=1` (logs raw payload returned by Lunchflow)
96
97### Provider support notes
98
99- SimpleFIN: supports pending + FX metadata; stored under `extra["simplefin"]`.
100- Plaid: supports pending when the upstream Plaid payload includes `pending: true`; stored under `extra["plaid"]`.
101- Plaid investments: investment transactions currently do not store pending metadata.
102- Lunchflow: supports pending via `include_pending` query parameter; stored under `extra["lunchflow"]`.
103- Manual/CSV imports: no pending concept.
104
we-promise/sure · CLAUDE.md
@@ +1 @@
1# CLAUDE.md
2
3This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
5## Common Development Commands
6
7### Development Server
8- `bin/dev` - Start development server (Rails, Sidekiq, Tailwind CSS watcher)
9- `bin/rails server` - Start Rails server only
10- `bin/rails console` - Open Rails console
11
12### Testing
13- `bin/rails test` - Run all tests
14- `bin/rails test:db` - Run tests with database reset
15- `DISABLE_PARALLELIZATION=true bin/rails test:system` - Run system tests only (use sparingly - they take longer)
16- `bin/rails test test/models/account_test.rb` - Run specific test file
17- `bin/rails test test/models/account_test.rb:42` - Run specific test at line
18
19#### System Tests in the Dev Container
20When running inside the Dev Container, the `SELENIUM_REMOTE_URL` environment variable is automatically set to the bundled `selenium/standalone-chromium` service. System tests will connect to that remote browser — no local Chrome installation is required.
21
22```bash
23DISABLE_PARALLELIZATION=true bin/rails test:system
24```
25
26To watch the browser live, open `http://localhost:7900` or `http://localhost:4444` in your host browser (password: `secret`).
27
28### Linting & Formatting
29- `bin/rubocop` - Run Ruby linter
30- `npm run lint` - Check JavaScript/TypeScript code
31- `npm run lint:fix` - Fix JavaScript/TypeScript issues
32- `npm run format` - Format JavaScript/TypeScript code
33- `bin/brakeman` - Run security analysis
34
35### Database
36- `bin/rails db:prepare` - Create and migrate database
37- `bin/rails db:migrate` - Run pending migrations
38- `bin/rails db:rollback` - Rollback last migration
39- `bin/rails db:seed` - Load seed data
40
41### Setup
42- `bin/setup` - Initial project setup (installs dependencies, prepares database)
43
44## Pre-Pull Request CI Workflow
45
46ALWAYS run these commands before opening a pull request:
47
481. **Tests** (Required):
49 - `bin/rails test` - Run all tests (always required)
50 - `DISABLE_PARALLELIZATION=true bin/rails test:system` - Run system tests (only when applicable, they take longer)
51
522. **Linting** (Required):
53 - `bin/rubocop -f github -a` - Ruby linting with auto-correct
54 - `bundle exec erb_lint ./app/**/*.erb -a` - ERB linting with auto-correct
55
563. **Security** (Required):
57 - `bin/brakeman --no-pager` - Security analysis
58
59Only proceed with pull request creation if ALL checks pass.
60
61## General Development Rules
62
63### Authentication Context
64- Use `Current.user` for the current user. Do NOT use `current_user`.
65- Use `Current.family` for the current family. Do NOT use `current_family`.
66
67### Development Guidelines
68- Carefully read project conventions and guidelines before generating any code.
69- Do not run `rails server` in your responses
70- Do not run `touch tmp/restart.txt`
71- Do not run `rails credentials`
72- Do not automatically run migrations
73
74## High-Level Architecture
75
76### Application Modes
77The codebase runs in two distinct modes:
78- **Managed**: A team operates and manages servers for users (Rails.application.config.app_mode = "managed")
79- **Self Hosted**: Users host the codebase on their own infrastructure, typically through Docker Compose (Rails.application.config.app_mode = "self_hosted")
80
81### Core Domain Model
82The application is built around financial data management with these key relationships:
83- **User** → has many **Accounts** → has many **Transactions**
84- **Account** types: checking, savings, credit cards, investments, crypto, loans, properties
85- **Transaction** → belongs to **Category**, can have **Tags** and **Rules**
86- **Investment accounts** → have **Holdings** → track **Securities** via **Trades**
87
88### API Architecture
89The application provides both internal and external APIs:
90- Internal API: Controllers serve JSON via Turbo for SPA-like interactions
91- External API: `/api/v1/` namespace with Doorkeeper OAuth and API key authentication
92- API responses use Jbuilder templates for JSON rendering
93- Rate limiting via Rack Attack with configurable limits per API key
94- **OpenAPI Documentation**: All API endpoints MUST have corresponding OpenAPI specs in `spec/requests/api/` using rswag. See `docs/api/openapi.yaml` for the generated documentation.
95
96### Sync & Import System
97Two primary data ingestion methods:
981. **Plaid Integration**: Real-time bank account syncing
99 - `PlaidItem` manages connections
100 - `Sync` tracks sync operations
101 - Background jobs handle data updates
1022. **CSV Import**: Manual data import with mapping
103 - `Import` manages import sessions
104 - Supports transaction and balance imports
105 - Custom field mapping with transformation rules
106
107### Provider Integrations: Pending Transactions and FX (SimpleFIN/Plaid)
108
109- Detection
110 - SimpleFIN: pending via `pending: true` or `posted` blank/0 + `transacted_at`.
111 - Plaid: pending via Plaid `pending: true` (stored at `extra["plaid"]["pending"]` for bank/credit transactions imported via `PlaidEntry::Processor`).
112- Storage: provider data on `Transaction#extra` (e.g., `extra["simplefin"]["pending"]`; FX uses `fx_from`, `fx_date`).
113- UI: "Pending" badge when `transaction.pending?` is true; no badge if provider omits pendings.
114- Configuration (default-on for pending)
115 - SimpleFIN: `config/initializers/simplefin.rb` via `Rails.configuration.x.simplefin.*`.
116 - Plaid: `config/initializers/plaid_config.rb` via `Rails.configuration.x.plaid.*`.
117 - Pending transactions are fetched by default and handled via reconciliation/filtering.
118 - Set `SIMPLEFIN_INCLUDE_PENDING=0` to disable pending fetching for SimpleFIN.
119 - Set `PLAID_INCLUDE_PENDING=0` to disable pending fetching for Plaid.
120 - Set `SIMPLEFIN_DEBUG_RAW=1` to enable raw payload debug logging.
121 - Set `UP_DEBUG_RAW=1` to enable raw Up payload debug logging. DEV-ONLY: the dump contains PII and is gated to local environments, so it never logs in managed/production.
122
123Provider support notes:
124- SimpleFIN: supports pending + FX metadata (stored under `extra["simplefin"]`).
125- Plaid: supports pending when the upstream Plaid payload includes `pending: true` (stored under `extra["plaid"]`).
126- Plaid investments: investment transactions currently do not store pending metadata.
127- Lunchflow: does not currently store pending metadata.
128
129### Background Processing
130Sidekiq handles asynchronous tasks:
131- Account syncing (`SyncJob`)
132- Import processing (`ImportJob`)
133- AI chat responses (`AssistantResponseJob`)
134- Scheduled maintenance via sidekiq-cron
135
136### Debug Logging for Provider Syncs
137- Prefer `DebugLogEntry.capture(...)` over `Rails.logger.*` for provider sync/import failures, partial responses, and other support-relevant diagnostics.
138- Record support-relevant incidents in the super-admin `/settings/debug` UI rather than leaving them only in raw application logs.
139- Include `category`, `level`, `message`, `source`, `provider_key`, and structured `metadata`.
140- Attach `family` and `account_provider` whenever possible so support can filter to the affected provider connection.
141
142### Frontend Architecture
143- **Hotwire Stack**: Turbo + Stimulus for reactive UI without heavy JavaScript
144- **ViewComponents**: Reusable UI components in `app/components/`
145- **Stimulus Controllers**: Handle interactivity, organized alongside components
146- **Charts**: D3.js for financial visualizations (time series, donut, sankey)
147- **Styling**: Tailwind CSS v4.x with custom design system
148 - Design system defined in `app/assets/tailwind/sure-design-system.css`
149 - Always use functional tokens (e.g., `text-primary` not `text-white`)
150 - Prefer semantic HTML elements over JS components
151 - Use `icon` helper for icons, never `lucide_icon` directly
152- **i18n**: All user-facing strings must use localization (i18n). Update locale files for each new or changed element.
153
154### Internationalization (i18n) Guidelines
155- **Key Organization**: Use hierarchical keys by feature: `accounts.index.title`, `transactions.form.amount_label`
156- **Translation Helper**: Always use `t()` helper for user-facing strings
157- **Interpolation**: Use for dynamic content: `t("users.greeting", name: user.name)`
158- **Pluralization**: Use Rails pluralization: `t("transactions.count", count: @transactions.count)`
159- **Locale Files**: Update `config/locales/en.yml` for new strings
160- **Missing Translations**: Configure to raise errors in development for missing keys
161
162### Multi-Currency Support
163- All monetary values stored in base currency (user's primary currency)
164- `Money` objects handle currency conversion and formatting
165- Historical exchange rates for accurate reporting
166
167### Security & Authentication
168- Session-based auth for web users
169- API authentication via:
170 - OAuth2 (Doorkeeper) for third-party apps
171 - API keys with JWT tokens for direct API access
172- Scoped permissions system for API access
173- Strong parameters and CSRF protection throughout
174
175### Testing Philosophy
176- Comprehensive test coverage using Rails' built-in Minitest
177- Fixtures for test data (avoid FactoryBot)
178- Keep fixtures minimal (2-3 per model for base cases)
179- VCR for external API testing
180- System tests for critical user flows (use sparingly)
181- Test helpers in `test/support/` for common scenarios
182- Only test critical code paths that significantly increase confidence
183- Write tests as you go, when required
184- **API Endpoints require OpenAPI specs** in `spec/requests/api/` for documentation purposes ONLY, not test (uses RSpec + rswag)
185
186### Performance Considerations
187- Database queries optimized with proper indexes
188- N+1 queries prevented via includes/joins
189- Background jobs for heavy operations
190- Caching strategies for expensive calculations
191- Turbo Frames for partial page updates
192
193### Development Workflow
194- Feature branches merged to `main`
195- Docker support for consistent environments
196- Environment variables via `.env` files
197- Lookbook for component development (`/lookbook`)
198- Letter Opener for email preview in development
199
200## Project Conventions
201
202### Convention 1: Minimize Dependencies
203- Push Rails to its limits before adding new dependencies
204- Strong technical/business reason required for new dependencies
205- Favor old and reliable over new and flashy
206
207### Convention 2: Skinny Controllers, Fat Models
208- Business logic in `app/models/` folder, avoid `app/services/`
209- Use Rails concerns and POROs for organization
210- Models should answer questions about themselves: `account.balance_series` not `AccountSeries.new(account).call`
211
212### Convention 3: Hotwire-First Frontend
213- **Native HTML preferred over JS components**
214 - Use `<dialog>` for modals, `<details><summary>` for disclosures
215- **Leverage Turbo frames** for page sections over client-side solutions
216- **Query params for state** over localStorage/sessions
217- **Server-side formatting** for currencies, numbers, dates
218- **Always use `icon` helper** in `application_helper.rb`, NEVER `lucide_icon` directly
219
220### Convention 4: Optimize for Simplicity
221- Prioritize good OOP domain design over performance
222- Focus performance only on critical/global areas (avoid N+1 queries, mindful of global layouts)
223
224### Convention 5: Database vs ActiveRecord Validations
225- Simple validations (null checks, unique indexes) in DB
226- ActiveRecord validations for convenience in forms (prefer client-side when possible)
227- Complex validations and business logic in ActiveRecord
228
229## TailwindCSS Design System
230
231### Design System Rules
232- **Always reference `app/assets/tailwind/sure-design-system.css`** for primitives and tokens
233- **Use functional tokens** defined in design system:
234 - `text-primary` instead of `text-white`
235 - `bg-container` instead of `bg-white`
236 - `border border-primary` instead of `border border-gray-200`
237- **NEVER create new styles** in design system files without permission
238- **Always generate semantic HTML**
239
240## Component Architecture
241
242### ViewComponent vs Partials Decision Making
243
244**Use ViewComponents when:**
245- Element has complex logic or styling patterns
246- Element will be reused across multiple views/contexts
247- Element needs structured styling with variants/sizes
248- Element requires interactive behavior or Stimulus controllers
249- Element has configurable slots or complex APIs
250- Element needs accessibility features or ARIA support
251
252**Use Partials when:**
253- Element is primarily static HTML with minimal logic
254- Element is used in only one or few specific contexts
255- Element is simple template content
256- Element doesn't need variants, sizes, or complex configuration
257- Element is more about content organization than reusable functionality
258
259**Component Guidelines:**
260- Prefer components over partials when available
261- Keep domain logic OUT of view templates
262- Logic belongs in component files, not template files
263
264### Stimulus Controller Guidelines
265
266**Declarative Actions (Required):**
267```erb
268<!-- GOOD: Declarative - HTML declares what happens -->
269<div data-controller="toggle">
270 <button data-action="click->toggle#toggle" data-toggle-target="button">
271 <%= t("components.transaction_details.show_details") %>
272 </button>
273 <div data-toggle-target="content" class="hidden">
274 <p><%= t("components.transaction_details.amount_label") %>: <%= @transaction.amount %></p>
275 <p><%= t("components.transaction_details.date_label") %>: <%= @transaction.date %></p>
276 <p><%= t("components.transaction_details.category_label") %>: <%= @transaction.category.name %></p>
277 </div>
278</div>
279```
280
281**Example locale file structure (config/locales/en.yml):**
282```yaml
283en:
284 components:
285 transaction_details:
286 show_details: "Show Details"
287 hide_details: "Hide Details"
288 amount_label: "Amount"
289 date_label: "Date"
290 category_label: "Category"
291```
292
293**i18n Best Practices:**
294- Organize keys by feature/component: `components.transaction_details.show_details`
295- Use descriptive key names that indicate purpose: `show_details` not `button`
296- Group related translations together in the same namespace
297- Use interpolation for dynamic content: `t("users.welcome", name: user.name)`
298- Always update locale files when adding new user-facing strings
299
300**Controller Best Practices:**
301- Keep controllers lightweight and simple (< 7 targets)
302- Use private methods and expose clear public API
303- Single responsibility or highly related responsibilities
304- Component controllers stay in component directory, global controllers in `app/javascript/controllers/`
305- Pass data via `data-*-value` attributes, not inline JavaScript
306
307## Testing Philosophy
308
309### General Testing Rules
310- **ALWAYS use Minitest + fixtures** (NEVER RSpec or factories)
311- Keep fixtures minimal (2-3 per model for base cases)
312- Create edge cases on-the-fly within test context
313- Use Rails helpers for large fixture creation needs
314
315### Test Quality Guidelines
316- **Write minimal, effective tests** - system tests sparingly
317- **Only test critical and important code paths**
318- **Test boundaries correctly:**
319 - Commands: test they were called with correct params
320 - Queries: test output
321 - Don't test implementation details of other classes
322
323### Testing Examples
324
325```ruby
326# GOOD - Testing critical domain business logic
327test "syncs balances" do
328 Holding::Syncer.any_instance.expects(:sync_holdings).returns([]).once
329 assert_difference "@account.balances.count", 2 do
330 Balance::Syncer.new(@account, strategy: :forward).sync_balances
331 end
332end
333
334# BAD - Testing ActiveRecord functionality
335test "saves balance" do
336 balance_record = Balance.new(balance: 100, currency: "USD")
337 assert balance_record.save
338end
339```
340
341### Stubs and Mocks
342- Use `mocha` gem
343- Prefer `OpenStruct` for mock instances
344- Only mock what's necessary
345
346## API Development Guidelines
347
348### OpenAPI Documentation (MANDATORY)
349When adding or modifying API endpoints in `app/controllers/api/v1/`, you **MUST** create or update corresponding OpenAPI request specs:
350
3511. **Location**: `spec/requests/api/v1/{resource}_spec.rb`
3522. **Framework**: RSpec with rswag for OpenAPI generation
3533. **Schemas**: Define reusable schemas in `spec/swagger_helper.rb`
3544. **Generated Docs**: `docs/api/openapi.yaml`
355
356**Example structure for a new API endpoint:**
357```ruby
358# spec/requests/api/v1/widgets_spec.rb
359require 'swagger_helper'
360
361RSpec.describe 'API V1 Widgets', type: :request do
362 path '/api/v1/widgets' do
363 get 'List widgets' do
364 tags 'Widgets'
365 security [ { apiKeyAuth: [] } ]
366 produces 'application/json'
367
368 response '200', 'widgets listed' do
369 schema '$ref' => '#/components/schemas/WidgetCollection'
370 run_test!
371 end
372 end
373 end
374end
375```
376
377**Regenerate OpenAPI docs after changes:**
378```bash
379RAILS_ENV=test bundle exec rake rswag:specs:swaggerize
380```
381
382### Post-commit API consistency (issue #944)
383After every API endpoint commit, ensure:
384
3851. **Minitest behavioral coverage** — Add or update tests in `test/controllers/api/v1/{resource}_controller_test.rb`. Use API key and `api_headers` (X-Api-Key). Cover index/show, CRUD where relevant, 401/403/422/404. Do not rely on rswag for behavioral assertions.
386
3872. **rswag docs-only** — Do not add `expect(...)` or `assert_*` in `spec/requests/api/v1/`. Use `run_test!` only so specs document request/response and regenerate `docs/api/openapi.yaml`.
388
3893. **Same API key auth in rswag** — Every request spec in `spec/requests/api/v1/` must use the same API key pattern (`ApiKey.generate_secure_key`, `ApiKey.create!(...)`, `let(:'X-Api-Key') { api_key.plain_key }`). Do not use Doorkeeper/OAuth in those specs so generated docs stay consistent.
390
391Full checklist and pattern: [.cursor/rules/api-endpoint-consistency.mdc](.cursor/rules/api-endpoint-consistency.mdc).
392
393To verify the implementation: `ruby test/support/verify_api_endpoint_consistency.rb`. To scan the current APIs for violations: `ruby test/support/verify_api_endpoint_consistency.rb --compliance`.
@@ −1 +1 @@
1−# Repository Guidelines
1+# CLAUDE.md
22
3−## Project Structure & Module Organization
4−- Code: `app/` (Rails MVC, services, jobs, mailers, components), JS in `app/javascript/`, styles/assets in `app/assets/` (Tailwind, images, fonts).
5−- Config: `config/`, environment examples in `.env.local.example` and `.env.test.example`.
6−- Data: `db/` (migrations, seeds), fixtures in `test/fixtures/`.
7−- Tests: `test/` mirroring `app/` (e.g., `test/models/*_test.rb`).
8−- Tooling: `bin/` (project scripts), `docs/` (guides), `public/` (static), `lib/` (shared libs).
3+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
94
10−## Build, Test, and Development Commands
11−- Setup: `cp .env.local.example .env.local && bin/setup` — install deps, set DB, prepare app.
12−- Run app: `bin/dev` — starts Rails server and asset/watchers via `Procfile.dev`.
13−- Test suite: `bin/rails test` — run all Minitest tests; add `TEST=test/models/user_test.rb` to target a file.
14−- Lint Ruby: `bin/rubocop` — style checks; add `-A` to auto-correct safe cops.
15−- Lint/format JS/CSS: `npm run lint` and `npm run format` — uses Biome.
16−- Security scan: `bin/brakeman` — static analysis for common Rails issues.
5+## Common Development Commands
176
18−## Coding Style & Naming Conventions
19−- Ruby: 2-space indent, `snake_case` for methods/vars, `CamelCase` for classes/modules. Follow Rails conventions for folders and file names.
20−- Views: ERB checked by `erb-lint` (see `.erb_lint.yml`). Avoid heavy logic in views; prefer helpers/components.
21−- JavaScript: `lowerCamelCase` for vars/functions, `PascalCase` for classes/components. Let Biome format code.
22−- Commit small, cohesive changes; keep diffs focused.
7+### Development Server
8+- `bin/dev` - Start development server (Rails, Sidekiq, Tailwind CSS watcher)
9+- `bin/rails server` - Start Rails server only
10+- `bin/rails console` - Open Rails console
2311
24−## Testing Guidelines
25−- Framework: Minitest (Rails). Name files `*_test.rb` and mirror `app/` structure.
26−- Run: `bin/rails test` locally and ensure green before pushing.
27−- Fixtures/VCR: Use `test/fixtures` and existing VCR cassettes for HTTP. Prefer unit tests plus focused integration tests.
12+### Testing
13+- `bin/rails test` - Run all tests
14+- `bin/rails test:db` - Run tests with database reset
15+- `DISABLE_PARALLELIZATION=true bin/rails test:system` - Run system tests only (use sparingly - they take longer)
16+- `bin/rails test test/models/account_test.rb` - Run specific test file
17+- `bin/rails test test/models/account_test.rb:42` - Run specific test at line
2818
29−## Commit & Pull Request Guidelines
30−- Commits: Imperative subject ≤ 72 chars (e.g., "Add account balance validation"). Include rationale in body and reference issues (`#123`).
31−- PRs: Clear description, linked issues, screenshots for UI changes, and migration notes if applicable. Ensure CI passes, tests added/updated, and `rubocop`/Biome are clean.
19+#### System Tests in the Dev Container
20+When running inside the Dev Container, the `SELENIUM_REMOTE_URL` environment variable is automatically set to the bundled `selenium/standalone-chromium` service. System tests will connect to that remote browser — no local Chrome installation is required.
3221
33−## Security & Configuration Tips
34−- Never commit secrets. Start from `.env.local.example`; use `.env.local` for development only.
35−- Run `bin/brakeman` before major PRs. Prefer environment variables over hard-coded values.
22+```bash
23+DISABLE_PARALLELIZATION=true bin/rails test:system
24+```
3625
37−## API Development Guidelines
26+To watch the browser live, open `http://localhost:7900` or `http://localhost:4444` in your host browser (password: `secret`).
3827
39−### OpenAPI Documentation (MANDATORY)
40−When adding or modifying API endpoints in `app/controllers/api/v1/`, you **MUST** create or update corresponding OpenAPI request specs for **DOCUMENTATION ONLY**:
28+### Linting & Formatting
29+- `bin/rubocop` - Run Ruby linter
30+- `npm run lint` - Check JavaScript/TypeScript code
31+- `npm run lint:fix` - Fix JavaScript/TypeScript issues
32+- `npm run format` - Format JavaScript/TypeScript code
33+- `bin/brakeman` - Run security analysis
4134
42−1. **Location**: `spec/requests/api/v1/{resource}_spec.rb`
43−2. **Framework**: RSpec with rswag for OpenAPI generation
44−3. **Schemas**: Define reusable schemas in `spec/swagger_helper.rb`
45−4. **Generated Docs**: `docs/api/openapi.yaml`
46−5. **Regenerate**: Run `RAILS_ENV=test bundle exec rake rswag:specs:swaggerize` after changes
35+### Database
36+- `bin/rails db:prepare` - Create and migrate database
37+- `bin/rails db:migrate` - Run pending migrations
38+- `bin/rails db:rollback` - Rollback last migration
39+- `bin/rails db:seed` - Load seed data
4740
48−### Post-commit API consistency (LLM checklist)
49−After every API endpoint commit, ensure: (1) **Minitest** behavioral coverage in `test/controllers/api/v1/{resource}_controller_test.rb` (no behavioral assertions in rswag); (2) **rswag** remains docs-only (no `expect`/`assert_*` in `spec/requests/api/v1/`); (3) **rswag auth** uses the same API key pattern everywhere (`X-Api-Key`, not OAuth/Bearer). Full checklist: [.cursor/rules/api-endpoint-consistency.mdc](.cursor/rules/api-endpoint-consistency.mdc).
41+### Setup
42+- `bin/setup` - Initial project setup (installs dependencies, prepares database)
5043
51−## Design System Hygiene (UI PRs)
44+## Pre-Pull Request CI Workflow
5245
53−When a PR touches `.erb`, view components, or `.css`:
46+ALWAYS run these commands before opening a pull request:
5447
55−1. **Tokens, not palette.** Use functional tokens from `app/assets/tailwind/sure-design-system.css` (`bg-warning/10`, `text-destructive`, `bg-container`, `text-primary`, `border-primary`). No raw Tailwind palette (`bg-blue-50`, `text-red-500`, hex literals).
56−2. **Reach for `DS::*` first.** Check `app/components/DS/` (`DS::Alert`, `DS::Button`, `DS::Disclosure`, `DS::Dialog`, `DS::Menu`, etc.) before writing an alert, badge, button, disclosure, dialog, or input shape.
57−3. **Two copies → lift to DS.** Same hand-rolled shape ≥2× in a diff with no DS equivalent → propose a new `DS::*` primitive before the second copy lands.
58−4. **Conventions.** Use the `icon` helper (never `lucide_icon` directly), no raw SVG outside DS primitives, user-facing strings via `t()`, avoid arbitrary `*-[Npx]` values when a scale token fits.
48+1. **Tests** (Required):
49+ - `bin/rails test` - Run all tests (always required)
50+ - `DISABLE_PARALLELIZATION=true bin/rails test:system` - Run system tests (only when applicable, they take longer)
5951
60−Reviewers escalate violations of (2)–(3) to close/rewrite; (1) and (4) are request-changes.
52+2. **Linting** (Required):
53+ - `bin/rubocop -f github -a` - Ruby linting with auto-correct
54+ - `bundle exec erb_lint ./app/**/*.erb -a` - ERB linting with auto-correct
6155
62−## Securities Providers
56+3. **Security** (Required):
57+ - `bin/brakeman --no-pager` - Security analysis
6358
64−If you need to add a new securities price provider (Tiingo, EODHD, Binance-style crypto, etc.), see [adding-a-securities-provider.md](./docs/llm-guides/adding-a-securities-provider.md) for the full walkthrough — provider class, registry wiring, MIC handling, settings UI, locales, and tests.
59+Only proceed with pull request creation if ALL checks pass.
6560
66−## Debug Logging for Provider Syncs
61+## General Development Rules
6762
68−When a provider sync/import path hits a recoverable error or suspicious partial response that support may need to inspect later, prefer `DebugLogEntry.capture(...)` over `Rails.logger.*`.
63+### Authentication Context
64+- Use `Current.user` for the current user. Do NOT use `current_user`.
65+- Use `Current.family` for the current family. Do NOT use `current_family`.
6966
70−- Record support-relevant diagnostics in the debug log so they surface in the super-admin-friendly `/settings/debug` UI.
71−- Include `category`, `level`, `message`, `source`, `provider_key`, and useful structured `metadata`.
72−- Attach `family` and `account_provider` when available so support can filter and trace the affected connection.
73−- Reserve raw Rails logging for low-value local noise; anything operators may need should go to the debug log.
67+### Development Guidelines
68+- Carefully read project conventions and guidelines before generating any code.
69+- Do not run `rails server` in your responses
70+- Do not run `touch tmp/restart.txt`
71+- Do not run `rails credentials`
72+- Do not automatically run migrations
7473
75−## Providers: Pending Transactions and FX Metadata (SimpleFIN/Plaid/Lunchflow)
74+## High-Level Architecture
7675
77−- Pending detection
78− - SimpleFIN: pending when provider sends `pending: true`, or when `posted` is blank/0 and `transacted_at` is present.
79− - Plaid: pending when Plaid sends `pending: true` (stored at `transaction.extra["plaid"]["pending"]` for bank/credit transactions imported via `PlaidEntry::Processor`).
80− - Lunchflow: pending when API returns `isPending: true` in transaction response (stored at `transaction.extra["lunchflow"]["pending"]`).
81−- Storage (extras)
82− - Provider metadata lives on `Transaction#extra`, namespaced (e.g., `extra["simplefin"]["pending"]`).
83− - SimpleFIN FX: `extra["simplefin"]["fx_from"]`, `extra["simplefin"]["fx_date"]`.
84−- UI
85− - Shows a small “Pending” badge when `transaction.pending?` is true.
86−- Variability
87− - Some providers don’t expose pendings; in that case nothing is shown.
88−- Configuration (default-off)
89− - SimpleFIN runtime toggles live in `config/initializers/simplefin.rb` via `Rails.configuration.x.simplefin.*`.
90− - Lunchflow runtime toggles live in `config/initializers/lunchflow.rb` via `Rails.configuration.x.lunchflow.*`.
91− - ENV-backed keys:
92− - `SIMPLEFIN_INCLUDE_PENDING=1` (forces `pending=1` on SimpleFIN fetches when caller didn’t specify a `pending:` arg)
93− - `SIMPLEFIN_DEBUG_RAW=1` (logs raw payload returned by SimpleFIN)
94− - `LUNCHFLOW_INCLUDE_PENDING=1` (forces `include_pending=true` on Lunchflow API requests)
95− - `LUNCHFLOW_DEBUG_RAW=1` (logs raw payload returned by Lunchflow)
76+### Application Modes
77+The codebase runs in two distinct modes:
78+- **Managed**: A team operates and manages servers for users (Rails.application.config.app_mode = "managed")
79+- **Self Hosted**: Users host the codebase on their own infrastructure, typically through Docker Compose (Rails.application.config.app_mode = "self_hosted")
9680
97−### Provider support notes
81+### Core Domain Model
82+The application is built around financial data management with these key relationships:
83+- **User** → has many **Accounts** → has many **Transactions**
84+- **Account** types: checking, savings, credit cards, investments, crypto, loans, properties
85+- **Transaction** → belongs to **Category**, can have **Tags** and **Rules**
86+- **Investment accounts** → have **Holdings** → track **Securities** via **Trades**
9887
99−- SimpleFIN: supports pending + FX metadata; stored under `extra["simplefin"]`.
100−- Plaid: supports pending when the upstream Plaid payload includes `pending: true`; stored under `extra["plaid"]`.
88+### API Architecture
89+The application provides both internal and external APIs:
90+- Internal API: Controllers serve JSON via Turbo for SPA-like interactions
91+- External API: `/api/v1/` namespace with Doorkeeper OAuth and API key authentication
92+- API responses use Jbuilder templates for JSON rendering
93+- Rate limiting via Rack Attack with configurable limits per API key
94+- **OpenAPI Documentation**: All API endpoints MUST have corresponding OpenAPI specs in `spec/requests/api/` using rswag. See `docs/api/openapi.yaml` for the generated documentation.
95+
96+### Sync & Import System
97+Two primary data ingestion methods:
98+1. **Plaid Integration**: Real-time bank account syncing
99+ - `PlaidItem` manages connections
100+ - `Sync` tracks sync operations
101+ - Background jobs handle data updates
102+2. **CSV Import**: Manual data import with mapping
103+ - `Import` manages import sessions
104+ - Supports transaction and balance imports
105+ - Custom field mapping with transformation rules
106+
107+### Provider Integrations: Pending Transactions and FX (SimpleFIN/Plaid)
108+
109+- Detection
110+ - SimpleFIN: pending via `pending: true` or `posted` blank/0 + `transacted_at`.
111+ - Plaid: pending via Plaid `pending: true` (stored at `extra["plaid"]["pending"]` for bank/credit transactions imported via `PlaidEntry::Processor`).
112+- Storage: provider data on `Transaction#extra` (e.g., `extra["simplefin"]["pending"]`; FX uses `fx_from`, `fx_date`).
113+- UI: "Pending" badge when `transaction.pending?` is true; no badge if provider omits pendings.
114+- Configuration (default-on for pending)
115+ - SimpleFIN: `config/initializers/simplefin.rb` via `Rails.configuration.x.simplefin.*`.
116+ - Plaid: `config/initializers/plaid_config.rb` via `Rails.configuration.x.plaid.*`.
117+ - Pending transactions are fetched by default and handled via reconciliation/filtering.
118+ - Set `SIMPLEFIN_INCLUDE_PENDING=0` to disable pending fetching for SimpleFIN.
119+ - Set `PLAID_INCLUDE_PENDING=0` to disable pending fetching for Plaid.
120+ - Set `SIMPLEFIN_DEBUG_RAW=1` to enable raw payload debug logging.
121+ - Set `UP_DEBUG_RAW=1` to enable raw Up payload debug logging. DEV-ONLY: the dump contains PII and is gated to local environments, so it never logs in managed/production.
122+
123+Provider support notes:
124+- SimpleFIN: supports pending + FX metadata (stored under `extra["simplefin"]`).
125+- Plaid: supports pending when the upstream Plaid payload includes `pending: true` (stored under `extra["plaid"]`).
101126 - Plaid investments: investment transactions currently do not store pending metadata.
102−- Lunchflow: supports pending via `include_pending` query parameter; stored under `extra["lunchflow"]`.
103−- Manual/CSV imports: no pending concept.
127+- Lunchflow: does not currently store pending metadata.
104128
129+### Background Processing
130+Sidekiq handles asynchronous tasks:
131+- Account syncing (`SyncJob`)
132+- Import processing (`ImportJob`)
133+- AI chat responses (`AssistantResponseJob`)
134+- Scheduled maintenance via sidekiq-cron
135+
136+### Debug Logging for Provider Syncs
137+- Prefer `DebugLogEntry.capture(...)` over `Rails.logger.*` for provider sync/import failures, partial responses, and other support-relevant diagnostics.
138+- Record support-relevant incidents in the super-admin `/settings/debug` UI rather than leaving them only in raw application logs.
139+- Include `category`, `level`, `message`, `source`, `provider_key`, and structured `metadata`.
140+- Attach `family` and `account_provider` whenever possible so support can filter to the affected provider connection.
141+
142+### Frontend Architecture
143+- **Hotwire Stack**: Turbo + Stimulus for reactive UI without heavy JavaScript
144+- **ViewComponents**: Reusable UI components in `app/components/`
145+- **Stimulus Controllers**: Handle interactivity, organized alongside components
146+- **Charts**: D3.js for financial visualizations (time series, donut, sankey)
147+- **Styling**: Tailwind CSS v4.x with custom design system
148+ - Design system defined in `app/assets/tailwind/sure-design-system.css`
149+ - Always use functional tokens (e.g., `text-primary` not `text-white`)
150+ - Prefer semantic HTML elements over JS components
151+ - Use `icon` helper for icons, never `lucide_icon` directly
152+- **i18n**: All user-facing strings must use localization (i18n). Update locale files for each new or changed element.
153+
154+### Internationalization (i18n) Guidelines
155+- **Key Organization**: Use hierarchical keys by feature: `accounts.index.title`, `transactions.form.amount_label`
156+- **Translation Helper**: Always use `t()` helper for user-facing strings
157+- **Interpolation**: Use for dynamic content: `t("users.greeting", name: user.name)`
158+- **Pluralization**: Use Rails pluralization: `t("transactions.count", count: @transactions.count)`
159+- **Locale Files**: Update `config/locales/en.yml` for new strings
160+- **Missing Translations**: Configure to raise errors in development for missing keys
161+
162+### Multi-Currency Support
163+- All monetary values stored in base currency (user's primary currency)
164+- `Money` objects handle currency conversion and formatting
165+- Historical exchange rates for accurate reporting
166+
167+### Security & Authentication
168+- Session-based auth for web users
169+- API authentication via:
170+ - OAuth2 (Doorkeeper) for third-party apps
171+ - API keys with JWT tokens for direct API access
172+- Scoped permissions system for API access
173+- Strong parameters and CSRF protection throughout
174+
175+### Testing Philosophy
176+- Comprehensive test coverage using Rails' built-in Minitest
177+- Fixtures for test data (avoid FactoryBot)
178+- Keep fixtures minimal (2-3 per model for base cases)
179+- VCR for external API testing
180+- System tests for critical user flows (use sparingly)
181+- Test helpers in `test/support/` for common scenarios
182+- Only test critical code paths that significantly increase confidence
183+- Write tests as you go, when required
184+- **API Endpoints require OpenAPI specs** in `spec/requests/api/` for documentation purposes ONLY, not test (uses RSpec + rswag)
185+
186+### Performance Considerations
187+- Database queries optimized with proper indexes
188+- N+1 queries prevented via includes/joins
189+- Background jobs for heavy operations
190+- Caching strategies for expensive calculations
191+- Turbo Frames for partial page updates
192+
193+### Development Workflow
194+- Feature branches merged to `main`
195+- Docker support for consistent environments
196+- Environment variables via `.env` files
197+- Lookbook for component development (`/lookbook`)
198+- Letter Opener for email preview in development
199+
200+## Project Conventions
201+
202+### Convention 1: Minimize Dependencies
203+- Push Rails to its limits before adding new dependencies
204+- Strong technical/business reason required for new dependencies
205+- Favor old and reliable over new and flashy
206+
207+### Convention 2: Skinny Controllers, Fat Models
208+- Business logic in `app/models/` folder, avoid `app/services/`
209+- Use Rails concerns and POROs for organization
210+- Models should answer questions about themselves: `account.balance_series` not `AccountSeries.new(account).call`
211+
212+### Convention 3: Hotwire-First Frontend
213+- **Native HTML preferred over JS components**
214+ - Use `<dialog>` for modals, `<details><summary>` for disclosures
215+- **Leverage Turbo frames** for page sections over client-side solutions
216+- **Query params for state** over localStorage/sessions
217+- **Server-side formatting** for currencies, numbers, dates
218+- **Always use `icon` helper** in `application_helper.rb`, NEVER `lucide_icon` directly
219+
220+### Convention 4: Optimize for Simplicity
221+- Prioritize good OOP domain design over performance
222+- Focus performance only on critical/global areas (avoid N+1 queries, mindful of global layouts)
223+
224+### Convention 5: Database vs ActiveRecord Validations
225+- Simple validations (null checks, unique indexes) in DB
226+- ActiveRecord validations for convenience in forms (prefer client-side when possible)
227+- Complex validations and business logic in ActiveRecord
228+
229+## TailwindCSS Design System
230+
231+### Design System Rules
232+- **Always reference `app/assets/tailwind/sure-design-system.css`** for primitives and tokens
233+- **Use functional tokens** defined in design system:
234+ - `text-primary` instead of `text-white`
235+ - `bg-container` instead of `bg-white`
236+ - `border border-primary` instead of `border border-gray-200`
237+- **NEVER create new styles** in design system files without permission
238+- **Always generate semantic HTML**
239+
240+## Component Architecture
241+
242+### ViewComponent vs Partials Decision Making
243+
244+**Use ViewComponents when:**
245+- Element has complex logic or styling patterns
246+- Element will be reused across multiple views/contexts
247+- Element needs structured styling with variants/sizes
248+- Element requires interactive behavior or Stimulus controllers
249+- Element has configurable slots or complex APIs
250+- Element needs accessibility features or ARIA support
251+
252+**Use Partials when:**
253+- Element is primarily static HTML with minimal logic
254+- Element is used in only one or few specific contexts
255+- Element is simple template content
256+- Element doesn't need variants, sizes, or complex configuration
257+- Element is more about content organization than reusable functionality
258+
259+**Component Guidelines:**
260+- Prefer components over partials when available
261+- Keep domain logic OUT of view templates
262+- Logic belongs in component files, not template files
263+
264+### Stimulus Controller Guidelines
265+
266+**Declarative Actions (Required):**
267+```erb
268+<!-- GOOD: Declarative - HTML declares what happens -->
269+<div data-controller="toggle">
270+ <button data-action="click->toggle#toggle" data-toggle-target="button">
271+ <%= t("components.transaction_details.show_details") %>
272+ </button>
273+ <div data-toggle-target="content" class="hidden">
274+ <p><%= t("components.transaction_details.amount_label") %>: <%= @transaction.amount %></p>
275+ <p><%= t("components.transaction_details.date_label") %>: <%= @transaction.date %></p>
276+ <p><%= t("components.transaction_details.category_label") %>: <%= @transaction.category.name %></p>
277+ </div>
278+</div>
279+```
280+
281+**Example locale file structure (config/locales/en.yml):**
282+```yaml
283+en:
284+ components:
285+ transaction_details:
286+ show_details: "Show Details"
287+ hide_details: "Hide Details"
288+ amount_label: "Amount"
289+ date_label: "Date"
290+ category_label: "Category"
291+```
292+
293+**i18n Best Practices:**
294+- Organize keys by feature/component: `components.transaction_details.show_details`
295+- Use descriptive key names that indicate purpose: `show_details` not `button`
296+- Group related translations together in the same namespace
297+- Use interpolation for dynamic content: `t("users.welcome", name: user.name)`
298+- Always update locale files when adding new user-facing strings
299+
300+**Controller Best Practices:**
301+- Keep controllers lightweight and simple (< 7 targets)
302+- Use private methods and expose clear public API
303+- Single responsibility or highly related responsibilities
304+- Component controllers stay in component directory, global controllers in `app/javascript/controllers/`
305+- Pass data via `data-*-value` attributes, not inline JavaScript
306+
307+## Testing Philosophy
308+
309+### General Testing Rules
310+- **ALWAYS use Minitest + fixtures** (NEVER RSpec or factories)
311+- Keep fixtures minimal (2-3 per model for base cases)
312+- Create edge cases on-the-fly within test context
313+- Use Rails helpers for large fixture creation needs
314+
315+### Test Quality Guidelines
316+- **Write minimal, effective tests** - system tests sparingly
317+- **Only test critical and important code paths**
318+- **Test boundaries correctly:**
319+ - Commands: test they were called with correct params
320+ - Queries: test output
321+ - Don't test implementation details of other classes
322+
323+### Testing Examples
324+
325+```ruby
326+# GOOD - Testing critical domain business logic
327+test "syncs balances" do
328+ Holding::Syncer.any_instance.expects(:sync_holdings).returns([]).once
329+ assert_difference "@account.balances.count", 2 do
330+ Balance::Syncer.new(@account, strategy: :forward).sync_balances
331+ end
332+end
333+
334+# BAD - Testing ActiveRecord functionality
335+test "saves balance" do
336+ balance_record = Balance.new(balance: 100, currency: "USD")
337+ assert balance_record.save
338+end
339+```
340+
341+### Stubs and Mocks
342+- Use `mocha` gem
343+- Prefer `OpenStruct` for mock instances
344+- Only mock what's necessary
345+
346+## API Development Guidelines
347+
348+### OpenAPI Documentation (MANDATORY)
349+When adding or modifying API endpoints in `app/controllers/api/v1/`, you **MUST** create or update corresponding OpenAPI request specs:
350+
351+1. **Location**: `spec/requests/api/v1/{resource}_spec.rb`
352+2. **Framework**: RSpec with rswag for OpenAPI generation
353+3. **Schemas**: Define reusable schemas in `spec/swagger_helper.rb`
354+4. **Generated Docs**: `docs/api/openapi.yaml`
355+
356+**Example structure for a new API endpoint:**
357+```ruby
358+# spec/requests/api/v1/widgets_spec.rb
359+require 'swagger_helper'
360+
361+RSpec.describe 'API V1 Widgets', type: :request do
362+ path '/api/v1/widgets' do
363+ get 'List widgets' do
364+ tags 'Widgets'
365+ security [ { apiKeyAuth: [] } ]
366+ produces 'application/json'
367+
368+ response '200', 'widgets listed' do
369+ schema '$ref' => '#/components/schemas/WidgetCollection'
370+ run_test!
371+ end
372+ end
373+ end
374+end
375+```
376+
377+**Regenerate OpenAPI docs after changes:**
378+```bash
379+RAILS_ENV=test bundle exec rake rswag:specs:swaggerize
380+```
381+
382+### Post-commit API consistency (issue #944)
383+After every API endpoint commit, ensure:
384+
385+1. **Minitest behavioral coverage** — Add or update tests in `test/controllers/api/v1/{resource}_controller_test.rb`. Use API key and `api_headers` (X-Api-Key). Cover index/show, CRUD where relevant, 401/403/422/404. Do not rely on rswag for behavioral assertions.
386+
387+2. **rswag docs-only** — Do not add `expect(...)` or `assert_*` in `spec/requests/api/v1/`. Use `run_test!` only so specs document request/response and regenerate `docs/api/openapi.yaml`.
388+
389+3. **Same API key auth in rswag** — Every request spec in `spec/requests/api/v1/` must use the same API key pattern (`ApiKey.generate_secure_key`, `ApiKey.create!(...)`, `let(:'X-Api-Key') { api_key.plain_key }`). Do not use Doorkeeper/OAuth in those specs so generated docs stay consistent.
390+
391+Full checklist and pattern: [.cursor/rules/api-endpoint-consistency.mdc](.cursor/rules/api-endpoint-consistency.mdc).
392+
393+To verify the implementation: `ruby test/support/verify_api_endpoint_consistency.rb`. To scan the current APIs for violations: `ruby test/support/verify_api_endpoint_consistency.rb --compliance`.
