Commit Graph

22 Commits

Author SHA1 Message Date
Fabio Zorzetto
89b08ca6ea Add Italian translations for various views and functionalities (#2625)
* Add Italian translations for various views and functionalities

- Introduced translations for splits, subscriptions, tag deletions, tags, trades, transactions, transfer matches, transfers, users, valuations, and vehicles.
- Enhanced user experience by providing localized content in Italian for better accessibility and understanding.

* Correzioni traduzioni italiano: API keys shared/index, super admin jobs, imports readback, snaptrade idiomatica, subscriptions progetto, valuations grammatica

* Aggiornamenti delle traduzioni italiane: miglioramenti e aggiunte per vari moduli, inclusi conti, obiettivi, trasferimenti e integrazione con SnapTrade.
2026-07-17 07:25:08 +02:00
cuppabot
7fb6df71f2 Russian language support added (#2005)
* Add russian localization

* Update localization files from upstream

* Add russian to supported languages

* Add CHANGELOG.ru.md

* Changes in CHANGELOG.ru.md

* Fix some errors after PR

* Remove duplicated 'merge_duplicate' key in the same mapping in locales/views/transactions/ru.yml

* Fixed a translation error into Russian about creating a new account by invitation.

* Clarification of financial terms in Russian translation

* Add russian localization

* Update localization files from upstream

* Add russian to supported languages

* Add CHANGELOG.ru.md

* Changes in CHANGELOG.ru.md

* Fix some errors after PR

* Remove duplicated 'merge_duplicate' key in the same mapping in locales/views/transactions/ru.yml

* Fixed a translation error into Russian about creating a new account by invitation.

* Clarification of financial terms in Russian translation

* Improve Russian i18n translations

* Improve Russian i18n translations

* Improve Russian i18n translations

* Improve Russian i18n translations

* Improve Russian i18n translations

* Improve tests for Russian i18n translations

* Improve Russian i18n translations

* Improve Russian i18n translations

* Minimalistic SECURITY.md

Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>

* ci(preview): render Cloudflare config from trusted template (#2207)

* Fix SSO provider settings updates (#2210)

* fix(goals): UI polish — submit validation, container-responsive cards, picker & filter fixes (#2160)

* fix(ds): disabled buttons use not-allowed cursor

Tailwind v4 preflight sets cursor:pointer on every <button>, including
disabled ones. Add disabled:cursor-not-allowed to the DS button base so
disabled buttons read as non-interactive.

* fix(goals): disable new-goal submit until required fields valid

The submit button was always enabled, and the funding-accounts checkbox
group has no native 'required', so a goal with no linked account could be
submitted and only fail server-side (422). Disable the submit until name,
a positive target amount, and >=1 account are present, and add a
submit-time guard that surfaces inline errors and focuses the first
offending field as a backstop.

* fix(goals): keep color/icon picker from shifting the form

DS::Disclosure wraps its body in an mt-2 div that sits in normal flow, so
opening the absolutely-positioned picker popover nudged the form down ~8px.
Use a raw <details> for the picker so the popover overlays without
reflowing the content beneath it.

* fix(goals): container-responsive cards, meta-line status, scrollable tabs

- Card grids + KPI strip switch from viewport breakpoints to container
  queries (@container), so columns track the actual content width when the
  account/AI sidebars are open instead of crushing three columns into a
  narrow center.
- Move the status pill from the title row to the meta line; the title now
  gets the full header width (no more 'Hou...' truncation at 3-up). Drop
  the secondary line when the pill already states it (completed / open).
- Card internals harden for narrow widths (ring shrink-0, footer wrap,
  shorter 'N days left' secondary).
- Filter tabs scroll horizontally instead of wrapping 'On track' mid-label
  or forcing the row wider than the viewport.

* fix(goals): only require a funding account on the create form

GoalsController#edit renders account checkboxes from visible accounts only,
so a goal backed solely by a now-hidden (disabled / pending_deletion)
account renders none checked. The submit-validation then wedged the edit
form — a name/notes change couldn't be saved even though #update preserves
existing links when account_ids is omitted. Gate the account requirement on
a require-account Stimulus value that is true only for the create form.

* fix(goals): give the color/icon picker trigger an accessible name

The hand-rolled <summary> is icon-only (pen), so screen readers announced an
unlabeled control. Add a localized aria-label.

* fix(goals): render form validation errors inline on server re-render

The client-side controller already surfaces name / amount / account errors
inline and disables submit, but a server 422 (JS disabled, or a race that
lets an invalid submit through) fell back to the top run-on banner
("X must be filled and Y and Z."). Make the existing inline error hints
server-aware so a failed submit shows the same per-field red text the
client path does, and drop the base-error banner — base only ever carries
the account requirement, which now renders beside the funding-accounts
list.

* fix(goals): blur projection chart under privacy mode

The projection chart's SVG axis labels and annotations (target, "$X short",
$200K/$400K ticks) rendered in cleartext while privacy mode blurred every
other number on the page. Tag the chart container `privacy-sensitive`, the
same wrapper-level treatment the net-worth and cashflow-sankey charts
already use, so it blurs with the rest.

* fix(goals): stop target-date input stretching when amount error shows

The target-amount / target-date row is a two-column grid. The amount
column carries its inline error <p> underneath, so when that error
appears the column grows and the default `items-stretch` makes the
sibling date input stretch to match — the date box visibly grew taller
than the amount box. Anchor the row with `items-start` so each input
keeps its natural height and the error just extends below its own column.

* fix(goals): make invalid submit announce errors instead of dead-ending

Review follow-ups:
- Swap the submit gate from the disabled attribute to aria-disabled: a
  truly disabled default submit also blocks Enter-key implicit
  submission, so an invalid form was a dead button with every inline
  error still hidden. The button now stays clickable and lets
  validateOnSubmit surface the errors; DS buttonish styles the
  aria-disabled state (cursor-not-allowed + opacity-50). Side effect:
  loading_button_controller submits also dim while busy, which reads as
  an upgrade.
- Focus the first funding-account checkbox when accounts are the only
  missing field (focus previously went nowhere).
- Drop the now-orphaned goals.goal_card.days_left_by locale key.

* fix(ds): canonical transaction-row — stop category pills truncating (#2147)

* fix(ds): canonical transaction-row — stop category pills truncating (#2137)

The desktop transaction grid gave the name column col-span-8 (67%, usually
half-empty) and the category column only col-span-2 (17%), so the category
pill's name area was clamped to ~44px and ellipsized nearly every value
("Misc...", "Shop...", "Rest...") despite the empty space the audit flagged.

- Rebalance the lg:grid-cols-12 row: name col-span-8 -> 7, category 2 -> 3
  (amount unchanged; still 12). Category gains 50% width from the over-wide
  name column.
- categories/_badge: the pill was `flex w-full` (stretched to fill the column);
  make it `inline-flex max-w-full` so it hugs its content and short names render
  fully, capping + truncating only when genuinely too long.

Verified on /transactions: visible-row category truncation dropped 7/7 -> 2/7
even in the compressed (AI-panel-open) view; Payment / Shopping / Restaurants
now render in full. Fixes both the interactive categories/menu and the static
transfer categories/badge (menu reuses the badge partial).

* feat(ds): lift categories/_badge onto DS::Pill

Addresses the Drift Patrol finding (and jjmata's request) properly
instead of patching the bespoke span: the badge becomes a DS::Pill in
badge mode. The pill primitive grows the three capabilities the badge
needed and the bot's one-liner glossed over:

- truncate: pills that may shrink inside min-w-0 columns drop their
  shrink-0/whitespace-nowrap and ellipsize the label instead of
  overflowing (the transaction-row category cell this PR fixes).
- label_testid: stamps data-testid on the label span; five test files
  target [data-testid='category-name'].
- icon_size: passthrough to the icon helper (badge keeps its
  established sm glyph; default stays xs).

custom_color was already the sanctioned escape hatch for user-chosen
hues. Owned visual deltas from pill standardization: px-1.5/py-1 ->
px-2/py-0.5, gap-1 -> gap-1.5, border mix 10% -> 20%; bg mix, hex text,
radius, text scale, icon size, truncation and testid are parity.

Label wrapper only renders when truncate/label_testid ask for it, so
existing pill DOM (and the find("span", text:) assertions on it) is
unchanged. Four new pill tests + a Lookbook case cover the recipe.

* feat(merchants): add raw data import (csv) for merchants (#1992)

* feat(merchants): add csv import endpoint for merchants

* docs: update endpoint docs

* fix(merchant): recommended ai fixes

* chore(ci): finish Node 24 GitHub Actions migration (#2221)

* chore(ci): finish Node 24 GitHub Actions migration

* chore(ci): update preview security check for github-script v8

* fix(mobile): redact sensitive diagnostic logs (#2199)

* fix(mobile): redact sensitive diagnostic logs

* fix(mobile): tighten diagnostic log redaction

* fix(mobile): harden sanitized diagnostics

* fix(mobile): clarify offline fallback diagnostics

* fix(mobile): refine diagnostic log redaction

* fix(mobile): tighten diagnostic log redaction

* fix(mobile): harden auth diagnostic failures

* feat(ds): DS::SegmentedControl — fix invisible dark selected pill (#2145)

* feat(ds): DS::SegmentedControl; fix invisible dark selected pill (#2137)

Ships DS::SegmentedControl — a single-select pill group (filters, mode
switches) — plus a Lookbook preview, tests, and an exemplar migration of the
budget filter tabs.

The audit flagged the dark selected pill as invisible: the bespoke controls
paired a container-inset track (gray-800) with a gray-700 active pill, barely a
step apart. The primitive uses the DS tab-token values (tab-bg-group -> a
near-black alpha-black-700 track in dark), against which the gray-700 active
pill clearly reads. Values are inlined with @variant theme-dark because
@apply-ing the custom tab utilities drops their dark override.

- app/components/DS/segmented_control.rb: slot-based with_segment(label,
  active:, href:, **opts); link or button; full_width: for equal footprint;
  selected style isolated in .segmented-control__segment--active so a controller
  can toggle it as one class.
- .segmented-control recipe in components.css.
- Migrate budgets/_budget_tabs; budget_filter_controller now toggles the single
  --active class instead of five raw utility classes.

Verified in-browser: dark active pill reads (gray-700 on near-black track);
filter toggle still works. Tests + rubocop clean.

Deferred (follow-up): auth sign-in/up switch, transaction-type tabs, and other
bespoke segmented controls — same primitive, one migration each.

* fix(a11y): expose segmented-control selection + derive active from filter param

- DS::SegmentedControl: set aria-current (links) / aria-pressed (buttons) from
  the segment's active state so screen readers announce the selection.
- budget_filter_controller: mirror aria-pressed when it toggles the active class.
- _budget_tabs: compute each segment's initial active: from params[:filter] so
  a ?filter=over_budget request server-renders the correct pill (no flash before
  Stimulus runs). Addresses CodeRabbit reviews on #2145.

* fix(ds): close unterminated segmented-control CSS rule

The --active rule swallowed the table-scroll block's comment opener and
never closed, so the Tailwind build died with 'Missing closing } at
@layer components' and both test jobs failed at boot. Restore the
closing brace and the /* opener.

Also add the budgets.show.filter.aria_label locale key the budget tabs
view referenced only through its inline default.

---------

Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>

* fix(ds): one height rail for icon and text buttons (#2202)

* fix(ds): put icon buttons on the text-button height rail

Icon-only DS::Button containers were 32/44/48px squares while text
buttons of the same nominal size render ~28/36/48px tall, so every
mixed header row (icon menu trigger next to text buttons — the
transactions index, account pages, the app header) sat misaligned.

- buttonish SIZES: icon containers now share the text rail
  (sm w-7, md w-9, lg w-12 unchanged).
- DS::Popover's hand-rolled w-11 trigger joins the rail at w-9.
- The layout's hand-rolled privacy toggle (mobile + desktop) matches
  the md icon-button chrome: w-9, rounded-lg, container-inset hover —
  it sat at w-8 with a different hover next to a DS icon button.
- DS::Select's panel adopts shadow-border-lg, the elevation Menu and
  Popover already use, replacing the weaker shadow-lg+border-xs combo.

Measured on the transactions header after the change: 36/38/36px.

* fix(ds): keep the 44px touch target on coarse pointers

The height rail trades icon-button size for row alignment, which is a
pointer-precision tradeoff: WCAG 2.5.5's 44x44 minimum is about
fingers, not mice. sm/md icon containers (and the two off-rail
consumers: the popover trigger and the layout privacy toggles) gain
pointer-coarse:w-11/h-11, so touch devices keep the full target while
fine-pointer layouts get the aligned 36px row.

Measured via Playwright: desktop 36x36, iPhone emulation 44x44 on both
the menu trigger and the privacy toggle.

* fix(sync-toast): morph refresh, defer behind modals, DS conformance (#2105)

* fix(sync-toast): morph refresh, defer behind modals, DS conformance

Follow-up to #1964 (addresses #2071).

- Refresh via Turbo morph visit instead of window.location.reload, so
  scroll position and data-turbo-permanent elements (the AI chat panel)
  survive and there is no white flash.
- Defer the toast while a <dialog> is open and reveal it on close. A
  refresh mid-modal closes the dialog and discards its in-progress input,
  which is the exact data loss this toast exists to prevent. Handles
  stacked modals.
- Refresh CTA and close button now use DS::Button (secondary / icon). The
  close is always visible, inside the card, focusable, and has an
  aria-label; the old hover-only corner chip was unreachable on touch and
  not keyboard-focusable.
- Add role="status" / aria-live="polite" to the toast.
- Fix icon color: "inverse" is not a key in the icon helper color map, so
  it silently rendered no color class (dark icon on bg-info). Use "white",
  which maps to the functional text-inverse token.
- Tighten copy: "New data available" / "Refresh".
- Sync the broadcast comment with the actual replace/morph behavior.

* fix(sync-toast): detach deferred dialog listener on disconnect

A toast replaced by a newer broadcast_replace_to while a <dialog> was open kept
its 'close' listener attached, so the detached controller fired #reveal()/#arm()
when the dialog closed — a spurious auto-refresh from a stale toast (and repeated
syncs could queue several). Store the dialog + handler refs and remove the
listener in disconnect(). Flagged by codex + coderabbit on #2105.

* fix(sync-toast): re-check interaction and dialogs at refresh-fire time

The interaction check ran once at arm time but the refresh fired two
seconds later. The post-dialog reveal made that window matter: the user
closes a dialog sitting on a form, resumes typing, and the timer morphs
the page — wiping non-turbo-permanent input, the exact data-loss class
this toast exists to prevent. A dialog opened during the window had the
mirror problem (the refresh would close it).

Bail inside the callback instead, leaving the toast visible for a
manual refresh, matching the mid-form behavior. Also documents the
dialog-removed-without-close edge on the deferred listener.

* perf(transactions): preload new form options (#2189)

* perf(transactions): preload new form options

* refactor(transactions): reuse new form account scope

* perf(reports): collapse investment flow aggregates (#2190)

* perf(reports): collapse investment flow aggregates

* fix(reports): avoid shared-account flow duplication

* fix: Savings Goal Marked Reached While Still Short (#2180)

* fix: saving goal is marke while several states

* feat(enable_banking): support MFA/decoupled banks and harden session handling (#2174)

Decoupled/MFA banks (e.g. VR Bank in Holstein) were hard-blocked because the
authorize flow aborted whenever auth_methods[0] was DECOUPLED. Enable Banking's
hosted /auth page actually coordinates decoupled SCA and redirects back with a
code, so route these banks through it instead:

- Provider#start_authorization accepts and forwards an auth_method param
- EnableBankingItem#select_auth_method picks the best method
  (REDIRECT > DECOUPLED > EMBEDDED), filtering by psu_type and skipping hidden
  methods
- Shared begin_authorization! re-fetches ASPSP metadata on each authorize and
  reauthorize, so the method is always re-derived (no persistence required)
- Remove the DECOUPLED block in the controller

Also stop the integration from constantly reporting "session expired":

- Only a session-level GET /sessions 401/404 flips the connection to
  requires_update; per-account 401/404 are retried and no longer kill the
  whole connection
- Reconcile session_expires_at from the API's access.valid_until on every sync
- Treat an expired session as a graceful requires_update state instead of
  raising a bare error

No schema changes. Adds covering tests.

* fix: request change reach

---------

Co-authored-by: Tobias Rahloff <rahloff@gmail.com>

* Improve Russian i18n translations

* Improve Russian i18n translations

* Improve Russian i18n translations

* Improve Russian i18n translations

* No need for language-specific CHANGELOG.md file

---------

Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: tea <john.fx@gmail.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: ghost <49853598+JSONbored@users.noreply.github.com>
Co-authored-by: Guillem Arias Fauste <accounts@gariasf.com>
Co-authored-by: Blaž Dular <22869613+xBlaz3kx@users.noreply.github.com>
Co-authored-by: Sure Admin (bot) <sure-admin@splashblot.com>
Co-authored-by: Jonathan Chang <55106972+jonathanchang31@users.noreply.github.com>
Co-authored-by: Tobias Rahloff <rahloff@gmail.com>
Co-authored-by: Juan José Mata <jjmata@jjmata.com>
2026-07-08 08:10:18 +02:00
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
Guillem Arias Fauste
1ddd8bd040 feat(i18n): complete Catalan translations + extract residual hardcoded strings (#1836)
* 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.
2026-05-19 13:37:10 +02:00
Brendon Scheiber
dce2213a98 feat: add Hungarian (hu) localization (#1677)
* Add Hungarian (hu) localization

Add complete Hungarian translation files and register hu locale

* Update hu.yml

* Hungarian locale: currency formatting & fixes
2026-05-06 22:38:51 +02:00
UberDudePL
cebdf1d4f7 Polish localization: complete translations, pluralization fixes, and reusable locale audit tooling (#1356)
* Add production-ready Polish localization and reusable locale audit tooling

- add and update Polish locale files across models, views, mailers, and shared translations
- add runtime rails-i18n dependency and Polish locale support in language helper
- add regression coverage for Polish pluralization and locale-aware money formatting
- introduce reusable locale audit script for any locale plus backward-compatible PL wrapper
- add localization audit docs and generated PL readiness/pluralization reports
- resolve one/few/many/other pluralization consistency for Polish locales

* Fix Polish locale review feedback

* Fix locale compatibility regressions

* Polish locale typo pass and wrapper cleanup

* Final language improvements and test isolation for Polish locales

- Improved partial_success wording in SnapTrade with proper noun inflection
- Fixed typos: Pomin → Pomiń in Mercury and LunchFlow items
- Isolated I18n backend state in polish_pluralization_test to prevent test coupling

* Fix code review comments in locale audit scripts

- Use RbConfig.ruby instead of 'ruby' to ensure consistent interpreter
- Remove Symbol from permitted_classes and explicitly allow CLDR plural symbols (one, few, many, other) in YAML loading

* Simplify i18n flow and align locale interpolation keys

* Remove locale audit scripts and localization docs
2026-04-07 11:55:58 +02:00
Vegim Çarkaxhija
fc987ededb Add Kosovo to country list (#1066)
Kosovo was missing from the COUNTRY_MAPPING in LanguagesHelper.
Added Kosovo with ISO 3166-1 alpha-2 code XK and flag emoji 🇽🇰.

The entry is placed in alphabetical order between Kuwait (KW) and
Kyrgyzstan (KG) to maintain consistency with the existing list.


Fix Investment account subtype not saving on creation (#1039)

* Add permitted_accountable_attributes for investments

* Include fields_for for accountable subtype selection
feat: Add tag badge in filter window (#1038)

* feat: Add tag badge in filter window

* fix: validate Tag color attribute as hex format and increase transparency mix in border color

* fix: use fallback for tag color
fix/qol: Add Callback URL the Enable Banking Instructions (#1060)

* fix/qol: Add wich Callback URL to use to the Enable Banking Instructions

* CodeRabbit suggestion

* CodeRabbit suggestion

* Skip CI failure on findings

---------


fix: Update PWA icons to use current logo (#1052)

* fix: Update PWA icons to use current logo (#997)

Replace outdated android-chrome-192x192.png and logo-pwa.png with the
current logo. The old icons showed the previous branding (cyan border /
old logomark) which appeared when creating web shortcuts on smartphones.

Also add the 192x192 icon entry to the PWA manifest for better Android
home screen icon support.



* fix: Replace transparent background with solid #F9F9F9 in 192x192 PWA icon

The android-chrome-192x192.png had an RGBA transparent background which
can cause display issues on Android home-screen shortcuts. Regenerated
with a solid #F9F9F9 background to match theme_color/background_color
in the PWA manifest and the 512x512 icon.



---------


Bump version to next iteration after v0.6.8-alpha.13 release

Use Langfuse client trace upsert API (#1041)

Replace direct trace.update calls with client trace upserts so OpenAI provider is compatible with langfuse-ruby 0.1.6 behavior. Add richer warning logs that include full exception details for trace creation, trace upserts, and generation logging failures. Add tests for client-based trace upserts and detailed error logging.
Add build ID to Flutter app version display

Add MCP server endpoint for external AI assistants (#1051)

* Add MCP server endpoint for external AI assistants

Expose Sure's Assistant::Function tools via JSON-RPC 2.0 at POST /mcp,
enabling external AI clients (Claude, GPT, etc.) to query financial data
through the Model Context Protocol.

- Bearer token auth via MCP_API_TOKEN / MCP_USER_EMAIL env vars
- JSON-RPC 2.0 with proper id threading, notification handling (204)
- Transient session (sessions.build) to prevent impersonation leaks
- Centralize function_classes in Assistant module
- Docker Compose example with Pipelock forward proxy
- 18 integration tests with scoped env (ClimateControl)

* Update compose for full Pipelock MCP reverse proxy integration

Use Pipelock's --mcp-listen/--mcp-upstream flags (PR #127) to run
bidirectional MCP scanning in the same container as the forward proxy.
External AI clients connect to port 8889, Pipelock scans requests
(DLP, injection, tool policy) and responses (injection, tool poisoning)
before forwarding to Sure's /mcp endpoint.

This supersedes the standalone compose in PR #1050.

* Fix compose --preset→--mode, add port 3000 trust comment, notification test

Review fixes:
- pipelock run uses --mode not --preset (would prevent stack startup)
- Document port 3000 exposes /mcp directly (auth still required)
- Add version requirement note for Pipelock MCP listener support
- Add test: tools/call sent as notification does not execute
Feat/Abstract Assistant into module with registry (#1020)

* Abstract Assistant into module with registry (fixes #1016)

- Add Assistant module with registry/factory (builtin, external)
- Assistant.for_chat(chat) routes by family.assistant_type
- Assistant.config_for(chat) delegates to Builtin for backward compat
- Assistant.available_types returns registered types
- Add Assistant::Base (Broadcastable, respond_to contract)
- Move current behavior to Assistant::Builtin (Provided + Configurable)
- Add Assistant::External stub for future OpenClaw/WebSocket
- Migration: add families.assistant_type (default builtin)
- Family: validate assistant_type inclusion
- Tests: for_chat routing, available_types, External stub, blank chat guard

* Fix RuboCop layout: indentation in Assistant module and tests

* Move new test methods above private so Minitest discovers them

* Clear thinking indicator in External#respond_to to avoid stuck UI

* Rebase onto upstream main: fix schema to avoid spurious diffs

- Rebase feature/abstract-assistant-1016 onto we-promise/main
- Rename migration to 20260218120001 to avoid duplicate version with backfill_crypto_subtype
- Regenerate schema from upstream + assistant_type only (keeps vector_store_id, realized_gain, etc.)
- PR schema diff now shows only assistant_type addition and version bump

---------


Add Pipelock agent security scan to CI (#1049)

* Add Pipelock agent security scan to CI

Scans PR diffs for leaked secrets and agent security risks.
Zero config, runs on every PR to main.

* Retrigger CI (v1 action tag now available)

* Harden checkout: persist-credentials false

Pipelock only reads local git history for diff scanning,
no auth token needed in .git/config.
Improvements to Flutter client (#1042)

* Chat improvements

* Delete/reset account via API for Flutter app

* Fix tests.

* Add "contact us" to settings

* Update mobile/lib/screens/chat_conversation_screen.dart




* Improve LLM special token detection

* Deactivated user shouldn't have API working

* Fix tests

* API-Key usage

* Flutter app launch failure on no network

* Handle deletion/reset delays

* Local cached data may become stale

* Use X-Api-Key correctly!

---------



Skip unnecessary sync when account balance unchanged on update (#1040)

The update action was calling set_current_balance (which triggers
sync_later internally) on every form submission, even when the balance
hadn't changed. This caused the account to enter a syncing state,
replacing the visible balance with a pulsing skeleton placeholder
until the sync completed.

Now we compare the submitted balance against the current value and
only call set_current_balance when it actually differs. Also removes
a redundant sync_later call that duplicated the one already inside
set_current_balance.


feat: Add merchant logo in filter selection (#1037)

* feat: Add merchant logo in filter selection

* fix: move external div outside of if statement
Sync Helm chart and Rails app versions in CI and release workflows (#1030)

* Sync Helm chart and Rails app versions in CI and release workflows

- values.yaml: default image.tag to "" so it uses Chart.appVersion
  (was hardcoded to stale "0.6.6" while app was at 0.6.8-alpha.13)
- chart-ci.yml: add version-sync job that fails if version.rb,
  Chart.yaml version, and Chart.yaml appVersion diverge; trigger on
  version.rb changes too
- chart-release.yml: derive chart version from version.rb (single
  source of truth) instead of auto-incrementing independent chart-v* tags

https://claude.ai/code/session_01Eq3WHBn3Uwjezxb6ctdjMB

* Default to `false` AI_DEBUG_MODE

* Apply suggestions from CodeRabbit




---------




Fix mobile-build using tag name instead of branch for filenames

When workflow_dispatch is triggered from a tag (e.g. v0.6.7) instead
of a branch, github.ref_name returns the tag name, causing filenames
like sure-v0.6.7-{stamp}.apk instead of sure-main-{stamp}.apk.

Guard against this by checking github.ref_type and falling back to the
repository's default branch when a tag is selected.

https://claude.ai/code/session_01TDfNkNxQ6uWxQxLAwJY5Qa

Add workflow to build mobile apps from main without tagging (#1028)

* Add workflow to build mobile apps from main without tagging

Adds a new `mobile-main-build.yml` workflow that can be triggered
manually via workflow_dispatch to build Android APK and iOS unsigned
builds from the main branch. Uses a `main-YYYYMMDDHHMI` stamp for
versioning (e.g. sure-main-202602181259.apk) and updates the gh-pages
README.md MOBILE_DOWNLOADS section with direct download links.

https://claude.ai/code/session_01TDfNkNxQ6uWxQxLAwJY5Qa

* Rename to mobile-build.yml and support any branch

Instead of hardcoding "main", derive the branch name from
github.ref_name, sanitise it for filenames/tags (slashes → hyphens),
and use it throughout: version, tag, release notes, and gh-pages
README. The checkout step now explicitly pins ref: ${{ github.ref }}
so the tag always matches the dispatched branch.

Example artifacts from main:  sure-main-202602181259.apk
Example from feature/foo:    sure-feature-foo-202602181259.apk

https://claude.ai/code/session_01TDfNkNxQ6uWxQxLAwJY5Qa

* Add continue-on-error to artifact download steps

If either the Android or iOS build fails, the download step would
hard-fail and abort the release job before the conditional logic in
"Prepare release assets" could handle the partial result. Adding
continue-on-error lets the workflow proceed so a release can still
be created with whichever artifacts succeeded.

https://claude.ai/code/session_01TDfNkNxQ6uWxQxLAwJY5Qa

* Fix in-place replacement of MOBILE_DOWNLOADS section in README

The previous logic stripped the marker block then appended the new
section at the end of the file, causing it to drift to the bottom on
every run. Now writes the section to a temp file and uses awk to
replace the block between the markers in-place, preserving the
section's original position in the README.

https://claude.ai/code/session_01TDfNkNxQ6uWxQxLAwJY5Qa

---------


Bump version to next iteration after v0.6.8-alpha.12 release

Enable inclusion of hidden files in helm chart package


Remove OPENAI_URI_BASE and OPENAI_MODEL from Helm secret values (#1025)

These are optional app configuration values (not secrets), and listing
them in rails.secret.values alongside required keys like SECRET_KEY_BASE
makes users think they must be specified. Users who need them can set
them via rails.extraEnv or rails.settings instead.

https://claude.ai/code/session_01BP8Nr2cZWDdu9zGL9vD8Mw


fix: Enable Banking DNS issues and provide better UI sync feedback (#1021)

* fix(docker): add explicit DNS config to fix enable banking sync

* fix(enable-banking): surface sync errors in the UI

* fix: add spaces inside array brackets for RuboCop

* fix(enable-banking): surface sync errors and partial failures in UI
chore(deps): bump nokogiri from 1.18.9 to 1.19.1 (#1024)

Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.18.9 to 1.19.1.
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.18.9...v1.19.1)

---
updated-dependencies:
- dependency-name: nokogiri
  dependency-version: 1.19.1
  dependency-type: indirect
...



Refactor GitHub Actions workflows (#1023)

* Unify release workflows and add chart/mobile wrappers

* Update chart CI to kube 1.25

* Fetch tagged commit before pushing release branch

* Old `azure/setup-helm`

* Base chart dispatch version on existing chart tags

* `grep` failure with `pipefail` bypasses the user-friendly error message

* `gh-pages` push lacks retry logic

* Auto-incremented chart tag collision

* `grep -Ev` pipeline will crash

* Missed one
feat: New tag creation UI (#1014)

* feat: Update tag creation UI

* fix: remove unused target

* fix: remove connect/disconnect functions

* fix: remove unnecessary target
Fix crypto subtype for trades api (#1022)

* fix: crypto subtype not persisted by permitting :subtype in CryptosController

* Backfill crypto subtype for existig accounts so Trades API works

* fix: backfill only unlinked cryptos; use raw SQL in migration; deterministic redirect in test

* Update schema.rb for BackfillcryptoSubtypeForTrades migration

---------


fix: add logic to skip future pending transactions and add cleanup ta… (#1011)

* fix: add logic to skip future pending transactions and add cleanup task for stuck entries

* Update lib/tasks/cleanup_stuck_pending_lunchflow.rake




* Update app/models/lunchflow_entry/processor.rb




* fix(coderabbit): assertions use entryable instead of transaction for pending state checks

* chore(codex): add comments to clarify handling of pending transactions, exclude self in cleanup task

* fix(coderabbit): memoize external_id

---------



fix: show Edit button in account view bulk selection bar (#1002)


Fix foreign currency accounts using wrong exchange rate in balance sheet totals (#1010)

Balance sheet totals and accountable type summaries used a SQL JOIN on
exchange_rates matching only today's date, which returned NULL (defaulting
to 1:1) when no rate existed for that exact date. This caused foreign
currency accounts to show incorrect totals.

Changes:
- Refactor BalanceSheet::AccountTotals to batch-fetch exchange rates via
  ExchangeRate.rates_for, with provider fallback, instead of a SQL join
- Refactor Accountable.balance_money to use the same batch approach
- Add ExchangeRate.rates_for helper for deduplicated rate lookups
- Fix net worth chart query to fall back to the nearest future rate when
  no historical rate exists for a given date
- Add composite index on accounts (family_id, status, accountable_type)
- Reuse nearest cached exchange rate within a 5-day lookback window
  before calling the provider, preventing redundant API calls on
  weekends and holidays when providers return prior-day rates

https://claude.ai/code/session_01GyssBJxQqdWnuYofQRjUu8


Syntax error in workflow

Bump version to next iteration after v0.6.8-alpha.11 release

Default tap on URL opens settings

No credentials = no TestFlight

Upload to TestFlight after `release` / fix version name

Fix Android icon color

Add demo account to Flutter client also

Default login for Flutter client beta

Wire TestFlight up to mobile releases

iOS build fixes/prep for TestFlight

LLC is Sure Finances, keep it the same

New icon set and name

Safe area around icon

Add flutter_export_environment.sh to .gitignore


Ignore changes in `mobile/` directory for publish workflow


Enhance mobile release workflow with dispatch and script

Added workflow_dispatch trigger and updated GitHub Release step to use a script for release notes and asset uploads.


New icons

New icon

Fix version number for Android

Version number in Gradle build

Version number to bundle files

More .gitignore noise

chore(deps): bump rack from 3.1.18 to 3.1.20 (#1013)

Bumps [rack](https://github.com/rack/rack) from 3.1.18 to 3.1.20.
- [Release notes](https://github.com/rack/rack/releases)
- [Changelog](https://github.com/rack/rack/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rack/rack/compare/v3.1.18...v3.1.20)

---
updated-dependencies:
- dependency-name: rack
  dependency-version: 3.1.20
  dependency-type: indirect
...



Update Flutter iOS run command in `mobile/README.md`


fix: Remove additional padding from JS controller and add directly as a class (#1007)


PDF ai import (#1006)

Add support to review transactions for AI pdf import
Bump version to next iteration after v0.6.8-alpha.10 release

Improve tests

Add new columns and sorting to admin users list (#1004)

* Add trial end date to admin users list

* Add new columns

* Regression
feat: Update gh-pages README with latest mobile release links (#1003)

Add steps to the mobile-release workflow that checkout the gh-pages
branch and update its README.md with direct download links to the
latest Flutter mobile clients (Android APK, debug APK, iOS unsigned
build). Uses HTML comment markers for idempotent updates on subsequent
releases.

https://claude.ai/code/session_01GuUjjmMzxvdSwfvhrjvJr1


Fix: Handle missing category in SetTransactionCategory executor (#1001)


fix: Transfers were not syncing between accounts (#987)

* fix: Include investment_contribution in transfer? check and protect transfer entries from sync

Transfer transactions with kind "investment_contribution" were not recognized
as transfers by the UI, causing missing +/- indicators, "Transfer" labels,
and showing regular transaction forms instead of transfer details.

Also adds user_modified: true to entries created via TransferMatchesController
and SetAsTransferOrPayment rule action to protect them from provider sync
overwrites, matching the existing behavior in Transfer::Creator.

https://claude.ai/code/session_019BZ5Z1aqKSK3cRdR81P5Jg

* fix: Centralize transfer/budget kind constants for consistent investment_contribution handling

Define TRANSFER_KINDS and BUDGET_EXCLUDED_KINDS on Transaction to eliminate
hard-coded kind lists scattered across filters, rules, and analytics code.

investment_contribution is now consistently treated as a transfer in search
filters, rule conditions, and UI display (via TRANSFER_KINDS), while budget
analytics correctly continue treating it as an expense (via BUDGET_EXCLUDED_KINDS).

https://claude.ai/code/session_019BZ5Z1aqKSK3cRdR81P5Jg

* fix: Update tests for consistent investment_contribution as transfer kind

- search_test: loan_payment is now in TRANSFER_KINDS, so uncategorized
  filter correctly excludes it (same as funds_movement/cc_payment)
- condition_test: investment_contribution is now a transfer kind, so it
  matches the transfer filter rather than expense filter

https://claude.ai/code/session_019BZ5Z1aqKSK3cRdR81P5Jg

* fix: Eliminate SQL injection warnings in Transaction::Search

Replace string-interpolated SQL with parameterized queries:
- totals: use sanitize_sql_array with ? placeholders
- apply_category_filter: pass TRANSFER_KINDS as bind parameter
- apply_type_filter: use where(kind:)/where.not(kind:) and
  parameterized IN (?) for compound OR conditions
- Remove unused transfer_kinds_sql helper

https://claude.ai/code/session_019BZ5Z1aqKSK3cRdR81P5Jg

---------


feat: Display dashboard as a 2-columns grid on big screens (#1000)

* feat: display 2 columns grid in dashboard for wide screens

* fix: update sortable controller to consider X/Y coordinates

* fix: lint issues + missing variable init
Flutter title and icon alignment fixes

Small Flutter UI tweaks

Intro mode in Flutter client fixes

Replace text-tertiary with text-subdued in views (#999)

* Replace text-tertiary with text-subdued in views

Replace usages of the text-tertiary utility with text-subdued across several view partials to standardize subdued text styling because text-tertiary does not exist in the design system (reports, doorkeeper auth, simplefin items). Also adjust the net worth empty-liabilities markup to use a grid layout for consistent spacing, and update the related controller test selector to match the new CSS class.

* Standardize empty net worth message markup

Replace inconsistent markup and classes for empty asset/liability sections in the net worth partial. Swap text-secondary/p-2/text-center for text-subdued with unified padding (py-3 px-4 lg:px-6), and simplify the liabilities block from a grid/div to a single paragraph for consistent styling and spacing.
Fix separators in breakdown table view (#996)

* Fix separators in breakdown table view

Correct conditional logic for rendering column separators (rulers) in the reports breakdown table. The top-level check now compares idx to groups.size instead of group.size, and the subcategory check compares idx to group[:subcategories].size. This ensures separators are shown between categories and subcategories correctly, avoiding missing or extra rulers.

* Fix subcategory index variable name in partial

Rename the inner loop index from `idx` to `sub_idx` in app/views/reports/_breakdown_table.html.erb to avoid shadowing the outer `idx`. This ensures the conditional that renders the separator (`shared/ruler`) uses the correct index for subcategories, preventing incorrect rendering of separators between subcategory rows.

* Fix conditional block order in breakdown table

Reorder ERB tags to properly nest the subcategory conditional and the ruler render in the reports breakdown partial. This ensures the divider is only rendered between subcategories and prevents mismatched ERB/end tags that could break template rendering.
fix: Viewport issue in PWA (#995)

* fix: move safe-area padding from body/HTML to navbars. Add script to compute app height dynamically.

* fix: Initialize sankey tooltip with top-0 to avoid overflow

* fix: add fallback to HTML height

* fix: properly set bottom spacing and use position fixed for bottom navbar

* fix: move viewport controller initialization
fix: locale-dependent category duplication bug (#956)

* fix: locale-dependent category duplication bug

* fix: use family locale for investment contributions category to prevent duplicates and handle legacy data

* Remove v* tag trigger from flutter-build to fix double-runs

publish.yml already calls flutter-build via workflow_call on v* tags,
so the direct push trigger was causing duplicate workflow runs.



* Refactor mobile release asset flow

* fix: category uniqueness and workflow issues

* fix: fix test issue

* fix: solve test issue

* fix: resolve legacy problem

* fix: solve lint test issue

* fix: revert unrelated changes

---------



fix: prevent New rule modal from closing when removing conditions or actions (#991)


fix: prevent value overflow in Assets vs Liabilities card (#992)

* fix: prevent value overflow in Assets vs Liabilities card

Fixes issue where large asset/liability values overflow the container
when AI side panel is open and reduces horizontal space.

Changes:
- Added flex-wrap to allow values to wrap to next line if needed
- Added break-all to both asset and liability values for long numbers
- Added shrink-0 to minus sign to prevent it from shrinking

Fixes #976



* refactor: use break-words instead of break-all per code review

Changed from break-all to break-words for currency values to prevent
awkward mid-number breaks (e.g., $1,234,5 / 67.89). break-words only
breaks when content overflows and keeps values intact when possible,
providing cleaner line breaks while still preventing overflow.



---------


fix: inverted Buy/Sell type selection by changing amount.negative to amount.positive in trade edit views (#952)


Bump version to next iteration after v0.6.8-alpha.9 release

Show disabled import options when no accounts exist (#977)

* Show disabled import options before accounts exist

Keep account-dependent import choices visible on /imports/new and render them as disabled with guidance when no accounts are available.

* Refactor disabled import options: extract partial, fix accessibility (#986)

- Extract _import_option partial to eliminate duplicated enabled/disabled
  markup across TransactionImport, TradeImport, and MintImport (also
  used by AccountImport, CategoryImport, RuleImport for consistency)
- Replace misleading chevron-right with lock icon in disabled state
- Add aria-disabled="true" for screen reader accessibility
- Remove redundant default: parameter from t() call
- Fix locale key ordering (requires_account after import_* keys)
- Fix extra blank line in test file
- Add assertion for aria-disabled attribute in test

https://claude.ai/code/session_016j9tDYEBfWX9Dzd99rAYjX



* Tailwind fixes

---------


Expose ui_layout and ai_enabled to mobile clients and add enable_ai endpoint (#983)

* Wire ui layout and AI flags into mobile auth

Include ui_layout and ai_enabled in mobile login/signup/SSO payloads,
add an authenticated endpoint to enable AI from Flutter, and gate
mobile navigation based on intro layout and AI consent flow.

* Linter

* Ensure write scope on enable_ai

* Make sure AI is available before enabling it

* Test improvements

* PR comment

* Fix review issues: test assertion bug, missing coverage, and Dart defaults (#985)

- Fix login test to use ai_enabled? (method) instead of ai_enabled (column)
  to match what mobile_user_payload actually serializes
- Add test for enable_ai when ai_available? returns false (403 path)
- Default aiEnabled to false when user is null in AuthProvider to avoid
  showing AI as available before authentication completes
- Remove extra blank lines in auth_provider.dart and auth_service.dart

https://claude.ai/code/session_01LEYYmtsDBoqizyihFtkye4



---------


fix: Handle empty compound conditions on rules index (#965)

* fix: Handle empty compound conditions on rules index

* fix: avoid contradictory rule condition summary on /rules

* refactor: move rules condition display logic from view to model

* fix: localize rule title fallback and preload conditions in rules index
Document merchants API endpoints (#980)

Add rswag request specs for merchants index/show and define a MerchantDetail schema used by the docs. Update the generated OpenAPI document with merchants paths and schema.
Add family moniker selection and dynamic UI labels (#981)

* Add family moniker selection and dynamic UI labels

Introduce a Family moniker persisted in the database with allowed values Family/Group, add required onboarding selection for it, and thread moniker-aware copy through key user-facing views and locales. Also add helper methods and tests for onboarding form presence and family moniker behavior.

* Small copy edits/change moniker question order

* Conditional Group/Family onboarding flow fixes

* Fix label

* Grouping of fields

* Profile Info page Group/Family changes

* Only admins can change Group/Family moniker

* Repetitive defaults

* Moniker in Account model

* Moniker in User model

* Auth fix

* Sure product is also a moniker

---------


Disable Turbo prefetch on dashboard outflow category links

Hovering over category links in the outflow donut chart triggered
Turbo 8's default link prefetching, which made a real request to the
transactions controller. The controller's store_params! before_action
saved those filter params (category + date range) to the session.
When the user later navigated to /transactions via the nav menu,
the stored params were restored, showing an unexpected filtered view.

Adding data-turbo-prefetch="false" prevents the prefetch on hover
while preserving the intended click-to-navigate behavior.

https://claude.ai/code/session_01Na7AF1wyidPwFdPq5w8oaw

Flutter login polish (#973)

* Skip config screen by setting demo site default

* Small copy edits

* Login page polish
Mobile-only GitHub workflow builds (#975)

* Mobile build only

* Fix copy on debug

* PR review comments
Bump version to next iteration after v0.6.8-alpha.8 release

Bring back `/reports` link


Fix flaky expectations in import-related tests (#963)


Fix tests

Generalize from PDF import to just files

Index all PDF imports into vector store with type metadata

Update backend table with status and requirements

Clarify status of non-OpenAI vector store


Add `Family` vector search function call / support for document vault (#961)

* Add SearchFamilyImportedFiles assistant function with vector store support

Implement per-Family document search using OpenAI vector stores, allowing
the AI assistant to search through uploaded financial documents (tax returns,
statements, contracts, etc.). The architecture is modular with a provider-
agnostic VectorStoreConcept interface so other RAG backends can be added.

Key components:
- Assistant::Function::SearchFamilyImportedFiles - tool callable from any LLM
- Provider::VectorStoreConcept - abstract vector store interface
- Provider::Openai vector store methods (create, upload, search, delete)
- Family::VectorSearchable concern with document management
- FamilyDocument model for tracking uploaded files
- Migration adding vector_store_id to families and family_documents table

https://claude.ai/code/session_01TSkKc7a9Yu2ugm1RvSf4dh

* Extract VectorStore adapter layer for swappable backends

Replace the Provider::VectorStoreConcept mixin with a standalone adapter
architecture under VectorStore::. This cleanly separates vector store
concerns from the LLM provider and makes it trivial to swap backends.

Components:
- VectorStore::Base — abstract interface (create/delete/upload/remove/search)
- VectorStore::Openai — uses ruby-openai gem's native vector_stores.search
- VectorStore::Pgvector — skeleton for local pgvector + embedding model
- VectorStore::Qdrant — skeleton for Qdrant vector DB
- VectorStore::Registry — resolves adapter from VECTOR_STORE_PROVIDER env
- VectorStore::Response — success/failure wrapper (like Provider::Response)

Consumers updated to go through VectorStore.adapter:
- Family::VectorSearchable
- Assistant::Function::SearchFamilyImportedFiles
- FamilyDocument

Removed: Provider::VectorStoreConcept, vector store methods from Provider::Openai

https://claude.ai/code/session_01TSkKc7a9Yu2ugm1RvSf4dh

* Add Vector Store configuration docs to ai.md

Documents how to configure the document search feature, covering all
three supported backends (OpenAI, pgvector, Qdrant), environment
variables, Docker Compose examples, supported file types, and privacy
considerations.

https://claude.ai/code/session_01TSkKc7a9Yu2ugm1RvSf4dh

* No need to specify `imported` in code

* Missed a couple more places

* Tiny reordering for the human OCD

* Update app/models/assistant/function/search_family_files.rb




* PR comments

* More PR comments

---------




Fix property subtype not persisting on edit (#930)

* Fix property subtype not persisting on edit

* Add regression test for property subtype persistence

This change introduces model specs and factories to cover
property subtype persistence on update.

FactoryBot setup and test dependencies were adjusted to
support the new specs.

* Add regression test for property subtype persistence

* remove unused FactoryBot factories and test

* remove FactoryBot in Gemfile.lock

* Fix no-op regression test for property subtype update

* Delete no-op property_test

* add pimary_residence in properties fixtures

* add capybara system test for property subtype persistence

* fix spelling and indent

* rename test to "can persist property subtype"



---------


Bump version to next iteration after v0.6.8-alpha.7 release

fix: restore drawer positioning for transaction modals on desktop (#857) (#896)

* feat: Add responsive dialog behavior for transaction modals

Add responsive option to DS::Dialog component that switches between:
- Mobile (< 1024px): Modal style (centered) with inline close button
- Desktop (≥ 1024px): Drawer style (right side panel) with header close button
Update transaction, transfer, holding, trade, and valuation views to use
responsive behavior, maintaining mobile experience while reverting desktop
to drawer style like budget categories.

Changes:
- app/components/DS/dialog.rb: Add responsive parameter and helper methods
- app/components/DS/dialog.html.erb: Apply responsive styling
- app/views/*/show.html.erb: Add responsive: true and hide close icons on mobile

* fix: Enhance close button accessibility in dialog components

* fix: Refactor dialog component to improve close button handling and accessibility
Include newer providers in automatic family sync (#934)

* Include newer providers in automatic family sync

Coinbase, CoinStats, Mercury, and SnapTrade all implement Syncable
and have Syncer classes but were not listed in child_syncables,
meaning their data only refreshed on manual sync button clicks.

* refactor(syncer): Open/Closed principle for provider sync

- Adding new providers requires modifying child_syncables (violates O/C)
- plaid_items missing .active scope (bug: syncs deleted items)
- snaptrade_items can exist without user registration → fails on sync
- Scattered knowledge about 'ready to sync' logic

1. **Registry pattern**: SYNCABLE_ITEM_ASSOCIATIONS constant lists all
   provider associations that participate in family sync

2. **Encapsulated sync-readiness**: Each item model defines its own
   `syncable` scope that knows when it's ready for auto-sync:
   - Most providers: `syncable = active` (not scheduled for deletion)
   - SnapTrade: `syncable = active + user_registered` (has API creds)

3. **Single loop**: child_syncables iterates the registry, calling
   `.syncable` on each association

- Adding a provider = add to registry + define syncable scope
- Each model owns its 'ready to sync' business logic
- Fixes plaid_items bug (now uses .active via .syncable)
- Fixes snaptrade auto-sync failures (filters unregistered items)
- Easy to extend with new conditions per provider

- family/syncer.rb: Registry + dynamic collection
- *_item.rb (7 files): Add `scope :syncable, -> { active }`
- snaptrade_item.rb: Add syncable with user_registered filter

* Fix rubocop bracket spacing in SnaptradeItem syncable scope
fix: keep nav bar sticky at top (#943)

* fix: keep nav bar sticky at top

* fix: sticky on settings page

* fix: keep padding in settings page

* fix: make all settings page title sticky

* fix: make buttons sticky with title

* fix: set header bar min height

* fix: mobile responsive

* fix: reduce header bar
feat: Add LLM prompt for API endpoint consistency (#949)

* Add LLM prompt for API endpoint consistency (fixes #944)

- Add .cursor/rules/api-endpoint-consistency.mdc: checklist that applies
  when editing app/controllers/api/v1, spec/requests/api/v1, or
  test/controllers/api/v1. Enforces (1) Minitest-only behavioral coverage
  for new endpoints, (2) rswag docs-only (no expect/assert), (3) same
  API key auth pattern in all rswag specs.
- Reference the rule in AGENTS.md under API Development Guidelines.

* Add tests for API endpoint consistency implementation

- Minitest: test/api_endpoint_consistency_rule_test.rb checks rule file
  exists, globs, and all three sections (Minitest, rswag docs-only,
  API key auth) plus AGENTS.md reference.
- Standalone: test/support/verify_api_endpoint_consistency.rb runs
  same checks without loading Rails (use when app fails to boot).
- Rule: add mdc: links for Cursor, note valuations_spec OAuth outlier.

* Address review: add --compliance check, CLAUDE.md section

- Verification script: --compliance scans current APIs and reports
  rswag OAuth vs API key, missing Minitest for controllers, expect/assert.
- CLAUDE.md: add Post-commit API consistency subsection under
  API Development Guidelines (links to rule, documents script + --compliance).

---------


Remove Flipper and replace with ENV-driven FeatureFlags (#957)

* Presence of valid DEFAULT_UI_LAYOUT is sufficient

* Linter
Normalize legacy SSO icon values before validation (#955)


chore(deps): bump faraday from 2.13.2 to 2.14.1 (#953)

Bumps [faraday](https://github.com/lostisland/faraday) from 2.13.2 to 2.14.1.
- [Release notes](https://github.com/lostisland/faraday/releases)
- [Changelog](https://github.com/lostisland/faraday/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lostisland/faraday/compare/v2.13.2...v2.14.1)

---
updated-dependencies:
- dependency-name: faraday
  dependency-version: 2.14.1
  dependency-type: direct:production
...



Bump version to next iteration after v0.6.8-alpha.6 release

Remove reference to nonexistent Sidekiq::Throttled gem (#950)

The sidekiq-throttled gem is not in the Gemfile, so including
Sidekiq::Throttled::Job causes an uninitialized constant NameError
at boot time, breaking production builds.

https://claude.ai/code/session_01Bj7xgndJt28BcUHW1v1M9S


Bump version to next iteration after v0.6.8-alpha.5 release

Forgot to make test Flipper-conditional

Add Intro UI feature flag

First cut of a simplified "intro" UI layout (#265)

* First cut of a simplified "intro" UI layout

* Linter

* Add guest role and intro-only access

* Fix guest role UI defaults (#940)

Use enum predicate to avoid missing role helper.

* Remove legacy user role mapping (#941)

Drop the unused user role references in role normalization
and SSO role mapping forms to avoid implying a role that
never existed.

Refs: #0

* Remove role normalization (#942)

Remove role normalization

Roles are now stored directly without legacy mappings.

* Revert role mapping logic

* Remove `normalize_role_settings`

* Remove unnecessary migration

* Make `member` the default

* Broken `.erb`

---------


Implement Indexa Capital provider with real API integration (#933)

* Add Indexa Capital provider scaffold

Generate Indexa Capital provider scaffolding and align credential fields with the API authentication requirements.

* Fix PR 926 lint and schema CI failures

* Implement Indexa Capital provider with real API integration

- Rewrite all broken view templates (were meta-ERB from code generator)
- Create missing select_accounts.html.erb template
- Implement real API calls: list_accounts via /users/me, get_holdings
  via /accounts/{number}/fiscal-results, get_account_balance via
  /accounts/{number}/performance
- Add API token auth support (stored token > env token > credentials)
- Add api_token column with encryption support
- Redesign settings panel: API token prominent, credentials collapsible
- Fix account balances display using performance endpoint portfolios
- Fix accounts index empty-state guard missing indexa_capital_items
- Simplify activities fetch job (no activities API endpoint exists)
- Fix i18n interpolation (%%{ -> %{) throughout locale file

* Add tests for Indexa Capital provider integration

- IndexaCapitalItemTest: validations, credentials, scopes, sync status
- IndexaCapitalAccountTest: upsert, holdings, account provider linking
- Provider::IndexaCapitalTest: auth modes, API stubs, error handling
- IndexaCapitalItemsControllerTest: CRUD, setup, linking, authorization
- Fixtures for items (token + credentials) and accounts (mutual + pension)

52 tests, 98 assertions, 0 failures

* Address code review feedback from PR #933

- Fix zero balance bug: use `nil?` instead of `present?` so 0 is stored
- Fix has_indexa_capital_credentials? to check api_token (was ignored)
- Fix build_provider to delegate to Provided concern (was ignoring token)
- Fix IndexaCapital section outside encryption_error guard in settings
- Add account_number sanitization to prevent path traversal in API URLs
- Replace all skipped processor tests with real working tests
- Add zero-balance and path-traversal test coverage

61 tests, 107 assertions, 0 failures

* Address code review round 2: credentials validation, RuboCop, test quality

- Fix RuboCop SpaceInsideArrayLiteralBrackets in credentials check
- Chain where.not calls so all three username/document/password must be present
- Require all three credentials (||) instead of any one (&&) in validate_configuration!
- Move attr_reader to private to avoid exposing credentials publicly
- Parse dates with Date.parse in extract_balance for robustness
- Remove stale TODO and Crypto from supported_account_types
- Order build_provider query deterministically by created_at
- Replace no-op holdings assertion with meaningful assert_difference

* Address code review round 3: JSON parse safety and test precision

- Rescue JSON::ParserError on 2xx responses for clearer error messages
- Fix weak balance assertion: set balance to 0 before processing, assert
  expected value (27093.01 = sum of holdings amounts)

* Include Indexa Capital in automatic family sync

Add indexa_capital_items to Family::Syncer#child_syncables so balances
and holdings refresh on daily auto-sync and login sync, not only on
manual sync button clicks.

---------




Add REST API for holdings and trades (Discussion #905) (#918)

* Add REST API for holdings and trades (Discussion #905)

- Trades: GET index (filter by account_id, account_ids, start_date, end_date),
  GET show, POST create (buy/sell with security_id or ticker), PATCH update,
  DELETE destroy. Create restricted to accounts that support trades (investment
  or crypto exchange). Uses existing Trade::CreateForm for creation.
- Holdings: GET index (filter by account_id, account_ids, date, start_date,
  end_date, security_id), GET show. Read-only; scoped to family.
- Auth: read scope for index/show; write scope for create/update/destroy.
- Responses: JSON via jbuilder (trade: id, date, amount, qty, price, account,
  security, category; holding: id, date, qty, price, amount, account, security,
  avg_cost). Pagination for index endpoints (page, per_page).


* API v1 holdings & trades: validation, docs, specs

- Holdings: validate date params, return 400 for invalid dates (parse_date!)
- Trades: validate start_date/end_date, return 422 for invalid dates
- Trades: accept buy/sell and inflow/outflow in update (trade_sell_from_type_or_nature?)
- Trades view: nil guard for trade.security
- Trades apply_filters: single join(:entry) when filtering
- OpenAPI: add Trade/TradeCollection schemas, ErrorResponse.errors
- Add spec/requests/api/v1/holdings_spec.rb and trades_spec.rb (rswag)
- Regenerate docs/api/openapi.yaml


* CI: fix Brakeman and test rate-limit failures

- Disable Rack::Attack in test (use existing enabled flag) so parallel
  API tests no longer hit 429 from shared api_ip throttle
- Add Brakeman ignore for trades_controller trade_params mass-assignment
  (account_id/security_id validated in create/update)
- Trades/holdings API and OpenAPI spec updates


* Trades: partial qty/price update fallback; fix PATCH OpenAPI schema

- Fall back to existing trade qty/price when only one is supplied so sign
  normalisation and amount recalculation always run
- OpenAPI: remove top-level qty, price, investment_activity_label,
  category_id from PATCH body; document entryable_attributes only


* Trades: fix update/DELETE OpenAPI and avoid sell-trade corruption

- Only run qty/price normalisation when client sends qty or price; preserve
  existing trade direction when type/nature omitted
- OpenAPI: remove duplicate PATCH path param; add 422 for PATCH; document
  DELETE 200 body (DeleteResponse)


* API: flat trade update params, align holdings errors, spec/OpenAPI fixes

- Trades update: accept flat params (qty, price, type, etc.), build
  entryable_attributes in build_entry_params_for_update (match transactions)
- Holdings: ArgumentError → 422 validation_failed; parse_date!(value, name)
  with safe message; extract render_validation_error, log_and_render_error
- Specs: path id required (trades, holdings); trades delete 200 DeleteResponse;
  remove holdings 500; trades update body flat; holdings 422 invalid date
- OpenAPI: PATCH trade request body flat


* OpenAPI: add 422 invalid date filter to holdings index


* API consistency and RSwag doc-only fixes

- Trades: use render_validation_error in all 4 validation paths; safe_per_page_param case/when
- Holdings: set_holding to family.holdings.find; price as Money.format in API; safe_per_page_param case/when
- Swagger: Holding qty/price descriptions (Quantity of shares held, Formatted price per share)
- RSwag: trades delete and valuations 201 use bare run_test! (documentation only, no expect)


* Fix index-vs-show visibility inconsistencies and preserve custom activity labels

- Add account status filter to set_holding to match index behavior
- Add visible scope to set_trade to match index behavior
- Preserve existing investment_activity_label when updating qty/price


* Trades: clearer validation for non-numeric qty/price

Return 'must be valid numbers' when qty or price is non-numeric (e.g. abc)
instead of misleading 'must be present and positive'.


---------


Add "Link to existing" option in SnapTrade Setup Accounts modal (#935)

* Add account linking functionality for SnapTrade items

- Introduced UI to link existing accounts when setting up SnapTrade items, preventing duplicate account creation.
- Updated controller to fetch linkable accounts.
- Added tests to verify proper filtering of accounts and linking behavior.

* Add `snaptrade_item_id` to account linking flow for SnapTrade items

- Updated controller to allow specifying `snaptrade_item_id` when linking accounts.
- Adjusted form and views to include `snaptrade_item_id` as a hidden field.
- Enhanced tests to validate behavior with the new parameter.
Add tests for SnapTrade error handling and refine unlink behavior (#931)

- Introduced new tests to cover SnapTrade decryption and connection errors in `SnaptradeItemsControllerTest`.
- Updated error messages for improved user clarity.
- Modified `unlink` functionality to preserve `SnaptradeAccount` records while ensuring proper detachment of associated holdings.
Auto-categorize investment contributions across all transfer paths (#924)

* Ensure investment contributions are auto-categorized with proper kind and category creation.

* Retrigger CI
Fix SSO provider warning timing (#927)

Warn after providers are registered to avoid false empty state.
fix: loan transfer kind assignment to use destination account (#874)

* fix: loan transfer kind assignment to use destination account

* fix: update system test to use depository account instead of investment account
Bump version to next iteration after v0.6.8-alpha.4 release

Protect demo API key from deletion (#919)

* feat: Protect demo monitoring API key from deletion

- Add DEMO_MONITORING_KEY constant to ApiKey model
- Add `demo_monitoring_key?` method to identify the monitoring key
- Add `visible` scope to exclude monitoring key from UI queries
- Update controller to use `visible` scope, hiding the monitoring key
- Prevent revocation of the monitoring key with explicit error handling
- Update Demo::Generator to use the shared constant

Users on the demo instance can still create their own API keys,
but cannot see or delete the monitoring key used for uptime checks.

https://claude.ai/code/session_01RQFsw39K7PB5kztboVdBdB

* Linter

* Protect demo monitoring API key from deletion

* Use monitoring source for demo API key

* Add test for demo monitoring revoke guard

* Disable Rack::Attack in test and development

---------


feat: Allow to create rules to mark transactions as transfers or payments (#920)

* feat: Allow to create rules to define transfer or payments

* fix: lint issues

* Update app/models/rule/action_executor/set_as_transfer_or_payment.rb




* fix: indentation issue

* fix: add explicit return

* fix: add guard on target_account

* fix: use local variable for transfer and revert explicit return as it doesn't work

* fix: Adjust transaction naming

---------



fix: allow refreshes from the same source for cost basis updates (#917)

* fix: allow refreshes from the same source for cost basis updates

* test: update cost basis priority expectations
Fix mobile login "Record not found" for unseeded instances (#916)

* Auto-create mobile OAuth application when missing (#912)

Self-hosted users who set up their instance without running `db:seed`
(or reset their database) got "Record not found" on mobile login because
`MobileDevice.shared_oauth_application` used `find_by!` which raises
when the "Sure Mobile" Doorkeeper application does not exist.

Switch to `find_or_create_by!` so the record is created transparently
on first use, matching the attributes from the seed file.

* Nice Claude Code suggestion

---------


feat: add SSL_CA_FILE and SSL_VERIFY environment variables to support… (#894)

* feat: add SSL_CA_FILE and SSL_VERIFY environment variables to support self-signed certificates in self-hosted environments

* fix: NoMethodError by defining SSL helper methods before configure block executes

* refactor: Refactor SessionsController to use shared SslConfigurable module and simplify SSL initializer redundant checks

* refactor: improve SSL configuration robustness and error detection accuracy

* fix:HTTParty SSL options, add file validation guards, prevent Tempfile GC, and redact URLs in error logs

* fix:  Fix SSL concern indentation and stub Simplefin POST correctly in tests

* fix: normalize ssl_verify to always return boolean instead of nil

* fix: solve failing SimpleFin test

* refactor:  trim unused error-handling code from SslConfigurable, replace Tempfile with fixed-path CA bundle, fix namespace pollution in initializers, and add unit tests for core SSL configuration and Langfuse CRL callback.

* fix: added require ileutils in the initializer and require ostruct in the test file.

* fix: solve autoload conflict that broke provider loading, validate all certs in PEM bundles, and add missing requires.
Fix OIDC household invitation (issue #900) (#904)

* Fix OIDC household invitation (issue #900)

- Auto-add existing user when inviting by email (no invite email sent)
- Accept page: choose 'Create account' or 'Sign in' (supports OIDC)
- Store invitation token in session on sign-in; accept after login (password,
  OIDC, OIDC link, OIDC JIT, MFA)
- Invitation#accept_for!(user): add user to household and mark accepted
- Defensive guards: nil/blank user, token normalization, accept_for! return check

* Address PR review: rename accept_for! to accept_for, i18n OIDC notice, test fixes, stub Rails.application.config

* Fix flaky system test: assert only configure step, not flash message


---------




fix: Preserve tags on bulk edits (take 3) (#889)

* fix: handle tags separately from entryable_attributes in bulk updates

Tags use a join table (taggings) rather than a direct column, which means
empty tag_ids clears all tags rather than meaning "no change". This caused
bulk category-only edits to accidentally clear existing tags.

This fix:
- Removes tag_ids from entryable_attributes in Entry.bulk_update!
- Adds update_tags parameter to explicitly control tag updates
- Uses params.key?(:tag_ids) in controller to detect explicit tag changes
- Preserves existing tags when tag_ids is not provided in the request

This is a cleaner architectural solution compared to tracking "touched"
state in the frontend, as it properly acknowledges the semantic difference
between column attributes and join table associations.

https://claude.ai/code/session_014CsmTwjteP4qJs6YZqCKnY

* fix: handle tags separately in API transaction updates

Apply the same pattern to the API endpoint: tags are now handled
separately from entryable_attributes to distinguish between "not
provided" (preserve existing tags) and "explicitly set to empty"
(clear all tags).

This allows API consumers to:
- Update other fields without affecting tags (omit tag_ids)
- Clear all tags (send tag_ids: [])
- Set specific tags (send tag_ids: [id1, id2])

https://claude.ai/code/session_014CsmTwjteP4qJs6YZqCKnY

* Proposed fix

* fix: improve tag handling in bulk updates for transactions

* fix: allow bulk edit to clear/preserve tags by omitting hidden multi-select field

* PR comments

* Dumb copy/paste error

* Linter

---------




Fix flash message persistence in drag-and-drop CSV import (#915)

* Initial plan

* Disable Turbo on drag-and-drop import form to fix flash message display



---------



Bump version to next iteration after v0.6.8-alpha.3 release

Add Google Sign-In (SSO) support to Flutter mobile app (#860)

* Add mobile SSO support to sessions controller

Add /auth/mobile/:provider route and mobile_sso_start action that
captures device params in session and renders an auto-submitting POST
form to OmniAuth (required by omniauth-rails_csrf_protection).

Modify openid_connect callback to detect mobile_sso session, issue
Doorkeeper tokens via MobileDevice, and redirect to sureapp://oauth/callback
with tokens. Handles MFA users and unlinked accounts with error redirects.

Validates provider name against configured SSO providers and device info
before proceeding.

* Add SSO auth flow to Flutter service and provider

Add buildSsoUrl() and handleSsoCallback() to AuthService for
constructing the mobile SSO URL and parsing tokens from the deep
link callback.

Add startSsoLogin() and handleSsoCallback() to AuthProvider for
launching browser-based SSO and processing the redirect.

* Register deep link listener for SSO callback

Listen for sureapp://oauth/* deep links via app_links package,
handling both cold start (getInitialLink) and warm (uriLinkStream)
scenarios. Routes callbacks to AuthProvider.handleSsoCallback().

* Add Google Sign-In button to Flutter login screen

Add "or" divider and outlined Google Sign-In button that triggers
browser-based SSO via startSsoLogin('google_oauth2').

Add app_links and url_launcher dependencies to pubspec.yaml.

* Fix mobile SSO failure handling to redirect back to app

When OmniAuth fails during mobile SSO flow, redirect to
sureapp://oauth/callback with the error instead of the web login page.
Cleans up mobile_sso session data on failure.

* Address PR review feedback for mobile SSO flow

- Use strong params for device info in mobile_sso_start
- Guard against nil session data in handle_mobile_sso_callback
- Add error handling for AppLinks initialization and stream
- Handle launchUrl false return value in SSO login
- Use user-friendly error messages instead of exposing exceptions
- Reject empty token strings in SSO callback validation

* Consolidate mobile device token logic into MobileDevice model

Extract duplicated device upsert and token issuance code from
AuthController and SessionsController into MobileDevice. Add
CALLBACK_URL constant and URL builder helpers to eliminate repeated
deep-link strings. Add mobile SSO integration tests covering the
full flow, MFA rejection, unlinked accounts, and failure handling.

* Fix CI: resolve Brakeman redirect warnings and rubocop empty line

Move mobile SSO redirect into a private controller method with an
inline string literal so Brakeman can statically verify the target.
Remove unused URL builder helpers from MobileDevice. Fix extra empty
line at end of AuthController class body.

* Use authorization code exchange for mobile SSO and add signup error handling

Replace passing plaintext tokens in mobile SSO redirect URLs with a
one-time authorization code pattern. Tokens are now stored server-side
in Rails.cache (5min TTL) and exchanged via a secure POST to
/api/v1/auth/sso_exchange. Also wraps device/token creation in the
signup action with error handling and sanitizes device error messages.

* Add error handling for login device registration and blank SSO code guard

* Address PR #860 review: fix SSO race condition, add OpenAPI spec, and cleanup

- Fix race condition in sso_exchange by checking Rails.cache.delete return
  value to ensure only one request can consume an authorization code
- Use strong parameters (params.require) for sso_exchange code param
- Move inline HTML from mobile_sso_start to a proper view template
- Clear stale session[:mobile_sso] flag on web login paths to prevent
  abandoned mobile flows from hijacking subsequent web SSO logins
- Add OpenAPI/rswag spec for all auth API endpoints



* Fix mobile SSO test to match authorization code exchange pattern

The test was asserting tokens directly in the callback URL, but the code
uses an authorization code exchange pattern. Updated to exchange the code
via the sso_exchange API endpoint. Also swaps in a MemoryStore for this
test since the test environment uses null_store which discards writes.



* Refactor mobile OAuth to use single shared application

Replace per-device Doorkeeper::Application creation with a shared
"Sure Mobile" OAuth app. Device tracking uses mobile_device_id on
access tokens instead of oauth_application_id on mobile_devices.

---------


Normalize whitespace in text rule matching (#890)

Normalize whitespace in text-based rule filters so

transaction names with irregular spacing still match.

Refs #886
Add mailer subject tests and refine i18n keys (#910)

* Add mailer subject tests and refine i18n keys

* Linter

* Fix test

* More fixes

* More fixes

---------



feat: Customizable Budget Month Start Day (#810)

* Add customizable budget month start day (#253)

Allow users to set a custom month-to-date start date (1st-28th) for
budgeting and MTD calculations. Useful for users who want budget
periods aligned with their pay schedule (e.g., 25th to 24th).

Changes:
- Add month_start_day column to families table (default: 1)
- Add database check constraint for valid range (1-28)
- Add Family#uses_custom_month_start?, custom_month_start_for,
  custom_month_end_for, current_custom_month_period helper methods
- Add Period.current_month_for(family), last_month_for(family) methods
- Update Budget model for custom month boundaries in find_or_bootstrap,
  param_to_date, budget_date_valid?, current?, and name methods
- Add month_start_day setting to Settings > Preferences UI
- Add warning message when custom month start day is configured
- Add comprehensive tests with travel_to for date robustness

Fixes #253

* Add /api/v1/user endpoint for Flutter mobile app and PWA

Expose user preferences including month_start_day via API endpoint
following existing pattern for default_period. This allows Flutter
mobile app and PWA to read/update user preferences through a
consistent API contract.

Endpoints:
- GET /api/v1/user - Read user preferences including family settings
- PATCH /api/v1/user - Update user preferences

Response includes: id, email, first_name, last_name, default_period,
locale, and family settings (currency, timezone, date_format, country,
month_start_day).

* Update Periodable to use family-aware MTD periods

When users select 'current_month' or 'last_month' period filters on
dashboard/reports, now respects the family's custom month_start_day
setting instead of using static calendar month boundaries.

This ensures MTD filter on dashboard is consistent with how budgets
calculate their periods when custom month start day is configured.

* Fix param_to_date to correctly map budget params to custom periods

When a family uses a custom start day, the previous implementation
called custom_month_start_for on the 1st of the month, which incorrectly
shifted dates before the start day to the previous month.

Now we directly construct the date using family.month_start_day, so
'jan-2026' with month_start_day=25 correctly returns Jan 25, 2026
instead of Dec 25, 2025.

* Fix param_to_date and use Current pattern in API controller

- Fix param_to_date to directly construct date with family.month_start_day
  instead of using custom_month_start_for which incorrectly shifted dates
- Replace current_user with Current.user/Current.family in API controller
  to follow project convention used in other API v1 controllers

* Add i18n for budget name method

Use I18n.t for localizable budget period names to follow
project conventions for user-facing strings.

* Remove unused budget_end variable in budget_date_valid?

* Use Date.current for timezone consistency in Budget#current?

* Address PR review feedback

- Remove API users endpoint (mobile won't use yet)
- Remove user route from config/routes.rb
- Remove ai_summary/document_type schema bleed from pdf-import-ai branch

* Pass family to param_to_date for custom month logic

* Run migration to add month_start_day column to schema

* Schema regressions

---------



Add encryption support to provider account models (#815)

* Enable encryption for raw payloads in account models.

* Add backfill support for Snaptrade, Coinbase, Coinstats, and Mercury accounts.
API v1: add amount_cents + signed_amount_cents to transactions (#899)

* feat(api): add amount_cents + signed_amount_cents to transactions

* fix: use currency.minor_unit_conversion for amount_cents

- Replace hardcoded *100 with currency.minor_unit_conversion
- Handles JPY (0 decimals), KWD/BHD (3 decimals), etc. correctly
- Add assert_amount_cents_fields helper to validate sign/scale invariants
Update Romanian localization for profile subtitle placeholder (#885)


Use `dependent: :purge_later` for ActiveRecord attachments (#882)

* Use dependent: :purge_later for user profile_image cleanup

This is a simpler alternative to PR #787's callback-based approach.
Instead of adding a custom callback and method, we use Rails' built-in
`dependent: :purge_later` option which is already used by FamilyExport
and other models in the codebase.

This single-line change ensures orphaned ActiveStorage attac…

Signed-off-by: Josh Pigford <josh@joshpigford.com>
2026-03-24 19:21:24 +01:00
Abdallatif Sulaiman
ea80e92b6b Add Palestine to country options in preferences (#845)
Co-authored-by: Abdallatif Sulaiman <asulaiman@restaurant365.com>
2026-01-31 09:45:39 +01:00
Mark Hendriksen
7f0781179c Add Dutch (nl) translations for UI and models (#702)
* Add Dutch (nl) translations for UI and models

Added comprehensive Dutch translation files for models, views, mailers, and Doorkeeper, covering accounts, categories, admin, and more. Updated languages_helper.rb to include 'nl' as a supported language. Improved capitalization and consistency in existing Dutch date and number formats.

* ai sugestions

* nitpick fix

* Fix Dutch translations and improve consistency

Corrected minor issues and improved consistency in Dutch locale files, including fixing typos, updating terminology (e.g., 'Self-Hosting' to 'Zelfhosting', 'Provider selectie' to 'Providerselectie'), and ensuring proper formatting. No functional changes were made; this commit only affects translation files.

* Update Dutch translations for product name and pluralization

Replaced hardcoded product names with %{product_name} in password reset and API key views for improved reusability. Updated wallet setup message in CoinStats item model to support correct Dutch pluralization.

* Update nl.yml
2026-01-19 15:42:55 +01:00
foXaCe
3fc3e20c36 i18n: Add French translations (#658)
* i18n: Add French translations

Add complete French translation files for the application.

Co-Authored-By: Norman Alié <mail@normanalie.fr>
Co-Authored-By: Xurron <corentin.boeglin2005@gmail.com>

* fix: Correct YAML syntax in French securities translations

Co-Authored-By: Norman Alié <mail@normanalie.fr>
Co-Authored-By: Xurron <corentin.boeglin2005@gmail.com>

---------

Co-authored-by: Norman Alié <mail@normanalie.fr>
Co-authored-by: Xurron <corentin.boeglin2005@gmail.com>
2026-01-15 21:42:39 +01:00
Rukeith
dddc2182a4 feat(zh-TW): add Traditional Chinese localization support (#503)
* feat(zh-TW): add Traditional Chinese localization support

Integrates comprehensive zh-TW locale files across UI, models, emails, and helpers.
Updates language mapping for Chinese (Traditional) and adds translations for various modules.
Establishes full Traditional Chinese support in the app.

* feat(locales): add zh-TW translations

Add comprehensive Traditional Chinese (zh-TW) translations for UI, defaults,
Doorkeeper, mailers, models, and views to provide full Taiwanese localization
and improve wording consistency.

Replace and update several existing zh-TW entries for clarity and consistency.
Also expose the Postgres port in the example compose for easier local
development and apply minor locale/typo/whitespace fixes.

* feat(locales): add zh-TW translations

Add Traditional Chinese (zh-TW) locale files across many views and settings
to provide Taiwanese localization. Introduce updated translations for
authentication, onboarding, settings, integrations (Plaid, SimpleFin,
Lunch Flow), accounts, reports, and various resource pages.

Remove or replace legacy locale files to align with the revamped i18n
structure and copy organization. This enables full zh-TW support for the UI.

* chore(docker): remove published Postgres port

Remove the published Postgres port mapping (5432) from the example
docker-compose file to avoid exposing the database to the host and to
prevent accidental port conflicts. Keeps the example more secure and
focused on internal service networking.

* docs(i18n): 統一 SimpleFIN 在繁體中文翻譯的大小寫

將 zh-TW 翻譯中所有出現的 "SimpleFin" 更新為品牌正確的 "SimpleFIN",
包含標題、提示文字、成功/錯誤訊息及表單標籤,以維持品牌名稱一致性
並提升使用者介面的翻譯準確性。
2026-01-10 20:16:15 +01:00
jiang123574
3b8888c8de Add Chinese localization (#471)
* add zh-CN.yml for chinese

* The files appear to use CRLF line endings instead of LF (Unix-style).

* Add the missing entries to the zh-CN.yml file and include the Simplified Chinese option.

* Fix grammatical errors

Signed-off-by: jiang123574 <jiang123574@163.com>

* Update languages_helper.rb

Signed-off-by: jiang123574 <jiang123574@163.com>

* Update 'SimpleFin' to 'SimpleFIN' in translations

Signed-off-by: jiang123574 <jiang123574@163.com>

* update zh-CN.yml

* update zh-CN.yml

* add new zh-CN.yml

* CodeRabbit comments

* Enable Banking i18n

---------

Signed-off-by: jiang123574 <jiang123574@163.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2025-12-23 11:21:53 +01:00
Dylan Corrales
70b050e4a4 Rules: Fix no action conditions (#447)
* Fix Rules page when no action on rule

* Reject new rules without actions

* Rule with no action translation

* Easy one to keep translations going

* Fix tests

* Learn something new every day

---------

Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2025-12-12 16:58:57 +01:00
Marcon Neves
ea35296def feat(lang): add all brazilian portuguese translations (#416)
* feat(lang): add all brazilian portuguese translations

* feat: update pt-BR errors on translation

* fix: atualizar fix base

* feat: add reports translations

* feat: finish translation to brazilian portuguese

* fix: add to supported locales

* fix: number of translations

* fix: errors on translations

* fix: error on rubocop lint

---------

Co-authored-by: Leonardo Ralph <theleoralph@gmail.com>
2025-12-09 21:47:58 +01:00
bzoicas
eb4b978a97 Add RO locales (#359)
* Add RO locales

* Fixed some missing translations, added ro to SUPPORTED_LOCALES in languages_helper.rb

* Fixed a typo

---------

Co-authored-by: bzo <bzo@bzo.zresidence.lan>
2025-11-24 00:29:03 +01:00
ErNobyl
54656c943a added german translation (de) (#208)
* added german translation

consistently added all translation yml files for german language

* Correct quotation and syntax errors in new de locale files

Corrected misplaced or missing quotation marks in YAML

* Updated German translations

Added missing files, fixed for customizable branding

* corrected yml formatting

added missing "" when : where used in the string

* Interpolation errors

* More interpolation issues

* Last round of interpolation errors?

* Add German to supported locales

* Still a few more interpolations

---------

Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2025-11-14 13:38:16 +01:00
Juan José Mata
66c403438d Filter language selector to show only supported locales (#324)
Updated the language selector in /settings/preferences to display only
locales with extensive translations. Currently supported locales are:
- en (English) - 61 translation files
- es (Spanish) - 60 translation files
- tr (Turkish) - 57 translation files
- nb (Norwegian Bokmål) - 56 translation files
- ca (Catalan) - 56 translation files

Changes:
- Added SUPPORTED_LOCALES constant to LanguagesHelper
- Modified language_options method to filter using SUPPORTED_LOCALES
  instead of excluding specific locales

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-13 23:53:21 +01:00
neo773
65e1bc6edd Feature: Implement Mobile Responsiveness (#2092)
* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* format

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* fix conflict

* fix conflict

* chore: run rubocop

* fix test

* update PWA logo

* fix tests

* chore: lint

* fix test

* Refactor: Remove duplicate data attribute in activity partial and add chat form rendering in chats index

---------

Co-authored-by: Josh Pigford <josh@joshpigford.com>
2025-04-18 08:23:10 -05:00
Josh Pigford
4a768d0358 Clean up timezone selector 2025-02-03 09:46:40 -06:00
Zach Gollwitzer
49c353e10c Plaid portfolio sync algorithm and calculation improvements (#1526)
* Start tests rework

* Cash balance on schema

* Add reverse syncer

* Reverse balance sync with holdings

* Reverse holdings sync

* Reverse holdings sync should work with only trade entries

* Consolidate brokerage cash

* Add forward sync option

* Update new balance info after syncs

* Intraday balance calculator and sync fixes

* Show only balance for trade entries

* Tests passing

* Update Gemfile.lock

* Cleanup, performance improvements

* Remove account reloads for reliable sync outputs

* Simplify valuation view logic

* Special handling for Plaid cash holding
2024-12-10 17:41:20 -05:00
Zach Gollwitzer
ba49fea89a Locale updates 2024-10-24 16:43:59 -04:00
Zach Gollwitzer
1d20de770f User Onboarding + Bug Fixes (#1352)
* Bump min supported date to 20 years

* Add basic onboarding

* User onboarding

* Complete onboarding flow

* Cleanup, add user profile update test
2024-10-23 11:20:55 -04:00