* perf(accounts): kill sidebar/sparkline N+1s and cache the sidebar
The dashboard was issuing hundreds of per-account `SELECT 1` and
polymorphic `accountable` lookups on every page load. Sidebar render
alone hit the DB ~50–100× and ran twice per request (mobile + desktop).
Changes:
- AccountableSparklinesController: short-circuit
`requires_normalized_aggregation?` to Investment/Crypto only and
collapse the per-account `linked?` loop into a single `EXISTS`. Kills
the N+1 `AccountProvider Exists?` queries on every sparkline endpoint.
- BalanceSheet::AccountTotals#visible_accounts: preload `:accountable`,
`:plaid_account`, `:simplefin_account`, and
`account_providers: :provider` so the sidebar's
`account.subtype` / `account.linked?` / `account.provider` calls don't
trigger per-row polymorphic loads.
- AccountsController#index: same preloads on `@manual_accounts`.
- accounts/index/_account_groups.erb: extend the existing `Preloader`
call to batch-load accountable + provider associations so the
per-provider-item partials (Plaid, SimpleFIN, Coinbase, etc.) stop
re-issuing N+1s when rendering account rows on /accounts.
- accounts/_account_sidebar_tabs.html.erb: wrap the partial in a
`cache` block keyed on the family's data-version, the current user,
shares fingerprint, locale, mobile flag, active tab, and a
path-derived "current account" component (`sidebar_active_account_id`
helper). The sidebar is rendered on every page in the layout
(twice — mobile + desktop drawers), so most navigations now serve
the cached fragment instead of re-walking accounts/balances.
Local impact (DZG family, 23 accounts, 6.1k transactions):
- Dashboard `/`: ~6.5s → ~1.95s
- /accounts: ~2.7s → ~0.85s on warm cache
- /accountable_sparklines/*: per-request N+1s eliminated; remaining
cost is request boilerplate which can be addressed by bumping
`RAILS_MAX_THREADS` (the dashboard fans out 5 sparkline turbo frames
in parallel and Puma's default 3 threads serialize them).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(perf): address PR review on sidebar/sparkline perf changes
- AccountableSparklinesController#requires_normalized_aggregation?
also matches legacy plaid_account_id / simplefin_account_id links,
not just new-style account_providers, so investment/crypto accounts
in the legacy linking state still get LinkedInvestmentSeriesNormalizer
applied (Codex P1 / CodeRabbit major).
- Sidebar share fingerprint includes both `count` and `max(updated_at)`
so deleting a non-most-recent AccountShare invalidates the cached
fragment for users who lost access (Codex P1).
- Move the sidebar cache-key construction (incl. the AccountShare
query) from the ERB into a new `account_sidebar_tabs_cache_key`
helper, per the project's "no heavy logic in ERB" rule (CodeRabbit).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(perf): address human review on perf PR
- Account.linked: new SQL-level scope mirroring `Account#linked?` so
the controller and per-instance method share one definition. Removes
the duplicated raw SQL string in
`AccountableSparklinesController#requires_normalized_aggregation?`,
which now reads `accounts.linked.exists?` (jjmata, sure-design).
- AccountsHelper: move `sidebar_active_account_id` and
`account_sidebar_tabs_cache_key` out of `ApplicationHelper`. The
cache-key helper also collapses the AccountShare `count` + `max(updated_at)`
fingerprint into a single `pick` query so we don't pay two round-trips
on every render (jjmata, sure-design).
- test/models/account/linkable_test.rb: pin the `Account.linked` scope
against all three link types (account_providers, legacy plaid_account,
legacy simplefin_account) so any future schema change that diverges
the SQL definition from `linked?` breaks a test instead of silently
serving wrong sparkline aggregations (sure-design).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(perf): correct shares cache fingerprint on raw-SQL pick
`pick(Arel.sql("count(*), max(updated_at)"))` passes a single comma-
separated fragment, which Rails returns as a String (per the documented
behavior of `pluck` with SQL fragments). The previous `max_at&.to_i`
silently truncated `"2025-05-06 12:34:56.789 UTC"` to `2025`, so the
sidebar cache key would not change for share `updated_at` movements
within the same calendar year — including share deletions — leaving
revoked users with a stale sidebar until the 12h expiry.
Pass the aggregates as two separate `Arel.sql` args and just concatenate
the raw String values into the cache key. The values only need to be
stable for a given DB state, not numerically meaningful.
Caught by CodeRabbit on PR #1683.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <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
* 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
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