| Dimension | Shared | Only in A | Only in B | Overlap |
|---|---|---|---|---|
| Sections | 0 | 0 | 47 | 0% |
| Commands | 0 | 0 | 6 | 0% |
| Section tags | 2 | 0 | 13 | 13% |
What each file covers
Sections
0 shared · 0 only in A · 47 only in B- + 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
- + Debug Logging for Provider Syncs
- + 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
- + API Development Guidelines
- + OpenAPI Documentation (MANDATORY)
- + spec/requests/api/v1/widgets_spec.rb
- + Post-commit API consistency (issue #944)
Commands
0 shared · 0 only in A · 6 only in B- + npm run lint
- + npm run lint:fix
- + npm run format
- + bundle exec erb_lint ./app/**/*.erb -a
- + rails server
- + rails credentials
Section tags
2 shared · 0 only in A · 13 only in B- + setup
- + test
- + lint-format
- + git-pr
- + security
- + dependencies
- + database
- + api
- + ui
- + performance
- + do-not
- + agent-behaviour
- + docs
- code-style
- testing-strategy
Line diff
we-promise/sure · .cursor/rules/testing.mdc
@@ −1 @@
1---
2description:
3globs: test/**
4alwaysApply: false
5---
6Use this rule to learn how to write tests for the codebase.
7
8Due to the open-source nature of this project, we have chosen Minitest + Fixtures for testing to maximize familiarity and predictability.
9
10- **General testing rules**
11 - Always use Minitest and fixtures for testing, NEVER rspec or factories
12 - Keep fixtures to a minimum. Most models should have 2-3 fixtures maximum that represent the "base cases" for that model. "Edge cases" should be created on the fly, within the context of the test which it is needed.
13 - For tests that require a large number of fixture records to be created, use Rails helpers to help create the records needed for the test, then inline the creation. For example, [entries_test_helper.rb](mdc:test/support/entries_test_helper.rb) provides helpers to easily do this.
14
15- **Write minimal, effective tests**
16 - Use system tests sparingly as they increase the time to complete the test suite
17 - Only write tests for critical and important code paths
18 - Write tests as you go, when required
19 - Take a practical approach to testing. Tests are effective when their presence _significantly increases confidence in the codebase_.
20
21 Below are examples of necessary vs. unnecessary tests:
22
23 ```rb
24 # GOOD!!
25 # Necessary test - in this case, we're testing critical domain business logic
26 test "syncs balances" do
27 Holding::Syncer.any_instance.expects(:sync_holdings).returns([]).once
28
29 @account.expects(:start_date).returns(2.days.ago.to_date)
30
31 Balance::ForwardCalculator.any_instance.expects(:calculate).returns(
32 [
33 Balance.new(date: 1.day.ago.to_date, balance: 1000, cash_balance: 1000, currency: "USD"),
34 Balance.new(date: Date.current, balance: 1000, cash_balance: 1000, currency: "USD")
35 ]
36 )
37
38 assert_difference "@account.balances.count", 2 do
39 Balance::Syncer.new(@account, strategy: :forward).sync_balances
40 end
41 end
42
43 # BAD!!
44 # Unnecessary test - in this case, this is simply testing ActiveRecord's functionality
45 test "saves balance" do
46 balance_record = Balance.new(balance: 100, currency: "USD")
47
48 assert balance_record.save
49 end
50 ```
51
52- **Test boundaries correctly**
53 - Distinguish between commands and query methods. Test output of query methods; test that commands were called with the correct params. See an example below:
54
55 ```rb
56 class ExampleClass
57 def do_something
58 result = 2 + 2
59
60 CustomEventProcessor.process_result(result)
61
62 result
63 end
64 end
65
66 class ExampleClass < ActiveSupport::TestCase
67 test "boundaries are tested correctly" do
68 result = ExampleClass.new.do_something
69
70 # GOOD - we're only testing that the command was received, not internal implementation details
71 # The actual tests for CustomEventProcessor belong in a different test suite!
72 CustomEventProcessor.expects(:process_result).with(4).once
73
74 # GOOD - we're testing the implementation of ExampleClass inside its own test suite
75 assert_equal 4, result
76 end
77 end
78 ```
79
80 - Never test the implementation details of one class in another classes test suite
81
82- **Stubs and mocks**
83 - Use `mocha` gem
84 - Always prefer `OpenStruct` when creating mock instances, or in complex cases, a mock class
85 - Only mock what's necessary. If you're not testing return values, don't mock a return value.
86
87
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−---
2−description:
3−globs: test/**
4−alwaysApply: false
5−---
6−Use this rule to learn how to write tests for the codebase.
1+# CLAUDE.md
72
8−Due to the open-source nature of this project, we have chosen Minitest + Fixtures for testing to maximize familiarity and predictability.
3+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
94
10−- **General testing rules**
11− - Always use Minitest and fixtures for testing, NEVER rspec or factories
12− - Keep fixtures to a minimum. Most models should have 2-3 fixtures maximum that represent the "base cases" for that model. "Edge cases" should be created on the fly, within the context of the test which it is needed.
13− - For tests that require a large number of fixture records to be created, use Rails helpers to help create the records needed for the test, then inline the creation. For example, [entries_test_helper.rb](mdc:test/support/entries_test_helper.rb) provides helpers to easily do this.
5+## Common Development Commands
146
15−- **Write minimal, effective tests**
16− - Use system tests sparingly as they increase the time to complete the test suite
17− - Only write tests for critical and important code paths
18− - Write tests as you go, when required
19− - Take a practical approach to testing. Tests are effective when their presence _significantly increases confidence in the codebase_.
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
2011
21− Below are examples of necessary vs. unnecessary 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
2218
23− ```rb
24− # GOOD!!
25− # Necessary test - in this case, we're testing critical domain business logic
26− test "syncs balances" do
27− Holding::Syncer.any_instance.expects(:sync_holdings).returns([]).once
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.
2821
29− @account.expects(:start_date).returns(2.days.ago.to_date)
22+```bash
23+DISABLE_PARALLELIZATION=true bin/rails test:system
24+```
3025
31− Balance::ForwardCalculator.any_instance.expects(:calculate).returns(
32− [
33− Balance.new(date: 1.day.ago.to_date, balance: 1000, cash_balance: 1000, currency: "USD"),
34− Balance.new(date: Date.current, balance: 1000, cash_balance: 1000, currency: "USD")
35− ]
36− )
26+To watch the browser live, open `http://localhost:7900` or `http://localhost:4444` in your host browser (password: `secret`).
3727
38− assert_difference "@account.balances.count", 2 do
39− Balance::Syncer.new(@account, strategy: :forward).sync_balances
40− end
41− end
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
4234
43− # BAD!!
44− # Unnecessary test - in this case, this is simply testing ActiveRecord's functionality
45− test "saves balance" do
46− balance_record = Balance.new(balance: 100, currency: "USD")
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− assert balance_record.save
49− end
50− ```
41+### Setup
42+- `bin/setup` - Initial project setup (installs dependencies, prepares database)
5143
52−- **Test boundaries correctly**
53− - Distinguish between commands and query methods. Test output of query methods; test that commands were called with the correct params. See an example below:
44+## Pre-Pull Request CI Workflow
5445
55− ```rb
56− class ExampleClass
57− def do_something
58− result = 2 + 2
46+ALWAYS run these commands before opening a pull request:
5947
60− CustomEventProcessor.process_result(result)
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)
6151
62− result
63− end
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
55+
56+3. **Security** (Required):
57+ - `bin/brakeman --no-pager` - Security analysis
58+
59+Only 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
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")
80+
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**
87+
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"]`).
126+- Plaid investments: investment transactions currently do not store pending metadata.
127+- Lunchflow: does not currently store pending metadata.
128+
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
64331 end
332+end
65333
66− class ExampleClass < ActiveSupport::TestCase
67− test "boundaries are tested correctly" do
68− result = ExampleClass.new.do_something
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+```
69340
70− # GOOD - we're only testing that the command was received, not internal implementation details
71− # The actual tests for CustomEventProcessor belong in a different test suite!
72− CustomEventProcessor.expects(:process_result).with(4).once
341+### Stubs and Mocks
342+- Use `mocha` gem
343+- Prefer `OpenStruct` for mock instances
344+- Only mock what's necessary
73345
74− # GOOD - we're testing the implementation of ExampleClass inside its own test suite
75− assert_equal 4, result
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
76372 end
77373 end
78− ```
374+end
375+```
79376
80− - Never test the implementation details of one class in another classes test suite
377+**Regenerate OpenAPI docs after changes:**
378+```bash
379+RAILS_ENV=test bundle exec rake rswag:specs:swaggerize
380+```
81381
82−- **Stubs and mocks**
83− - Use `mocha` gem
84− - Always prefer `OpenStruct` when creating mock instances, or in complex cases, a mock class
85− - Only mock what's necessary. If you're not testing return values, don't mock a return value.
382+### Post-commit API consistency (issue #944)
383+After every API endpoint commit, ensure:
86384
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.
87386
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`.
