* Add "Money In / Out" dashboard widget
Adds a new dashboard section showing a monthly bar chart of cash
activity alongside a summary card (net balance, income, expenses),
with per-widget month navigation and account filtering.
- IncomeStatement#totals_for computes income/expense totals for an
arbitrary period, optionally scoped to a set of account ids
- New bar_chart_controller.js (D3) renders the monthly bars
- Income/expense rows link through to filtered transactions
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Fix tooltip/dropdown positioning and split money flow chart by income/expense
The widget's @container wrapper established a new containing block for
position:fixed descendants, so the month-picker and account-filter
menus (floating-ui, strategy: fixed) and the D3 tooltip (container-
relative coordinates) rendered away from their trigger/bar. Drop
@container in favor of regular viewport breakpoints for this
full-width widget, and position the tooltip with page-relative
coordinates like the other chart controllers.
Also replace the single combined bar per month with a grouped
expense/income pair (red/green, with a legend) so each month's
inflow and outflow are visible independently.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Cap and scroll the money flow widget's month picker
Add an optional max_height to DS::Menu (opt-in, backward compatible)
so a long item list scrolls inside a fixed-height panel instead of
overflowing the viewport. Use it for the money flow widget's 12-month
picker.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Use design system tokens for income/expense indicators in money flow widget
Swap the four bg-green-500/bg-red-500 dot indicators (legend + income/expense row links) in the money flow widget for bg-success/bg-destructive, matching the "functional tokens only" rule. The same partial already uses
text-success/text-destructive for the balance figure, and bg-success/ bg-destructive are already established elsewhere (DS::Alert, budget_categories/_budget_category.html.erb).
* Fix future-month 500 and pending-transaction link mismatch in money flow widget
Two CI review findings on the money flow widget:
- A future month passed via ?money_flow_month= (e.g. a bookmarked/hand-edited URL) made end_date earlier than month_start once capped at Date.current, which Period.custom rejects, causing a 500. money_flow_month_param now clamps future months to the current month, same as it already does for malformed input.
- The income/expense row links passed type/date/account filters but no status, so Transaction::Search included pending transactions even though the displayed totals (IncomeStatement#totals_for) exclude them via excluding_pending. Add status: ["confirmed"] to both links so the linked list matches the card total.
Also strengthens the widget's controller tests: asserts the highlighted bar's actual income/expense values instead of just its presence, and adds a regression test proving an account id outside the current user's accessible accounts is dropped (falls back to the unfiltered state) rather than leaking or erroring.
* Fix account filter eligibility and SVG dark mode fill in money flow widget
Two more CI review findings on the money flow widget:
The account filter iterated over all visible/accessible accounts, a broader set than IncomeStatement actually counts (accounts excluded from reports, tax-advantaged accounts like 401k/IRA, or shared accounts not included in the user's finances). Selecting one of these silently computed to zero while its drill-down link could still list its transactions. Add IncomeStatement#eligible_accounts, mirroring the same criteria already applied in the totals SQL, and use it for both the checkbox list and the account_ids intersection.
The D3 axis tick text elements used text-primary/text-secondary, which set CSS color, not SVG fill, so labels rendered with the default black fill and were unreadable in dark mode. Add fill-current, matching the pattern already used in sankey_chart_controller.js.
Adds controller/model tests for eligible_accounts (excluded from the account filter, ignored when passed as a filter id, excluded from totals) and verifies the dark-mode fill fix visually.
* Extract duplicated bar-chart JSON parsing into a test helper
The css_select("[data-controller='bar-chart']").first + JSON.parse(chart["data-bar-chart-data-value"]) pattern was repeated across four money flow widget tests in pages_controller_test.rb. Extract it into a private money_flow_bars helper and use it everywhere instead.
* Preserve eligible account scope in money flow drill-down links when unfiltered
The income/expense drill-down links used money_flow_data[:account_ids] directly, which is nil in the widget's default unfiltered state, so .compact dropped the account filter entirely from the link. TransactionsController treats an absent account_ids as all accessible accounts, a broader set than IncomeStatement#eligible_accounts (which excludes tax-advantaged, excluded-from-reports, and non-finance shared accounts). Users with any such account could click a displayed total and see transactions that were never counted in it.
Use selected_account_ids (already computed as money_flow_data[:account_ids] || accounts.map(&:id)) for both links instead, so they always pin to the same eligible accounts backing the total, filtered or not. Left the month-picker link on money_flow_data[:account_ids] so navigating months while unfiltered doesn't bloat the URL with every eligible account id.
Adds a regression test confirming the default (unfiltered) links include account_ids and exclude an ineligible account's id.
* Refine Money In/Out dashboard widget
- 6-month window (was 3), half-width default with responsive stack, income-first
bars, 2px floor + faded in-progress (partial) month
- Expense series/figures neutral (gray/text-primary) — app reserves red for
negative/overspend; neutral zero balance
- Account filter: outline DS::Button + list-filter icon trigger with in-panel
search (DS::SearchInput + list-filter), matching the app's filter convention
- i18n search_accounts (en + fr); bump money_flow bar-count test 3 -> 6
* Clarify Money In/Out scope: month label, 6-month caption, filter "All"
Addresses confusion between the widget's own month picker and the dashboard's
global period, and between the picked month and the 6-month chart span.
- Card now headed with the selected month (e.g. "July 2026") so it's clear the
totals below are that month's, driven by the picker
- Chart legend row captioned "Last N months" so the trailing window is explicit
- Info tooltip by the month picker: the widget scopes to the month you pick
here, independent of the dashboard's top-level period
- Account filter trigger reads "All accounts" when nothing is filtered out,
instead of "Filter accounts (N)"
- i18n (en + fr) for the new strings
* Match tooltip expense dot to the gray bar palette
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Guillem Arias <accounts@gariasf.com>
Adds a toggle to mark accounts as excluded from all financial reports
while keeping them active and visible individually.
- Migration: add exclude_from_reports boolean column to accounts
- Model: included_in_reports scope
- BalanceSheet: filter excluded from ClassificationGroup and AccountGroup totals,
HistoricalAccountScope, AccountRow flag
- IncomeStatement: exclude via SQL fragments in Totals, FamilyStats, CategoryStats
- InvestmentStatement/Budget: chain included_in_reports scope
- ReportsController: filter in breakdown view, export queries, trades
- AccountsController: toggle_exclude_from_reports action + route
- UI: DS::Toggle in account form, eye-off indicator in sidebar, menu toggle items
- Locale: all labels in en.yml
- Tests: model scope, controller toggle, income statement/balance sheet filtering
- 5070 tests pass (0 failures, 0 regressions)
* perf(reports): avoid residual category lazy loads
* test(reports): reuse SQL query capture helper
Move the reports SQL capture regression helper into test/support and document the category loading and budget reload choices called out in review.
* fix IncomeStatement performance issue
* fix gemini-code-assist FEEDBACK
* Address reports review: nil-safe sort and explicit income_statement
* run migrations after pulling remote updates
---------
Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
* fix(family): include HSA depository accounts in tax-advantaged exclusion
`Family#tax_advantaged_account_ids` is the ID set the budget engine uses
to exclude tax-advantaged account activity from income / expense /
cashflow totals. PR #724 originated this method and explicitly listed HSA
in scope ("401k, IRA, HSA, Roth IRA, etc."), but the implementation only
joined `investments` and `cryptos`. `Depository::SUBTYPES["hsa"]` already
exists and Plaid routes `depository.hsa` accounts to `Depository` (not
`Investment`) via `PlaidAccount::TypeMappable`, so HSA cash accounts were
silently absent from the filter and HSA contributions/withdrawals showed
up in household expense totals.
- Add `Depository::TAX_ADVANTAGED_SUBTYPES = %w[hsa]` + a `tax_treatment`
instance method (mirrors `Investment#tax_treatment`).
`TaxTreatable#tax_advantaged?` picks it up via the existing `respond_to?`
check, so `Account#tax_advantaged?` now flips to true for HSA depositories
without touching the concern.
- Extract `Family#tax_advantaged_depository_account_ids` (private) that
joins `depositories` and filters by `Depository::TAX_ADVANTAGED_SUBTYPES`,
mirroring the existing `investment_ids` / `crypto_ids` extraction style.
Append it to the union in `tax_advantaged_account_ids`.
Behavior change is scoped: HSA depositories now exit the budget engine via
the same path as 401k / IRA / Roth IRA. Non-HSA depositories continue to
report `tax_treatment: :taxable` (was `nil`), so `Account#taxable?` returns
true for them via the existing `== :taxable` clause — no expense-total
change for Checking / Savings / CD / Money Market.
Tests:
- `test/models/account_test.rb` — rewrite "tax_treatment returns nil for
non-investment accounts" (was implicitly testing the bug) into two tests:
one asserting `:taxable` for non-HSA depositories and a new sibling
asserting `nil` for accountables that genuinely lack `tax_treatment`
(CreditCard). Add an HSA-depository test asserting `tax_advantaged?`.
- `test/models/income_statement_test.rb` — new test asserting an HSA
depository is included in `tax_advantaged_account_ids` and a `savings`
depository is not.
No schema migration, no controller change, no provider integration change.
* [200~fix(family): return nil for non-HSA depository tax_treatment
* optimize net_category_totals() by using memoized cache
* fix issue - net_category_totals cache is never populated - suggested by coderabbitAI
* fix 422 error for service-worker
* remove warning of [assigned but unused variables] - income_statement.rb
* remove warnings of [assigned but unused] from Prism - income_statement_test.rb
* add some measurements to improve docstring coverage, follow CodeRabbit recommendation
* attach Skylight monitoring for dev env as well - use my own Skylight auth token
* integrate Skylight with my own account auth token for local benchmark
* fix PR review suggestion - Move fallback release-note copy to i18n keys
* follow PR review - Fix changelog GitHub fetch timeout bounding
* FIX - Variable shadowing; Prefer stubbing the specific instance over any_instance.expects
* fix CodeRabbit feedback - Reusing the same stub for both classifications hides a contract mismatch
* fix CodeRabbit FEEDBACK - Reconsider enabling Skylight by default in development
* fix CodeRabbitAI FEEDBACK - reconsider unconditionally enabling Skylight in development
* fix Security scan FEEDBACK before PR merge
* fix jjmata feedback
* third party provider scoping
* Simplify logic and allow only admins to mange providers
* Broadcast fixes
* FIX tests and build
* Fixes
* Reviews
* Scope merchants
* DRY fixes
* Make categories global
This solves us A LOT of cash flow and budgeting problems.
* Update schema.rb
* Update auto_categorizer.rb
* Update income_statement.rb
* FIX budget sub-categories
* FIX sub-categories and tests
* Add 2 step migration
- Removed `exclude_from_cashflow` attribute across models, controllers, and views.
- Updated queries to rely solely on the `excluded` flag for filtering transactions and entries.
- Simplified migration by consolidating `exclude_from_cashflow` functionality into the existing `excluded` toggle.
- Refactored related tests to remove outdated logic and ensured compatibility with the updated implementation.
- Introduced `InvestmentActivityDetector` to mark internal investment activity as excluded from cashflow and assign appropriate labels.
- Added `exclude_from_cashflow` flag to `entries` and `investment_activity_label` to `transactions` with migrations.
- Implemented rake tasks to backfill and clear investment activity labels.
- Updated `PlaidAccount::Investments::TransactionsProcessor` to map Plaid transaction types to labels.
- Included comprehensive test coverage for new functionality.
* 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>
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