* 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>
Deutsch | Español | Français | 日本語 | 한국어 | Português | Русский | 中文
Sure: The personal finance app for everyone
Get involved: Discord • Website • Issues
Important
This repository is a community fork of the now-abandoned Maybe Finance project.
Learn more in their final release doc.
Backstory
The Maybe Finance (archived/abandoned repo) team spent most of 2021–2022 building a full-featured personal finance and wealth management app. It even included an “Ask an Advisor” feature that connected users with a real CFP/CFA — all included with your subscription.
The business end of things didn't work out, and so they stopped developing the app in mid-2023.
After spending nearly $1 million on development (employees, contractors, data providers, infra, etc.), the team open-sourced the app. Their goal was to let users self-host it for free — and eventually launch a hosted version for a small fee.
They actually did launch that hosted version … briefly.
That also didn’t work out — at least not as a sustainable B2C business — so now here we are: hosting a community-maintained fork to keep the codebase alive and see where this can go next.
Join us!
Hosting Sure
Sure is a fully working personal finance app that can be self hosted with Docker.
Forking and Attribution
This repo is a community fork of the archived Maybe Finance repo. You’re free to fork it under the AGPLv3 license — but we’d love it if you stuck around and contributed here instead.
To stay compliant and avoid trademark issues:
- Be sure to include the original AGPLv3 license and clearly state in your README that your fork is based on Maybe Finance but is not affiliated with or endorsed by Maybe Finance Inc.
- "Maybe" is a trademark of Maybe Finance Inc. and therefore, use of it is NOT allowed in forked repositories (or the logo)
Performance Issues
With data-heavy apps, inevitably, there are performance issues. We've set up a public dashboard showing the problematic requests seen on the demo site, along with the stacktraces to help debug them.
https://www.skylight.io/app/applications/s6PEZSKwcklL/recent/6h/endpoints
Any contributions that help improve performance are very much welcome.
Local Development Setup
If you are trying to self-host the app, read this guide to get started.
The instructions below are for developers to get started with contributing to the app.
Requirements
- See
.ruby-versionfile for required Ruby version - PostgreSQL >9.3 (latest stable version recommended)
- Redis > 5.4 (latest stable version recommended)
Getting Started
cd sure
cp .env.local.example .env.local
bin/setup
bin/dev
# Optionally, load demo data
rake demo_data:default
Visit http://localhost:3000 to view the app.
If you loaded the optional demo data, log in with these credentials:
- Email:
user@example.com - Password:
Password1!
For further instructions, see guides below.
Setup Guides
- Mac dev setup
- Linux dev setup
- Windows dev setup
- Dev containers - visit this guide
One-click Install
Managed OpenClaw for Sure Finances
License and Trademarks
Maybe and Sure are both distributed under an AGPLv3 license.
- "Maybe" is a trademark of Maybe Finance, Inc.
- "Sure" is not, and refers to this community fork.
