mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-18 20:59:39 +02:00
11 KiB
11 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Common Development Commands
Development Server
bin/dev
- Start development server (Rails, Sidekiq, Tailwind CSS watcher)bin/rails server
- Start Rails server onlybin/rails console
- Open Rails console
Testing
bin/rails test
- Run all testsbin/rails test:db
- Run tests with database resetbin/rails test:system
- Run system tests only (use sparingly - they take longer)bin/rails test test/models/account_test.rb
- Run specific test filebin/rails test test/models/account_test.rb:42
- Run specific test at line
Linting & Formatting
bin/rubocop
- Run Ruby linternpm run lint
- Check JavaScript/TypeScript codenpm run lint:fix
- Fix JavaScript/TypeScript issuesnpm run format
- Format JavaScript/TypeScript codebin/brakeman
- Run security analysis
Database
bin/rails db:prepare
- Create and migrate databasebin/rails db:migrate
- Run pending migrationsbin/rails db:rollback
- Rollback last migrationbin/rails db:seed
- Load seed data
Setup
bin/setup
- Initial project setup (installs dependencies, prepares database)
Pre-Pull Request CI Workflow
ALWAYS run these commands before opening a pull request:
-
Tests (Required):
bin/rails test
- Run all tests (always required)bin/rails test:system
- Run system tests (only when applicable, they take longer)
-
Linting (Required):
bin/rubocop -f github -a
- Ruby linting with auto-correctbundle exec erb_lint ./app/**/*.erb -a
- ERB linting with auto-correct
-
Security (Required):
bin/brakeman --no-pager
- Security analysis
Only proceed with pull request creation if ALL checks pass.
General Development Rules
Authentication Context
- Use
Current.user
for the current user. Do NOT usecurrent_user
. - Use
Current.family
for the current family. Do NOT usecurrent_family
.
Development Guidelines
- Prior to generating any code, carefully read the project conventions and guidelines
- Ignore i18n methods and files. Hardcode strings in English for now to optimize speed of development
- Do not run
rails server
in your responses - Do not run
touch tmp/restart.txt
- Do not run
rails credentials
- Do not automatically run migrations
High-Level Architecture
Application Modes
The Maybe app runs in two distinct modes:
- Managed: The Maybe team operates and manages servers for users (Rails.application.config.app_mode = "managed")
- Self Hosted: Users host the Maybe app on their own infrastructure, typically through Docker Compose (Rails.application.config.app_mode = "self_hosted")
Core Domain Model
The application is built around financial data management with these key relationships:
- User → has many Accounts → has many Transactions
- Account types: checking, savings, credit cards, investments, crypto, loans, properties
- Transaction → belongs to Category, can have Tags and Rules
- Investment accounts → have Holdings → track Securities via Trades
API Architecture
The application provides both internal and external APIs:
- Internal API: Controllers serve JSON via Turbo for SPA-like interactions
- External API:
/api/v1/
namespace with Doorkeeper OAuth and API key authentication - API responses use Jbuilder templates for JSON rendering
- Rate limiting via Rack Attack with configurable limits per API key
Sync & Import System
Two primary data ingestion methods:
- Plaid Integration: Real-time bank account syncing
PlaidItem
manages connectionsSync
tracks sync operations- Background jobs handle data updates
- CSV Import: Manual data import with mapping
Import
manages import sessions- Supports transaction and balance imports
- Custom field mapping with transformation rules
Background Processing
Sidekiq handles asynchronous tasks:
- Account syncing (
SyncAccountsJob
) - Import processing (
ImportDataJob
) - AI chat responses (
CreateChatResponseJob
) - Scheduled maintenance via sidekiq-cron
Frontend Architecture
- Hotwire Stack: Turbo + Stimulus for reactive UI without heavy JavaScript
- ViewComponents: Reusable UI components in
app/components/
- Stimulus Controllers: Handle interactivity, organized alongside components
- Charts: D3.js for financial visualizations (time series, donut, sankey)
- Styling: Tailwind CSS v4.x with custom design system
- Design system defined in
app/assets/tailwind/maybe-design-system.css
- Always use functional tokens (e.g.,
text-primary
nottext-white
) - Prefer semantic HTML elements over JS components
- Use
icon
helper for icons, neverlucide_icon
directly
- Design system defined in
Multi-Currency Support
- All monetary values stored in base currency (user's primary currency)
- Exchange rates fetched from Synth API
Money
objects handle currency conversion and formatting- Historical exchange rates for accurate reporting
Security & Authentication
- Session-based auth for web users
- API authentication via:
- OAuth2 (Doorkeeper) for third-party apps
- API keys with JWT tokens for direct API access
- Scoped permissions system for API access
- Strong parameters and CSRF protection throughout
Testing Philosophy
- Comprehensive test coverage using Rails' built-in Minitest
- Fixtures for test data (avoid FactoryBot)
- Keep fixtures minimal (2-3 per model for base cases)
- VCR for external API testing
- System tests for critical user flows (use sparingly)
- Test helpers in
test/support/
for common scenarios - Only test critical code paths that significantly increase confidence
- Write tests as you go, when required
Performance Considerations
- Database queries optimized with proper indexes
- N+1 queries prevented via includes/joins
- Background jobs for heavy operations
- Caching strategies for expensive calculations
- Turbo Frames for partial page updates
Development Workflow
- Feature branches merged to
main
- Docker support for consistent environments
- Environment variables via
.env
files - Lookbook for component development (
/lookbook
) - Letter Opener for email preview in development
Project Conventions
Convention 1: Minimize Dependencies
- Push Rails to its limits before adding new dependencies
- Strong technical/business reason required for new dependencies
- Favor old and reliable over new and flashy
Convention 2: Skinny Controllers, Fat Models
- Business logic in
app/models/
folder, avoidapp/services/
- Use Rails concerns and POROs for organization
- Models should answer questions about themselves:
account.balance_series
notAccountSeries.new(account).call
Convention 3: Hotwire-First Frontend
- Native HTML preferred over JS components
- Use
<dialog>
for modals,<details><summary>
for disclosures
- Use
- Leverage Turbo frames for page sections over client-side solutions
- Query params for state over localStorage/sessions
- Server-side formatting for currencies, numbers, dates
- Always use
icon
helper inapplication_helper.rb
, NEVERlucide_icon
directly
Convention 4: Optimize for Simplicity
- Prioritize good OOP domain design over performance
- Focus performance only on critical/global areas (avoid N+1 queries, mindful of global layouts)
Convention 5: Database vs ActiveRecord Validations
- Simple validations (null checks, unique indexes) in DB
- ActiveRecord validations for convenience in forms (prefer client-side when possible)
- Complex validations and business logic in ActiveRecord
TailwindCSS Design System
Design System Rules
- Always reference
app/assets/tailwind/maybe-design-system.css
for primitives and tokens - Use functional tokens defined in design system:
text-primary
instead oftext-white
bg-container
instead ofbg-white
border border-primary
instead ofborder border-gray-200
- NEVER create new styles in design system files without permission
- Always generate semantic HTML
Component Architecture
ViewComponent vs Partials Decision Making
Use ViewComponents when:
- Element has complex logic or styling patterns
- Element will be reused across multiple views/contexts
- Element needs structured styling with variants/sizes
- Element requires interactive behavior or Stimulus controllers
- Element has configurable slots or complex APIs
- Element needs accessibility features or ARIA support
Use Partials when:
- Element is primarily static HTML with minimal logic
- Element is used in only one or few specific contexts
- Element is simple template content
- Element doesn't need variants, sizes, or complex configuration
- Element is more about content organization than reusable functionality
Component Guidelines:
- Prefer components over partials when available
- Keep domain logic OUT of view templates
- Logic belongs in component files, not template files
Stimulus Controller Guidelines
Declarative Actions (Required):
<!-- GOOD: Declarative - HTML declares what happens -->
<div data-controller="toggle">
<button data-action="click->toggle#toggle" data-toggle-target="button">Show</button>
<div data-toggle-target="content" class="hidden">Hello World!</div>
</div>
Controller Best Practices:
- Keep controllers lightweight and simple (< 7 targets)
- Use private methods and expose clear public API
- Single responsibility or highly related responsibilities
- Component controllers stay in component directory, global controllers in
app/javascript/controllers/
- Pass data via
data-*-value
attributes, not inline JavaScript
Testing Philosophy
General Testing Rules
- ALWAYS use Minitest + fixtures (NEVER RSpec or factories)
- Keep fixtures minimal (2-3 per model for base cases)
- Create edge cases on-the-fly within test context
- Use Rails helpers for large fixture creation needs
Test Quality Guidelines
- Write minimal, effective tests - system tests sparingly
- Only test critical and important code paths
- Test boundaries correctly:
- Commands: test they were called with correct params
- Queries: test output
- Don't test implementation details of other classes
Testing Examples
# GOOD - Testing critical domain business logic
test "syncs balances" do
Holding::Syncer.any_instance.expects(:sync_holdings).returns([]).once
assert_difference "@account.balances.count", 2 do
Balance::Syncer.new(@account, strategy: :forward).sync_balances
end
end
# BAD - Testing ActiveRecord functionality
test "saves balance" do
balance_record = Balance.new(balance: 100, currency: "USD")
assert balance_record.save
end
Stubs and Mocks
- Use
mocha
gem - Prefer
OpenStruct
for mock instances - Only mock what's necessary