AGENTS.md already nails API-endpoint consistency for backend work but says nothing about UI conventions. Result: contributor PRs and review agents repeatedly flag the same shape — raw Tailwind palette where a semantic token exists, hand-rolled alert / button / disclosure blocks when DS::* already covers them — and the rule has to be re-discovered in review every time. Adds a short Design-System-Hygiene block (token use, 'reach for DS::* first', the 'two copies => lift to DS' threshold, and the icon helper / t() conventions) so the rule lives next to the API one and reviewers have something to cite. Mirrors the weight of the API section, links the DS-consolidation umbrella issue (#1715). Refs #1715
6.7 KiB
Repository Guidelines
Project Structure & Module Organization
- Code:
app/(Rails MVC, services, jobs, mailers, components), JS inapp/javascript/, styles/assets inapp/assets/(Tailwind, images, fonts). - Config:
config/, environment examples in.env.local.exampleand.env.test.example. - Data:
db/(migrations, seeds), fixtures intest/fixtures/. - Tests:
test/mirroringapp/(e.g.,test/models/*_test.rb). - Tooling:
bin/(project scripts),docs/(guides),public/(static),lib/(shared libs).
Build, Test, and Development Commands
- Setup:
cp .env.local.example .env.local && bin/setup— install deps, set DB, prepare app. - Run app:
bin/dev— starts Rails server and asset/watchers viaProcfile.dev. - Test suite:
bin/rails test— run all Minitest tests; addTEST=test/models/user_test.rbto target a file. - Lint Ruby:
bin/rubocop— style checks; add-Ato auto-correct safe cops. - Lint/format JS/CSS:
npm run lintandnpm run format— uses Biome. - Security scan:
bin/brakeman— static analysis for common Rails issues.
Coding Style & Naming Conventions
- Ruby: 2-space indent,
snake_casefor methods/vars,CamelCasefor classes/modules. Follow Rails conventions for folders and file names. - Views: ERB checked by
erb-lint(see.erb_lint.yml). Avoid heavy logic in views; prefer helpers/components. - JavaScript:
lowerCamelCasefor vars/functions,PascalCasefor classes/components. Let Biome format code. - Commit small, cohesive changes; keep diffs focused.
Testing Guidelines
- Framework: Minitest (Rails). Name files
*_test.rband mirrorapp/structure. - Run:
bin/rails testlocally and ensure green before pushing. - Fixtures/VCR: Use
test/fixturesand existing VCR cassettes for HTTP. Prefer unit tests plus focused integration tests.
Commit & Pull Request Guidelines
- Commits: Imperative subject ≤ 72 chars (e.g., "Add account balance validation"). Include rationale in body and reference issues (
#123). - 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.
Security & Configuration Tips
- Never commit secrets. Start from
.env.local.example; use.env.localfor development only. - Run
bin/brakemanbefore major PRs. Prefer environment variables over hard-coded values.
API Development Guidelines
OpenAPI Documentation (MANDATORY)
When adding or modifying API endpoints in app/controllers/api/v1/, you MUST create or update corresponding OpenAPI request specs for DOCUMENTATION ONLY:
- Location:
spec/requests/api/v1/{resource}_spec.rb - Framework: RSpec with rswag for OpenAPI generation
- Schemas: Define reusable schemas in
spec/swagger_helper.rb - Generated Docs:
docs/api/openapi.yaml - Regenerate: Run
RAILS_ENV=test bundle exec rake rswag:specs:swaggerizeafter changes
Post-commit API consistency (LLM checklist)
After every API endpoint commit, ensure: (1) Minitest behavioral coverage in test/controllers/api/v1/{resource}_controller_test.rb (no behavioral assertions in rswag); (2) rswag remains docs-only (no expect/assert_* in spec/requests/api/v1/); (3) rswag auth uses the same API key pattern everywhere (X-Api-Key, not OAuth/Bearer). Full checklist: .cursor/rules/api-endpoint-consistency.mdc.
Design System Hygiene (UI PRs)
When a PR touches .erb, view components, or .css:
- 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). - Reach for
DS::*first. Checkapp/components/DS/(DS::Alert,DS::Button,DS::Disclosure,DS::Dialog,DS::Menu, etc.) before writing an alert, badge, button, disclosure, dialog, or input shape. - 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. - Conventions. Use the
iconhelper (neverlucide_icondirectly), no raw SVG outside DS primitives, user-facing strings viat(), avoid arbitrary*-[Npx]values when a scale token fits.
Reviewers escalate violations of (2)–(3) to close/rewrite; (1) and (4) are request-changes.
Securities Providers
If you need to add a new securities price provider (Tiingo, EODHD, Binance-style crypto, etc.), see adding-a-securities-provider.md for the full walkthrough — provider class, registry wiring, MIC handling, settings UI, locales, and tests.
Providers: Pending Transactions and FX Metadata (SimpleFIN/Plaid/Lunchflow)
- Pending detection
- SimpleFIN: pending when provider sends
pending: true, or whenpostedis blank/0 andtransacted_atis present. - Plaid: pending when Plaid sends
pending: true(stored attransaction.extra["plaid"]["pending"]for bank/credit transactions imported viaPlaidEntry::Processor). - Lunchflow: pending when API returns
isPending: truein transaction response (stored attransaction.extra["lunchflow"]["pending"]).
- SimpleFIN: pending when provider sends
- Storage (extras)
- Provider metadata lives on
Transaction#extra, namespaced (e.g.,extra["simplefin"]["pending"]). - SimpleFIN FX:
extra["simplefin"]["fx_from"],extra["simplefin"]["fx_date"].
- Provider metadata lives on
- UI
- Shows a small “Pending” badge when
transaction.pending?is true.
- Shows a small “Pending” badge when
- Variability
- Some providers don’t expose pendings; in that case nothing is shown.
- Configuration (default-off)
- SimpleFIN runtime toggles live in
config/initializers/simplefin.rbviaRails.configuration.x.simplefin.*. - Lunchflow runtime toggles live in
config/initializers/lunchflow.rbviaRails.configuration.x.lunchflow.*. - ENV-backed keys:
SIMPLEFIN_INCLUDE_PENDING=1(forcespending=1on SimpleFIN fetches when caller didn’t specify apending:arg)SIMPLEFIN_DEBUG_RAW=1(logs raw payload returned by SimpleFIN)LUNCHFLOW_INCLUDE_PENDING=1(forcesinclude_pending=trueon Lunchflow API requests)LUNCHFLOW_DEBUG_RAW=1(logs raw payload returned by Lunchflow)
- SimpleFIN runtime toggles live in
Provider support notes
- SimpleFIN: supports pending + FX metadata; stored under
extra["simplefin"]. - Plaid: supports pending when the upstream Plaid payload includes
pending: true; stored underextra["plaid"]. - Plaid investments: investment transactions currently do not store pending metadata.
- Lunchflow: supports pending via
include_pendingquery parameter; stored underextra["lunchflow"]. - Manual/CSV imports: no pending concept.