{"owner":"we-promise","repo":"sure","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md","CLAUDE.md"],"skills":{"AGENTS.md":"# Repository Guidelines\n\n## Project Structure & Module Organization\n- Code: `app/` (Rails MVC, services, jobs, mailers, components), JS in `app/javascript/`, styles/assets in `app/assets/` (Tailwind, images, fonts).\n- Config: `config/`, environment examples in `.env.local.example` and `.env.test.example`.\n- Data: `db/` (migrations, seeds), fixtures in `test/fixtures/`.\n- Tests: `test/` mirroring `app/` (e.g., `test/models/*_test.rb`).\n- Tooling: `bin/` (project scripts), `docs/` (guides), `public/` (static), `lib/` (shared libs).\n\n## Build, Test, and Development Commands\n- Setup: `cp .env.local.example .env.local && bin/setup` — install deps, set DB, prepare app.\n- Run app: `bin/dev` — starts Rails server and asset/watchers via `Procfile.dev`.\n- Test suite: `bin/rails test` — run all Minitest tests; add `TEST=test/models/user_test.rb` to target a file.\n- Lint Ruby: `bin/rubocop` — style checks; add `-A` to auto-correct safe cops.\n- Lint/format JS/CSS: `npm run lint` and `npm run format` — uses Biome.\n- Security scan: `bin/brakeman` — static analysis for common Rails issues.\n\n## Coding Style & Naming Conventions\n- Ruby: 2-space indent, `snake_case` for methods/vars, `CamelCase` for classes/modules. Follow Rails conventions for folders and file names.\n- Views: ERB checked by `erb-lint` (see `.erb_lint.yml`). Avoid heavy logic in views; prefer helpers/components.\n- JavaScript: `lowerCamelCase` for vars/functions, `PascalCase` for classes/components. Let Biome format code.\n- Commit small, cohesive changes; keep diffs focused.\n\n## Testing Guidelines\n- Framework: Minitest (Rails). Name files `*_test.rb` and mirror `app/` structure.\n- Run: `bin/rails test` locally and ensure green before pushing.\n- Fixtures/VCR: Use `test/fixtures` and existing VCR cassettes for HTTP. Prefer unit tests plus focused integration tests.\n\n## Commit & Pull Request Guidelines\n- Commits: Imperative subject ≤ 72 chars (e.g., \"Add account balance validation\"). Include rationale in body and reference issues (`#123`).\n- 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.\n\n## Security & Configuration Tips\n- Never commit secrets. Start from `.env.local.example`; use `.env.local` for development only.\n- Run `bin/brakeman` before major PRs. Prefer environment variables over hard-coded values.\n\n## API Development Guidelines\n\n### OpenAPI Documentation (MANDATORY)\nWhen adding or modifying API endpoints in `app/controllers/api/v1/`, you **MUST** create or update corresponding OpenAPI request specs for **DOCUMENTATION ONLY**:\n\n1. **Location**: `spec/requests/api/v1/{resource}_spec.rb`\n2. **Framework**: RSpec with rswag for OpenAPI generation\n3. **Schemas**: Define reusable schemas in `spec/swagger_helper.rb`\n4. **Generated Docs**: `docs/api/openapi.yaml`\n5. **Regenerate**: Run `RAILS_ENV=test bundle exec rake rswag:specs:swaggerize` after changes\n\n### Post-commit API consistency (LLM checklist)\nAfter 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).\n\n## Design System Hygiene (UI PRs)\n\nWhen a PR touches `.erb`, view components, or `.css`:\n\n1. **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).\n2. **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.\n3. **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.\n4. **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.\n\nReviewers escalate violations of (2)–(3) to close/rewrite; (1) and (4) are request-changes.\n\n## Securities Providers\n\nIf 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.\n\n## Debug Logging for Provider Syncs\n\nWhen 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.*`.\n\n- Record support-relevant diagnostics in the debug log so they surface in the super-admin-friendly `/settings/debug` UI.\n- Include `category`, `level`, `message`, `source`, `provider_key`, and useful structured `metadata`.\n- Attach `family` and `account_provider` when available so support can filter and trace the affected connection.\n- Reserve raw Rails logging for low-value local noise; anything operators may need should go to the debug log.\n\n## Providers: Pending Transactions and FX Metadata (SimpleFIN/Plaid/Lunchflow)\n\n- Pending detection\n  - SimpleFIN: pending when provider sends `pending: true`, or when `posted` is blank/0 and `transacted_at` is present.\n  - Plaid: pending when Plaid sends `pending: true` (stored at `transaction.extra[\"plaid\"][\"pending\"]` for bank/credit transactions imported via `PlaidEntry::Processor`).\n  - Lunchflow: pending when API returns `isPending: true` in transaction response (stored at `transaction.extra[\"lunchflow\"][\"pending\"]`).\n- Storage (extras)\n  - Provider metadata lives on `Transaction#extra`, namespaced (e.g., `extra[\"simplefin\"][\"pending\"]`).\n  - SimpleFIN FX: `extra[\"simplefin\"][\"fx_from\"]`, `extra[\"simplefin\"][\"fx_date\"]`.\n- UI\n  - Shows a small “Pending” badge when `transaction.pending?` is true.\n- Variability\n  - Some providers don’t expose pendings; in that case nothing is shown.\n- Configuration (default-off)\n  - SimpleFIN runtime toggles live in `config/initializers/simplefin.rb` via `Rails.configuration.x.simplefin.*`.\n  - Lunchflow runtime toggles live in `config/initializers/lunchflow.rb` via `Rails.configuration.x.lunchflow.*`.\n  - ENV-backed keys:\n    - `SIMPLEFIN_INCLUDE_PENDING=1` (forces `pending=1` on SimpleFIN fetches when caller didn’t specify a `pending:` arg)\n    - `SIMPLEFIN_DEBUG_RAW=1` (logs raw payload returned by SimpleFIN)\n    - `LUNCHFLOW_INCLUDE_PENDING=1` (forces `include_pending=true` on Lunchflow API requests)\n    - `LUNCHFLOW_DEBUG_RAW=1` (logs raw payload returned by Lunchflow)\n\n### Provider support notes\n\n- SimpleFIN: supports pending + FX metadata; stored under `extra[\"simplefin\"]`.\n- Plaid: supports pending when the upstream Plaid payload includes `pending: true`; stored under `extra[\"plaid\"]`.\n- Plaid investments: investment transactions currently do not store pending metadata.\n- Lunchflow: supports pending via `include_pending` query parameter; stored under `extra[\"lunchflow\"]`.\n- Manual/CSV imports: no pending concept.\n","CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Common Development Commands\n\n### Development Server\n- `bin/dev` - Start development server (Rails, Sidekiq, Tailwind CSS watcher)\n- `bin/rails server` - Start Rails server only\n- `bin/rails console` - Open Rails console\n\n### Testing\n- `bin/rails test` - Run all tests\n- `bin/rails test:db` - Run tests with database reset\n- `DISABLE_PARALLELIZATION=true bin/rails test:system` - Run system tests only (use sparingly - they take longer)\n- `bin/rails test test/models/account_test.rb` - Run specific test file\n- `bin/rails test test/models/account_test.rb:42` - Run specific test at line\n\n#### System Tests in the Dev Container\nWhen 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.\n\n```bash\nDISABLE_PARALLELIZATION=true bin/rails test:system\n```\n\nTo watch the browser live, open `http://localhost:7900` or `http://localhost:4444` in your host browser (password: `secret`).\n\n### Linting & Formatting\n- `bin/rubocop` - Run Ruby linter\n- `npm run lint` - Check JavaScript/TypeScript code\n- `npm run lint:fix` - Fix JavaScript/TypeScript issues\n- `npm run format` - Format JavaScript/TypeScript code\n- `bin/brakeman` - Run security analysis\n\n### Database\n- `bin/rails db:prepare` - Create and migrate database\n- `bin/rails db:migrate` - Run pending migrations\n- `bin/rails db:rollback` - Rollback last migration\n- `bin/rails db:seed` - Load seed data\n\n### Setup\n- `bin/setup` - Initial project setup (installs dependencies, prepares database)\n\n## Pre-Pull Request CI Workflow\n\nALWAYS run these commands before opening a pull request:\n\n1. **Tests** (Required):\n   - `bin/rails test` - Run all tests (always required)\n   - `DISABLE_PARALLELIZATION=true bin/rails test:system` - Run system tests (only when applicable, they take longer)\n\n2. **Linting** (Required):\n   - `bin/rubocop -f github -a` - Ruby linting with auto-correct\n   - `bundle exec erb_lint ./app/**/*.erb -a` - ERB linting with auto-correct\n\n3. **Security** (Required):\n   - `bin/brakeman --no-pager` - Security analysis\n\nOnly proceed with pull request creation if ALL checks pass.\n\n## General Development Rules\n\n### Authentication Context\n- Use `Current.user` for the current user. Do NOT use `current_user`.\n- Use `Current.family` for the current family. Do NOT use `current_family`.\n\n### Development Guidelines\n- Carefully read project conventions and guidelines before generating any code.\n- Do not run `rails server` in your responses\n- Do not run `touch tmp/restart.txt`\n- Do not run `rails credentials`\n- Do not automatically run migrations\n\n## High-Level Architecture\n\n### Application Modes\nThe codebase runs in two distinct modes:\n- **Managed**: A team operates and manages servers for users (Rails.application.config.app_mode = \"managed\")\n- **Self Hosted**: Users host the codebase on their own infrastructure, typically through Docker Compose (Rails.application.config.app_mode = \"self_hosted\")\n\n### Core Domain Model\nThe application is built around financial data management with these key relationships:\n- **User** → has many **Accounts** → has many **Transactions**\n- **Account** types: checking, savings, credit cards, investments, crypto, loans, properties\n- **Transaction** → belongs to **Category**, can have **Tags** and **Rules**\n- **Investment accounts** → have **Holdings** → track **Securities** via **Trades**\n\n### API Architecture\nThe application provides both internal and external APIs:\n- Internal API: Controllers serve JSON via Turbo for SPA-like interactions\n- External API: `/api/v1/` namespace with Doorkeeper OAuth and API key authentication\n- API responses use Jbuilder templates for JSON rendering\n- Rate limiting via Rack Attack with configurable limits per API key\n- **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.\n\n### Sync & Import System\nTwo primary data ingestion methods:\n1. **Plaid Integration**: Real-time bank account syncing\n   - `PlaidItem` manages connections\n   - `Sync` tracks sync operations\n   - Background jobs handle data updates\n2. **CSV Import**: Manual data import with mapping\n   - `Import` manages import sessions\n   - Supports transaction and balance imports\n   - Custom field mapping with transformation rules\n\n### Provider Integrations: Pending Transactions and FX (SimpleFIN/Plaid)\n\n- Detection\n  - SimpleFIN: pending via `pending: true` or `posted` blank/0 + `transacted_at`.\n  - Plaid: pending via Plaid `pending: true` (stored at `extra[\"plaid\"][\"pending\"]` for bank/credit transactions imported via `PlaidEntry::Processor`).\n- Storage: provider data on `Transaction#extra` (e.g., `extra[\"simplefin\"][\"pending\"]`; FX uses `fx_from`, `fx_date`).\n- UI: \"Pending\" badge when `transaction.pending?` is true; no badge if provider omits pendings.\n- Configuration (default-on for pending)\n  - SimpleFIN: `config/initializers/simplefin.rb` via `Rails.configuration.x.simplefin.*`.\n  - Plaid: `config/initializers/plaid_config.rb` via `Rails.configuration.x.plaid.*`.\n  - Pending transactions are fetched by default and handled via reconciliation/filtering.\n  - Set `SIMPLEFIN_INCLUDE_PENDING=0` to disable pending fetching for SimpleFIN.\n  - Set `PLAID_INCLUDE_PENDING=0` to disable pending fetching for Plaid.\n  - Set `SIMPLEFIN_DEBUG_RAW=1` to enable raw payload debug logging.\n  - 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.\n\nProvider support notes:\n- SimpleFIN: supports pending + FX metadata (stored under `extra[\"simplefin\"]`).\n- Plaid: supports pending when the upstream Plaid payload includes `pending: true` (stored under `extra[\"plaid\"]`).\n- Plaid investments: investment transactions currently do not store pending metadata.\n- Lunchflow: does not currently store pending metadata.\n\n### Background Processing\nSidekiq handles asynchronous tasks:\n- Account syncing (`SyncJob`)\n- Import processing (`ImportJob`)\n- AI chat responses (`AssistantResponseJob`)\n- Scheduled maintenance via sidekiq-cron\n\n### Debug Logging for Provider Syncs\n- Prefer `DebugLogEntry.capture(...)` over `Rails.logger.*` for provider sync/import failures, partial responses, and other support-relevant diagnostics.\n- Record support-relevant incidents in the super-admin `/settings/debug` UI rather than leaving them only in raw application logs.\n- Include `category`, `level`, `message`, `source`, `provider_key`, and structured `metadata`.\n- Attach `family` and `account_provider` whenever possible so support can filter to the affected provider connection.\n\n### Frontend Architecture\n- **Hotwire Stack**: Turbo + Stimulus for reactive UI without heavy JavaScript\n- **ViewComponents**: Reusable UI components in `app/components/`\n- **Stimulus Controllers**: Handle interactivity, organized alongside components\n- **Charts**: D3.js for financial visualizations (time series, donut, sankey)\n- **Styling**: Tailwind CSS v4.x with custom design system\n  - Design system defined in `app/assets/tailwind/sure-design-system.css`\n  - Always use functional tokens (e.g., `text-primary` not `text-white`)\n  - Prefer semantic HTML elements over JS components\n  - Use `icon` helper for icons, never `lucide_icon` directly\n- **i18n**: All user-facing strings must use localization (i18n). Update locale files for each new or changed element.\n\n### Internationalization (i18n) Guidelines\n- **Key Organization**: Use hierarchical keys by feature: `accounts.index.title`, `transactions.form.amount_label`\n- **Translation Helper**: Always use `t()` helper for user-facing strings\n- **Interpolation**: Use for dynamic content: `t(\"users.greeting\", name: user.name)`\n- **Pluralization**: Use Rails pluralization: `t(\"transactions.count\", count: @transactions.count)`\n- **Locale Files**: Update `config/locales/en.yml` for new strings\n- **Missing Translations**: Configure to raise errors in development for missing keys\n\n### Multi-Currency Support\n- All monetary values stored in base currency (user's primary currency)\n- `Money` objects handle currency conversion and formatting\n- Historical exchange rates for accurate reporting\n\n### Security & Authentication\n- Session-based auth for web users\n- API authentication via:\n  - OAuth2 (Doorkeeper) for third-party apps\n  - API keys with JWT tokens for direct API access\n- Scoped permissions system for API access\n- Strong parameters and CSRF protection throughout\n\n### Testing Philosophy\n- Comprehensive test coverage using Rails' built-in Minitest\n- Fixtures for test data (avoid FactoryBot)\n- Keep fixtures minimal (2-3 per model for base cases)\n- VCR for external API testing\n- System tests for critical user flows (use sparingly)\n- Test helpers in `test/support/` for common scenarios\n- Only test critical code paths that significantly increase confidence\n- Write tests as you go, when required\n- **API Endpoints require OpenAPI specs** in `spec/requests/api/` for documentation purposes ONLY, not test (uses RSpec + rswag)\n\n### Performance Considerations\n- Database queries optimized with proper indexes\n- N+1 queries prevented via includes/joins\n- Background jobs for heavy operations\n- Caching strategies for expensive calculations\n- Turbo Frames for partial page updates\n\n### Development Workflow\n- Feature branches merged to `main`\n- Docker support for consistent environments\n- Environment variables via `.env` files\n- Lookbook for component development (`/lookbook`)\n- Letter Opener for email preview in development\n\n## Project Conventions\n\n### Convention 1: Minimize Dependencies\n- Push Rails to its limits before adding new dependencies\n- Strong technical/business reason required for new dependencies\n- Favor old and reliable over new and flashy\n\n### Convention 2: Skinny Controllers, Fat Models\n- Business logic in `app/models/` folder, avoid `app/services/`\n- Use Rails concerns and POROs for organization\n- Models should answer questions about themselves: `account.balance_series` not `AccountSeries.new(account).call`\n\n### Convention 3: Hotwire-First Frontend\n- **Native HTML preferred over JS components**\n  - Use `<dialog>` for modals, `<details><summary>` for disclosures\n- **Leverage Turbo frames** for page sections over client-side solutions\n- **Query params for state** over localStorage/sessions\n- **Server-side formatting** for currencies, numbers, dates\n- **Always use `icon` helper** in `application_helper.rb`, NEVER `lucide_icon` directly\n\n### Convention 4: Optimize for Simplicity\n- Prioritize good OOP domain design over performance\n- Focus performance only on critical/global areas (avoid N+1 queries, mindful of global layouts)\n\n### Convention 5: Database vs ActiveRecord Validations\n- Simple validations (null checks, unique indexes) in DB\n- ActiveRecord validations for convenience in forms (prefer client-side when possible)\n- Complex validations and business logic in ActiveRecord\n\n## TailwindCSS Design System\n\n### Design System Rules\n- **Always reference `app/assets/tailwind/sure-design-system.css`** for primitives and tokens\n- **Use functional tokens** defined in design system:\n  - `text-primary` instead of `text-white`\n  - `bg-container` instead of `bg-white`\n  - `border border-primary` instead of `border border-gray-200`\n- **NEVER create new styles** in design system files without permission\n- **Always generate semantic HTML**\n\n## Component Architecture\n\n### ViewComponent vs Partials Decision Making\n\n**Use ViewComponents when:**\n- Element has complex logic or styling patterns\n- Element will be reused across multiple views/contexts\n- Element needs structured styling with variants/sizes\n- Element requires interactive behavior or Stimulus controllers\n- Element has configurable slots or complex APIs\n- Element needs accessibility features or ARIA support\n\n**Use Partials when:**\n- Element is primarily static HTML with minimal logic\n- Element is used in only one or few specific contexts\n- Element is simple template content\n- Element doesn't need variants, sizes, or complex configuration\n- Element is more about content organization than reusable functionality\n\n**Component Guidelines:**\n- Prefer components over partials when available\n- Keep domain logic OUT of view templates\n- Logic belongs in component files, not template files\n\n### Stimulus Controller Guidelines\n\n**Declarative Actions (Required):**\n```erb\n<!-- GOOD: Declarative - HTML declares what happens -->\n<div data-controller=\"toggle\">\n  <button data-action=\"click->toggle#toggle\" data-toggle-target=\"button\">\n    <%= t(\"components.transaction_details.show_details\") %>\n  </button>\n  <div data-toggle-target=\"content\" class=\"hidden\">\n    <p><%= t(\"components.transaction_details.amount_label\") %>: <%= @transaction.amount %></p>\n    <p><%= t(\"components.transaction_details.date_label\") %>: <%= @transaction.date %></p>\n    <p><%= t(\"components.transaction_details.category_label\") %>: <%= @transaction.category.name %></p>\n  </div>\n</div>\n```\n\n**Example locale file structure (config/locales/en.yml):**\n```yaml\nen:\n  components:\n    transaction_details:\n      show_details: \"Show Details\"\n      hide_details: \"Hide Details\"\n      amount_label: \"Amount\"\n      date_label: \"Date\"\n      category_label: \"Category\"\n```\n\n**i18n Best Practices:**\n- Organize keys by feature/component: `components.transaction_details.show_details`\n- Use descriptive key names that indicate purpose: `show_details` not `button`\n- Group related translations together in the same namespace\n- Use interpolation for dynamic content: `t(\"users.welcome\", name: user.name)`\n- Always update locale files when adding new user-facing strings\n\n**Controller Best Practices:**\n- Keep controllers lightweight and simple (< 7 targets)\n- Use private methods and expose clear public API\n- Single responsibility or highly related responsibilities\n- Component controllers stay in component directory, global controllers in `app/javascript/controllers/`\n- Pass data via `data-*-value` attributes, not inline JavaScript\n\n## Testing Philosophy\n\n### General Testing Rules\n- **ALWAYS use Minitest + fixtures** (NEVER RSpec or factories)\n- Keep fixtures minimal (2-3 per model for base cases)\n- Create edge cases on-the-fly within test context\n- Use Rails helpers for large fixture creation needs\n\n### Test Quality Guidelines\n- **Write minimal, effective tests** - system tests sparingly\n- **Only test critical and important code paths**\n- **Test boundaries correctly:**\n  - Commands: test they were called with correct params\n  - Queries: test output\n  - Don't test implementation details of other classes\n\n### Testing Examples\n\n```ruby\n# GOOD - Testing critical domain business logic\ntest \"syncs balances\" do\n  Holding::Syncer.any_instance.expects(:sync_holdings).returns([]).once\n  assert_difference \"@account.balances.count\", 2 do\n    Balance::Syncer.new(@account, strategy: :forward).sync_balances\n  end\nend\n\n# BAD - Testing ActiveRecord functionality\ntest \"saves balance\" do \n  balance_record = Balance.new(balance: 100, currency: \"USD\")\n  assert balance_record.save\nend\n```\n\n### Stubs and Mocks\n- Use `mocha` gem\n- Prefer `OpenStruct` for mock instances\n- Only mock what's necessary\n\n## API Development Guidelines\n\n### OpenAPI Documentation (MANDATORY)\nWhen adding or modifying API endpoints in `app/controllers/api/v1/`, you **MUST** create or update corresponding OpenAPI request specs:\n\n1. **Location**: `spec/requests/api/v1/{resource}_spec.rb`\n2. **Framework**: RSpec with rswag for OpenAPI generation\n3. **Schemas**: Define reusable schemas in `spec/swagger_helper.rb`\n4. **Generated Docs**: `docs/api/openapi.yaml`\n\n**Example structure for a new API endpoint:**\n```ruby\n# spec/requests/api/v1/widgets_spec.rb\nrequire 'swagger_helper'\n\nRSpec.describe 'API V1 Widgets', type: :request do\n  path '/api/v1/widgets' do\n    get 'List widgets' do\n      tags 'Widgets'\n      security [ { apiKeyAuth: [] } ]\n      produces 'application/json'\n      \n      response '200', 'widgets listed' do\n        schema '$ref' => '#/components/schemas/WidgetCollection'\n        run_test!\n      end\n    end\n  end\nend\n```\n\n**Regenerate OpenAPI docs after changes:**\n```bash\nRAILS_ENV=test bundle exec rake rswag:specs:swaggerize\n```\n\n### Post-commit API consistency (issue #944)\nAfter every API endpoint commit, ensure:\n\n1. **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.\n\n2. **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`.\n\n3. **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.\n\nFull checklist and pattern: [.cursor/rules/api-endpoint-consistency.mdc](.cursor/rules/api-endpoint-consistency.mdc).\n\nTo 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`."},"files":{"AGENTS.md":"# Repository Guidelines\n\n## Project Structure & Module Organization\n- Code: `app/` (Rails MVC, services, jobs, mailers, components), JS in `app/javascript/`, styles/assets in `app/assets/` (Tailwind, images, fonts).\n- Config: `config/`, environment examples in `.env.local.example` and `.env.test.example`.\n- Data: `db/` (migrations, seeds), fixtures in `test/fixtures/`.\n- Tests: `test/` mirroring `app/` (e.g., `test/models/*_test.rb`).\n- Tooling: `bin/` (project scripts), `docs/` (guides), `public/` (static), `lib/` (shared libs).\n\n## Build, Test, and Development Commands\n- Setup: `cp .env.local.example .env.local && bin/setup` — install deps, set DB, prepare app.\n- Run app: `bin/dev` — starts Rails server and asset/watchers via `Procfile.dev`.\n- Test suite: `bin/rails test` — run all Minitest tests; add `TEST=test/models/user_test.rb` to target a file.\n- Lint Ruby: `bin/rubocop` — style checks; add `-A` to auto-correct safe cops.\n- Lint/format JS/CSS: `npm run lint` and `npm run format` — uses Biome.\n- Security scan: `bin/brakeman` — static analysis for common Rails issues.\n\n## Coding Style & Naming Conventions\n- Ruby: 2-space indent, `snake_case` for methods/vars, `CamelCase` for classes/modules. Follow Rails conventions for folders and file names.\n- Views: ERB checked by `erb-lint` (see `.erb_lint.yml`). Avoid heavy logic in views; prefer helpers/components.\n- JavaScript: `lowerCamelCase` for vars/functions, `PascalCase` for classes/components. Let Biome format code.\n- Commit small, cohesive changes; keep diffs focused.\n\n## Testing Guidelines\n- Framework: Minitest (Rails). Name files `*_test.rb` and mirror `app/` structure.\n- Run: `bin/rails test` locally and ensure green before pushing.\n- Fixtures/VCR: Use `test/fixtures` and existing VCR cassettes for HTTP. Prefer unit tests plus focused integration tests.\n\n## Commit & Pull Request Guidelines\n- Commits: Imperative subject ≤ 72 chars (e.g., \"Add account balance validation\"). Include rationale in body and reference issues (`#123`).\n- 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.\n\n## Security & Configuration Tips\n- Never commit secrets. Start from `.env.local.example`; use `.env.local` for development only.\n- Run `bin/brakeman` before major PRs. Prefer environment variables over hard-coded values.\n\n## API Development Guidelines\n\n### OpenAPI Documentation (MANDATORY)\nWhen adding or modifying API endpoints in `app/controllers/api/v1/`, you **MUST** create or update corresponding OpenAPI request specs for **DOCUMENTATION ONLY**:\n\n1. **Location**: `spec/requests/api/v1/{resource}_spec.rb`\n2. **Framework**: RSpec with rswag for OpenAPI generation\n3. **Schemas**: Define reusable schemas in `spec/swagger_helper.rb`\n4. **Generated Docs**: `docs/api/openapi.yaml`\n5. **Regenerate**: Run `RAILS_ENV=test bundle exec rake rswag:specs:swaggerize` after changes\n\n### Post-commit API consistency (LLM checklist)\nAfter 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).\n\n## Design System Hygiene (UI PRs)\n\nWhen a PR touches `.erb`, view components, or `.css`:\n\n1. **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).\n2. **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.\n3. **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.\n4. **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.\n\nReviewers escalate violations of (2)–(3) to close/rewrite; (1) and (4) are request-changes.\n\n## Securities Providers\n\nIf 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.\n\n## Debug Logging for Provider Syncs\n\nWhen 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.*`.\n\n- Record support-relevant diagnostics in the debug log so they surface in the super-admin-friendly `/settings/debug` UI.\n- Include `category`, `level`, `message`, `source`, `provider_key`, and useful structured `metadata`.\n- Attach `family` and `account_provider` when available so support can filter and trace the affected connection.\n- Reserve raw Rails logging for low-value local noise; anything operators may need should go to the debug log.\n\n## Providers: Pending Transactions and FX Metadata (SimpleFIN/Plaid/Lunchflow)\n\n- Pending detection\n  - SimpleFIN: pending when provider sends `pending: true`, or when `posted` is blank/0 and `transacted_at` is present.\n  - Plaid: pending when Plaid sends `pending: true` (stored at `transaction.extra[\"plaid\"][\"pending\"]` for bank/credit transactions imported via `PlaidEntry::Processor`).\n  - Lunchflow: pending when API returns `isPending: true` in transaction response (stored at `transaction.extra[\"lunchflow\"][\"pending\"]`).\n- Storage (extras)\n  - Provider metadata lives on `Transaction#extra`, namespaced (e.g., `extra[\"simplefin\"][\"pending\"]`).\n  - SimpleFIN FX: `extra[\"simplefin\"][\"fx_from\"]`, `extra[\"simplefin\"][\"fx_date\"]`.\n- UI\n  - Shows a small “Pending” badge when `transaction.pending?` is true.\n- Variability\n  - Some providers don’t expose pendings; in that case nothing is shown.\n- Configuration (default-off)\n  - SimpleFIN runtime toggles live in `config/initializers/simplefin.rb` via `Rails.configuration.x.simplefin.*`.\n  - Lunchflow runtime toggles live in `config/initializers/lunchflow.rb` via `Rails.configuration.x.lunchflow.*`.\n  - ENV-backed keys:\n    - `SIMPLEFIN_INCLUDE_PENDING=1` (forces `pending=1` on SimpleFIN fetches when caller didn’t specify a `pending:` arg)\n    - `SIMPLEFIN_DEBUG_RAW=1` (logs raw payload returned by SimpleFIN)\n    - `LUNCHFLOW_INCLUDE_PENDING=1` (forces `include_pending=true` on Lunchflow API requests)\n    - `LUNCHFLOW_DEBUG_RAW=1` (logs raw payload returned by Lunchflow)\n\n### Provider support notes\n\n- SimpleFIN: supports pending + FX metadata; stored under `extra[\"simplefin\"]`.\n- Plaid: supports pending when the upstream Plaid payload includes `pending: true`; stored under `extra[\"plaid\"]`.\n- Plaid investments: investment transactions currently do not store pending metadata.\n- Lunchflow: supports pending via `include_pending` query parameter; stored under `extra[\"lunchflow\"]`.\n- Manual/CSV imports: no pending concept.\n","CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Common Development Commands\n\n### Development Server\n- `bin/dev` - Start development server (Rails, Sidekiq, Tailwind CSS watcher)\n- `bin/rails server` - Start Rails server only\n- `bin/rails console` - Open Rails console\n\n### Testing\n- `bin/rails test` - Run all tests\n- `bin/rails test:db` - Run tests with database reset\n- `DISABLE_PARALLELIZATION=true bin/rails test:system` - Run system tests only (use sparingly - they take longer)\n- `bin/rails test test/models/account_test.rb` - Run specific test file\n- `bin/rails test test/models/account_test.rb:42` - Run specific test at line\n\n#### System Tests in the Dev Container\nWhen 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.\n\n```bash\nDISABLE_PARALLELIZATION=true bin/rails test:system\n```\n\nTo watch the browser live, open `http://localhost:7900` or `http://localhost:4444` in your host browser (password: `secret`).\n\n### Linting & Formatting\n- `bin/rubocop` - Run Ruby linter\n- `npm run lint` - Check JavaScript/TypeScript code\n- `npm run lint:fix` - Fix JavaScript/TypeScript issues\n- `npm run format` - Format JavaScript/TypeScript code\n- `bin/brakeman` - Run security analysis\n\n### Database\n- `bin/rails db:prepare` - Create and migrate database\n- `bin/rails db:migrate` - Run pending migrations\n- `bin/rails db:rollback` - Rollback last migration\n- `bin/rails db:seed` - Load seed data\n\n### Setup\n- `bin/setup` - Initial project setup (installs dependencies, prepares database)\n\n## Pre-Pull Request CI Workflow\n\nALWAYS run these commands before opening a pull request:\n\n1. **Tests** (Required):\n   - `bin/rails test` - Run all tests (always required)\n   - `DISABLE_PARALLELIZATION=true bin/rails test:system` - Run system tests (only when applicable, they take longer)\n\n2. **Linting** (Required):\n   - `bin/rubocop -f github -a` - Ruby linting with auto-correct\n   - `bundle exec erb_lint ./app/**/*.erb -a` - ERB linting with auto-correct\n\n3. **Security** (Required):\n   - `bin/brakeman --no-pager` - Security analysis\n\nOnly proceed with pull request creation if ALL checks pass.\n\n## General Development Rules\n\n### Authentication Context\n- Use `Current.user` for the current user. Do NOT use `current_user`.\n- Use `Current.family` for the current family. Do NOT use `current_family`.\n\n### Development Guidelines\n- Carefully read project conventions and guidelines before generating any code.\n- Do not run `rails server` in your responses\n- Do not run `touch tmp/restart.txt`\n- Do not run `rails credentials`\n- Do not automatically run migrations\n\n## High-Level Architecture\n\n### Application Modes\nThe codebase runs in two distinct modes:\n- **Managed**: A team operates and manages servers for users (Rails.application.config.app_mode = \"managed\")\n- **Self Hosted**: Users host the codebase on their own infrastructure, typically through Docker Compose (Rails.application.config.app_mode = \"self_hosted\")\n\n### Core Domain Model\nThe application is built around financial data management with these key relationships:\n- **User** → has many **Accounts** → has many **Transactions**\n- **Account** types: checking, savings, credit cards, investments, crypto, loans, properties\n- **Transaction** → belongs to **Category**, can have **Tags** and **Rules**\n- **Investment accounts** → have **Holdings** → track **Securities** via **Trades**\n\n### API Architecture\nThe application provides both internal and external APIs:\n- Internal API: Controllers serve JSON via Turbo for SPA-like interactions\n- External API: `/api/v1/` namespace with Doorkeeper OAuth and API key authentication\n- API responses use Jbuilder templates for JSON rendering\n- Rate limiting via Rack Attack with configurable limits per API key\n- **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.\n\n### Sync & Import System\nTwo primary data ingestion methods:\n1. **Plaid Integration**: Real-time bank account syncing\n   - `PlaidItem` manages connections\n   - `Sync` tracks sync operations\n   - Background jobs handle data updates\n2. **CSV Import**: Manual data import with mapping\n   - `Import` manages import sessions\n   - Supports transaction and balance imports\n   - Custom field mapping with transformation rules\n\n### Provider Integrations: Pending Transactions and FX (SimpleFIN/Plaid)\n\n- Detection\n  - SimpleFIN: pending via `pending: true` or `posted` blank/0 + `transacted_at`.\n  - Plaid: pending via Plaid `pending: true` (stored at `extra[\"plaid\"][\"pending\"]` for bank/credit transactions imported via `PlaidEntry::Processor`).\n- Storage: provider data on `Transaction#extra` (e.g., `extra[\"simplefin\"][\"pending\"]`; FX uses `fx_from`, `fx_date`).\n- UI: \"Pending\" badge when `transaction.pending?` is true; no badge if provider omits pendings.\n- Configuration (default-on for pending)\n  - SimpleFIN: `config/initializers/simplefin.rb` via `Rails.configuration.x.simplefin.*`.\n  - Plaid: `config/initializers/plaid_config.rb` via `Rails.configuration.x.plaid.*`.\n  - Pending transactions are fetched by default and handled via reconciliation/filtering.\n  - Set `SIMPLEFIN_INCLUDE_PENDING=0` to disable pending fetching for SimpleFIN.\n  - Set `PLAID_INCLUDE_PENDING=0` to disable pending fetching for Plaid.\n  - Set `SIMPLEFIN_DEBUG_RAW=1` to enable raw payload debug logging.\n  - 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.\n\nProvider support notes:\n- SimpleFIN: supports pending + FX metadata (stored under `extra[\"simplefin\"]`).\n- Plaid: supports pending when the upstream Plaid payload includes `pending: true` (stored under `extra[\"plaid\"]`).\n- Plaid investments: investment transactions currently do not store pending metadata.\n- Lunchflow: does not currently store pending metadata.\n\n### Background Processing\nSidekiq handles asynchronous tasks:\n- Account syncing (`SyncJob`)\n- Import processing (`ImportJob`)\n- AI chat responses (`AssistantResponseJob`)\n- Scheduled maintenance via sidekiq-cron\n\n### Debug Logging for Provider Syncs\n- Prefer `DebugLogEntry.capture(...)` over `Rails.logger.*` for provider sync/import failures, partial responses, and other support-relevant diagnostics.\n- Record support-relevant incidents in the super-admin `/settings/debug` UI rather than leaving them only in raw application logs.\n- Include `category`, `level`, `message`, `source`, `provider_key`, and structured `metadata`.\n- Attach `family` and `account_provider` whenever possible so support can filter to the affected provider connection.\n\n### Frontend Architecture\n- **Hotwire Stack**: Turbo + Stimulus for reactive UI without heavy JavaScript\n- **ViewComponents**: Reusable UI components in `app/components/`\n- **Stimulus Controllers**: Handle interactivity, organized alongside components\n- **Charts**: D3.js for financial visualizations (time series, donut, sankey)\n- **Styling**: Tailwind CSS v4.x with custom design system\n  - Design system defined in `app/assets/tailwind/sure-design-system.css`\n  - Always use functional tokens (e.g., `text-primary` not `text-white`)\n  - Prefer semantic HTML elements over JS components\n  - Use `icon` helper for icons, never `lucide_icon` directly\n- **i18n**: All user-facing strings must use localization (i18n). Update locale files for each new or changed element.\n\n### Internationalization (i18n) Guidelines\n- **Key Organization**: Use hierarchical keys by feature: `accounts.index.title`, `transactions.form.amount_label`\n- **Translation Helper**: Always use `t()` helper for user-facing strings\n- **Interpolation**: Use for dynamic content: `t(\"users.greeting\", name: user.name)`\n- **Pluralization**: Use Rails pluralization: `t(\"transactions.count\", count: @transactions.count)`\n- **Locale Files**: Update `config/locales/en.yml` for new strings\n- **Missing Translations**: Configure to raise errors in development for missing keys\n\n### Multi-Currency Support\n- All monetary values stored in base currency (user's primary currency)\n- `Money` objects handle currency conversion and formatting\n- Historical exchange rates for accurate reporting\n\n### Security & Authentication\n- Session-based auth for web users\n- API authentication via:\n  - OAuth2 (Doorkeeper) for third-party apps\n  - API keys with JWT tokens for direct API access\n- Scoped permissions system for API access\n- Strong parameters and CSRF protection throughout\n\n### Testing Philosophy\n- Comprehensive test coverage using Rails' built-in Minitest\n- Fixtures for test data (avoid FactoryBot)\n- Keep fixtures minimal (2-3 per model for base cases)\n- VCR for external API testing\n- System tests for critical user flows (use sparingly)\n- Test helpers in `test/support/` for common scenarios\n- Only test critical code paths that significantly increase confidence\n- Write tests as you go, when required\n- **API Endpoints require OpenAPI specs** in `spec/requests/api/` for documentation purposes ONLY, not test (uses RSpec + rswag)\n\n### Performance Considerations\n- Database queries optimized with proper indexes\n- N+1 queries prevented via includes/joins\n- Background jobs for heavy operations\n- Caching strategies for expensive calculations\n- Turbo Frames for partial page updates\n\n### Development Workflow\n- Feature branches merged to `main`\n- Docker support for consistent environments\n- Environment variables via `.env` files\n- Lookbook for component development (`/lookbook`)\n- Letter Opener for email preview in development\n\n## Project Conventions\n\n### Convention 1: Minimize Dependencies\n- Push Rails to its limits before adding new dependencies\n- Strong technical/business reason required for new dependencies\n- Favor old and reliable over new and flashy\n\n### Convention 2: Skinny Controllers, Fat Models\n- Business logic in `app/models/` folder, avoid `app/services/`\n- Use Rails concerns and POROs for organization\n- Models should answer questions about themselves: `account.balance_series` not `AccountSeries.new(account).call`\n\n### Convention 3: Hotwire-First Frontend\n- **Native HTML preferred over JS components**\n  - Use `<dialog>` for modals, `<details><summary>` for disclosures\n- **Leverage Turbo frames** for page sections over client-side solutions\n- **Query params for state** over localStorage/sessions\n- **Server-side formatting** for currencies, numbers, dates\n- **Always use `icon` helper** in `application_helper.rb`, NEVER `lucide_icon` directly\n\n### Convention 4: Optimize for Simplicity\n- Prioritize good OOP domain design over performance\n- Focus performance only on critical/global areas (avoid N+1 queries, mindful of global layouts)\n\n### Convention 5: Database vs ActiveRecord Validations\n- Simple validations (null checks, unique indexes) in DB\n- ActiveRecord validations for convenience in forms (prefer client-side when possible)\n- Complex validations and business logic in ActiveRecord\n\n## TailwindCSS Design System\n\n### Design System Rules\n- **Always reference `app/assets/tailwind/sure-design-system.css`** for primitives and tokens\n- **Use functional tokens** defined in design system:\n  - `text-primary` instead of `text-white`\n  - `bg-container` instead of `bg-white`\n  - `border border-primary` instead of `border border-gray-200`\n- **NEVER create new styles** in design system files without permission\n- **Always generate semantic HTML**\n\n## Component Architecture\n\n### ViewComponent vs Partials Decision Making\n\n**Use ViewComponents when:**\n- Element has complex logic or styling patterns\n- Element will be reused across multiple views/contexts\n- Element needs structured styling with variants/sizes\n- Element requires interactive behavior or Stimulus controllers\n- Element has configurable slots or complex APIs\n- Element needs accessibility features or ARIA support\n\n**Use Partials when:**\n- Element is primarily static HTML with minimal logic\n- Element is used in only one or few specific contexts\n- Element is simple template content\n- Element doesn't need variants, sizes, or complex configuration\n- Element is more about content organization than reusable functionality\n\n**Component Guidelines:**\n- Prefer components over partials when available\n- Keep domain logic OUT of view templates\n- Logic belongs in component files, not template files\n\n### Stimulus Controller Guidelines\n\n**Declarative Actions (Required):**\n```erb\n<!-- GOOD: Declarative - HTML declares what happens -->\n<div data-controller=\"toggle\">\n  <button data-action=\"click->toggle#toggle\" data-toggle-target=\"button\">\n    <%= t(\"components.transaction_details.show_details\") %>\n  </button>\n  <div data-toggle-target=\"content\" class=\"hidden\">\n    <p><%= t(\"components.transaction_details.amount_label\") %>: <%= @transaction.amount %></p>\n    <p><%= t(\"components.transaction_details.date_label\") %>: <%= @transaction.date %></p>\n    <p><%= t(\"components.transaction_details.category_label\") %>: <%= @transaction.category.name %></p>\n  </div>\n</div>\n```\n\n**Example locale file structure (config/locales/en.yml):**\n```yaml\nen:\n  components:\n    transaction_details:\n      show_details: \"Show Details\"\n      hide_details: \"Hide Details\"\n      amount_label: \"Amount\"\n      date_label: \"Date\"\n      category_label: \"Category\"\n```\n\n**i18n Best Practices:**\n- Organize keys by feature/component: `components.transaction_details.show_details`\n- Use descriptive key names that indicate purpose: `show_details` not `button`\n- Group related translations together in the same namespace\n- Use interpolation for dynamic content: `t(\"users.welcome\", name: user.name)`\n- Always update locale files when adding new user-facing strings\n\n**Controller Best Practices:**\n- Keep controllers lightweight and simple (< 7 targets)\n- Use private methods and expose clear public API\n- Single responsibility or highly related responsibilities\n- Component controllers stay in component directory, global controllers in `app/javascript/controllers/`\n- Pass data via `data-*-value` attributes, not inline JavaScript\n\n## Testing Philosophy\n\n### General Testing Rules\n- **ALWAYS use Minitest + fixtures** (NEVER RSpec or factories)\n- Keep fixtures minimal (2-3 per model for base cases)\n- Create edge cases on-the-fly within test context\n- Use Rails helpers for large fixture creation needs\n\n### Test Quality Guidelines\n- **Write minimal, effective tests** - system tests sparingly\n- **Only test critical and important code paths**\n- **Test boundaries correctly:**\n  - Commands: test they were called with correct params\n  - Queries: test output\n  - Don't test implementation details of other classes\n\n### Testing Examples\n\n```ruby\n# GOOD - Testing critical domain business logic\ntest \"syncs balances\" do\n  Holding::Syncer.any_instance.expects(:sync_holdings).returns([]).once\n  assert_difference \"@account.balances.count\", 2 do\n    Balance::Syncer.new(@account, strategy: :forward).sync_balances\n  end\nend\n\n# BAD - Testing ActiveRecord functionality\ntest \"saves balance\" do \n  balance_record = Balance.new(balance: 100, currency: \"USD\")\n  assert balance_record.save\nend\n```\n\n### Stubs and Mocks\n- Use `mocha` gem\n- Prefer `OpenStruct` for mock instances\n- Only mock what's necessary\n\n## API Development Guidelines\n\n### OpenAPI Documentation (MANDATORY)\nWhen adding or modifying API endpoints in `app/controllers/api/v1/`, you **MUST** create or update corresponding OpenAPI request specs:\n\n1. **Location**: `spec/requests/api/v1/{resource}_spec.rb`\n2. **Framework**: RSpec with rswag for OpenAPI generation\n3. **Schemas**: Define reusable schemas in `spec/swagger_helper.rb`\n4. **Generated Docs**: `docs/api/openapi.yaml`\n\n**Example structure for a new API endpoint:**\n```ruby\n# spec/requests/api/v1/widgets_spec.rb\nrequire 'swagger_helper'\n\nRSpec.describe 'API V1 Widgets', type: :request do\n  path '/api/v1/widgets' do\n    get 'List widgets' do\n      tags 'Widgets'\n      security [ { apiKeyAuth: [] } ]\n      produces 'application/json'\n      \n      response '200', 'widgets listed' do\n        schema '$ref' => '#/components/schemas/WidgetCollection'\n        run_test!\n      end\n    end\n  end\nend\n```\n\n**Regenerate OpenAPI docs after changes:**\n```bash\nRAILS_ENV=test bundle exec rake rswag:specs:swaggerize\n```\n\n### Post-commit API consistency (issue #944)\nAfter every API endpoint commit, ensure:\n\n1. **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.\n\n2. **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`.\n\n3. **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.\n\nFull checklist and pattern: [.cursor/rules/api-endpoint-consistency.mdc](.cursor/rules/api-endpoint-consistency.mdc).\n\nTo 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`."},"items":[{"name":"AGENTS.md","path":"AGENTS.md","title":"AGENTS.md","content":"# Repository Guidelines\n\n## Project Structure & Module Organization\n- Code: `app/` (Rails MVC, services, jobs, mailers, components), JS in `app/javascript/`, styles/assets in `app/assets/` (Tailwind, images, fonts).\n- Config: `config/`, environment examples in `.env.local.example` and `.env.test.example`.\n- Data: `db/` (migrations, seeds), fixtures in `test/fixtures/`.\n- Tests: `test/` mirroring `app/` (e.g., `test/models/*_test.rb`).\n- Tooling: `bin/` (project scripts), `docs/` (guides), `public/` (static), `lib/` (shared libs).\n\n## Build, Test, and Development Commands\n- Setup: `cp .env.local.example .env.local && bin/setup` — install deps, set DB, prepare app.\n- Run app: `bin/dev` — starts Rails server and asset/watchers via `Procfile.dev`.\n- Test suite: `bin/rails test` — run all Minitest tests; add `TEST=test/models/user_test.rb` to target a file.\n- Lint Ruby: `bin/rubocop` — style checks; add `-A` to auto-correct safe cops.\n- Lint/format JS/CSS: `npm run lint` and `npm run format` — uses Biome.\n- Security scan: `bin/brakeman` — static analysis for common Rails issues.\n\n## Coding Style & Naming Conventions\n- Ruby: 2-space indent, `snake_case` for methods/vars, `CamelCase` for classes/modules. Follow Rails conventions for folders and file names.\n- Views: ERB checked by `erb-lint` (see `.erb_lint.yml`). Avoid heavy logic in views; prefer helpers/components.\n- JavaScript: `lowerCamelCase` for vars/functions, `PascalCase` for classes/components. Let Biome format code.\n- Commit small, cohesive changes; keep diffs focused.\n\n## Testing Guidelines\n- Framework: Minitest (Rails). Name files `*_test.rb` and mirror `app/` structure.\n- Run: `bin/rails test` locally and ensure green before pushing.\n- Fixtures/VCR: Use `test/fixtures` and existing VCR cassettes for HTTP. Prefer unit tests plus focused integration tests.\n\n## Commit & Pull Request Guidelines\n- Commits: Imperative subject ≤ 72 chars (e.g., \"Add account balance validation\"). Include rationale in body and reference issues (`#123`).\n- 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.\n\n## Security & Configuration Tips\n- Never commit secrets. Start from `.env.local.example`; use `.env.local` for development only.\n- Run `bin/brakeman` before major PRs. Prefer environment variables over hard-coded values.\n\n## API Development Guidelines\n\n### OpenAPI Documentation (MANDATORY)\nWhen adding or modifying API endpoints in `app/controllers/api/v1/`, you **MUST** create or update corresponding OpenAPI request specs for **DOCUMENTATION ONLY**:\n\n1. **Location**: `spec/requests/api/v1/{resource}_spec.rb`\n2. **Framework**: RSpec with rswag for OpenAPI generation\n3. **Schemas**: Define reusable schemas in `spec/swagger_helper.rb`\n4. **Generated Docs**: `docs/api/openapi.yaml`\n5. **Regenerate**: Run `RAILS_ENV=test bundle exec rake rswag:specs:swaggerize` after changes\n\n### Post-commit API consistency (LLM checklist)\nAfter 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).\n\n## Design System Hygiene (UI PRs)\n\nWhen a PR touches `.erb`, view components, or `.css`:\n\n1. **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).\n2. **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.\n3. **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.\n4. **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.\n\nReviewers escalate violations of (2)–(3) to close/rewrite; (1) and (4) are request-changes.\n\n## Securities Providers\n\nIf 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.\n\n## Debug Logging for Provider Syncs\n\nWhen 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.*`.\n\n- Record support-relevant diagnostics in the debug log so they surface in the super-admin-friendly `/settings/debug` UI.\n- Include `category`, `level`, `message`, `source`, `provider_key`, and useful structured `metadata`.\n- Attach `family` and `account_provider` when available so support can filter and trace the affected connection.\n- Reserve raw Rails logging for low-value local noise; anything operators may need should go to the debug log.\n\n## Providers: Pending Transactions and FX Metadata (SimpleFIN/Plaid/Lunchflow)\n\n- Pending detection\n  - SimpleFIN: pending when provider sends `pending: true`, or when `posted` is blank/0 and `transacted_at` is present.\n  - Plaid: pending when Plaid sends `pending: true` (stored at `transaction.extra[\"plaid\"][\"pending\"]` for bank/credit transactions imported via `PlaidEntry::Processor`).\n  - Lunchflow: pending when API returns `isPending: true` in transaction response (stored at `transaction.extra[\"lunchflow\"][\"pending\"]`).\n- Storage (extras)\n  - Provider metadata lives on `Transaction#extra`, namespaced (e.g., `extra[\"simplefin\"][\"pending\"]`).\n  - SimpleFIN FX: `extra[\"simplefin\"][\"fx_from\"]`, `extra[\"simplefin\"][\"fx_date\"]`.\n- UI\n  - Shows a small “Pending” badge when `transaction.pending?` is true.\n- Variability\n  - Some providers don’t expose pendings; in that case nothing is shown.\n- Configuration (default-off)\n  - SimpleFIN runtime toggles live in `config/initializers/simplefin.rb` via `Rails.configuration.x.simplefin.*`.\n  - Lunchflow runtime toggles live in `config/initializers/lunchflow.rb` via `Rails.configuration.x.lunchflow.*`.\n  - ENV-backed keys:\n    - `SIMPLEFIN_INCLUDE_PENDING=1` (forces `pending=1` on SimpleFIN fetches when caller didn’t specify a `pending:` arg)\n    - `SIMPLEFIN_DEBUG_RAW=1` (logs raw payload returned by SimpleFIN)\n    - `LUNCHFLOW_INCLUDE_PENDING=1` (forces `include_pending=true` on Lunchflow API requests)\n    - `LUNCHFLOW_DEBUG_RAW=1` (logs raw payload returned by Lunchflow)\n\n### Provider support notes\n\n- SimpleFIN: supports pending + FX metadata; stored under `extra[\"simplefin\"]`.\n- Plaid: supports pending when the upstream Plaid payload includes `pending: true`; stored under `extra[\"plaid\"]`.\n- Plaid investments: investment transactions currently do not store pending metadata.\n- Lunchflow: supports pending via `include_pending` query parameter; stored under `extra[\"lunchflow\"]`.\n- Manual/CSV imports: no pending concept.\n","category":"root","tokens":1869},{"name":"CLAUDE.md","path":"CLAUDE.md","title":"CLAUDE.md","content":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Common Development Commands\n\n### Development Server\n- `bin/dev` - Start development server (Rails, Sidekiq, Tailwind CSS watcher)\n- `bin/rails server` - Start Rails server only\n- `bin/rails console` - Open Rails console\n\n### Testing\n- `bin/rails test` - Run all tests\n- `bin/rails test:db` - Run tests with database reset\n- `DISABLE_PARALLELIZATION=true bin/rails test:system` - Run system tests only (use sparingly - they take longer)\n- `bin/rails test test/models/account_test.rb` - Run specific test file\n- `bin/rails test test/models/account_test.rb:42` - Run specific test at line\n\n#### System Tests in the Dev Container\nWhen 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.\n\n```bash\nDISABLE_PARALLELIZATION=true bin/rails test:system\n```\n\nTo watch the browser live, open `http://localhost:7900` or `http://localhost:4444` in your host browser (password: `secret`).\n\n### Linting & Formatting\n- `bin/rubocop` - Run Ruby linter\n- `npm run lint` - Check JavaScript/TypeScript code\n- `npm run lint:fix` - Fix JavaScript/TypeScript issues\n- `npm run format` - Format JavaScript/TypeScript code\n- `bin/brakeman` - Run security analysis\n\n### Database\n- `bin/rails db:prepare` - Create and migrate database\n- `bin/rails db:migrate` - Run pending migrations\n- `bin/rails db:rollback` - Rollback last migration\n- `bin/rails db:seed` - Load seed data\n\n### Setup\n- `bin/setup` - Initial project setup (installs dependencies, prepares database)\n\n## Pre-Pull Request CI Workflow\n\nALWAYS run these commands before opening a pull request:\n\n1. **Tests** (Required):\n   - `bin/rails test` - Run all tests (always required)\n   - `DISABLE_PARALLELIZATION=true bin/rails test:system` - Run system tests (only when applicable, they take longer)\n\n2. **Linting** (Required):\n   - `bin/rubocop -f github -a` - Ruby linting with auto-correct\n   - `bundle exec erb_lint ./app/**/*.erb -a` - ERB linting with auto-correct\n\n3. **Security** (Required):\n   - `bin/brakeman --no-pager` - Security analysis\n\nOnly proceed with pull request creation if ALL checks pass.\n\n## General Development Rules\n\n### Authentication Context\n- Use `Current.user` for the current user. Do NOT use `current_user`.\n- Use `Current.family` for the current family. Do NOT use `current_family`.\n\n### Development Guidelines\n- Carefully read project conventions and guidelines before generating any code.\n- Do not run `rails server` in your responses\n- Do not run `touch tmp/restart.txt`\n- Do not run `rails credentials`\n- Do not automatically run migrations\n\n## High-Level Architecture\n\n### Application Modes\nThe codebase runs in two distinct modes:\n- **Managed**: A team operates and manages servers for users (Rails.application.config.app_mode = \"managed\")\n- **Self Hosted**: Users host the codebase on their own infrastructure, typically through Docker Compose (Rails.application.config.app_mode = \"self_hosted\")\n\n### Core Domain Model\nThe application is built around financial data management with these key relationships:\n- **User** → has many **Accounts** → has many **Transactions**\n- **Account** types: checking, savings, credit cards, investments, crypto, loans, properties\n- **Transaction** → belongs to **Category**, can have **Tags** and **Rules**\n- **Investment accounts** → have **Holdings** → track **Securities** via **Trades**\n\n### API Architecture\nThe application provides both internal and external APIs:\n- Internal API: Controllers serve JSON via Turbo for SPA-like interactions\n- External API: `/api/v1/` namespace with Doorkeeper OAuth and API key authentication\n- API responses use Jbuilder templates for JSON rendering\n- Rate limiting via Rack Attack with configurable limits per API key\n- **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.\n\n### Sync & Import System\nTwo primary data ingestion methods:\n1. **Plaid Integration**: Real-time bank account syncing\n   - `PlaidItem` manages connections\n   - `Sync` tracks sync operations\n   - Background jobs handle data updates\n2. **CSV Import**: Manual data import with mapping\n   - `Import` manages import sessions\n   - Supports transaction and balance imports\n   - Custom field mapping with transformation rules\n\n### Provider Integrations: Pending Transactions and FX (SimpleFIN/Plaid)\n\n- Detection\n  - SimpleFIN: pending via `pending: true` or `posted` blank/0 + `transacted_at`.\n  - Plaid: pending via Plaid `pending: true` (stored at `extra[\"plaid\"][\"pending\"]` for bank/credit transactions imported via `PlaidEntry::Processor`).\n- Storage: provider data on `Transaction#extra` (e.g., `extra[\"simplefin\"][\"pending\"]`; FX uses `fx_from`, `fx_date`).\n- UI: \"Pending\" badge when `transaction.pending?` is true; no badge if provider omits pendings.\n- Configuration (default-on for pending)\n  - SimpleFIN: `config/initializers/simplefin.rb` via `Rails.configuration.x.simplefin.*`.\n  - Plaid: `config/initializers/plaid_config.rb` via `Rails.configuration.x.plaid.*`.\n  - Pending transactions are fetched by default and handled via reconciliation/filtering.\n  - Set `SIMPLEFIN_INCLUDE_PENDING=0` to disable pending fetching for SimpleFIN.\n  - Set `PLAID_INCLUDE_PENDING=0` to disable pending fetching for Plaid.\n  - Set `SIMPLEFIN_DEBUG_RAW=1` to enable raw payload debug logging.\n  - 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.\n\nProvider support notes:\n- SimpleFIN: supports pending + FX metadata (stored under `extra[\"simplefin\"]`).\n- Plaid: supports pending when the upstream Plaid payload includes `pending: true` (stored under `extra[\"plaid\"]`).\n- Plaid investments: investment transactions currently do not store pending metadata.\n- Lunchflow: does not currently store pending metadata.\n\n### Background Processing\nSidekiq handles asynchronous tasks:\n- Account syncing (`SyncJob`)\n- Import processing (`ImportJob`)\n- AI chat responses (`AssistantResponseJob`)\n- Scheduled maintenance via sidekiq-cron\n\n### Debug Logging for Provider Syncs\n- Prefer `DebugLogEntry.capture(...)` over `Rails.logger.*` for provider sync/import failures, partial responses, and other support-relevant diagnostics.\n- Record support-relevant incidents in the super-admin `/settings/debug` UI rather than leaving them only in raw application logs.\n- Include `category`, `level`, `message`, `source`, `provider_key`, and structured `metadata`.\n- Attach `family` and `account_provider` whenever possible so support can filter to the affected provider connection.\n\n### Frontend Architecture\n- **Hotwire Stack**: Turbo + Stimulus for reactive UI without heavy JavaScript\n- **ViewComponents**: Reusable UI components in `app/components/`\n- **Stimulus Controllers**: Handle interactivity, organized alongside components\n- **Charts**: D3.js for financial visualizations (time series, donut, sankey)\n- **Styling**: Tailwind CSS v4.x with custom design system\n  - Design system defined in `app/assets/tailwind/sure-design-system.css`\n  - Always use functional tokens (e.g., `text-primary` not `text-white`)\n  - Prefer semantic HTML elements over JS components\n  - Use `icon` helper for icons, never `lucide_icon` directly\n- **i18n**: All user-facing strings must use localization (i18n). Update locale files for each new or changed element.\n\n### Internationalization (i18n) Guidelines\n- **Key Organization**: Use hierarchical keys by feature: `accounts.index.title`, `transactions.form.amount_label`\n- **Translation Helper**: Always use `t()` helper for user-facing strings\n- **Interpolation**: Use for dynamic content: `t(\"users.greeting\", name: user.name)`\n- **Pluralization**: Use Rails pluralization: `t(\"transactions.count\", count: @transactions.count)`\n- **Locale Files**: Update `config/locales/en.yml` for new strings\n- **Missing Translations**: Configure to raise errors in development for missing keys\n\n### Multi-Currency Support\n- All monetary values stored in base currency (user's primary currency)\n- `Money` objects handle currency conversion and formatting\n- Historical exchange rates for accurate reporting\n\n### Security & Authentication\n- Session-based auth for web users\n- API authentication via:\n  - OAuth2 (Doorkeeper) for third-party apps\n  - API keys with JWT tokens for direct API access\n- Scoped permissions system for API access\n- Strong parameters and CSRF protection throughout\n\n### Testing Philosophy\n- Comprehensive test coverage using Rails' built-in Minitest\n- Fixtures for test data (avoid FactoryBot)\n- Keep fixtures minimal (2-3 per model for base cases)\n- VCR for external API testing\n- System tests for critical user flows (use sparingly)\n- Test helpers in `test/support/` for common scenarios\n- Only test critical code paths that significantly increase confidence\n- Write tests as you go, when required\n- **API Endpoints require OpenAPI specs** in `spec/requests/api/` for documentation purposes ONLY, not test (uses RSpec + rswag)\n\n### Performance Considerations\n- Database queries optimized with proper indexes\n- N+1 queries prevented via includes/joins\n- Background jobs for heavy operations\n- Caching strategies for expensive calculations\n- Turbo Frames for partial page updates\n\n### Development Workflow\n- Feature branches merged to `main`\n- Docker support for consistent environments\n- Environment variables via `.env` files\n- Lookbook for component development (`/lookbook`)\n- Letter Opener for email preview in development\n\n## Project Conventions\n\n### Convention 1: Minimize Dependencies\n- Push Rails to its limits before adding new dependencies\n- Strong technical/business reason required for new dependencies\n- Favor old and reliable over new and flashy\n\n### Convention 2: Skinny Controllers, Fat Models\n- Business logic in `app/models/` folder, avoid `app/services/`\n- Use Rails concerns and POROs for organization\n- Models should answer questions about themselves: `account.balance_series` not `AccountSeries.new(account).call`\n\n### Convention 3: Hotwire-First Frontend\n- **Native HTML preferred over JS components**\n  - Use `<dialog>` for modals, `<details><summary>` for disclosures\n- **Leverage Turbo frames** for page sections over client-side solutions\n- **Query params for state** over localStorage/sessions\n- **Server-side formatting** for currencies, numbers, dates\n- **Always use `icon` helper** in `application_helper.rb`, NEVER `lucide_icon` directly\n\n### Convention 4: Optimize for Simplicity\n- Prioritize good OOP domain design over performance\n- Focus performance only on critical/global areas (avoid N+1 queries, mindful of global layouts)\n\n### Convention 5: Database vs ActiveRecord Validations\n- Simple validations (null checks, unique indexes) in DB\n- ActiveRecord validations for convenience in forms (prefer client-side when possible)\n- Complex validations and business logic in ActiveRecord\n\n## TailwindCSS Design System\n\n### Design System Rules\n- **Always reference `app/assets/tailwind/sure-design-system.css`** for primitives and tokens\n- **Use functional tokens** defined in design system:\n  - `text-primary` instead of `text-white`\n  - `bg-container` instead of `bg-white`\n  - `border border-primary` instead of `border border-gray-200`\n- **NEVER create new styles** in design system files without permission\n- **Always generate semantic HTML**\n\n## Component Architecture\n\n### ViewComponent vs Partials Decision Making\n\n**Use ViewComponents when:**\n- Element has complex logic or styling patterns\n- Element will be reused across multiple views/contexts\n- Element needs structured styling with variants/sizes\n- Element requires interactive behavior or Stimulus controllers\n- Element has configurable slots or complex APIs\n- Element needs accessibility features or ARIA support\n\n**Use Partials when:**\n- Element is primarily static HTML with minimal logic\n- Element is used in only one or few specific contexts\n- Element is simple template content\n- Element doesn't need variants, sizes, or complex configuration\n- Element is more about content organization than reusable functionality\n\n**Component Guidelines:**\n- Prefer components over partials when available\n- Keep domain logic OUT of view templates\n- Logic belongs in component files, not template files\n\n### Stimulus Controller Guidelines\n\n**Declarative Actions (Required):**\n```erb\n<!-- GOOD: Declarative - HTML declares what happens -->\n<div data-controller=\"toggle\">\n  <button data-action=\"click->toggle#toggle\" data-toggle-target=\"button\">\n    <%= t(\"components.transaction_details.show_details\") %>\n  </button>\n  <div data-toggle-target=\"content\" class=\"hidden\">\n    <p><%= t(\"components.transaction_details.amount_label\") %>: <%= @transaction.amount %></p>\n    <p><%= t(\"components.transaction_details.date_label\") %>: <%= @transaction.date %></p>\n    <p><%= t(\"components.transaction_details.category_label\") %>: <%= @transaction.category.name %></p>\n  </div>\n</div>\n```\n\n**Example locale file structure (config/locales/en.yml):**\n```yaml\nen:\n  components:\n    transaction_details:\n      show_details: \"Show Details\"\n      hide_details: \"Hide Details\"\n      amount_label: \"Amount\"\n      date_label: \"Date\"\n      category_label: \"Category\"\n```\n\n**i18n Best Practices:**\n- Organize keys by feature/component: `components.transaction_details.show_details`\n- Use descriptive key names that indicate purpose: `show_details` not `button`\n- Group related translations together in the same namespace\n- Use interpolation for dynamic content: `t(\"users.welcome\", name: user.name)`\n- Always update locale files when adding new user-facing strings\n\n**Controller Best Practices:**\n- Keep controllers lightweight and simple (< 7 targets)\n- Use private methods and expose clear public API\n- Single responsibility or highly related responsibilities\n- Component controllers stay in component directory, global controllers in `app/javascript/controllers/`\n- Pass data via `data-*-value` attributes, not inline JavaScript\n\n## Testing Philosophy\n\n### General Testing Rules\n- **ALWAYS use Minitest + fixtures** (NEVER RSpec or factories)\n- Keep fixtures minimal (2-3 per model for base cases)\n- Create edge cases on-the-fly within test context\n- Use Rails helpers for large fixture creation needs\n\n### Test Quality Guidelines\n- **Write minimal, effective tests** - system tests sparingly\n- **Only test critical and important code paths**\n- **Test boundaries correctly:**\n  - Commands: test they were called with correct params\n  - Queries: test output\n  - Don't test implementation details of other classes\n\n### Testing Examples\n\n```ruby\n# GOOD - Testing critical domain business logic\ntest \"syncs balances\" do\n  Holding::Syncer.any_instance.expects(:sync_holdings).returns([]).once\n  assert_difference \"@account.balances.count\", 2 do\n    Balance::Syncer.new(@account, strategy: :forward).sync_balances\n  end\nend\n\n# BAD - Testing ActiveRecord functionality\ntest \"saves balance\" do \n  balance_record = Balance.new(balance: 100, currency: \"USD\")\n  assert balance_record.save\nend\n```\n\n### Stubs and Mocks\n- Use `mocha` gem\n- Prefer `OpenStruct` for mock instances\n- Only mock what's necessary\n\n## API Development Guidelines\n\n### OpenAPI Documentation (MANDATORY)\nWhen adding or modifying API endpoints in `app/controllers/api/v1/`, you **MUST** create or update corresponding OpenAPI request specs:\n\n1. **Location**: `spec/requests/api/v1/{resource}_spec.rb`\n2. **Framework**: RSpec with rswag for OpenAPI generation\n3. **Schemas**: Define reusable schemas in `spec/swagger_helper.rb`\n4. **Generated Docs**: `docs/api/openapi.yaml`\n\n**Example structure for a new API endpoint:**\n```ruby\n# spec/requests/api/v1/widgets_spec.rb\nrequire 'swagger_helper'\n\nRSpec.describe 'API V1 Widgets', type: :request do\n  path '/api/v1/widgets' do\n    get 'List widgets' do\n      tags 'Widgets'\n      security [ { apiKeyAuth: [] } ]\n      produces 'application/json'\n      \n      response '200', 'widgets listed' do\n        schema '$ref' => '#/components/schemas/WidgetCollection'\n        run_test!\n      end\n    end\n  end\nend\n```\n\n**Regenerate OpenAPI docs after changes:**\n```bash\nRAILS_ENV=test bundle exec rake rswag:specs:swaggerize\n```\n\n### Post-commit API consistency (issue #944)\nAfter every API endpoint commit, ensure:\n\n1. **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.\n\n2. **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`.\n\n3. **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.\n\nFull checklist and pattern: [.cursor/rules/api-endpoint-consistency.mdc](.cursor/rules/api-endpoint-consistency.mdc).\n\nTo 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`.","category":"root","tokens":4414}]}