* Initial data objects
* Remove trend calculator
* Fill in balance reconciliation for entry group
* Initial tooltip component
* Balance trends in activity view
* Lint fixes
* trade partial alignment fix
* Tweaks to balance calculation to acknowledge holdings value better
* More lint fixes
* Bump brakeman dep
* Test fixes
* Remove unused class
* fix: replace hardcoded bg-white with bg-container in notification notice
* fix: replace hardcoded text-white with fg-inverse in notification CTA
* fix: replace hardcoded text-white with fg-inverse in text tooltip
* fix: replace hardcoded bg-gray-900 text-white with bg-inverse fg-inverse in invitations form
* fix: replace hardcoded bg-gray-800 text-white with bg-inverse fg-inverse in AI consent form
* fix: replace hardcoded text-white with fg-inverse in changelog page
* fix: replace hardcoded text-white and border-gray-500 with fg-inverse and border-secondary in investment tooltip
* fix: replace hardcoded text-white with fg-inverse in holdings missing price tooltip
* fix: replace hardcoded text-white and bg-gray-400 with fg-inverse and bg-surface-inset in settings profiles
* fix: replace hardcoded bg-orange-500 text-white with bg-yellow-600 fg-inverse in settings hosting danger zone
---------
Co-authored-by: Eran Avidor <eavidor@Eran-Avidor-MBP.lan>
* Handle reverse calculator starting portfolio generation correctly
* Fix current_holdings to handle different dates and hide zero quantities
- Use DISTINCT ON to get most recent holding per security instead of assuming same date
- Filter out zero quantity holdings from UI display
- Maintain cash display regardless of zero balance
- Use single efficient query with proper Rails syntax
* Continue to process holdings even if one is not resolvable
* Lint fixes
Updates outdated timezone identifiers in the database to their current
equivalents. This resolves ArgumentError exceptions when users have
outdated timezones like "Europe/Kiev" stored in their preferences.
Timezone mappings:
- Europe/Kiev → Europe/Kyiv
- Asia/Calcutta → Asia/Kolkata
- Asia/Katmandu → Asia/Kathmandu
- Asia/Rangoon → Asia/Yangon
- Asia/Saigon → Asia/Ho_Chi_Minh
- Pacific/Ponape → Pacific/Pohnpei
- Pacific/Truk → Pacific/Chuuk
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
* Separate exclude and one-time transaction handling
- Split transaction "exclude" and "one-time" toggles into separate controls in transaction detail view
- Updated Transaction::Search to show excluded transactions with grayed-out styling instead of filtering them out
- Modified IncomeStatement calculations to exclude both excluded and one_time transactions from totals
- Added migration to convert existing excluded transactions to also be one_time for backward compatibility
- Updated transaction list view to show asterisk for one_time transactions and gray out excluded ones
- Added controller support for kind parameter in transaction updates
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix linting issues
- Remove trailing whitespace from migration
- Fix ERB formatting throughout templates
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
- Remove trailing whitespace in auth controller and mobile device model
- Update API key test to expect new validation message with source
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Replace API keys with OAuth2 tokens for mobile apps
- Add device tracking and management for mobile sessions
- Implement 30-day token expiration with refresh tokens
- Add MFA/2FA support for mobile login
- Create dedicated auth endpoints (signup/login/refresh)
- Skip CSRF protection for API endpoints
- Return plaintext tokens (not hashed) in responses
- Track devices with unique IDs and metadata
- Enable seamless native mobile experience without OAuth redirects
This provides enterprise-grade security for the iOS/Android apps while maintaining a completely native authentication flow.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Configure Doorkeeper to allow custom URL schemes (maybeapp://)
- Disable force_ssl_in_redirect_uri to support non-HTTPS schemes
- Add custom Doorkeeper views with mobile OAuth detection
- Disable Turbo for mobile OAuth flows to prevent redirect interference
- Add display parameter preservation through OAuth flow
- Create custom Doorkeeper layouts with proper styling
- Add comprehensive integration tests for mobile OAuth flows
- Ensure all OAuth pages use proper doorkeeper/application layout
This allows the mobile app to complete OAuth authorization flows
without the web app interfering with custom URL scheme redirects.
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
- Add chats#index and chats#show endpoints to list and view AI conversations
- Add messages#create endpoint to send messages to AI chats
- Include API documentation for chat endpoints
- Add controller tests for new endpoints
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Auto-generate encryption keys based on SECRET_KEY_BASE when not provided.
This ensures API key encryption works out of the box for self-hosted users
without requiring manual setup steps.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Fix API key scopes validation in test (only one scope allowed)
- Update validation error test to match actual behavior
- Fix regenerating API key test path assertion
- Fix revoke confirmation dialog test to work with custom modal
- Fix trades test ticker symbol reference
- Add small delays for modal animations in system tests
All 59 system tests now pass.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* OAuth
* Add API test routes and update Doorkeeper token handling for test environment
- Introduced API namespace with test routes for controller testing in the test environment.
- Updated Doorkeeper configuration to allow fallback to plain tokens in the test environment for easier testing.
- Modified schema to change resource_owner_id type from bigint to string.
* Implement API key authentication and enhance access control
- Replaced Doorkeeper OAuth authentication with a custom method supporting both OAuth and API keys in the BaseController.
- Added methods for API key authentication, including validation and logging.
- Introduced scope-based authorization for API keys in the TestController.
- Updated routes to include API key management endpoints.
- Enhanced logging for API access to include authentication method details.
- Added tests for API key functionality, including validation, scope checks, and access control enforcement.
* Add API key rate limiting and usage tracking
- Implemented rate limiting for API key authentication in BaseController.
- Added methods to check rate limits, render appropriate responses, and include rate limit headers in responses.
- Updated routes to include a new usage resource for tracking API usage.
- Enhanced tests to verify rate limit functionality, including exceeding limits and per-key tracking.
- Cleaned up Redis data in tests to ensure isolation between test cases.
* Add Jbuilder for JSON rendering and refactor AccountsController
- Added Jbuilder gem for improved JSON response handling.
- Refactored index action in AccountsController to utilize Jbuilder for rendering JSON.
- Removed manual serialization of accounts and streamlined response structure.
- Implemented a before_action in BaseController to enforce JSON format for all API requests.
* Add transactions resource to API routes
- Added routes for transactions, allowing index, show, create, update, and destroy actions.
- This enhancement supports comprehensive transaction management within the API.
* Enhance API authentication and onboarding handling
- Updated BaseController to skip onboarding requirements for API endpoints and added manual token verification for OAuth authentication.
- Improved error handling and logging for invalid access tokens.
- Introduced a method to set up the current context for API requests, ensuring compatibility with session-like behavior.
- Excluded API paths from onboarding redirects in the Onboardable concern.
- Updated database schema to change resource_owner_id type from bigint to string for OAuth access grants.
* Fix rubocop offenses
- Fix indentation and spacing issues
- Convert single quotes to double quotes
- Add spaces inside array brackets
- Fix comment alignment
- Add missing trailing newlines
- Correct else/end alignment
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix API test failures and improve test reliability
- Fix ApiRateLimiterTest by removing mock users method and using fixtures
- Fix UsageControllerTest by removing mock users method and using fixtures
- Fix BaseControllerTest by using different users for multiple API keys
- Use unique display_key values with SecureRandom to avoid conflicts
- Fix double render issue in UsageController by returning after authorize_scope\!
- Specify controller name in routes for usage resource
- Remove trailing whitespace and empty lines per Rubocop
All tests now pass and linting is clean.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add API transactions controller warning to brakeman ignore
The account_id parameter in the API transactions controller is properly
validated on line 79: family.accounts.find(transaction_params[:account_id])
This ensures users can only create transactions in accounts belonging to
their family, making this a false positive.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Signed-off-by: Josh Pigford <josh@joshpigford.com>
Co-authored-by: Claude <noreply@anthropic.com>
* Balance sheet cache layer with cache-busting
* Update family cache timestamps during Sync
* Less blocking sync loaders
* Consolidate family data caching key logic
* Fix turbo stream broadcasts
* Remove dev delay
* Add back account group sorting
- Added pre-loading of series in AccountableSparklinesController and AccountsController to catch errors before rendering.
- Updated the accounts view to use the pre-loaded sparkline series variable.
- Adjusted the test for graceful handling of errors in the sparkline series method.
This enhances the robustness of the sparkline feature and improves error visibility in the UI.
- Added rescue blocks to handle exceptions in the Accounts and AccountableSparklines controllers, logging errors and rendering error partials.
- Enhanced error handling in the Account::Chartable and Balance::ChartSeriesBuilder models, logging specific error messages for series generation failures.
- Updated the accounts view to include a timeout for Turbo frame loading.
- Added a test to ensure graceful handling of sparkline errors in the AccountsController.
In reference to bug #2315
* Fix changelog page crash when GitHub release notes are unavailable
* Refactor changelog view to handle missing avatars gracefully and improve session sign-out logic in tests
* Enhance changelog view to display fallback messages for unavailable release notes and publication dates
* Update onboarding system tests to reflect UI changes and improve assertions
- Changed button labels from "Get started" to "Continue" and "Complete" to align with updated UI.
- Updated text assertions for clarity, changing "Set your preferences" to "Configure your preferences".
- Adjusted locale selection options to include language codes.
- Enhanced validation error handling in preferences form.
- Improved navigation assertions to ensure accurate path checks.
Breaks our Plaid sync process out into more manageable classes. Notably, this moves the sync process to a distinct, 2-step flow:
1. Import stage - we first make API calls and import Plaid data to "mirror" tables
2. Processing stage - read the raw data, apply business rules, build internal domain models and sync balances
This provides several benefits:
- Plaid syncs can now be "replayed" without fetching API data again
- Mirror tables provide better audit and debugging capabilities
- Eliminates the "all or nothing" sync behavior that is currently in place, which is brittle
- Update BalanceSheet model to directly calculate account weights based on converted balances.
- Modify dashboard view to compute account weight as a percentage of classification total, enhancing clarity.
- Adjust group weight partial to handle effective weight, ensuring accurate rendering of weight representation.
- Add caching for classification groups and account groups in the BalanceSheet model to improve performance.
- Update views for accountable sparklines to utilize caching for rendered HTML, enhancing load times and reducing database queries.
- Implement caching for transaction totals to enhance performance, using a unique cache key based on family ID and search parameters.
- Adjust default date filter logic to use the user's preferred period when no explicit date filters are provided, reducing the load on the database for large datasets.
Note to self hosters:
If you started self hosting prior to this commit, you may have duplicate securities in your database.
This is usually not a problem, but if you'd like to clean things up, you can run the data migration
by opening a terminal on the machine you're hosting with and running:
```sh
rake data_migration:migrate_duplicate_securities
```
* Setup health check
* Security health checker cron
* Use resolver throughout codebase
* Use resolver for trade builder
* Add security health checks to schedule
* Handle no provider
* Lint fixes
* Add new chat button
* Tweak chat navigation
* Fix chat nav padding on new chat
* Make the button nicer
* Fix bad tailwind class
* Use menu icon instead of left
* Fix path
* Use rounded-full on budget allocation bar
* Fix backgrounds when balance sheet groups are open
* Add rulers between accounts and classification groups in balance sheet and account groups views.
* Update Security model to include country_code in the data definition.
* Modify Provider::SecurityConcept to define country_code for security.
* Enhance Provider::Synth to extract country_code from security data.
* Update Security::Provided to include country_code when creating security instances.
* Adjust Security::SynthComboboxOption to add country_code as an attribute.
* Revise combobox_security partial to conditionally display country flag and code.
* Create shared ruler view
* Use collection rendering/spacer templates for rules, and new shared_ruler
* Use shared ruler for all the places a ruler is used
* Use shared ruler for imports and balance sheet
* Fix brakeman by using a static partial with a defined collection
* Standardize & improve a bunch of corners, fix some backgrounds, fix merchants for dark mode
* Update balance sheet
* misc cleanup
* Fix import table
* Remove middot
* Create shared ruler view
* Use collection rendering/spacer templates for rules, and new shared_ruler
* Use shared ruler for all the places a ruler is used
* Use shared ruler for imports and balance sheet
* Fix brakeman by using a static partial with a defined collection
* Update balance sheet
* Enhance cash flow dashboard with new cash flow period handling and improved Sankey diagram rendering. Update D3 and related dependencies for better performance and features.
* Fix Rubocop offenses
* Refactor Sankey chart controller to use Number.parseFloat for value formatting and improve code readability by restructuring conditional logic for node shapes.
* Add ability to name a rule
* Add sorting by name and date,
* Improve rule page and form design
* Small header tweak
* Improve sorting click areas by including icon
* Fix brakeman
* Use icon helper instead of lucide_icon helper
* Fix double headers with new DialogComponent
* Use updated_at for sorting instead of created_at
* Use copy-plus icon for compound rules
* Remove icons and change IF/THEN/FOR font in edit form
* Use text-secondary on disabled rules
* First pass at redesigning the sorting menu
* New rule list
* Borders instead of shadows
* Apply proper text color to TO in edit form
* Improve dark mode with proper background color classes
* Use border-secondary
* Add touch: true to conditions and actions of a rule, so updated_at works as expected
* Fix db schema
* Change sort direction to be a LinkComponent outside of the form for better sort behavior
* Clean up dropdown design to match figma
* Match tags/categories design
* Fix name text color, add bg-divider background for dividers
* Fix family subscription tests (thanks zach!)
* Fix AND prefix on rule form
This new condition prefix data target is used to ensure the AND prefix is added/removed to additional conditions/groups when there aren't any saved conditions yet.
* Ensure the condition group "Add condition" button is type button to avoid form submission
* Add prefix update when removing a subcondition
* Use data condition to update the prefix on conditions, condition groups, and subconditions
* Use condition remove instead of element remove for condition groups so prefix logic runs
* Add back explicit show_prefixes to ensure subconditions never have a prefix
* Run the prefix update runs on a load of a form, which handles prefixes on an edit since no conditions change
* Ensure saved items that are marked for removal don't impact the index
* Simplify logic since we don't process subconditions
* Clean up comments
* Add primary_condition_title field to rule model
old config exposed the Redis server to the internet if the user had not configured a firewall to block port 6379
Signed-off-by: JIBSIL <40243545+JIBSIL@users.noreply.github.com>
* Fix category dark mode styles
* Fix sidebar account tab states
* Fix dashboard balance sheet group styles
* Fix budget dark mode styles
* Fix chart gradient split
* Fix prose styles in dark mode
* Add back chat nav id for tests
* Save work
* Subscriptions and trials domain
* Store family ID on customer
* Remove indirection of stripe calls
* Test simplifications
* Update brakeman
* Fix stripe tests in CI
* Update billing page to show subscription details
* Remove legacy columns
* Complete billing settings page
* Fix hardcoded plan name
* Handle subscriptions for self hosting mode
* Lint fixes
* Add change name rule for transaction
* Use HTML template in the ERB, clone and inject those templates from the stimulus controller
* Put back the ai_enabled check
* Update docs
* Example of what no case statement would look like
* Remove action_type and needs_value now that controller is injecting templates/hiding action target
* add "to" to template, improve no-option selection, ensure text box is cleared
* Onboarding redirect tests and trial status bar
* use helper method
* Fix time tolerance failure
* Update post-onboarding message to be generic
* Disable turbo frames on Trial start button
* Update flash notice in test
- Implemented checks for the existence of accounts before rendering links in the transfers partial.
- Added error messaging for missing accounts to improve user feedback and prevent broken links.
- Improved error handling for invalid institution URLs by rescuing URI::InvalidURIError and logging a warning.
- Refactored the method to use safe navigation and streamline the URL parsing process.
- Added a check to determine if a trial was started within the last few seconds, allowing for the assumption that onboarding was just completed even if the onboarded_at timestamp appears blank momentarily. This improves the user experience during onboarding transitions.
- Adjusted padding for mobile sidebar to account for safe area insets.
- Modified main content and bottom navigation styles to enhance layout consistency on mobile devices.
* Add lookbook + viewcomponent, organize design system file
* Build menu component
* Button updates
* More button fixes
* Replace all menus with new ViewComponent
* Checkpoint: fix tests, all buttons and menus converted
* Split into Link and Button components for clarity
* Button cleanup
* Simplify custom confirmation configuration in views
* Finalize button, link component API
* Add toggle field to custom form builder + Component
* Basic tabs component
* Custom tabs, convert all menu / tab instances in app
* Gem updates
* Centralized icon helper
* Update all icon usage to central helper
* Lint fixes
* Centralize all disclosure instances
* Dialog replacements
* Consolidation of all dialog styles
* Test fixes
* Fix app layout issues, move to component with slots
* Layout simplification
* Flakey test fix
* Fix dashboard mobile issues
* Finalize homepage
* Lint fixes
* Fix shadows and borders in dark mode
* Fix tests
* Remove stale class
* Fix filled icon logic
* Move transparent? to public interface
* Improve net worth hover
* Improve graph tooltip
* Use locales files for some text on net worth and account charts
* Consolidate and simplify trend change between net worth and account charts
* Fix test and self-review stuff
* Clean up some stuff on the holding sidebar
Changed transaction form to only display relevant categories based on transaction type (income or expense), improving usability and preventing misclassification. Created a shared transaction type tabs component for consistent navigation between expense, income, and transfer forms, providing a better user experience and reducing code duplication.
Signed-off-by: Zach Gollwitzer <zach@maybe.co>
Co-authored-by: Zach Gollwitzer <zach@maybe.co>
* fix: improve dark mode readability across the app
* fix: improve dark mode support for asset percentage text
* fix: apply suggested patch for theme-related improvements
* chore: apply PR feedback – remove dark:, align with design tokens, update form builder
* chore: revert background token and restore original style for visual consistency
* chore: remove unnecessary class attributes from form fields using builder
* refactor: move number_field and date_field into metaprogramming block
* refactor: replace bg-divider-adaptive divs with <hr> and border-secondary
* fix: apply requested changes and linting fixes
* Add nice formatting for subtypes on account list
* Fix rubocop linting errors
* Implement better mapping
* Fix rubocop linting
* Add short and long versions of subtypes
* Simplify subtype reference
Co-authored-by: Zach Gollwitzer <zach.gollwitzer@gmail.com>
Signed-off-by: Alex Hatzenbuhler <hatz@hey.com>
* Simplify reference logic, add a small test
* Fix test
* Fix tests
---------
Signed-off-by: Alex Hatzenbuhler <hatz@hey.com>
Co-authored-by: Zach Gollwitzer <zach.gollwitzer@gmail.com>
* Fixes #2100: added 'text-primary' in class , this will fix the problem of text not visible in dark theme inside the modals
* added text-primary in class for form fields
---------
Signed-off-by: Zach Gollwitzer <zach@maybe.co>
Co-authored-by: Zach Gollwitzer <zach@maybe.co>
* Domain model sketch
* Scaffold out rules domain
* Migrations
* Remove existing data enrichment for clean slate
* Sketch out business logic and basic tests
* Simplify rule scope building and action executions
* Get generator working again
* Basic implementation + tests
* Remove manual merchant management (rules will replace)
* Revert "Remove manual merchant management (rules will replace)"
This reverts commit 83dcbd9ff0.
* Family and Provider merchants model
* Fix brakeman warnings
* Fix notification loader
* Update notification position
* Add Rule action and condition registries
* Rule form with compound conditions and tests
* Split out notification types, add CTA type
* Rules form builder and Stimulus controller
* Clean up rule registry domain
* Clean up rules stimulus controller
* CTA message for rule when user changes transaction category
* Fix tests
* Lint updates
* Centralize notifications in Notifiable concern
* Implement category rule prompts with auto backoff and option to disable
* Fix layout bug caused by merge conflict
* Initialize rule with correct action for category CTA
* Add rule deletions, get rules working
* Complete dynamic rule form, split Stimulus controllers by resource
* Fix failing tests
* Change test password to avoid chromium conflicts
* Update integration tests
* Centralize all test password references
* Add re-apply rule action
* Rule confirm modal
* Run migrations
* Trigger rule notification after inline category updates
* Clean up rule styles
* Basic attribute locking for rules
* Apply attribute locks on user edits
* Log data enrichments, only apply rules to unlocked attributes
* Fix merge errors
* Additional merge conflict fixes
* Form UI improvements, ignore attribute locks on manual rule application
* Batch AI auto-categorization of transactions
* Auto merchant detection, ai enrichment in batches
* Fix Plaid merchant assignments
* Plaid category matching
* Cleanup 1
* Test cleanup
* Remove stale route
* Fix desktop chat UI issues
* Fix mobile nav styling issues
Previously, the symbol for the 'Uncategorized' segment defaulted to `$`, which is incorrect for non-USD budgets. This change ensures the correct currency symbol is shown based on the budget's currency.
Co-authored-by: Zach Gollwitzer <zach@maybe.co>
* AI sidebar
* Add chat and message models with associations
* Implement AI chat functionality with sidebar and messaging system
- Add chat and messages controllers
- Create chat and message views
- Implement chat-related routes
- Add message broadcasting and user interactions
- Update application layout to support chat sidebar
- Enhance user model with initials method
* Refactor AI sidebar with enhanced chat menu and interactions
- Update sidebar layout with dynamic width and improved responsiveness
- Add new chat menu Stimulus controller for toggling between chat and chat list views
- Improve chat list display with recent chats and empty state
- Extract AI avatar to a partial for reusability
- Enhance message display and interaction styling
- Add more contextual buttons and interaction hints
* Improve chat scroll behavior and message styling
- Refactor chat scroll functionality with Stimulus controller
- Optimize message scrolling in chat views
- Update message styling for better visual hierarchy
- Enhance chat container layout with flex and auto-scroll
- Simplify message rendering across different chat views
* Extract AI avatar to a shared partial for consistent styling
- Refactor AI avatar rendering across chat views
- Replace hardcoded avatar markup with a reusable partial
- Simplify avatar display in chats and messages views
* Update sidebar controller to handle right panel width dynamically
- Add conditional width class for right sidebar panel
- Ensure consistent sidebar toggle behavior for both left and right panels
- Use specific width class for right panel (w-[375px])
* Refactor chat form and AI greeting with flexible partials
- Extract message form to a reusable partial with dynamic context support
- Create flexible AI greeting partial for consistent welcome messages
- Simplify chat and sidebar views by leveraging new partials
- Add support for different form scenarios (chat, new chat, sidebar)
- Improve code modularity and reduce duplication
* Add chat clearing functionality with dynamic menu options
- Implement clear chat action in ChatsController
- Add clear chat route to support clearing messages
- Update AI sidebar with dropdown menu for chat actions
- Preserve system message when clearing chat
- Enhance chat interaction with new menu options
* Add frontmatter to project structure documentation
- Create initial frontmatter for structure.mdc file
- Include description and configuration options
- Prepare for potential dynamic documentation rendering
* Update general project rules with additional guidelines
- Add rule for using `Current.family` instead of `current_family`
- Include new guidelines for testing, API routes, and solution approach
- Expand project-specific rules for more consistent development practices
* Add OpenAI gem and AI-friendly data representations
- Add `ruby-openai` gem for AI integration
- Implement `to_ai_readable_hash` methods in BalanceSheet and IncomeStatement
- Include Promptable module in both models
- Add savings rate calculation method in IncomeStatement
- Prepare financial models for AI-powered insights and interactions
* Enhance AI Financial Assistant with Advanced Querying and Debugging Capabilities
- Implement comprehensive AI financial query system with function-based interactions
- Add detailed debug logging for AI responses and function calls
- Extend BalanceSheet and IncomeStatement models with AI-friendly methods
- Create robust error handling and fallback mechanisms for AI queries
- Update chat and message views to support debug mode and enhanced rendering
- Add AI query routes and initial test coverage for financial assistant
* Refactor AI sidebar and chat layout with improved structure and comments
- Remove inline AI chat from application layout
- Enhance AI sidebar with more semantic HTML structure
- Add descriptive comments to clarify different sections of chat view
- Improve flex layout and scrolling behavior in chat messages container
- Optimize message rendering with more explicit class names and structure
* Add Markdown rendering support for AI chat messages
- Implement `markdown` helper method in ApplicationHelper using Redcarpet
- Update message view to render AI messages with Markdown formatting
- Add comprehensive Markdown rendering options (tables, code blocks, links)
- Enhance AI Financial Assistant prompt to encourage Markdown usage
- Remove commented Markdown CSS in Tailwind application stylesheet
* Missing comma
* Enhance AI response processing with chat history context
* Improve AI debug logging with payload size limits and internal message flag
* Enhance AI chat interaction with improved thinking indicator and scrolling behavior
* Add AI consent and enable/disable functionality for AI chat
* Upgrade Biome and refactor JavaScript template literals
- Update @biomejs/biome to latest version with caret (^) notation
- Refactor AI query and chat controllers to use template literals
- Standardize npm scripts formatting in package.json
* Add beta testing usage note to AI consent modal
* Update test fixtures and configurations for AI chat functionality
- Add family association to chat fixtures and tests
- Set consistent password digest for test users
- Enable AI for test users
- Add OpenAI access token for test environment
- Update chat and user model tests to include family context
* Simplify data model and get tests passing
* Remove structure.mdc from version control
* Integrate AI chat styles into existing prose pattern
* Match Figma design spec, implement Turbo frames and actions for chats controller
* AI rules refresh
* Consolidate Stimulus controllers, thinking state, controllers, and views
* Naming, domain alignment
* Reset migrations
* Improve data model to support tool calls and message types
* Tool calling tests and fixtures
* Tool call implementation and test
* Get assistant test working again
* Test updates
* Process tool calls within provider
* Chat UI back to working state again
* Remove stale code
* Tests passing
* Update openai class naming to avoid conflicts
* Reconfigure test env
* Rebuild gemfile
* Fix naming conflicts for ChatResponse
* Message styles
* Use OpenAI conversation state management
* Assistant function base implementation
* Add back thinking messages, clean up error handling for chat
* Fix sync error when security price has bad data from provider
* Add balance sheet function to assistant
* Add better function calling error visibility
* Add income statement function
* Simplify and clean up "thinking" interactions with Turbo frames
* Remove stale data definitions from functions
* Ensure VCR fixtures working with latest code
* basic stream implementation
* Get streaming working
* Make AI sidebar wider when left sidebar is collapsed
* Get tests working with streaming responses
* Centralize provider error handling
* Provider data boundaries
---------
Co-authored-by: Josh Pigford <josh@joshpigford.com>
* devContainer: Use Redis for ActiveJob and ActionCable
* devContainer: Simplify environment variables for services.
* devContainer: Remove version field as it's no longer required in Compose.
* Placeholder logic for missing prices
* Generate holdings properly for "offline" securities
* Separate forward and reverse calculators for holdings and balances
* Remove unnecessary currency conversion during sync
* Clearer sync process
* Move price caching logic to dedicated model
* Base holding calculator
* Base calculator for balances
* Finish balance calculators
* Better naming
* Logs cleanup
* Remove stale data type
* Remove stale test
* Fix price lookup logic for holdings sync
* Fix Plaid item sync regression
* Remove temp logging
* Calculate cash and holdings series
* Add holdings, cash, and balance series dropdown for investments
* Replaced data-action click event with data-action mousedown to prevent the modal from hiding on mouse up whenever mouse down starts within the modal
* Changed click events to mousedown within dialog elements to trigger the closing of the element
* Allow CSV imports to be configured to a single account or multiple accounts
* Initialize import directly from accounts page
* Fix brakeman warnings
* Fix schema
* Fix Synth check
* Do not show billing settings navbar item when self hosted
* Do not show billing settings navbar item when self hosted
* Add condition to settings helper
* Let Stripe::AuthenticationError bubble up
* Add breadcrumbs support across application
Fixes #1896
* Potential fix for tests
* Simplify breadcrumbs implementation
Remove complex breadcrumbs logic from controllers and concern, replacing with a simpler default approach that sets a basic breadcrumb based on the current controller name
* Refactor page header and breadcrumbs rendering
Remove complex breadcrumbs helper method and update layout to use more flexible content_for approach for page headers and breadcrumbs
* Add fallback breadcrumbs rendering to settings layout
linux/arm/v7 is not compatible with the latest Tailwind package + takes a significant amount of time when using buildx. Most Raspberry Pi OS now run on 64 bit ARM architecture, so we should still have significant coverage with linux/amd + linux/arm builds.
Signed-off-by: Zach Gollwitzer <zach@maybe.co>
Since the very first 0.1.0-alpha.1 release, we've been moving quickly to add new features to the Maybe app. In doing so, some parts of the codebase have become outdated, unnecessary, or overly-complex as a natural result of this feature prioritization.
Now that "core" Maybe is complete, we're moving into a second phase of development where we'll be working hard to improve the accuracy of existing features and build additional features on top of "core". This PR is a quick overhaul of the existing codebase aimed to:
- Establish the brand new and simplified dashboard view (pictured above)
- Establish and move towards the conventions introduced in Cursor rules and project design overview #1788
- Consolidate layouts and improve the performance of layout queries
- Organize the core models of the Maybe domain (i.e. Account::Entry, Account::Transaction, etc.) and break out specific traits of each model into dedicated concerns for better readability
- Remove stale / dead code from codebase
- Remove overly complex code paths in favor of simpler ones
* Add geist font
* Design system css file
* Add cursor ui/ux rules
* Add shadows and shadow borders
* Replace primitives with tokens for common text and backgrounds
* Organize css
* Update switch and checkbox class names
* Add back global color variables
* Enhance Plaid connection management with re-authentication and error handling
- Add support for Plaid item status tracking (good/requires_update)
- Implement re-authentication flow for Plaid connections
- Handle connection errors and provide user-friendly reconnection options
- Update Plaid link token generation to support item updates
- Add localization for new connection management states
* Remove redundant 'reconnect' localization for Plaid items
* Enhance security information retrieval and handling
- Add support for operating MIC codes in security info fetching
- Update security uniqueness validation to handle unknown securities
- Improve security creation and update logic in Plaid investment sync
- Update combobox and view components to handle operating MIC codes
- Add unknown flag for securities with incomplete information
* Update schema.rb
* Refactor the need for mic codes
* Don't fetch prices unless a security has the necessary mic code
* Deduplication
* Lint
* Update Securities and Plaid Investment Sync
- Modify PlaidInvestmentSync to return plaid_security for USD cash
- Add non-null constraint to Securities ticker column
- Update Securities fixture to use exchange_operating_mic instead of exchange_mic
---------
Signed-off-by: Josh Pigford <josh@joshpigford.com>
* feat(import): add currency and number format support for CSV imports
* feat(import): add currency and format for mint and trade
* fix(imports): remove currency field in favor of currency csv col
* fix(imports): remove validate column from import model
* test(import): add some tests for imports
* test(import): add some tests for generate_rows_from_csv
* fix: change method name for import model
* fix: change before validation
---------
Co-authored-by: danestves <danestves@users.noreply.github.com>
* Fix Account Holding validation and synchronization
Fixes #1781
- Add comprehensive validations for Account::Holding
- Implement validation to ensure amount matches qty * price
- Update Account::Syncer to include qty and price during synchronization
- Add database constraints for holding attributes and calculations
* Remove database check constraints for Account Holdings
Align with project convention of keeping complex validations in ActiveRecord
- Remove database-level check constraints for quantity, price, and amount
- Maintain database-level null and unique constraints
- Prepare for more flexible validation in the model layer
* feat: Add institution details to Plaid items
- Fetch and store institution URL, ID, and primary color for Plaid items
- Update PlaidItem model to retrieve and save institution metadata
- Add new method in Plaid provider to get institution details
- Update account logo view to use institution domain for logo generation
* Add institution domain method to Account model
- Extract institution domain logic from view to Account model
- Simplify logo view by using new institution_domain method
- Improve code reusability and separation of concerns
* Add scope to filter transactions from active accounts
* Add test for transfer match candidates with active accounts
* Refactor active account filtering for transactions and entries
* Refactor transaction enrichment to support batch processing
- Add method to enrich transactions in batches
- Implement job scheduling for unenriched transactions
- Improve logging and error handling for transaction enrichment
* Re-enable enrichment
* Fix transaction enrichment query to use correct table references
- Update queries to explicitly join and reference account_entries and account_transactions tables
- Remove unnecessary name presence check before enrichment
- Improve query precision for unenriched transaction selection
* Optimize transaction enrichment query joins
- Refactor database joins to use explicit table references
- Improve query performance for unenriched transaction selection
- Ensure correct table aliasing in enrichment methods
* Remove deprecated data enrichment job and method
- Delete EnrichDataJob as it's no longer used
- Remove `enrich_data_later` method from Account model
- Update Account::Syncer to directly call `enrich_data` instead of scheduling a job
- Add error handling for individual security price loading
- Log detailed error information for problematic securities
- Prevent single security errors from halting entire price loading process
- Enhance logging with more specific security identification details
* Fix: make date format year consistent overall
* chore: Consolidating all date formatting options
* adding disabled condition back to mint import
* chore: Moving formats from helper to models/family.rd
* Adding date_format_label to the en translation for import/configurations
* nit: making changes to use individual translations
* Initial pass at Plaid EU
* Add EU support to Plaid Items
* Lint
* Temp fix for rubocop isseus
* Merge cleanup
* Pass in region and get tests passing
* Use absolute path for translation
---------
Signed-off-by: Josh Pigford <josh@joshpigford.com>
* Change email address
* Email confirmation
* Email change test
* Lint
* Schema reset
* Set test email sender
* Select specific user fixture
* Refactor/cleanup
* Remove unused email_confirmation_token
* Current user would never be true
* Fix translation test failures
* feat: add validation to require consistent category color
* feat: reflect color requirement in new category form
* refactor: move logic inline over shared component
* rubocop
* tests: fix breaking and add case for new validation
* feat: hide color selector when parent category selected
* feat: override color with parent color in model
* tests: remove case for unnecessary validation
---------
Signed-off-by: Julien Bertazzo Lambert <42924425+JLambertazzo@users.noreply.github.com>
* Improve category level limit validation
* Set categories list only for non parents
* Disable select field
* Add info about the disabled select
* Don’t render a select input for parent categories
* Handle correctly turbo_stream request format
* Add turbo_stream format to requests on create and update action's tests
* Remove no_content status from update action
* Revert "Remove no_content status from update action"
This reverts commit 866140c196.
* Revert "Add turbo_stream format to requests on create and update action's tests"
This reverts commit c6bf21490f.
* Add correct redirect url for both html and turbo_stream formats
* Remove useless turbo_frame_tag
Add where statement to account_transactions overview to only give transactions and not valuations
Signed-off-by: Jasper Delahaije <47220315+Repsay@users.noreply.github.com>
* Transfer data model migration
* Transfers and payment modeling and UI improvements
* Fix CI
* Transfer matching flow
* Better UI for transfers
* Auto transfer matching, approve, reject flow
* Mark transfers created from form as confirmed
* Account filtering
* Excluded rejected transfers from calculations
* Calculation tweaks with transfer exclusions
* Clean up migration
* Add data enrichment
* Make data enrichment optional for self-hosters
* Add categories to data enrichment
* Only update category and merchant if nil
* Fix name overrides
* Lint fixes
* Fix bug: Loan % doesn't allow exact rate
Fixes #1487
Change the step of the interest rate field to 0.005.
It's unlikely that we'll see interest rates in smaller increments.
* step 0.005
* migration for loan interest rates precision
* add new line
* Basic plaid data model and linking
* Remove institutions, add plaid items
* Improve schema and Plaid provider
* Add webhook verification sketch
* Webhook verification
* Item accounts and balances sync setup
* Provide test encryption keys
* Fix test
* Only provide encryption keys in prod
* Try defining keys in test env
* Consolidate account sync logic
* Add back plaid account initialization
* Plaid transaction sync
* Sync UI overhaul for Plaid
* Add liability and investment syncing
* Handle investment webhooks and process current day holdings
* Remove logs
* Remove "all" period select for performance
* fix amount calc
* Remove todo comment
* Coming soon for investment historical data
* Document Plaid configuration
* Listen for holding updates
* Initial pass at Synth-based ticker selection
* Update _tickers.turbo_stream.erb
* Functional combobox display
* A few cleanup steps
* Linter
* Prevent long strings
* Another step towards functional combobox
* Deprecated files
* Custom Combobox implementation
* Lint
* Test suite fixes
* Lint
* Make direct use of mic codes
* Update splits
* Update trades_test.rb
* First pass at security price reference
* Data cleanup
* Synth security fetching does better with a mic_code
* Update test suite
😭
* Update schema.rb
* Update generator.rb
* Initial pass at stock filtering
* Rough in filter
* Cleaning up security listing
* Tweak to search function
* Combobox tweaks
* Clean up search query
* Update trades test with combobox
* Update securities.yml
* Adds custom debounce timeout to autosubmit form controller
- There's a default debounce timeout based on element type
- You can parameterize debounce timeout on a data-attribute
* Adds corrections based on js_lint
* Restores sleep on test
---------
Co-authored-by: Nicolás Galdámez <nicolas.galdamez@unagisoftware.com>
* Move accountable partials
* Split accountables into separate view partials
* Fix test
* Add form to permitted partials
* Fix failing system tests
* Update new account modal views
* New sync algorithm implementation
* Update account system test assertions to match new behavior
* Fix off by 1 date error
* Revert new balance sync algorithm
* Add missing account overviews
* chore: add formatting and linting for javascript code relates to #1295
* use spaces instaed
* add to recommended extensions
* only enforce lint
* auto save
<!-- Copy this file to .cursorrules in the root of the project on your local machine if you'd like to use these rules with Cursor. -->
You are an expert in Ruby, Ruby on Rails, Postgres, Tailwind, Stimulus, Hotwire and Turbo and always use the latest stable versions of those technologies.
**Code Style and Structure**
- Write concise, technical Ruby code with accurate examples.
- Prefer iteration and modularization over code duplication.
- Use descriptive variable names with auxiliary verbs (e.g., is_loading, has_error).
- Use snake_case for file names and directories (e.g., app/models/user_profile.rb).
- Use CamelCase for classes and modules (e.g., UserProfile).
**Ruby on Rails Usage**
- Use Rails conventions for MVC structure.
- Favor scopes over class methods for queries.
- Use strong parameters for mass assignment protection.
- Use partials to DRY up views.
**Syntax and Formatting**
- Use two spaces for indentation.
- Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements.
- Use descriptive method names and keep methods short.
**Commenting Code**
- Write clear, concise comments to explain the purpose of individual functions and methods.
- Use comments to describe the intent and functionality of complex logic.
- Avoid redundant comments that state the obvious.
**UI and Styling**
- Use Tailwind CSS for styling.
- Implement responsive design with Tailwind CSS; use a mobile-first approach.
- Use Stimulus for JavaScript behavior.
- Use Turbo for asynchronous actions and updates.
**Performance Optimization**
- Use eager loading to avoid N+1 queries.
- Cache expensive queries and partials where appropriate.
- Use background jobs for long-running tasks.
- Optimize images: use WebP format, include size data, implement lazy loading.
**Database Querying & Data Model Creation**
- Use ActiveRecord for data querying and model creation.
- Favor database constraints and indexes for data integrity and performance.
- Use migrations to manage schema changes.
**Key Conventions**
- Follow Rails best practices for RESTful routing.
- Optimize for performance and security.
- Use environment variables for configuration.
- Write tests for models, controllers, and features.
**AI Guidelines**
- Follow the user’s requirements carefully & to the letter.
- Confirm, then write code!
- Suggest solutions that I didn't think about—anticipate my needs
- Focus on readability over being performant.
- Fully implement all requested functionality.
- Leave NO todo’s, placeholders or missing pieces.
- Don't say things like "additional logic can be added here" — instead, add the logic.
- Be concise. Minimize any other prose.
- Consider new technologies and contrarian ideas, not just the conventional wisdom
- If I ask for adjustments to code, do not repeat all of my code unnecessarily. Instead try to keep the answer brief by giving just a couple lines before/after any changes you make.
This rule serves as high-level documentation for how you should write code for the Maybe codebase.
## Project Tech Stack
- Web framework: Ruby on Rails
- Minitest + fixtures for testing
- Propshaft for asset pipeline
- Hotwire Turbo/Stimulus for SPA-like UI/UX
- TailwindCSS for styles
- Lucide Icons for icons
- OpenAI for AI chat
- Database: PostgreSQL
- Jobs: Sidekiq + Redis
- External
- Payments: Stripe
- User bank data syncing: Plaid
- Market data: Synth (our custom API)
## Project conventions
These conventions should be used when writing code for Maybe.
### Convention 1: Minimize dependencies, vanilla Rails is plenty
Dependencies are a natural part of building software, but we aim to minimize them when possible to keep this open-source codebase easy to understand, maintain, and contribute to.
- Push Rails to its limits before adding new dependencies
- When a new dependency is added, there must be a strong technical or business reason to add it
- When adding dependencies, you should favor old and reliable over new and flashy
### Convention 2: Leverage POROs and concerns over "service objects"
This codebase adopts a "skinny controller, fat models" convention. Furthermore, we put almost _everything_ directly in the `app/models/` folder and avoid separate folders for business logic such as `app/services/`.
- Organize large pieces of business logic into Rails concerns and POROs (Plain ole' Ruby Objects)
- While a Rails concern _may_ offer shared functionality (i.e. "duck types"), it can also be a "one-off" concern that is only included in one place for better organization and readability.
- When concerns are used for code organization, they should be organized around the "traits" of a model; not for simply moving code to another spot in the codebase.
- When possible, models should answer questions about themselves—for example, we might have a method, `account.balance_series` that returns a time-series of the account's most recent balances. We prefer this over something more service-like such as `AccountSeries.new(account).call`.
- Native HTML is always preferred over JS-based components
- Example 1: Use `<dialog>` element for modals instead of creating a custom component
- Example 2: Use `<details><summary>...</summary></details>` for disclosures rather than custom components
- Leverage Turbo frames to break up the page over JS-driven client-side solutions
- Example 1: A good example of turbo frame usage is in [application.html.erb](mdc:app/views/layouts/application.html.erb) where we load [chats_controller.rb](mdc:app/controllers/chats_controller.rb) actions in a turbo frame in the global layout
- Leverage query params in the URL for state over local storage and sessions. If absolutely necessary, utilize the DB for persistent state.
- Use Turbo streams to enhance functionality, but do not solely depend on it
- Format currencies, numbers, dates, and other values server-side, then pass to Stimulus controllers for display only
- Keep client-side code for where it truly shines. For example, @bulk_select_controller.js is a case where server-side solutions would degrade the user experience significantly. When bulk-selecting entries, client-side solutions are the way to go and Stimulus provides the right toolset to achieve this.
- Always use the `icon` helper in [application_helper.rb](mdc:app/helpers/application_helper.rb) for icons. NEVER use `lucide_icon` helper directly.
The Hotwire suite (Turbo/Stimulus) works very well with these native elements and we optimize for this.
### Convention 4: Optimize for simplicitly and clarity
All code should maximize readability and simplicity.
- Prioritize good OOP domain design over performance
- Only focus on performance for critical and global areas of the codebase; otherwise, don't sweat the small stuff.
- Example 1: be mindful of loading large data payloads in global layouts
- Example 2: Avoid N+1 queries
### Convention 5: Use ActiveRecord for complex validations, DB for simple ones, keep business logic out of DB
- Enforce `null` checks, unique indexes, and other simple validations in the DB
- ActiveRecord validations _may_ mirror the DB level ones, but not 100% necessary. These are for convenience when error handling in forms. Always prefer client-side form validation when possible.
- Complex validations and business logic should remain in ActiveRecord
description: This rule explains the system architecture and data flow of the Rails app
globs: *
alwaysApply: true
---
This file outlines how the codebase is structured and how data flows through the app.
This is a personal finance application built in Ruby on Rails. The primary domain entities for this app are outlined below. For an authoritative overview of the relationships, [schema.rb](mdc:db/schema.rb) is the source of truth.
## App Modes
The Maybe app runs in two distinct "modes", dictated by `Rails.application.config.app_mode`, which can be `managed` or `self_hosted`.
- "Managed" - in managed mode, the Maybe team operates and manages servers for users
- "Self Hosted" - in self hosted mode, users host the Maybe app on their own infrastructure, typically through Docker Compose. We have an example [docker-compose.example.yml](mdc:docker-compose.example.yml) file that runs [Dockerfile](mdc:Dockerfile) for this mode.
## Families and Users
- `Family` - all Stripe subscriptions, financial accounts, and the majority of preferences are stored at the [family.rb](mdc:app/models/family.rb) level.
- `User` - all [session.rb](mdc:app/models/session.rb) happen at the [user.rb](mdc:app/models/user.rb) level. A user belongs to a `Family` and can either be an `admin` or a `member`. Typically, a `Family` has a single admin, or "head of household" that manages finances while there will be several `member` users who can see the family's finances from varying perspectives.
## Currency Preference
Each `Family` selects a currency preference. This becomes the "main" currency in which all records are "normalized" to via [exchange_rate.rb](mdc:app/models/exchange_rate.rb) records so that the Maybe app can calculate metrics, historical graphs, and other insights in a single family currency.
## Accounts
The center of the app's domain is the [account.rb](mdc:app/models/account.rb). This represents a single financial account that has a `balance` and `currency`. For example, an `Account` could be "Chase Checking", which is a single financial account at Chase Bank. A user could have multiple accounts at a single institution (i.e. "Chase Checking", "Chase Credit Card", "Chase Savings") or an account could be a standalone account, such as "My Home" (a primary residence).
### Accountables
In the app, [account.rb](mdc:app/models/account.rb) is a Rails "delegated type" with the following subtypes (separate DB tables). Each account has a `classification` or either `asset` or `liability`. While the types are a flat hierarchy, below, they have been organized by their classification:
- Asset accountables
- [depository.rb](mdc:app/models/depository.rb) - a typical "bank account" such as a savings or checking account
- [investment.rb](mdc:app/models/investment.rb) - an account that has "holdings" such as a brokerage, 401k, etc.
- [crypto.rb](mdc:app/models/crypto.rb) - an account that tracks the value of one or more crypto holdings
- [property.rb](mdc:app/models/property.rb) - an account that tracks the value of a physical property such as a house or rental property
- [vehicle.rb](mdc:app/models/vehicle.rb) - an account that tracks the value of a vehicle
- [other_asset.rb](mdc:app/models/other_asset.rb) - an asset that cannot be classified by the other account types. For example, "jewelry".
- Liability accountables
- [credit_card.rb](mdc:app/models/credit_card.rb) - an account that tracks the debt owed on a credit card
- [loan.rb](mdc:app/models/loan.rb) - an account that tracks the debt owed on a loan (i.e. mortgage, student loan)
- [other_liability.rb](mdc:app/models/other_liability.rb) - a liability that cannot be classified by the other account types. For example, "IOU to a friend"
### Account Balances
An account [balance.rb](mdc:app/models/account/balance.rb) represents a single balance value for an account on a specific `date`. A series of balance records is generated daily for each account and is how we show a user's historical balance graph.
- For simple accounts like a "Checking Account", the balance represents the amount of cash in the account for a date.
- For a more complex account like "Investment Brokerage", the `balance` represents the combination of the "cash balance" + "holdings value". Each accountable type has different components that make up the "balance", but in all cases, the "balance" represents "How much the account is worth" (when `classification` is `asset`) or "How much is owed on the account" (when `classification` is `liability`)
All balances are calculated daily by [balance_calculator.rb](mdc:app/models/account/balance_calculator.rb).
### Account Holdings
An account [holding.rb](mdc:app/models/holding.rb) applies to [investment.rb](mdc:app/models/investment.rb) type accounts and represents a `qty` of a certain [security.rb](mdc:app/models/security.rb) at a specific `price` on a specific `date`.
For investment accounts with holdings, [base_calculator.rb](mdc:app/models/holding/base_calculator.rb) is used to calculate the daily historical holding quantities and prices, which are then rolled up into a final "Balance" for the account in [base_calculator.rb](mdc:app/models/account/balance/base_calculator.rb).
### Account Entries
An account [entry.rb](mdc:app/models/entry.rb) is also a Rails "delegated type". `Entry` represents any record that _modifies_ an `Account` [balance.rb](mdc:app/models/account/balance.rb) and/or [holding.rb](mdc:app/models/holding.rb). Therefore, every entry must have a `date`, `amount`, and `currency`.
The `amount` of an [entry.rb](mdc:app/models/entry.rb) is a signed value. A _negative_ amount is an "inflow" of money to that account. A _positive_ value is an "outflow" of money from that account. For example:
- A negative amount for a credit card account represents a "payment" to that account, which _reduces_ its balance (since it is a `liability`)
- A negative amount for a checking account represents an "income" to that account, which _increases_ its balance (since it is an `asset`)
- A negative amount for an investment/brokerage trade represents a "sell" transaction, which _increases_ the cash balance of the account
There are 3 entry types, defined as [entryable.rb](mdc:app/models/entryable.rb) records:
- `Valuation` - an account [valuation.rb](mdc:app/models/valuation.rb) is an entry that says, "here is the value of this account on this date". It is an absolute measure of an account value / debt. If there is an `Valuation` of 5,000 for today's date, that means that the account balance will be 5,000 today.
- `Transaction` - an account [transaction.rb](mdc:app/models/transaction.rb) is an entry that alters the account balance by the `amount`. This is the most common type of entry and can be thought of as an "income" or "expense".
- `Trade` - an account [trade.rb](mdc:app/models/trade.rb) is an entry that only applies to an investment account. This represents a "buy" or "sell" of a holding and has a `qty` and `price`.
### Account Transfers
A [transfer.rb](mdc:app/models/transfer.rb) represents a movement of money between two accounts. A transfer has an inflow [transaction.rb](mdc:app/models/transaction.rb) and an outflow [transaction.rb](mdc:app/models/transaction.rb). The Maybe system auto-matches transfers based on the following criteria:
- Must be from different accounts
- Must be within 4 days of each other
- Must be the same currency
- Must be opposite values
There are two primary forms of a transfer:
- Regular transfer - a normal movement of money between two accounts. For example, "Transfer $500 from Checking account to Brokerage account".
- Debt payment - a special form of transfer where the _receiver_ of funds is a [loan.rb](mdc:app/models/loan.rb) type account.
Regular transfers are typically _excluded_ from income and expense calculations while a debt payment is considered an "expense".
## Plaid Items
A [plaid_item.rb](mdc:app/models/plaid_item.rb) represents a "connection" maintained by our external data provider, Plaid in the "hosted" mode of the app. An "Item" has 1 or more [plaid_account.rb](mdc:app/models/plaid_account.rb) records, which are each associated 1:1 with an internal Maybe [account.rb](mdc:app/models/account.rb).
All relevant metadata about the item and its underlying accounts are stored on [plaid_item.rb](mdc:app/models/plaid_item.rb) and [plaid_account.rb](mdc:app/models/plaid_account.rb), while the "normalized" data is then stored on internal Maybe domain models.
## "Syncs"
The Maybe app has the concept of a [syncable.rb](mdc:app/models/concerns/syncable.rb), which represents any model which can have its data "synced" in the background. "Syncables" include:
- `Account` - an account "sync" will sync account holdings, balances, and enhance transaction metadata
- `PlaidItem` - a Plaid Item "sync" fetches data from Plaid APIs, normalizes that data, stores it on internal Maybe models, and then finally performs an "Account sync" for each of the underlying accounts created from the Plaid Item.
- `Family` - a Family "sync" loops through the family's Plaid Items and individual Accounts and "syncs" each of them. A family is synced once per day, automatically through [auto_sync.rb](mdc:app/controllers/concerns/auto_sync.rb).
Each "sync" creates a [sync.rb](mdc:app/models/sync.rb) record in the database, which keeps track of the status of the sync, any errors that it encounters, and acts as an "audit table" for synced data.
Below are brief descriptions of each type of sync in more detail.
### Account Syncs
The most important type of sync is the account sync. It is orchestrated by the account's `sync_data` method, which performs a few important tasks:
- Auto-matches transfer records for the account
- Calculates daily [balance.rb](mdc:app/models/account/balance.rb) records for the account from `account.start_date` to `Date.current` using [base_calculator.rb](mdc:app/models/account/balance/base_calculator.rb)
- Balances are dependent on the calculation of [holding.rb](mdc:app/models/holding.rb), which uses [base_calculator.rb](mdc:app/models/account/holding/base_calculator.rb)
- Enriches transaction data if enabled by user
An account sync happens every time an [entry.rb](mdc:app/models/entry.rb) is updated.
### Plaid Item Syncs
A Plaid Item sync is an ETL (extract, transform, load) operation:
1. [plaid_item.rb](mdc:app/models/plaid_item.rb) fetches data from the external Plaid API
2. [plaid_item.rb](mdc:app/models/plaid_item.rb) creates and loads this data to [plaid_account.rb](mdc:app/models/plaid_account.rb) records
3. [plaid_item.rb](mdc:app/models/plaid_item.rb) and [plaid_account.rb](mdc:app/models/plaid_account.rb) transform and load data to [account.rb](mdc:app/models/account.rb) and [entry.rb](mdc:app/models/entry.rb), the internal Maybe representations of the data.
### Family Syncs
A family sync happens once daily via [auto_sync.rb](mdc:app/controllers/concerns/auto_sync.rb). A family sync is an "orchestrator" of Account and Plaid Item syncs.
## Data Providers
The Maybe app utilizes several 3rd party data services to calculate historical account balances, enrich data, and more. Since the app can be run in both "hosted" and "self hosted" mode, this means that data providers are _optional_ for self hosted users and must be configured.
Because of this optionality, data providers must be configured at _runtime_ through [registry.rb](mdc:app/models/provider/registry.rb) utilizing [setting.rb](mdc:app/models/setting.rb) for runtime parameters like API keys:
There are two types of 3rd party data in the Maybe app:
1. "Concept" data
2. One-off data
### "Concept" data
Since the app is self hostable, users may prefer using different providers for generic data like exchange rates and security prices. When data is generic enough where we can easily swap out different providers, we call it a data "concept".
Each "concept" has an interface defined in the `app/models/provider/concepts` directory.
```
app/models/
exchange_rate/
provided.rb # <- Responsible for selecting the concept provider from the registry
provider.rb # <- Base provider class
provider/
registry.rb <- Defines available providers by concept
concepts/
exchange_rate.rb <- defines the interface required for the exchange rate concept
synth.rb # <- Concrete provider implementation
```
### One-off data
For data that does not fit neatly into a "concept", an interface is not required and the concrete provider may implement ad-hoc methods called directly in code. For example, the [synth.rb](mdc:app/models/provider/synth.rb) provider has a `usage` method that is only applicable to this specific provider. This should be called directly without any abstractions:
```rb
class SomeModel < Application
def synth_usage
Provider::Registry.get_provider(:synth)&.usage
end
end
```
## "Provided" Concerns
In general, domain models should not be calling [registry.rb](mdc:app/models/provider/registry.rb) directly. When 3rd party data is required for a domain model, we use the `Provided` concern within that model's namespace. This concern is primarily responsible for:
- Choosing the provider to use for this "concept"
- Providing convenience methods on the model for accessing data
For example, [exchange_rate.rb](mdc:app/models/exchange_rate.rb) has a [provided.rb](mdc:app/models/exchange_rate/provided.rb) concern with the following convenience methods:
This exposes a generic access pattern where the caller does not care _which_ provider has been chosen for the concept of exchange rates and can get a predictable response:
```rb
def access_patterns_example
# Call exchange rate provider directly
ExchangeRate.provider.fetch_exchange_rate(from: "USD", to: "CAD", date: Date.current)
# Call convenience method
ExchangeRate.sync_provider_rates(from: "USD", to: "CAD", start_date: 2.days.ago.to_date)
end
```
## Concrete provider implementations
Each 3rd party data provider should have a class under the `Provider::` namespace that inherits from `Provider` and returns `with_provider_response`, which will return a `Provider::ProviderResponse` object:
```rb
class ConcreteProvider < Provider
def fetch_some_data
with_provider_response do
ExampleData.new(
example: "data"
)
end
end
end
```
The `with_provider_response` automatically catches provider errors, so concrete provider classes should raise when valid data is not possible:
```rb
class ConcreteProvider < Provider
def fetch_some_data
with_provider_response do
data = nil
# Raise an error if data cannot be returned
raise ProviderError.new("Could not find the data you need") if data.nil?
This rule describes how to write Stimulus controllers.
- **Use declarative actions, not imperative event listeners**
- Instead of assigning a Stimulus target and binding it to an event listener in the initializer, always write Controllers + ERB views declaratively by using Stimulus actions in ERB to call methods in the Stimulus JS controller. Below are good vs. bad code.
- **Keep Stimulus controllers lightweight and simple**
- Always aim for less than 7 controller targets. Any more is a sign of too much complexity.
- Use private methods and expose a clear public API
- **Keep Stimulus controllers focused on what they do best**
- Domain logic does NOT belong in a Stimulus controller
- Stimulus controllers should aim for a single responsibility, or a group of highly related responsibilities
- Make good use of Stimulus's callbacks, actions, targets, values, and classes
- **Component controllers should not be used outside the component**
- If a Stimulus controller is in the app/components directory, it should only be used in its component view. It should not be used anywhere in app/views.
Use this rule to learn how to write tests for the Maybe codebase.
Due to the open-source nature of this project, we have chosen Minitest + Fixtures for testing to maximize familiarity and predictability.
- **General testing rules**
- Always use Minitest and fixtures for testing, NEVER rspec or factories
- Keep fixtures to a minimum. Most models should have 2-3 fixtures maximum that represent the "base cases" for that model. "Edge cases" should be created on the fly, within the context of the test which it is needed.
- For tests that require a large number of fixture records to be created, use Rails helpers to help create the records needed for the test, then inline the creation. For example, [entries_test_helper.rb](mdc:test/support/entries_test_helper.rb) provides helpers to easily do this.
- **Write minimal, effective tests**
- Use system tests sparingly as they increase the time to complete the test suite
- Only write tests for critical and important code paths
- Write tests as you go, when required
- Take a practical approach to testing. Tests are effective when their presence _significantly increases confidence in the codebase_.
Below are examples of necessary vs. unnecessary tests:
```rb
# GOOD!!
# Necessary test - in this case, we're testing critical domain business logic
- Distinguish between commands and query methods. Test output of query methods; test that commands were called with the correct params. See an example below:
```rb
class ExampleClass
def do_something
result = 2 + 2
CustomEventProcessor.process_result(result)
result
end
end
class ExampleClass < ActiveSupport::TestCase
test "boundaries are tested correctly" do
result = ExampleClass.new.do_something
# GOOD - we're only testing that the command was received, not internal implementation details
# The actual tests for CustomEventProcessor belong in a different test suite!
- You are writing styles in a JavaScript Stimulus controller
## Rules for AI (mandatory)
The codebase uses TailwindCSS v4.x (the newest version) with a custom design system defined in [maybe-design-system.css](mdc:app/assets/tailwind/maybe-design-system.css)
- Always start by referencing [maybe-design-system.css](mdc:app/assets/tailwind/maybe-design-system.css) to see the base primitives, functional tokens, and component tokens we use in the codebase
- Always prefer using the functional "tokens" defined in @maybe-design-system.css when possible.
- Example 1: use `text-primary` rather than `text-white`
- Example 2: use `bg-container` rather than `bg-white`
- Example 3: use `border border-primary` rather than `border border-gray-200`
- Never create new styles in [maybe-design-system.css](mdc:app/assets/tailwind/maybe-design-system.css) or [application.css](mdc:app/assets/tailwind/application.css) without explicitly receiving permission to do so
# Optional: Synth API Key for exchange rates + stock prices
# (you can also set this in your self-hosted settings page)
# Get it here: https://synthfinance.com/
SYNTH_API_KEY=
# Custom port config
# For users who have other applications listening at 3000, this allows them to set a value puma will listen to.
PORT=3000
# Exchange Rate API
# This is used to convert between different currencies in the app. We use Synth, which is a Maybe product. You can sign up for a free account at synthfinance.com.
SYNTH_API_KEY=
# SMTP Configuration
# This is only needed if you intend on sending emails from your Maybe instance (such as for password resets or email financial reports).
# Resend.com is a good option that offers a free tier for sending emails.
@ -28,60 +48,28 @@ POSTGRES_USER=postgres
# This is the domain that your Maybe instance will be hosted at. It is used to generate links in emails and other places.
APP_DOMAIN=
## Error and Performance Monitoring
# The app uses Sentry to monitor errors and performance. In reality, you likely don't need this unless you're deploying Maybe to many users.
SENTRY_DSN=
# If enabled, an invite code generated by `rake invites:create` is required to sign up as a new user.
# This is useful for controlling who can sign up for your Maybe instance.
REQUIRE_INVITE_CODE=false
# Enables self hosting features (should be set to true for most folks)
SELF_HOSTED=true
# The hosting platform used to deploy the app (e.g. "render")
# `localhost` (or unset) is used for local development and testing
HOSTING_PLATFORM=localhost
# Secret key used to encrypt credentials (https://api.rubyonrails.org/v7.1.3.2/classes/Rails/Application.html#method-i-secret_key_base)
# Has to be a random string, generated eg. by running `openssl rand -hex 64`
# UPGRADES_ENABLED: Enables Upgrader class functionality.
# UPGRADES_MODE: Controls how the app will upgrade. `manual` means the user must manually upgrade the app. `auto` means the app will upgrade automatically (great for self-hosting)
# UPGRADES_TARGET: Controls what the app will upgrade to. `release` means the app will upgrade to the latest release. `commit` means the app will upgrade to the latest commit.
#
UPGRADES_ENABLED=false # unless editing the flow, you should keep this `false` locally in development
about: Open a bug report when you experience broken functionality within the latest
version of the Maybe app
title: 'Bug: [Add descriptive title here]'
labels: ''
assignees: ''
---
**Describe the bug**
## Before you start (required)
### General checklist
- [ ] I have removed personal / sensitive data from screenshots and logs
- [ ] I have searched [existing issues](https://github.com/maybe-finance/maybe/issues?q=is:issue) and [discussions](https://github.com/maybe-finance/maybe/discussions) to ensure this is not a duplicate issue
### How are you using Maybe?
- [ ] I am a paying Maybe customer (hosted version)
- Paying Maybe users can also open requests in Intercom (if there is sensitive info involved)
- [ ] I am a self-hosted user
### Self hoster checklist
_Paying, hosted users should delete this entire section._
If you are a self-hosted user, please complete all of the information below. Issues with incomplete information will be marked as `Needs Info` to help our small team prioritize bug fixes.
- Self hosted app commit SHA (find in user menu): [enter commit sha here]
- [ ] I have confirmed that my app's commit is the latest version of Maybe
- Where are you hosting?
- [ ] Render
- [ ] Docker Compose
- [ ] Umbrel
- [ ] Other (please specify)
---
## Bug description
A clear and concise description of what the bug is.
**To Reproduce**
### To Reproduce
Be as specific as possible so Maybe maintainers can quickly reproduce the bug you're experiencing.
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
### Expected behavior
**Screenshots / Recordings**
If applicable, add screenshots or short video recordings to help show the bug in more detail.
What is the intended behavior that you would expect?
**Additional context**
Add any other context about the problem here.
### Screenshots and/or recordings
We highly recommend providing additional context with screenshots and/or screen recordings. This will _significantly_ improve the chances of the bug being addressed and fixed quickly.
- Is this a feature request? Please [open a feature request discussion](https://github.com/maybe-finance/maybe/discussions/new?category=feature-requests).
- Do you need help or have a question? Please [open a discussion](https://github.com/maybe-finance/maybe/discussions/new/choose) or [join our Discord](https://link.maybe.co/discord) and post to the "help" channel.
### Is this a bug?
----------------------
A bug is _broken functionality_ of the app (i.e. it prevents you from using the app). For bugs, please use the ["Bug Report" template](https://github.com/maybe-finance/maybe/issues) instead.
**Is this issue related to a problem? Please describe.**
### Is this a bug with _sensitive info_?
**Describe the work that needs to be done to address this issue**
If you are a _paying_ Maybe user, you can open a support request in Intercom.
**Additional context**
### Is this a feature request?
A feature request is functionality that you would like that is not already on our [Roadmap](https://github.com/maybe-finance/maybe/wiki/Roadmap).
All feature requests should be opened in a [Feature request Discussion](https://github.com/maybe-finance/maybe/discussions/categories/feature-requests).
Be sure to search existing discussions prior to opening a new feature request.
### Is this related to Docker and/or hosting for self hosting?
If you are having a Docker configuration issue, please do not open a Github issue unless you've identified a bug in our Dockerfile. To get help with self hosting, there are several options:
- **First**: Read our [Docker hosting guide](https://github.com/maybe-finance/maybe/tree/main/docs/hosting/docker.md) and follow it step-by-step
- Open a [Docker Discussion](https://github.com/maybe-finance/maybe/discussions/categories/docker-compose-hosting)
---
## Issue description
If your issue does not fall into the categories above, please provide a **descriptive and complete** overview of your issue.
Only proceed with pull request creation if ALL checks pass.
## General Development Rules
### Authentication Context
- Use `Current.user` for the current user. Do NOT use `current_user`.
- Use `Current.family` for the current family. Do NOT use `current_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**
@ -4,6 +4,8 @@ It means so much that you're interested in contributing to Maybe! Seriously. Tha
## House Rules
- Before contributing, familiarize yourself with our project conventions. You should read through our [Project Conventions Rule](https://github.com/maybe-finance/maybe/.cursor/rules/project-conventions.mdc), which is intended for LLMs, but is also an excellent primer on how we write code for Maybe.
- While totally optional, consider using Cursor + VSCode as it will automatically apply our project conventions to your code via the `.cursor/rules` directory.
- Before contributing, please check if it already exists in [issues](https://github.com/maybe-finance/maybe/issues) or [PRs](https://github.com/maybe-finance/maybe/pulls)
- Given the speed at which we're moving on the codebase, we don't assign issues or "give" issues to anyone.
- When multiple PRs are submitted for the same issue, we take the one that most succinctly & efficiently solves a given problem and stays within the scope of work.
<sup><i>(Note: The image above is a mockup of what we're working towards. We're rapidly approaching the functionality shown, but not all of the parts are ready just yet.)</i></sup>
With data-heavy apps, inevitably, there are performance issues. We've set up a public dashboard showing the problematic requests, along with the stacktraces to help debug them.
Any contributions that help improve performance are very much welcome.