Commit Graph

2693 Commits

Author SHA1 Message Date
Tân Một Nắng
7ad287c4db feat(i18n): add Vietnamese (vi) locale (#2043)
* feat(i18n): add Vietnamese (vi) locale

- Add "vi" to SUPPORTED_LOCALES in LanguagesHelper
- Create 110 vi.yml translation files across all locale directories:
  breadcrumbs, models (27 files), views (75+ files), mailers, doorkeeper
- All files validated as valid YAML with no interpolation mismatches
- Fallback to English for any untranslated keys via existing fallbacks config

* fix(i18n): apply CodeRabbit review fixes to Vietnamese locale

- breadcrumbs: securities → Chứng khoán (was duplicate of security/Bảo mật)
- period label_short: distinguish years from days (5Năm/10Năm/NĐN vs N for days)
- imports: localize "Hover" → "Di chuột" in error hint
- recurring_transactions: transfer_feature_disabled uses "Chuyển khoản" not "Giao dịch"
- reports: YTD period labels "NNN %{year}" → "Từ đầu năm %{year}"
- sso_identities: explicit unlink success message instead of generic "Thành công"
- simplefin_items: standardize branding SimpleFin → SimpleFIN (4 occurrences)
- transactions: rename duplicate YAML key merge_duplicate → merge_duplicate_button
2026-05-30 01:32:30 +02:00
Jeff
84ad60d541 fix(ai-chat): disable submit on empty input instead of surfacing 'Content missing' (#1697) (#1872)
* fix(ai-chat): disable submit on empty input instead of surfacing 'Content missing' (#1697)

Empty-input clicks on the chat send button posted the form, which then
failed Message's `validates :content, presence: true` and surfaced
`Content missing` to the user. The right shape per ChatGPT / Claude
UX is to prevent the submission entirely until the input contains
non-whitespace content.

Add a `submit` target on the icon button and have the existing chat
Stimulus controller:

- Initialise the button to `disabled` when no `message_hint` is set.
- Toggle disabled on every input event (re-using the existing
  `autoResize` handler) based on `input.value.trim().length > 0`.
- Pre-clear disabled when a sample question is injected.
- Short-circuit the Enter-key submit path on empty content so keyboard
  users hit the same gate.

Closes #1697

* fix(ai-chat): drop server-rendered disabled attr, keep JS-driven gate (#1697)

Codex review (P1) + @JSONbored + @jjmata called out that rendering the
submit button with `disabled: message_hint.blank?` would lock the
form out for users without working JS (asset failure, exception during
Stimulus init, etc.). Server-side validation already catches empty
submits with a real error message — server-disabling the button on top
of that turns a soft fail into a hard one.

Remove the server-render `disabled:` attribute. The chat Stimulus
controller still runs `#updateSubmitState()` on connect, on every
input event, and after sample-question injection, and `handleInputKeyDown`
still short-circuits empty Enter submits. With JS the UX is identical;
without JS the form keeps its fallback path.

---------

Co-authored-by: jeffrey701 <jeffrey701@users.noreply.github.com>
2026-05-30 01:30:11 +02:00
Meng
f397b1a722 chore(i18n): complete Chinese locale coverage (#2010)
* i18n: expand Chinese locale batch 1

* i18n: expand Chinese locale batch 2

* i18n: expand Chinese locale batch 3

* i18n: expand Chinese locale batch 4

* i18n: expand Chinese locale batch 5

* i18n: expand Chinese locale batch 6

* i18n: expand Chinese locale batch 7

* i18n: expand Chinese locale batch 8

* i18n: expand Chinese locale batch 9

* i18n: expand Chinese locale batch 10

* i18n: expand Chinese locale batch 11

* i18n: expand Chinese locale batch 12

* i18n: expand Chinese locale batch 13

* i18n: expand Chinese locale batch 14

* i18n: expand Chinese locale batch 15

* i18n: finish Chinese locale coverage

* fix(i18n): quote zh-CN interpolation scalars

* fix(i18n): refine zh-CN locale wording

* fix(i18n): normalize Indexa Capital zh-CN copy

* fix(i18n): address zh-CN review feedback

* fix(i18n): resolve remaining zh-CN review items

* fix(i18n): refine remaining zh-CN copy

---------

Co-authored-by: Meng <19986978+ashanzzz@users.noreply.github.com>
Co-authored-by: Hermes Agent <hermes@local>
2026-05-30 01:06:43 +02:00
ghost
adabc55937 ci(preview): isolate preview deployment tooling (#2025)
* ci(preview): isolate deployment tooling

Keep PR preview source separate from the deployment toolchain by building a temporary deploy workspace from base-revision preview metadata and PR-owned source.

Add a focused CI guard so future preview workflow edits preserve the trusted tooling split.

* ci(preview): harden workflow guard checks

Address CodeRabbit feedback by making the preview deploy guard assertions collision-proof and more resilient to equivalent GitHub Actions expression and workspace path forms.

* ci(preview): normalize workflow guard paths

* ci(preview): defer workflow guard validation

* revert(preview): restore workflow guard validation

* ci(preview): gate preview deployments
2026-05-30 00:54:20 +02:00
Wes
7685650e63 feat(assistant): add get_budget function for budget tracking (#1966)
* feat(assistant): add get_budget function for budget tracking

Exposes the existing Budget / BudgetCategory pacing data to the AI
assistant as a `get_budget` function. Supports a target month and an
optional `prior_months` window for trend comparison, with the response
shape matching the budget UI (totals, income, per-category status,
suggested daily spend on the current month).

Honors custom month_start_day by matching `Budget.param_to_date`
semantics for explicit slug input, so `month` round-trips with the
response's `month` field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(assistant): use fixture reference for Food & Drink lookup

Replace fragile string match on `bc.category.name == "Food & Drink"`
with the `categories(:food_and_drink)` fixture so the test setup
isn't sensitive to category-name translations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(assistant): enforce strict month format in get_budget

`Date.strptime` is lenient about trailing characters, so inputs like
`"2026-05-01"` or `"may-2026foo"` were parsing successfully and being
silently truncated to May 2026. Pre-validate the raw string with anchored
regex patterns for the documented YYYY-MM and MMM-YYYY shapes so
malformed tool arguments raise Assistant::Error instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(budgets): suggested_daily_spending handles custom-month periods

The helper compared `budget.start_date.month/year` against
`Date.current.month/year` and returned nil whenever the current period
straddled two calendar months — common for families with
`month_start_day != 1` (e.g., May 15–Jun 14 viewed on Jun 1). Replace
the calendar-month check with `budget.current?` and compute remaining
days from `budget.end_date` so the helper works for both standard and
custom periods. This also restores the daily pacing row in the budget
UI for custom-month families.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(assistant): make get_budget read-only for prior months

`prior_months: N` was calling `Budget.find_or_bootstrap` for every
month, which created empty `Budget` rows (and synced `BudgetCategory`
children) as a side effect of an AI query. Only the explicit target
month now bootstraps; prior months use `Budget.find_by` and are
dropped from the response if they don't exist. The response now
includes `months_unavailable: N` so the LLM can phrase a sensible
answer when fewer months come back than requested.

Extract `Budget.period_for(date, family:)` to share the date-bracket
math between `find_or_bootstrap`, `budget_date_valid?`, and the new
read-only path in `get_budget`.

Adds two tests covering the no-bootstrap behavior for prior months
and the `prior_months` clamp at `MAX_PRIOR_MONTHS`. Updates the
existing N+1 sorted-months test to seed prior budgets explicitly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: wolstad <wesleyolstad@protonmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 00:51:16 +02:00
glorydavid03023
208b3dec9d fix(transactions): migrate transaction filter searches to DS::SearchInput (#1998)
* fix(transactions): migrate filter sidebar searches to DS::SearchInput

Replace hand-rolled search fields that used invalid focus:ring-gray-500 with DS::SearchInput (:embedded). Align date filter focus styles with the DS focus ring pattern.

Refs #1715

* fix(transactions): localize filter search copy and align date focus ring

Address validator feedback by replacing hardcoded filter input labels with i18n keys and updating date filter focus classes to the current design-system ring pattern.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-30 00:48:17 +02:00
ghost
3bcc86f4a8 refactor(imports): back PDF imports with statements (#1786) 2026-05-30 00:22:25 +02:00
ghost
92549bb82a feat(settings): add reviewed category merge flow (#1754) 2026-05-30 00:12:54 +02:00
Rene Arredondo
d7f51c3c3c fix(currencies): add DOGE entry to config/currencies.yml (#2008) (#2030) 2026-05-29 23:47:40 +02:00
CrossDrain
2620653b2a fix(balance): derive waypoint start from day's flows to prevent double-counting and phantom bumps (#2031)
* fix(balance): fix double-counting on reconciliation waypoints with same-day transactions

Waypoint branch was setting start = end = waypoint and passing real flows
to build_balance. Since end_balance is a PG generated column that recomputes
from flows, transactions were double-counted on waypoint days and the prior
gap day inherited a phantom jump.

Fix: pin only the end to the API value, derive start from the day's own
flows (same as current_anchor). Transaction attributed once, gap day
correct, investment cash/holdings split correct.

Adds regression test + GUI breakdown test verified against real PG columns
through UI::Account::BalanceReconciliation.

Fixes #2007.

* test(balance): add investment waypoint regression test

Covers reconciliation waypoint + same-day trade on investment accounts:
end_balance must match API-reported total (not double-count trade flows),
cash/non-cash flows must be preserved, and gap day total must be correct.
2026-05-28 19:37:57 +02:00
Rene Arredondo
be2d3aa3bb fix(plaid): surface configuration/product-access errors from the Link flow (#1792) (#1991)
* fix(plaid): surface configuration/product-access errors from Link flow (#1792)

* fix(plaid): harden Plaid Link onExit guard + nil-body JSON parse (#1792 review)

* fix lint check issue

* fix test unit check
2026-05-28 14:55:21 +02:00
glorydavid03023
e13683c389 fix(enable_banking): migrate select_bank UI to DS primitives (#1997)
Replace hand-rolled Beta pill and secondary cancel link with DS::Pill and DS::Link on the bank picker dialog.

Refs #1971
2026-05-28 14:50:49 +02:00
CrossDrain
52083d5774 feat(reports): add Period Return card to Investment Performance (#1962)
* feat(reports): add Period Return card to Investment Performance tab

Surfaces market-only return (absolute + %) for the selected period using
net_market_flows from the balances table, excluding contributions and
withdrawals. Appears in both the interactive report and the print view.

* docs: remove TODOS.md; fold FX fallback caveat into PR description

The single V2 item (Period Return's 1:1 FX fallback on missing rates) is
now documented under Known Limitations in the PR description, so a tracked
file in the repo root is redundant.

* fix(investment_statement): align start_value denominator scope and FX handling

Add status filter to match absolute_return, and move FX conversion into
SQL so pre-period balances are found even when an account's currency was
changed after balances were recorded.
2026-05-28 14:49:04 +02:00
glorydavid03023
79ad3e764f fix(views): DS drift — sankey tooltip tokens (#1996)
* fix(views): DS drift sankey tooltip and imports icon token

Replace raw palette classes on the cashflow Sankey tooltip with functional tokens (aligned with time_series_chart). Use bg-surface for the YNAB import option icon background.

Refs #1971, #1951

* fix(views): add privacy-sensitive sankey tooltip class

Align the sankey tooltip with privacy mode masking by appending privacy-sensitive while keeping the DS tokenized tooltip styling.

---------

Signed-off-by: glorydavid03023 <glorydavid03023@gmail.com>
2026-05-28 00:17:47 +02:00
galuis116
2e55bbe294 fix(jobs): delegate recurring-transaction sync gate to Sync.for_family (#1975)
* fix(jobs): delegate recurring-transaction sync gate to Sync.for_family

`IdentifyRecurringTransactionsJob#family_has_incomplete_syncs?` hand-rolled
the list of provider `*_items` associations it polled — plaid, simplefin,
lunchflow, enable_banking, sophtron — missing nine other `Syncable`
provider concerns on `Family`: coinbase, binance, kraken, coinstats,
snaptrade, mercury, brex, indexa_capital, ibkr. When a sync on any of those
nine was in flight, the debounce gate fell through and
`RecurringTransaction::Identifier` ran against a partial dataset; the
follow-up re-enqueue then hit the `find_or_initialize_by` upsert path and
inherited the stale `occurrence_count`. Same drift pattern that bolted
sophtron on as the 5th entry (#591) was already an iteration of.

The maintainers' own `Sync.for_family` (sync.rb:61) already enumerates every
`*_items` association via `Family.reflect_on_all_associations(:has_many)`
filtered by inclusion of `Syncable` — exactly the helper the gate should
delegate to so the list cannot drift again.

- Add `Sync.any_incomplete_for?(family)` class method that wraps
  `for_family(family).incomplete.exists?`.
- Rewrite `family_has_incomplete_syncs?` to delegate. 14 lines → 1.
- New test file `test/jobs/identify_recurring_transactions_job_test.rb`
  covers in-flight Coinbase + Mercury (gate fires), idle (identifier runs),
  missing family, and superseded-by-newer-schedule.
- `test/models/sync_test.rb` gets 2 new tests pinning
  `any_incomplete_for?` against a provider `_items` sync and a
  family-itself sync.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(jobs): stub Rails.cache.read for supersession test (NullStore in test env)

`Rails.cache` is `ActiveSupport::Cache::NullStore` in the Rails test env, so
the previous test's `Rails.cache.write(cache_key, @scheduled_at + 10, ...)`
was a no-op and `Rails.cache.read(cache_key)` returned `nil`. The
supersession short-circuit `return if latest_scheduled && latest_scheduled
> scheduled_at` then fell through, the job proceeded to invoke
`RecurringTransaction::Identifier`, and the Mocha
`.expects(:identify_recurring_patterns).never` failed in CI.

Switch to `Rails.cache.stubs(:read).with(cache_key).returns(...)` — the
same idiom `test/models/provider/twelve_data_test.rb:186-197` already uses
for the cache layer. Add an `assert_nil` on the bare `perform` return so
Minitest's assertion counter sees an explicit assertion (silences the
"missing assertions" warning).

No production-code change. Behavior under test is unchanged; only the test
mechanism for simulating "newer scheduled run already in cache" is fixed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 00:01:34 +02:00
Brian Richard
174f7e6be6 feat(binance): add full account sync and transaction processing (#1822)
* feat(binance): add full account sync and transaction processing

- Fixed a bug that hindered Account setup
- Wire up Binance accounts, sync statistics, and unlinked account tracking in the accounts dashboard.
- Support setting a sync_start_date during Binance account setup.
- Set Binance accounts' opening balance to zero to ensure the ledger builds cleanly from the actual trade history.
- Expand the Binance importer and processor to handle Spot, Margin, Earn, P2P, and Futures trades and assets.
- Implement TransactionBuilder to parse raw Binance trades, accurately calculating fees, base/quote asset amounts, and market values for proper ledger integration.
- Update Binance API timeout (`recvWindow`) to 60,000ms to prevent connection drops.

These changes provide comprehensive support for tracking Binance portfolios, ensuring accurate historical ledgers and proper visibility of sync statuses in the frontend dashboard.

* refactor(binance): enforce strong params, double-entry safety, and native fiat currency support

- Implement strong parameters in BinanceItemsController#complete_account_setup to satisfy Rails security guidelines.
- Add robust date parsing with a grace fallback to prevent controller crashes on malformed sync start dates.
- Wrap P2P transaction creations inside a database transaction block to guarantee ledger integrity and prevent orphan records.
- Optimize P2P deduplication queries by batching checks for both transaction and funding external IDs.
- Shift P2P entry persistence from forced USD tracking to native fiat values extracted directly from the Binance API payload.
- Update BinanceAccount::ProcessorTest assertions and fixtures to validate native fiat and fee calculation logic.

* fix(binance): process sync trades before caching transaction payload

- Reorder Binance processor execution to insert trade records into the database prior to updating the `raw_transactions_payload` cache. This guarantees that if a database insertion fails, the cache won't prematurely mark the sync as successful, ensuring the data is retried on the next run.
- Move `set_opening_anchor_balance(balance: 0)` out of the generic crypto exchange account builder and apply it specifically during Binance account creation.
- Refactor date parsing in BinanceItemsController to explicitly catch `ArgumentError` via a block instead of using a blanket inline `rescue`.
- Clean up the `setup_accounts` view template by removing hardcoded default translation strings.

* fix(binance): enhance trade sync logic and error propagation

- Pass `startTime` (from `sync_start_date`) to spot and futures trade endpoints on initial sync to optimize data fetching.
- Include previously synced futures pairs alongside spot pairs when resolving relevant symbols to properly recover sold-out assets.
- Re-raise exceptions in processor rescue blocks to prevent silent failures and ensure errors are correctly propagated to background jobs.
- Decrease Binance API `recvWindow` from 60000ms to 5000ms to align with recommended default timeout values.
2026-05-27 23:58:00 +02:00
CrossDrain
a3609b81d3 fix(enable_banking): clear stuck pending flag when ASPSP reuses same transaction_id (#1982)
* fix(enable_banking): clear stuck pending flag when ASPSP reuses same transaction_id for booked version

* fix: scope pending→booked bypass to user_modified entries only

* refactor: extract clear_pending_flags_from_extra helper to deduplicate pending-flag removal logic

* refactor: use clear_pending_flags_from_extra in user_modified bypass path

* fix(provider_import_adapter): add type check in clear_pending_flags_from_extra

Add a check to ensure that the value associated with a provider key in
the `extra` hash is a Hash before attempting to call `delete` on it.
This prevents a `NoMethodError` when encountering malformed data where
the provider key exists but does not map to a Hash.

* fix(provider_import_adapter): fix indentation and ensure proper return in clear_pending_flags_from_extra

* fix(provider_import_adapter): make clear_pending_flags_from_extra private

* fix: guard clear_pending_flags_from_extra against non-Hash extra values
2026-05-27 23:36:33 +02:00
Alessio Cappa
b3fce37424 fix: Keep with auto for category badge (#1963) 2026-05-27 23:34:42 +02:00
CrossDrain
b8ebb24e8b fix(holdings): carry provider cost_basis forward to calculated rows past snapshot date (#1818)
* fix(holdings): carry provider cost_basis forward to calculated rows

Providers like IBKR Flex emit holdings on report_date and only
include trades within the query window. The reverse calculator + gapfill therefore produces rows past report_date with nil cost_basis, even though the provider supplied a basis on the snapshot. That nil basis silently blanks `Trend`, the Reports "Total Return" card, the Top Holdings return column, and Gains by Tax Treatment, because every one of them gates on `holding.avg_cost`.

When a calculated row would otherwise have no usable cost_basis, backfill it with the most recent provider-supplied cost_basis for the same (security, currency) on or before the holding date. Existing calculated/manual values are preserved (they outrank a provider carry-forward), and existing provider carry-forwards are refreshed when a newer snapshot supersedes them.

* - Fix currency mismatch: provider snapshots were keyed by (security_id,
  currency) but calculated rows use account currency while IBKR provider
  rows use the security's native currency (e.g., USD vs EUR). Now keyed
  by security_id only; carry_forward_provider_cost_basis converts via
  Money#exchange_to at the snapshot date (same convention as
  ReverseCalculator for trade prices), with a ConversionError fallback.
- Trim long inline comment to three lines
- Fix safe-nav inconsistency: existing.cost_basis.positive? ->
  existing&.cost_basis&.positive?
- Add test: refreshes stale carry-forward when a newer provider snapshot
  arrives
- Add test: carry-forward is a no-op for forward-strategy accounts with
  no provider holdings

* fix(holdings): prevent overwriting zero-valued manual cost basis

Ensure that manual cost basis entries with a value of zero (e.g., for free
shares) are not overwritten by provider carry-forward values during
materialization.

Additionally, updated the logic to allow zero-valued manual or
calculated cost bases to be preserved, and added tests to verify
currency conversion and error handling during cost basis carry-forward.

* refactor(holdings): allow zero-valued cost basis in provider snapshots

Remove the filter that restricted provider cost basis snapshots to values
greater than zero. This ensures that manual cost basis entries with a
value of zero (e.g., for free shares) are correctly captured and
available for carry-forward logic.

* perf(holdings): optimize provider cost basis snapshot lookup

Filter provider cost basis snapshots by the security IDs present in the
current holdings set to reduce the amount of data loaded into memory.

* refactor(holdings): move PortfolioCache FX fix to dedicated branch

Remove date-accurate exchange rate fix from this branch — it has been
split into fix/portfolio-cache-historical-fx-rate to keep concerns
separate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* revert(portfolio_cache): restore date-accurate FX in get_price

36676784 removed date: date from exchange_to intending to move it to
fix/portfolio-cache-historical-fx-rate, but that branch was a duplicate
of db1051d2 which was already in main. The revert therefore regressed
portfolio_cache.rb below main's state. Restore the historical exchange
rate lookup so this branch no longer removes a fix already present in main.

* fix(portfolio_cache): restore date-accurate FX and its test

36676784 removed date: date from exchange_to and deleted the historical
FX test, intending to carry them in fix/portfolio-cache-historical-fx-rate.
That branch was a duplicate of db1051d2 already in main, so the removal
regressed portfolio_cache.rb below main's state. Restore both.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 23:33:08 +02:00
dripsmvcp
ab52b2b144 fix(family-sharing): prevent silent data loss when rehoming or removing users (#1896)
* fix(family-sharing): prevent silent data loss when rehoming or removing users

Fixes #1689.

Two destructive paths could strand a pre-existing user's family and accounts:

1. Invitation#accept_for unconditionally overwrote user.family_id, orphaning
   the prior family + its accounts with no user able to reach them.
2. Settings::ProfilesController#destroy then called @user.destroy when an admin
   removed the rehomed member, destroying the only login path back to the
   now-orphaned data.

Add hard-block guards on both paths. accept_for refuses when the invitee
already belongs to a family with accounts; ProfilesController#destroy refuses
when the member owns accounts in another family (legacy state from the old
flow). InvitationsController#create surfaces a specific, actionable flash so
the admin understands why the auto-accept was refused.

No automatic recovery of already-orphaned data — that needs a separate
one-shot script per dosubot's analysis on the issue.

* fix(family-sharing): scope invite orphan-guard to invitee-owned accounts (#1896 review)

Codex flagged (P1) and the maintainer review independently raised that
would_orphan_existing_family? keyed off user.family.accounts.exists? —
any account in the invitee's current family — which wrongly blocked a
non-owner member from leaving a multi-user household.

Rename to would_orphan_owned_accounts? and key off
user.owned_accounts.where.not(family_id: family_id), making the invite
guard symmetric with the destroy-path guard in
Settings::ProfilesController. A member who owns no accounts now orphans
nothing by moving and is free to accept the invitation; an owner is
still blocked.

Add a regression test for the non-owner case and update the existing
tests to give the invitee explicit account ownership.

* Remove extra comments per project conventions

---------

Co-authored-by: Juan José Mata <jjmata@jjmata.com>
2026-05-27 23:25:46 +02:00
sentry[bot]
12aff3cef7 fix(settings): add nil-safe operator to Current.family.can_manage_subscription? (#2006)
Co-authored-by: sentry[bot] <39604003+sentry[bot]@users.noreply.github.com>
2026-05-27 23:22:20 +02:00
Blaž Dular
326595ad71 fix(accounts): show activity label instead of category for accounts supporting trades (#1993) 2026-05-27 23:04:42 +02:00
Juan José Mata
1bbc2ea25d Remove Gittensor labeling 2026-05-27 21:28:49 +02:00
Juan José Mata
0342958a32 Create SECURITY.md template for security policy and reporting
Added a security policy document outlining supported versions and vulnerability reporting.

Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
2026-05-27 09:35:10 +02:00
CrossDrain
3e2990a52c feat(ibkr): compute net_market_flows from IBKR equity equity delta and trade flows (#1970)
* feat(ibkr): compute net_market_flows from IBKR equity delta and trade flows

Replace the hardcoded net_market_flows: 0 in HistoricalBalancesSync with an
exact derivation from IBKR's own equity summary data, eliminating any
dependency on third-party security price providers for Period Return.

Formula: nmf = Δnon_cash - net_buy_sell
  - non_cash = IBKR equity total - materializer cash (exact per IBKR)
  - net_buy_sell = sum of trade amounts converted to base currency using
    the stored fx_rate_to_base (IBKR's own FX rate, already on Trade#exchange_rate)

Sets non_cash_adjustments = net_buy_sell so the virtual column identity
(end_non_cash_balance = start + nmf + adjustments) resolves to IBKR's
exact equity figure.

* test(ibkr): add sell-trade and no-trade nmf tests; fix memoization guard

- Add test: sell trades (negative amount) correctly isolate market loss in nmf
- Add test: no-trade scenario produces nmf = full Δnon_cash
- Fix: `return {} unless account` inside ||= exited the method without memoizing;
  restructure to `if account ... else {} end` so the result is always cached

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ibkr): exclude dividend/interest trades from net_buy_sell; use historical FX date

Addresses two issues flagged in code review:

- P1: trades with qty=0 (Dividend, Interest) were included in net_buy_sell,
  inflating/deflating nmf on dates with income events. Filter to qty != 0 at
  the SQL level so only buy/sell trades affect the market-flow calculation.

- P2: Money#exchange_to defaulted to Date.current when no custom_rate was
  stored, causing historical nmf to drift as FX rates change over time.
  Pass date: entry.date so the fallback lookup uses the trade's own date.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(ibkr): cover Money::ConversionError fallback in trade_flows_by_date

Adds a test that stubs Money#exchange_to to raise ConversionError for a
cross-currency trade with no stored exchange_rate, verifying that the
rescue clause falls back to entry.amount and that nmf and
end_non_cash_balance still resolve correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ibkr): log warning when FX conversion falls back to unconverted amount

When Money::ConversionError is raised for a cross-currency trade with no
stored exchange_rate, warn with entry currency, account currency, date,
amount, and entry/account IDs so the silent fallback is visible in logs.
Same-currency ConversionErrors (unexpected but possible) stay silent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ibkr): skip unconvertible FX trades, redact log, tighten join

- On Money::ConversionError, skip the entry from net_buy_sell rather
  than falling back to the raw amount (which treated e.g. EUR as CHF);
  nmf now absorbs the full Δnon_cash for that date instead of silently
  misstating period return
- Remove entry amount, entry ID, and account ID from the FX warning log
  to avoid exposing financial data in log output
- Consolidate entryable_type guard into the JOIN condition rather than a
  separate WHERE clause
- Add inline comment on the first-day zero case to distinguish intent
  from a bug
- Update ConversionError test to assert skip behavior (nmf=200, not 50)

* fix(ibkr): exclude dates with unconvertible FX trades from balance upsert

* fix(ibkr): skip upsert_all when all balance rows are filtered by failed FX dates

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 22:48:23 +02:00
Alessio Cappa
bc3e5a824f feat: Add pagination in merchants page (#1965)
* feat: Add pagination in merchants page

* fix: Add separate paginations for family/provider merchants

* refactor: simplify conditions in view
2026-05-26 22:17:00 +02:00
Rene Arredondo
946c4d0391 fix(i18n): use %{product_name} in api_keys usage_instructions (#1505) (#2000) 2026-05-26 20:48:34 +02:00
Alessio Cappa
e0537a45e1 fix: Avoid overlay in provider section on mobile (#1990)
* fix: Avoid overlay in provider section on mobile

* feat: Reduce gap between divs

* fix: keep all the elements inside a dedicated container to avoid accessibility issues with the summary node
2026-05-26 09:56:42 +02:00
Sure Admin (bot)
d8a12ad6be fix(preview): only redeploy on preview-cf label changes (#1980) 2026-05-25 15:31:00 +02:00
dripsmvcp
8f5454ad29 fix(settings): preserve OpenAI form input on validation failure (#1862)
* fix(settings): preserve OpenAI form input on validation failure

Fixes #1824.

The OpenAI settings form auto-submits on blur, so typing the URI base
before the model triggers cross-field validation. The rescue re-renders
the page with values read from Setting.openai_*, which is still blank
because the failed save was rejected — so the user's input disappears
and they see 'OpenAI model is required' with no value to fix.

Stash the submitted uri_base and model on rescue and prefer them over
the saved Setting when rendering, so the user can finish typing the
missing field and re-submit.

* test(settings): cover openai_model preservation on validation fail (#1862)

jjmata asked for symmetric coverage of the model field. Add a test where
the user changes the URI base and clears the model in the same submit:
the cross-field validation fails and the re-rendered model input must
reflect the submitted (cleared) value rather than reverting to the saved
model. Complements the existing uri_base preservation test.
2026-05-25 11:23:52 +02:00
Sure Admin (bot)
89f42497a9 fix: invert non-gittensor label condition (#1960) 2026-05-24 17:36:17 +02:00
Juan José Mata
8c07236f71 Bump version by hand v0.7.1-alpha.11 2026-05-24 16:19:37 +02:00
Guillem Arias Fauste
adea16f694 fix(views): clear Rule 2 + Rule 5 findings from weekly DS drift (#1951) (#1955)
* fix(views): clear Rule 2 + Rule 5 findings from weekly DS drift (#1951)

Token swaps + i18n cleanup across the three files flagged in the
weekly merged-commit drift scan.

**`app/views/admin/users/index.html.erb`**
- `bg-green-100 text-green-800` → `bg-success/10 text-success` (2 callsites — active-subscription badge + super_admin role legend)
- `bg-surface-default` → `bg-surface` (`--color-surface-default` isn't defined; canonical token is `--color-surface`)
- `bg-red-50/30 dark:bg-red-950/20` → `bg-destructive/5` (pending-invitation row highlight; functional token resolves correctly in both themes via `--color-destructive`)
- Hand-rolled destructive button classes (`text-red-600`, `border-red-300`, `hover:bg-red-50`) → functional tokens (`text-destructive`, `border-destructive`, `hover:bg-destructive/10`)
- Drop redundant `default:` args from `t(".roles.member", default: "Member")` and `t(".role_descriptions.member", default: "Basic user access…")` — the locale keys exist in `config/locales/views/admin/users/en.yml`

**`app/views/imports/new.html.erb`**
- `icon_bg_class: "bg-gray-tint-5"` → `"bg-surface-inset"` (`gray-tint-5` isn't a defined utility; `bg-surface-inset` carries the same muted-background intent and theme-swaps correctly)

**`app/views/settings/profiles/show.html.erb`**
- Drop redundant `default:` args from `t(".group_title", default: "Group")`, `t(".group_form_label", default: "Group name")`, and `t(".group_form_input_placeholder", default: "Enter group name")` — all three keys exist in `config/locales/views/settings/en.yml`

**Deferred** to a separate PR (Rule 1 findings on admin/users):
- `<details>` block (lines 54–180) → `DS::Disclosure(:card)` — bigger refactor with custom summary content + Stimulus controller attributes; warrants its own diff.
- Destructive button shell → `DS::Button(:destructive)` — same reason; the class-token swap in this PR clears the immediate violation without changing the form-with structure or visual.

Refs #1951.

* fix(profiles): restore i18n default: args for group_* keys

@jjmata + @codex correctly flagged: `settings.profiles.show.group_title`,
`group_form_label`, and `group_form_input_placeholder` are defined in
en.yml + 4 other locales (de, es, pl, pt-BR), but missing from 8
locales (ca, fr, nb, nl, ro, tr, zh-CN, zh-TW).

With `config.i18n.fallbacks = true` those locales currently fall
back to en values, so end-users see English copy rather than a
translation-missing marker. The `default:` arg makes the fallback
explicit at the call site without depending on the Rails fallback
chain being configured a particular way — restores the original
defensive behavior from before #1955.

Admin/users role keys keep their `default:` removal — verified that
`roles.member` and `role_descriptions.member` exist in all 8
admin/users locales (`grep -c "^\s*member:"` returns 2 for every
locale file).
2026-05-24 16:05:14 +02:00
sentry[bot]
06518b49a1 fix(trades): prevent MissingTemplate for Turbo Stream requests on update/create failure (#1893)
* fix(trades): prevent MissingTemplate for Turbo Stream requests on update/create failure

* Linter noise

---------

Co-authored-by: sentry[bot] <39604003+sentry[bot]@users.noreply.github.com>
Co-authored-by: Juan José Mata <jjmata@jjmata.com>
2026-05-24 15:51:18 +02:00
sentry[bot]
2ce875f57f fix(messages): handle blank content submission gracefully (#1938)
Co-authored-by: sentry[bot] <39604003+sentry[bot]@users.noreply.github.com>
2026-05-24 15:50:47 +02:00
sentry[bot]
5520bacbb8 fix(i18n): standardize product name interpolation in import mapping descriptions (#1956)
Co-authored-by: sentry[bot] <39604003+sentry[bot]@users.noreply.github.com>
2026-05-24 15:37:13 +02:00
sentry[bot]
c93193cfbc fix(locale): Handle blank locale submission gracefully (#1876)
Co-authored-by: sentry[bot] <39604003+sentry[bot]@users.noreply.github.com>
2026-05-24 15:13:49 +02:00
Sure Admin (bot)
c7c63a50a7 Add PR workflow for not-gittensor labeling (#1957) 2026-05-24 14:55:08 +02:00
Abhinav Dhiman
0988e2d9d6 perf: use jemalloc as the default allocator (#1910)
* feat(docker): add jemalloc to reduce memory fragmentation

Install libjemalloc2 in the base image and preload it via LD_PRELOAD in
docker-entrypoint when available. Reduces RSS growth from glibc's default
allocator fragmentation under Rails workloads.

* feat(docker): add DISABLE_JEMALLOC env var + preserve existing LD_PRELOAD

* feat(docker): add jemalloc status logging to entrypoint

* refactor(docker): simplify jemalloc logging to warn-only when disabled/missing
2026-05-24 14:02:50 +02:00
Josh
ca895416a4 chore(helm): bump pipelock to 2.5.0 and surface 2.5 config (#1913)
* chore(helm): bump pipelock to 2.5.0 and surface 2.5 config

Bumps pipelock.image.tag from 2.2.0 to 2.5.0 and exposes the most
relevant 2.5 features as structured Helm values:

- pipelock.requestBodyScanning: scan outbound bodies and sensitive
  headers for prompt-injection and DLP payloads. Disabled by default;
  roll out with action=warn before flipping to block.
- pipelock.healthWatchdog: structured config for the wedge-detection
  watchdog with an exposeSubsystems toggle for /health detail.
- pipelock.mcpToolPolicy.rules: structured values for rendering
  mcp_tool_policy.rules including redirect-profile references.

Also fixes a latent config-validation regression: pipelock 2.x rejects
an enabled mcp_tool_policy with no rules, but the chart previously
defaulted to enabled=true with an empty rules list, which hard-fails
'pipelock check'. The default is now enabled=false; operators must
explicitly enable and provide at least one rule.

Refreshes README, CHANGELOG, docs/hosting/pipelock.md, docs/hosting/ai.md,
compose example pin comment, and pipelock.example.yaml to call out 2.5
highlights (Audit Packet v0 verifiers, SPIFFE-strict envelopes, scanner
attribution on MCP block receipts, pipelock doctor). Also fixes a stale
docs/hosting/mcp.md reference to the removed compose.example.pipelock.yml.

* chore(helm): fail helm template when mcp_tool_policy enabled with no rules

Adds a guard in asserts.tpl so an operator who sets
pipelock.mcpToolPolicy.enabled=true without populating
pipelock.mcpToolPolicy.rules gets a clear render-time error instead
of a container crash-loop with the pipelock validation message.

Per CodeRabbit feedback on #1913.

* Versions

---------

Co-authored-by: Juan José Mata <jjmata@jjmata.com>
2026-05-24 13:50:44 +02:00
dripsmvcp
98ca1608f4 fix(enable_banking): match bank list search against BIC, not just name (#1874)
* fix(enable_banking): match bank list search against BIC, not just name

Bank-search filter on the Enable Banking bank-selection modal only indexed
`aspsp[:name]`, so users searching by BIC code (e.g. `INGDDEFF`) got no
results even when the bank was rendered in the list. Switch the per-item
data attribute to a `name + BIC` haystack and read from it in the Stimulus
controller, so either token matches.

Refs #1814

* style(bank_search): apply Biome formatting to forEach callback (#1874 review)
2026-05-24 13:43:36 +02:00
arumaio
eca8c6ce1f fix : account destroyed cascade transfer destruction then … (#1795)
* fix: cascade destroy transfers and reset transaction kind on account destruction.

* Add rescue no method to transfer transaction reset

---------

Co-authored-by: arumaio <aruma.pro+git@protonmail.com>
2026-05-24 13:27:27 +02:00
Jake Armstrong
51271d9810 Prevent Brand Fetch client ID autofill (#1914) 2026-05-24 12:38:13 +02:00
Guillem Arias Fauste
3d182bc67a refactor(transactions): migrate transfer_match badges to DS::Pill (#1939)
Follow-up to #1917 — the responsive label-swap pair in
`_transfer_match.html.erb` was deferred because DS::Pill has no
caller-controlled `class:` arg yet. Wrapping each `DS::Pill` in a
`<span>` with the responsive visibility classes (`hidden lg:inline` /
`inline lg:hidden`) gets the same effect without expanding the
component API — the parent span's `display` controls visibility, the
child pill keeps its own `inline-flex` chrome when visible.

Closes the last open callsite from #1917's deferred-list. Same tone
(`:neutral`) and shape (`marker: false` rounded-full) as the other
neutral status badges migrated in PR B.
2026-05-24 12:16:03 +02:00
Mark Hendriksen
0497b1d7c1 Use date comparisons for interval thresholds (#1923)
* Use date comparisons for interval thresholds

Replace hard-coded day counts in Period#interval with direct date comparisons (end_date > start_date + 5.years and + 1.year) for clearer intent and to avoid magic numbers; updated inline comments. No behavioral change intended aside from improved readability.

* Use advance(years:) for year-based comparisons

Replace start_date + N.years with start_date.advance(years: N) to apply calendar-year semantics (respecting leap years/month boundaries). Update comments to clarify 'calendar years' and the resulting interval choices (monthly for >5 years, weekly for >1 year). Intent is to make the period interval calculation more correct for calendar-aware date comparisons.
2026-05-23 09:41:57 +02:00
Guillem Arias Fauste
9182346c6c fix(admin/users): use parent space-y-6 for sibling section spacing (#1934)
The admin users page wraps four top-level sibling sections inside a
single `bg-container rounded-xl shadow-border-xs p-4` card:

  1. description paragraph
  2. filter form
  3. trials-expiring summary grid
  4. families/groups list
  5. role descriptions (`settings_section` collapsible → DS::Disclosure :card)

The first three carried their own `mb-6`; the families list and the
role descriptions section had no margin at all, so the families card
sat flush against the role-descriptions card with zero gap — clearly
broken next to the well-spaced upper sections.

Apply spacing at the **layout** level: hoist `space-y-6` onto the
outer container and drop the per-child `mb-6`. All five siblings now
get a consistent 24px gap.

No other admin or settings pages match this exact pattern (single
outer card + multiple sibling sections without parent space-y) — the
settings layout already wraps `<%= yield %>` in `space-y-4`, and other
pages with outer cards (`api_keys/show`, `llm_usages/show`, etc.)
either rely on that layout or carry their own internal `space-y-N`.
2026-05-23 09:25:48 +02:00
Guillem Arias Fauste
ea51612ac7 refactor(views): migrate 6 residual inline alerts to DS::Alert (#1933)
* refactor(views): migrate 6 residual inline alerts to DS::Alert

PR #1731 extended DS::Alert and migrated 9 inline alert blocks. Six
hand-rolled alert blocks slipped through that sweep and stayed on raw
palette tokens with no `theme-dark:` variants:

- `app/views/settings/llm_usages/show.html.erb` — "About Cost Estimates"
  blue info block. Most visible offender: `bg-blue-50 border border-blue-200`
  + `text-blue-900 / text-blue-700 / text-blue-600` rendered as a bright
  white-blue island in dark mode (the bug spotted on the LLM usage page).
- `app/views/accounts/confirm_unlink.html.erb` — yellow warning with
  bullet list.
- `app/views/oidc_accounts/new_user.html.erb` — blue info heading.
- `app/views/oidc_accounts/link.html.erb` — two blocks (yellow verify
  warning + blue create info). Also flips the file's pre-existing
  `text-gray-600` hint paragraph to `text-secondary` (caught by the
  `DeprecatedClasses` erb_lint rule on save).
- `app/views/rules/confirm.html.erb` — AI cost notice.
- `app/views/rules/confirm_all.html.erb` — AI cost notice.

All six migrate to `DS::Alert.new(title:, variant:)` (with a block content
slot for the rich/conditional bodies). DS::Alert resolves `bg-info/10`,
`border-info/20`, etc. from the `@theme` semantic tokens, so dark mode
now renders a subtle blue/yellow tint over the page surface instead of
a hardcoded light-mode pill.

Out of scope (left as-is, not alert-shaped):

- `app/views/assistant_messages/_tool_calls.html.erb` — a tool-call
  display panel (not an alert; needs its own token sweep).
- `app/views/import/rows/_form.html.erb` — inline cell-error tooltip
  (`bg-red-50 border border-red-200`) — also not alert-shaped; a future
  PR can swap it to `bg-destructive/10 border-destructive-subtle` once
  #1932 lands.

Surfaced while scanning DS drift for the LLM usage page bug. Tracking
issue: #1715 (closed but conceptually relevant) / #1911 (active drift
patrol).

* fix(oidc): keep alert description in <p>, retarget tests for DS::Alert title

CI on #1933 caught three test failures introduced by migrating the
two OIDC link alerts and the verify-redirect copy from hand-rolled
`<h3>` / `<p>` markup to `DS::Alert`:

1. `OidcAccountsControllerTest#test_should_show_create_account_option_for_new_user`
2. `OidcAccountsControllerTest#test_does_not_show_create_account_button_when_JIT_link-only_mode`
3. `SessionsControllerTest#test_redirects_to_account_linking_when_no_OIDC_identity_exists`

DS::Alert renders its `title:` slot as a `<p>` (semantically the alert
heading lives on the container's `aria-labelledby`, not on a heading
tag) and renders block / message content directly inside a `<div>`,
not a `<p>`. The pre-migration markup used `<h3>` for the heading and
`<p class="...text-blue-700">` for the description, so the tests
above asserted those specific tags.

Two fixes:

- `app/views/oidc_accounts/link.html.erb` — wrap the html_safe
  description bodies in explicit `<p>` tags inside the DS::Alert
  block. Restores the `<p>` element the session-redirect test asserts
  on, and keeps the description as a semantic paragraph rather than
  a bare text node inside the alert container.
- `test/controllers/oidc_accounts_controller_test.rb` — flip the two
  `assert_select "h3", text: "Create New Account"` calls to match the
  DS::Alert title `<p>`. The test was asserting an implementation
  detail of the pre-migration markup; switching to the new tag keeps
  the assertion meaningful (the heading text still has to render)
  without re-introducing an `<h3>` outside of DS::Alert.

* fix(test): match Create New Account title with regex (sr-only "Info:" prefix)

DS::Alert prepends `<span class="sr-only">Info:</span>` inside the
title `<p>`, so the full text content is "Info: Create New Account",
not "Create New Account". `assert_select "p", text: "Create New Account"`
requires an exact text match and rejected the prefixed string. Switch
to a regex match — keeps the heading-text assertion meaningful without
coupling to the screen-reader prefix.
2026-05-23 09:23:30 +02:00
Guillem Arias Fauste
f0e270f578 fix(design-system): restore dark-mode contrast on Toggle + destructive borders (#1932)
Two regressions from the recent token sweep, both producing low-contrast
results in dark mode.

## DS::Toggle off-track

PR #1843 (DS::Toggle a11y + token swaps) replaced the raw
`bg-gray-100 theme-dark:bg-gray-700` off-track with `bg-surface-inset`
for semantic alignment. `bg-surface-inset` resolves to gray-800 in
dark mode, but the toggle typically sits inside `bg-container`
(gray-900). The contrast ratio dropped from ~2.45:1 (gray-700 vs
gray-900) to ~1.5:1 (gray-800 vs gray-900) — visibly worse than the
pre-#1843 baseline and below WCAG 1.4.11 (3:1 for UI components).

Most visible inside the transaction-edit modal SETTINGS section
(`Exclude`, `One-time Expense`) where the off-state switches nearly
vanished into the modal chrome.

Introduce `--color-toggle-track` (light: gray-100, dark: gray-700) and
swap `bg-surface-inset` → `bg-toggle-track` in DS::Toggle. Restores the
pre-#1843 off-track contrast while keeping a semantic token (instead
of the raw palette references the migration was trying to remove).

## border-destructive subtle borders

PR #1849 (single-color tokens to @theme) flagged that
`border-destructive/N` rendered the wrong shade (the `@utility
border-destructive` block defined red-500 light, while
`--color-destructive` in `@theme` is red-600 — `/N` resolves from
@theme), and swapped a couple of callsites to solid `border-destructive`.
Solid renders red-500/red-400 at full saturation in both modes, which
reads as a loud error border on contexts that were meant to be subtle
(left-rule on the provider-sync "view error details" pane, error-message
box in SimpleFIN settings, alert-component border, provider connection
error rows).

Two callsites (`DS::Alert`, settings/providers/_connection_row) still
carried the broken `border-destructive/20` / `/25` modifier — same
off-shade footgun #1849 was meant to retire.

Introduce `--color-destructive-subtle` (light: red-200, dark: red-800)
and swap the four subtle-by-intent callsites to `border-destructive-subtle`:

- app/components/DS/alert.rb (destructive variant)
- app/views/settings/providers/_connection_row.html.erb (err status)
- app/components/provider_sync_summary.html.erb (error-details left rule)
- app/views/simplefin_items/edit.html.erb (error-message box)

The handful of intentionally-loud `border-destructive` callsites
(split-transaction over-allocation, blank-name account labels, etc.)
keep the solid token.

Regenerated `_generated.css` via `npm run tokens:build`.
2026-05-23 09:21:46 +02:00
Guillem Arias Fauste
cc8e2abf18 fix(design-system): DS::Menu add :icon_sm variant for dense action lists (#1930)
PR #1840 bumped DS::Button icon-only `:md` size from `w-9 h-9` (36×36)
to `w-11 h-11` (44×44) for WCAG 2.5.5 enhanced touch target. DS::Menu's
`:icon` variant uses DS::Button at the default `:md` size, so every
row-level "..." action-list trigger grew from 36×36 to 44×44.

For dense lists where each row has a trigger — most visibly the
transaction category dropdown (`category/dropdowns/_row.html.erb`) —
the per-row height bump (+8px) compounds: a 5-category panel that
used to fit in ~220px now wants ~260px, the badges look smaller
relative to the row chrome, and the overall density that made the
dropdown scannable regresses visibly.

Add an `:icon_sm` variant that renders the trigger as DS::Button at
`size: :sm` (32×32). Meets WCAG 2.5.8 AA (24×24) — appropriate for
compact in-row triggers where 44×44 isn't required. Standalone
toolbar / row-action `...` triggers should keep `:icon` for AAA.

Migrate `category/dropdowns/_row.html.erb` to `:icon_sm` to restore
the pre-#1840 dropdown density.
2026-05-23 09:18:16 +02:00
Guillem Arias Fauste
c8b1d8cf92 fix(design-system): DS::Disclosure :default variant summary_content layout (#1929)
PRs #1855, #1857, #1858 (DS::Disclosure :card/:card_inset/:inline
variants) introduced a `<div class="w-full">` wrapper around
`summary_content`. The wrapper is required for non-default variants —
their `<summary>` is `display: list-item` (no flex), so a caller's
inner flex+justify-between div would shrink-wrap to content width.

But for the `:default` variant, `<summary>` is already
`flex items-center justify-between`. Wrapping caller siblings in a
single `w-full` block collapses them into one flex child, killing the
justify-between distribution. This regressed the only default-variant
summary_content caller — `accounts/_accountable_group.html.erb` (the
homepage account sidebar) — where the group name and total/sparkline
divs no longer aligned across the row.

Render `summary_content` bare for `:default` (summary is the flex
container) and keep the `w-full` wrapper for `:card`, `:card_inset`,
`:inline`.
2026-05-23 09:06:17 +02:00