* fix(design-system): DS::Tooltip a11y — focusable trigger, keyboard parity, Esc dismiss
Closes#1747. Five fixes on the tooltip primitive.
1. **Tooltip anchor not in a11y tree.** The trigger was a bare
Lucide icon, which Lucide renders with `aria-hidden="true"`.
The tooltip target had `role="tooltip"` but nothing referenced
it, so AT users had no way to discover the description. Wrap
the icon in a focusable `<button type="button">` with
`aria-describedby="<tooltip-id>"` so the underlying icon stays
`aria-hidden` and the button picks up the description binding.
2. **Stable per-instance id.** Each DS::Tooltip now mints a
`tooltip-<8-char hex>` id wired between the trigger's
`aria-describedby` and the tooltip's `id`.
3. **Keyboard parity.** Hover-only triggers locked keyboard-only
users out. Add `focusin` / `focusout` listeners on the
controller element so Tab onto the trigger reveals the
tooltip, Tab away dismisses it.
4. **Esc-to-dismiss.** Matches the WAI-ARIA tooltip pattern.
`Escape` while the tooltip is open closes it without removing
focus from the trigger.
5. **Resize-safe width cap.** Replace the hard-coded
`max-w-[200px]` with `max-w-[20rem]` so the tooltip scales
with the user's root font-size setting (large-text accessibility
pref). Slightly wider visual cap (320px @ default) but no longer
clips on text-zoom.
Plus: docstring note that tooltip content must be non-interactive
(no buttons / links / form controls inside) — `aria-describedby`
exposes content as a description, not as an interactive subtree.
Callers needing actions should reach for a popover/menu primitive.
API unchanged. Existing 30+ DS::Tooltip callsites work without
modification — they all pass `text:`-only payloads, which still
render correctly under the new markup.
* fix(review): as: option + alpha focus-ring on DS::Tooltip
Addresses two AI review findings on #1845:
1. **Button-inside-summary spec violation.** Wrapping the icon in
`<button>` regressed keyboard/AT behavior at 13 callsites where
DS::Tooltip lives inside a `<summary>` (8 provider items, lunchflow
disclosure, activity_date, 4 simplefin badges). HTML's content
model forbids interactive content inside `<summary>`; browsers
and AT can drop focus or conflate activation with the disclosure
toggle. Add `as:` parameter — default `:button` preserves the
standalone a11y wrap; `:span` renders a non-focusable wrapper for
summary-nested usage. `focusin` bubbles up to the controller from
the ancestor `<summary>`, so keyboard tooltips still appear on
tab. Migrate the 13 in-summary callsites to `as: :span`.
2. **Raw palette focus ring → alpha tokens.** Swap
`outline-gray-900 theme-dark:focus-visible:outline-white` to the
established focus-ring pattern `focus-visible:ring-2
focus-visible:ring-alpha-black-300
theme-dark:focus-visible:ring-alpha-white-300` — matches the
DS::Toggle fix landed in #1843 review and provider_card /
form-field tokens.
* fix(review): bind tooltip focus on ancestor <summary>
Codex P2 follow-up on #1845: \`as: :span\` renders a non-focusable
trigger inside the disclosure \`<summary>\`. Keyboard users hit Tab
and focus lands on the summary itself; \`focusin\` fires on the
summary and bubbles UP — never down to a descendant span — so the
existing listener on \`this.element\` never fires and the tooltip
stays hidden for keyboard-only users on every in-summary row
(provider _item partials, lunchflow disclosure, activity_date,
simplefin badges). My earlier reply that the focusin "bubbles up to
the Stimulus controller on the outer span" was wrong about the
direction; \`focusin\` only bubbles upward.
In \`addEventListeners\`, resolve \`this.element.closest("summary")\`
and bind \`focusin\` / \`focusout\` / \`keydown\` on it too. Track the
ancestor on the controller and undo the bindings in
\`removeEventListeners\` so reconnect-on-Turbo cycles don't leak.
Update the template comment to reflect the actual mechanism.
* docs(ds-tooltip): correct as=:span comment to match controller mechanism
---------
Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
* feat(i18n): complete Catalan translations + extract residual hardcoded strings
CA coverage
- All view/model/breadcrumb/doorkeeper/mailer locale files for ca: 0 missing
keys (was ~3,400). Translations follow informal "tu" register, sentence case,
domain glossary (Compte/Saldo/Transacció/Posició/Operació/Pressupost/...).
- Catalan pluralization test: ca uses one/other; mirrors
test/lib/polish_pluralization_test.rb.
- 8 LanguageTool-flagged grammar fixes applied (Connexió òrfena, Secret de
l'API, comma-pero, apostrophe elisions, etc).
Hardcoded string extraction (also fixes EN parity)
- UI::Account::Chart#title + chart.html.erb view tabs -> UI.account.chart.*
- UI::Account::BalanceReconciliation labels + tooltips ->
UI.account.balance_reconciliation.{labels,tooltips}.*
- transactions/_transfer_match.html.erb (Auto-matched, A/M, Confirm/Reject
match, Payment/Transfer is confirmed) -> transactions.transfer_match.*
- AccountOrder labels (Name/Balance asc/desc) -> account_order.* keys with
fallback to existing hardcoded labels.
- Depository::SUBTYPES surface in account list -> depositories.subtypes.*.*
- User role badge -> users.roles.* (admin / member / super_admin).
- 110+ country names -> countries.* (config/locales/countries.ca.yml).
Breadcrumb locale fix
- Breadcrumbable was a before_action that ran before Localize's around_action
switched I18n.locale, so default crumbs rendered in EN even when locale=ca.
- Convert to helper_method that defers translation to render-time (when
I18n.locale is already correct). Add all missing breadcrumb keys to ca + en.
- Layouts switched from @breadcrumbs to breadcrumbs helper.
Locale-aware helpers / formatters
- ApplicationHelper#localized_ordinal: ordinalize that respects ca
(1r/2n/3r/4t/Nè). Wired into preferences month_start_day select.
- Family#moniker_label / moniker_label_plural: translate the default "Family"/
"Group" monikers via shared.family_moniker.* with fallback to the family's
custom override.
- Budget#name: use I18n.l for month_year/short/long instead of strftime("%B %Y")
so the budget header date follows the active locale.
Tooling
- script/lt_check_ca.rb: batched LanguageTool checker (premium endpoint when
LT_USERNAME/LT_API_KEY are set, free fallback otherwise), picky mode,
motherTongue=en for false-friend detection.
- lib/tasks/i18n_screenshot.rake: dev-only rake to set user.locale=ca and
role=super_admin on the demo user so the i18n surfaces can be walked.
Out of scope (pre-existing, not introduced here)
- Native browser file input "Choose Files / No file chosen" (browser locale).
- D3.js client-side chart x-axis dates (JS-side Intl.DateTimeFormat needed).
- Sankey/donut labels = seed category names (data, not i18n).
- 2 rails-i18n datetime/errors interpolation warnings inherited from
config/locales/defaults/ca.yml.
* fix(i18n): apply idiomatic Catalan review (3-agent + native review)
Three parallel review agents flagged 203 findings (31 high / 73 medium / 99 low)
across all 111 ca.yml files. This commit applies the high-severity bugs plus a
curated subset of medium-impact fixes.
Grammar / agreement
- provider_sync_summary.health.stale_pending: `(exclòs)` -> `(exclosa/excloses)`
to agree with feminine `transacció(s)`.
- accounts.confirm_unlink.warning_no_sync: added reflexive `es` -
`el compte ja no es sincronitzarà`.
- sophtron_setup_required.heading: `no configurats` -> `sense configurar`
(avoids broken agreement across "ID" masc. + "clau" fem.).
- admin.sso_providers.form.errors_title: split into one/other pluralization
keys (en + ca); singular `ha impedit` was wrong for count > 1.
Brand consistency
- IndexaCapital -> Indexa Capital (37 occurrences across one file).
- Lunchflow -> Lunch Flow in two remaining places.
Anglicisms / domain mistranslations
- kraken_items setup_accounts.instructions: `ompliments d'operacions`
(lit. dental/food fillings) -> `execucions d'operacions`.
- settings kraken_panel.read_only_title: `Sincronització d'intercanvi`
(swap/trade) -> `Sincronització només de lectura amb l'exchange`.
- transactions convert_to_trade.security_custom + security_not_listed_hint:
`cotització` (price quote) -> `ticker` (the EN field IS a ticker symbol).
- loans.form.rate_type: `Tipus d'interès` collided with sibling
interest_rate -> `Modalitat del tipus`.
- brex_items.provider_panel.sandbox_note_html: `L'staging` (broken
contraction) -> `el staging`.
Idiom traps
- coinbase/binance/kraken wait_for_sync: `acabi de sincronitzar` is
ambiguous in CA (`acabar de + inf` reads as "has just done X") ->
`acabi la sincronització`.
- chats.ai_greeting.there: `a tothom` -> `''` (the EN fallback "Hey there"
is singular; literal CA `tothom` is plural and wrong for 1:1 chat).
- transactions.split_parent_row.split_label: `Divideix` (imperative) is
wrong as a status badge -> `Divisió` (noun).
- transactions.keep_both (2 occurrences): infinitive `mantenir ambdues` ->
imperative `mantén-les totes dues` to match the sibling Yes/No buttons.
- rules.clear_ai_cache: `Reinicia` (restart) -> `Buida` (empty/clear),
which matches the success notice (`s'està netejant`).
Moniker gender breakage (cross-file)
%{moniker} is interpolated downcased from family.moniker_label and may
resolve to feminine `família`/`llar` or masculine `grup`. Strings that
hard-code a gendered article ('al teu %{moniker}', 'aquesta %{moniker}',
'aquest/a %{moniker}') broke on at least one branch. Restructured the
affected sentences to drop the gendered determiner:
- account_sharings.show.no_members
- merchants.family_empty / family_title / provider_empty
- registrations.new.join_family_title
- settings.preferences.show.currencies_subtitle / sharing_subtitle
- simplefin_items.select_existing_account.no_accounts_found
- invitations.new.subtitle
- invitation_mailer.invite_email.subject (mailers/) + body (views/)
- snaptrade_items.providers.snaptrade.free_tier_warning
Terminology consistency
- models/account_statement/ca.yml attributes aligned with view-side
forms: `Saldo d'obertura`/`Saldo de tancament` ->
`Saldo inicial`/`Saldo final`; `Suggeriment de...` -> `Pista de...`.
- account_statements.coverage.status.not_expected:
`No s'esperava` -> `No previst` (status label, not past action).
- account_statements.index.empty_unmatched: aligned with the section's
own label `Safata sense aparellar`.
- imports.create.document_provider_not_configured + document_upload_failed:
`arxiu vectorial` -> `magatzem vectorial` (correct TermCat term).
- coinstats_items blockchain gender: `els blockchains` / `un blockchain` ->
`les blockchains` / `una blockchain` (feminine per TermCat).
- accounts.account.remove_default: `Treu el predeterminat` ->
`Treu com a predeterminat` (pairs with sibling `Estableix com a
predeterminat`).
- accounts.tax_treatments.tax_deferred: `Diferit fiscalment` (lit. calque)
-> `Tributació diferida` (standard CA tax-accounting term).
- settings.payments.show.currently_on_plan: `Actualment al` ->
`Actualment al pla:` (was a fragment).
Out of scope (review flagged, not applied here)
- LOW-severity stylistic preferences (Veure vs Mostra, etc).
- `models/category/ca.yml` default category names — seeded at family
creation, not via I18n at runtime, so changes wouldn't affect existing
families.
- `models/period/ca.yml` short labels mixing EN (MTD/YTD) and CA (STD/MA)
— needs a one-convention decision separately.
* fix(i18n,ca): drop gendered article in period_activity + tighten cash-flow terms
- pages.dashboard.investment_summary.period_activity: 'Activitat del
%{period}' contracted 'del' = 'de el' (masc.sg.). %{period} resolves
to mixed forms ('Setmana en curs' fem, 'Últims 30 dies' pl., 'Any en
curs' apostrophe), so hard-coded 'del' was wrong on most labels.
Replaced with 'Activitat — %{period}' (em-dash) to skip the
contraction entirely.
- pages.dashboard.outflows_donut.title / total_outflows: switched from
bare 'Sortides' / 'Total de sortides' to 'Sortides de caixa' /
'Total de sortides de caixa' to match TermCat's precise term
('sortida de caixa' = cash outflow).
* fix(i18n,ca): rephrase transfer source/destination amount labels
'Import d'origen' / 'Import de destinació' were literal calques of
'Source amount' / 'Destination amount'. In a multi-currency transfer
form (sender/receiver in different currencies) the natural CA pair is
'Import enviat' / 'Import rebut'.
* fix(i18n,ca): 'Dades en brut' -> 'Dades sense processar'
The literal calque of 'Raw data' read as too technical for personal-
finance UI. 'Dades sense processar' is the more natural Catalan
equivalent for raw/unprocessed data files.
* fix(i18n): localize Import col_sep label + separator options
The CSV upload form rendered 'Col sep' (the auto-humanized attribute
name) plus hardcoded English 'Comma (,)' / 'Semicolon (;)' options
from Import::SEPARATORS.
- activerecord.attributes.import.col_sep added (en + ca: 'Column
separator' / 'Separador de columnes').
- Import.separator_options class method returns translated tuples;
view switched from Import::SEPARATORS to Import.separator_options.
- activerecord.attributes.import.col_seps.{comma,semicolon} added so
the option labels follow the active locale.
* fix(i18n,ca): drop moniker apposition in sharing/currencies section titles
- sharing_title 'Compartició de %{moniker}' rendered as 'Compartició
de Família' (a noun-noun apposition that's odd in CA) -> 'Compartició
de comptes'.
- sharing_subtitle replaced '%{moniker}' with 'entre els membres' so
the sentence reads naturally and doesn't depend on moniker gender.
- currencies_title 'Divises de %{moniker}' had the same apposition
-> 'Divises'. Subtitle no longer references moniker either.
* fix(i18n,ca): keep 'Self Hosting' untranslated
Reverted 'Autoallotjament' / 'autoallotjada' / 'autoallotjats' usages
to the original English 'Self Hosting' (sidebar label, breadcrumbs,
hostings page title, chat assistant settings hint, redis configuration
subheading, LLM usages cost-estimates description).
The brand-style term reads more naturally in EN for technical users
configuring their own deployment.
* fix(i18n,ca): lowercase 'self hosting' (sentence case in labels)
* fix(i18n): extract budget_categories stepper + allocation_progress strings
Hardcoded English strings on the budget category editor:
- 'Setup' / 'Categories' stepper labels in budgets/_budget_nav.html.erb
- 'X% set' / '> 100% set' / 'left to allocate' / 'Budget exceeded by ...'
in budget_categories/_allocation_progress.erb
- '/m avg' caption + 'Shared' placeholder + 'Leave empty to share
parent's budget' tooltip in budget_categories/_budget_category_form
and _uncategorized_budget_category_form
Extracted to:
- budgets.budget_nav.{setup,categories}
- budget_categories.allocation_progress.{percent_set,over_set,left_to_allocate,budget_exceeded_html}
- budget_categories.budget_category_form.{monthly_average,shared_placeholder,shared_title}
CA translations added; EN keys mirror the prior literals.
* chore(i18n): drop translation tooling from PR
These were dev-only helpers used during the Catalan translation pass:
- script/lt_check_ca.rb: LanguageTool API checker (premium/free
endpoint, picky mode, batching). Useful for ongoing locale QA but
shouldn't ship in this feature PR.
- lib/tasks/i18n_screenshot.rake: rake task that flips user.locale and
role on the demo user for walking the i18n surfaces locally.
Both stay available locally; pulled out of the PR scope.
* fix(i18n): apply PR review feedback (CodeRabbit + Codex)
- balance_reconciliation crypto_items: use :end_balance_crypto tooltip
(was :end_balance_investment). Added new UI.account.balance_reconciliation.tooltips.end_balance_crypto key in en + ca.
- doorkeeper.ca.yml confidentiality.no: was YAML boolean false, now string 'No'.
- views/categories: 'Poor contrast, choose darker color or' continued with hardcoded 'auto-adjust.' button text; extracted to categories.form.auto_adjust key (en + ca).
- imports.create.document_upload_failed: 'a l'magatzem' was broken
contraction -> 'al magatzem'.
- invitation_mailer body + mailer subject: 'unir-se' -> 'unir-te' (was
3rd person, should be 2nd to match the rest of the copy).
- 7 strings across mercury_items / sophtron_items / simplefin_items /
lunchflow_items / brex_items / indexa_capital_items / other_assets:
'se sincronitzaran' -> 'es sincronitzaran', 'se segueixen' ->
'es segueixen' (correct reflexive pronoun before consonants).
- settings.providers.status: key was 'false' (YAML-coerced), now 'off'
to match settings/en.yml status.off used in view lookups.
- sophtron_items.sophtron_setup_required.message: stripped trailing
blank line from the quoted scalar.
- settings/profiles/show.html.erb: switched 'family_moniker ==
"Group"' branch checks to 'Current.family&.moniker == "Group"'.
After Family#moniker_label started returning translated values,
callers using the display label for branching would render the
household copy for group families in ca. Compare the stored sentinel
instead.
- Did not apply CodeRabbit's webauthn 'eliminada' -> 'desada' suggestion:
the key is wired to the destroy action (verified at
settings/webauthn_credentials_controller.rb:55), so 'eliminada' is
correct.
* Extract hardcoded strings to i18n
Replace numerous hardcoded English strings with I18n lookups (t / I18n.t) across controllers, views, helpers, and components, and convert model validation error messages to symbol keys. Added multiple locale files under config/locales for models and views. This centralizes user-facing notices/alerts, UI text, import/validation messages, and prepares the app for localization and easier translation maintenance.
* Update en.yml
* Update preview-cleanup.yml
* Revert "Update preview-cleanup.yml"
This reverts commit 1ba6d3c34c.
* test: align i18n assertions with translated messages
* Standardize balance error key and tweak locales
Replace SophtronAccount's :requires_balance error key with :no_balance and update related locale strings for sophtron, plaid, and simplefin accounts to use the new key and clearer copy. Also switch the QIF upload redirect notice to use a relative translation key (t('.qif_uploaded')), remove an unused SSO providers help line, and fix a trailing-newline/whitespace issue in the subscriptions locale. These changes standardize validation keys and improve translation consistency and messaging.
---------
Co-authored-by: KiloClaw <kiloclaw@openclaw.ai>
* feat(statements): add account statement vault
Add web-only statement uploads, account linking, duplicate detection, and per-account coverage/reconciliation checks without mutating transactions. Extend ActiveStorage authorization and targeted tests for family/account scoping.
* fix(statements): return deleted account statements to inbox
Preserve linked statement records when an account is deleted by moving them back to the unmatched inbox, then expand coverage for upload validation, sanitized parser metadata, unavailable reconciliation, and missing-month coverage.
* fix(statements): harden vault upload review flows
Address review and security findings in the statement vault by preserving sanitized parser metadata, failing closed on orphaned statement blobs, avoiding account_id mass assignment permits, and adding regression coverage for link/delete edge cases.
* fix(statements): harden vault upload and access controls
* fix(statements): address vault hardening review
* fix(statements): address vault review feedback
Prioritize SHA-256 duplicate detection while preserving MD5 fallback for legacy rows.
Remove free-form account notes from statement matching, document direct account-destroy unlinking, and add year-selectable historical coverage with muted out-of-range months.
* fix(statements): harden vault review follow-ups
Clarify legacy MD5 checksum use, whitelist statement balance helper dispatch, and preserve sanitized parser metadata.
Hide statement management controls from read-only viewers while keeping server-side authorization unchanged.
* fix(statements): repair settings system coverage
Allow the changelog provider lookup in the self-hosting settings system test, include Statement Vault in settings navigation coverage, and align the feature title casing. Update the devcontainer so ActiveStorage and parallel system tests can run in the documented environment.
* fix(statements): move vault beside accounts
Place Statement Vault with account settings instead of between Imports and Exports. Keep settings footer ordering and system navigation coverage aligned, including the non-admin visibility guard.
* fix(statements): address vault review cleanup
Resolve CodeRabbit review feedback for statement upload validation, duplicate race handling, account statement matching semantics, metadata detection, ActiveStorage authorization tests, and small UI/style cleanups.
* fix(statements): address vault cleanup review
* fix(statements): deduplicate vault style helpers
* fix(statements): close vault review follow-ups
* fix(statements): refresh schema after upstream rebase
* fix(statements): process vault uploads sequentially
* fix(statements): close vault review follow-ups
* fix(statements): scope vault index to accessible accounts
* fix(statements): harden statement vault readiness
Squash the statement vault migration hardening into the feature migration, tighten Active Storage authorization edge cases, bound CSV metadata detection, and add real PDF fixture coverage for stored statements.
Validation: targeted statement/auth/controller/provider tests, full Rails suite, system tests, RuboCop, Biome, Brakeman, Zeitwerk, importmap audit, npm audit, ERB lint, CodeRabbit, and Codex Security all passed locally.
* fix(statements): close vault review follow-ups
Move statement unlinking to after account destroy commit, keep Kraken account creation on the shared crypto helper, and add statement metadata length limits with DB checks.
Validation: fresh devcontainer with fresh DB via db:prepare, focused account/statement/Kraken/Binance tests, RuboCop, Brakeman, Zeitwerk, git diff --check, CodeRabbit, and Codex Security passed before commit.
* fix(statements): address vault scan follow-ups
Move statement tab data setup out of the ERB partial, harden reconciliation labels and coverage initialization, and tighten statement schema constraints.
Validation: CodeRabbit and Codex Security reviewed the current PR diff; Rails focused tests, full Rails tests, system tests, RuboCop, Brakeman, Zeitwerk, ERB lint, npm lint, importmap audit, npm audit, and git diff --check passed.
* fix(statements): defer vault tab loading
---------
Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
* chore(design-system): swap raw gray classes for semantic tokens in holdings/
Continues the raw-color sweep on the holdings/ domain plus the related
account activity feed component. 11 occurrences across 5 files.
Token additions:
- button-bg-secondary-strong (gray-200 / gray-700) and -hover (gray-300 /
gray-600). Holdings CTAs (Add Trade, Add Holding, Edit Cost Basis,
Sync Prices, etc.) used a hand-rolled "secondary-strong" pattern that
doesn't match the existing button-bg-secondary token (which is gray-50
/ gray-700, much subtler). Adding the strong variant preserves the
intentional visual weight of these CTAs and gives future PRs a name
to reuse.
- $version bump 1.0.0 -> 1.1.0 (additive).
Mappings:
- 8x text-primary bg-gray-200 hover:bg-gray-300 theme-dark:bg-gray-700
theme-dark:hover:bg-gray-600 (holdings/show + sync_prices +
cost_basis_cell)
-> text-primary button-bg-secondary-strong hover:button-bg-secondary-strong-hover
- 1x bg-gray-50 theme-dark:bg-gray-700 hover:bg-gray-100
theme-dark:hover:bg-gray-600 (holdings/index search button)
-> button-bg-secondary hover:button-bg-secondary-hover
- 1x hover:bg-gray-100 theme-dark:hover:bg-gray-700 (cost_basis_cell
hover row)
-> hover:bg-container-inset-hover
- 1x focus-within:border-gray-900 (activity_feed search wrapper)
-> focus-within:border-primary
Left intentionally:
- bg-gray-300 status indicator dot in show.html.erb (same pattern as
the settings pilot; no semantic equivalent for "neutral inactive
indicator" yet).
- bg-gray-700 in _missing_price_tooltip.html.erb (already fixed in
PR #1626; would conflict on rebase).
- focus-within:ring-gray-100 (subtle effect that works in both modes;
ring-color tokens are a separate concern).
* chore(design-system): bump $version to 2.1.0 for additive token additions
Per the design tokens semver contract: PR #1626 already bumped to 2.0.0
(major / breaking when fg-* utilities were removed). This PR adds
button-bg-secondary-strong + hover without removing or changing existing
tokens, so the correct bump is minor (2.0.0 → 2.1.0).
Spotted by CodeRabbit on the rebased branch.
* fix(design-system): drop dead focus-within:ring-gray-100 on activity feed search
The focus-within:ring-gray-100 class only sets --tw-ring-color, but the
parent has no ring-width utility, so it produces no visible ring — dead
code from before the focus-within:border-primary swap landed.
Same issue spotted on app/views/accounts/show/_activity.html.erb in the
finalize sweep PR; applying the equivalent fix here for the holdings
activity feed component.
---------
Signed-off-by: Guillem Arias Fauste <gariasf@proton.me>
Extend privacy mode coverage to remaining financial views
Transfers, trades, valuations, and holdings detail views were missing
the privacy-sensitive class, leaving amounts visible when privacy mode
was enabled. Also adds blur to the summary card partial (used by credit
cards, loans, etc.), account chart balances, and time series chart
containers (dashboard net worth and per-account charts).
* Add security remapping support to holdings
- Introduced `provider_security` tracking for holdings with schema updates.
- Implemented security remap/reset workflows in `Holding` model and UI.
- Updated routes, controllers, and tests to support new functionality.
- Enhanced client-side interaction with Stimulus controller for remapping.
# Conflicts:
# app/components/UI/account/activity_feed.html.erb
# db/schema.rb
* Refactor "New transaction" to "New activity" across UI and tests
- Updated localized strings, button labels, and ARIA attributes.
- Improved error handling in holdings' current price display.
- Scoped fallback queries in `provider_import_adapter` to prevent overwrites.
- Added safeguard for offline securities in price fetching logic.
* Update security remapping to merge holdings on collision by deleting duplicates
- Removed error handling for collisions in `remap_security!`.
- Added logic to merge holdings by deleting duplicates on conflicting dates.
- Modified associated test to validate merging behavior.
* Update security remapping to merge holdings on collision by combining qty and amount
- Modified `remap_security!` to merge holdings by summing `qty` and `amount` on conflicting dates.
- Adjusted logic to calculate `price` for merged holdings.
- Updated test to validate new merge behavior.
* Improve DOM handling in Turbo redirect action & enhance holdings merge logic
- Updated Turbo's custom `redirect` action to use the "replace" option for cleaner DOM updates without clearing the cache.
- Enhanced holdings merge logic to calculate weighted average cost basis during security remapping, ensuring more accurate cost_basis updates.
* Track provider_security_id during security updates to support reset workflows
* Fix provider tracking: guard nil ticker lookups and preserve merge attrs
- Guard fallback 1b lookup when security.ticker is blank to avoid matching NULL tickers
- Preserve external_id, provider_security_id, account_provider_id during collision merge
* Fix schema.rb version after merge (includes tax_treatment migration)
* fix: Rename migration to run after schema version
The migration 20260117000001 was skipped in CI because it had a timestamp
earlier than the schema version (2026_01_17_200000). CI loads schema.rb
directly and only runs migrations with versions after the schema version.
Renamed to 20260119000001 so it runs correctly.
* Update schema: remove Coinbase tables, add new fields and indexes
* Update schema: add back `tax_treatment` field with default value "taxable"
* Improve Turbo redirect action: use "replace" to avoid form submission in history
* Lock merged holdings to prevent provider overwrites and fix activity feed template indentation
* Refactor holdings transfer logic: enforce currency checks during collisions and enhance merge handling
---------
Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: luckyPipewrench <luckypipewrench@proton.me>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
* **Add Coinbase integration with item and account management**
- Creates migrations for `coinbase_items` and `coinbase_accounts`.
- Adds models, controllers, views, and background tasks to support account linking, syncing, and transaction handling.
- Implements Coinbase API client and adapter for seamless integration.
- Supports ActiveRecord encryption for secure credential storage.
- Adds UI components for provider setup, account management, and synchronization.
* Localize Coinbase-related UI strings, refine account linking for security, and add timeouts to Coinbase API requests.
* Localize Coinbase account handling to support native currencies (USD, EUR, GBP, etc.) across balances, trades, holdings, and transactions.
* Improve Coinbase processing with timezone-safe parsing, native currency support, and immediate holdings updates.
* Improve trend percentage formatting and enhance race condition handling for Coinbase account linking.
* Fix log message wording for orphan cleanup
* Ensure `selected_accounts` parameter is sanitized by rejecting blank entries.
* Add tests for Coinbase integration: account, item, and controller coverage
- Adds unit tests for `CoinbaseAccount` and `CoinbaseItem` models.
- Adds integration tests for `CoinbaseItemsController`.
- Introduces Stimulus `select-all` controller for UI checkbox handling.
- Localizes UI strings and logging for Coinbase integration.
* Update test fixtures to use consistent placeholder API keys and secrets
* Refine `coinbase_item` tests to ensure deterministic ordering and improve scope assertions.
* Integrate `SyncStats::Collector` into Coinbase syncer to streamline statistics collection and enhance consistency.
* Localize Coinbase sync status messages and improve sync summary test coverage.
* Update `CoinbaseItem` encryption: use deterministic encryption for `api_key` and standard for `api_secret`.
* fix schema drift
* Beta labels to lower expectations
---------
Co-authored-by: luckyPipewrench <luckypipewrench@proton.me>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
* Update activity menu to conditionally display options for linked and investment accounts
* Update transaction test to reflect "New activity" menu change
---------
Co-authored-by: luckyPipewrench <luckypipewrench@proton.me>
* Add pending transaction handling and duplicate reconciliation logic
- Implemented logic to exclude pending transactions from budgets and analytics calculations.
- Introduced mechanisms for reconciling pending transactions with posted versions.
- Added duplicate detection with support for merging or dismissing matches.
- Updated transaction search filters to include a `status_filter` for pending/confirmed transactions.
- Introduced UI elements for reviewing and resolving duplicates.
- Enhanced `ProviderSyncSummary` with stats for reconciled and stale pending transactions.
* Refactor translation handling and enhance transaction and sync logic
- Moved hardcoded strings to locale files for improved translation support.
- Refined styling for duplicate transaction indicators and sync summaries.
- Improved logic for excluding stale pending transactions and updating timestamps on batch exclusion.
- Added unique IDs to status filters for better element targeting in UI.
- Optimized database queries to avoid N+1 issues in stale pending calculations.
* Add sync settings and enhance pending transaction handling
- Introduced a new "Sync Settings" section in hosting settings with UI to toggle inclusion of pending transactions.
- Updated handling of pending transactions with improved inference logic for `posted=0` and `transacted_at` in processors.
- Added priority order for pending transaction inclusion: explicit argument > environment variable > runtime configurable setting.
- Refactored settings and controllers to store updated sync preferences.
* Refactor sync settings and pending transaction reconciliation
- Extracted logic for pending transaction reconciliation, stale exclusion, and unmatched tracking into dedicated methods for better maintainability.
- Updated sync settings to infer defaults from multiple provider environment variables (`SIMPLEFIN_INCLUDE_PENDING`, `PLAID_INCLUDE_PENDING`).
- Refined UI and messaging to handle multi-provider configurations in sync settings.
# Conflicts:
# app/models/simplefin_item/importer.rb
* Debounce transaction reconciliation during imports
- Added per-run reconciliation debouncing to prevent repeated scans for the same account during chunked history imports.
- Trimmed size of reconciliation stats to retain recent details only.
- Introduced error tracking for reconciliation steps to improve UI visibility of issues.
* Apply ABS() in pending transaction queries and improve error handling
- Updated pending transaction logic to use ABS() for consistent handling of negative amounts.
- Adjusted amount bounds calculations to ensure accuracy for both positive and negative values.
- Refined exception handling in `merge_duplicate` to log failures and update user alert.
- Replaced `Date.today` with `Date.current` in tests to ensure timezone consistency.
- Minor optimization to avoid COUNT queries by loading limited records directly.
* Improve error handling in duplicate suggestion and dismissal logic
- Added exception handling for `store_duplicate_suggestion` to log failures and prevent crashes during fuzzy/low-confidence matches.
- Enhanced `dismiss_duplicate` action to handle `ActiveRecord::RecordInvalid` and display appropriate user alerts.
---------
Co-authored-by: Josh Waldrep <joshua.waldrep5+github@gmail.com>
* feat: Add toggle on mobile to show/hide checkboxes in transaction page
* fix: Add multi-select toggle also in activities page. Make JS controller compatible also in this view.
* feat: Add category in mobile view
* feat: Add mobile layout for transaction categories
* feat: Add margin for pagination on mobile
* fix: Ensure category exists when displaying the name
* fix: Adjust mobile paddings
* fix: Display "uncategorized" label if no category is set
* fix: Expand transaction name/subtitle
* feat: Add merchant name on desktop view
* feat: Move merchant name before account name
* fix: Add class to hide merchant on mobile
* feat: Add merchant logo on mobile
* fix: add pointer-events-none to merchant image on mobile view
* feat: toggle header checkbox in transaction page when button is clicked
* Remove unnecessary CSS class
* Remove duplicate CSS class
* Remove wrong Enable Banking logo URL
* Update app/views/transactions/_transaction.html.erb
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Alessio Cappa <104093777+alessiocappa@users.noreply.github.com>
* Revert "Update app/views/transactions/_transaction.html.erb"
This reverts commit 9766c50a1d.
* Add translation for Loan Payment/Transfer
* Apply review comments
* Add accessible name for toggle based on review comments
* Use border instead of border-1 class
* Apply review comments
* Missing l10n key
---------
Signed-off-by: Alessio Cappa <104093777+alessiocappa@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
* Add CA locales for models
* Add CA locales for views
* Use translations in activity feed
* Additional CA locales
* Fix typo
---------
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
* Balance reconcilations with new components
* Fix materializer and test assumptions
* Fix investment valuation calculations and recon display
* Lint fixes
* Balance series uses new component fields
* 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