mirror of
https://github.com/we-promise/sure.git
synced 2026-09-04 06:11:12 +00:00
75aa16e4e2879003b4aee3268ea8a6782eed2e4a
20
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6245b9cbd0 |
fix(pagination): stop the pager wrapping on narrow screens (#2967)
* fix(pagination): stop the pager wrapping on narrow screens
The page-number strip put the last page on a second line on a phone. The
container was a plain block, so the inline-flex links flowed as inline
content and wrapped like words. Measured on a 760-page list: the strip needs
~198px, and a 360px viewport leaves ~158px once the chevrons and the
per-page select are taken out.
The failure was not even consistent — at 402px the row wrapped, at 360px the
same markup overflowed its parent instead, because a block box inside a flex
item resolves its width differently at each size.
Make the strip a nowrap flex row, and drop the pages that aren't useful on a
phone. Small screens keep the first page, the last page, the current page and
the gaps ("1 … 5 … 760", ~148px); the full series returns from `sm` up. Short
series are left alone — Pagy only emits those when the collection has that
few pages, so hiding there would render a 3-page pager as "1 3".
`sm:` is a viewport breakpoint and the pager also lives in narrow columns on
wide screens (the account activity feed with both sidebars open), so the row
keeps `overflow-x-auto` as a backstop. At 320px, where even the reduced set
is wider than the space, it scrolls rather than wrapping.
Shared by twelve call sites, so this covers transactions, imports, rules,
account activity, statements, merchants, exports and the debug log.
* fix(pagination): mark the pages the mobile pager drops
Hiding the middle page links without saying so made the survivors read as
adjacent. On a 760-page collection sitting on page 3, Pagy emits
[1, 2, "3", 4, 5, :gap, 760] and a phone rendered "1 3 … 760" — page 2 gone
with nothing to show for it. A gapless 6-page series was worse: "1 6".
Each collapsed run now renders its own mobile-only ellipsis, unless Pagy
already put a gap beside that run, which would otherwise read as "… …".
[1, 2, "3", 4, 5, :gap, 760] -> 1 … 3 … 760
["1", 2, 3, 4, 5, 6] -> 1 … 6
["1", 2, 3, 4, 5, :gap, 760] -> 1 … 760 (Pagy's gap already covers it)
[1, :gap, 3, 4, "5", 6, 7, …] -> 1 … 5 … 760
The helper now returns a per-slot plan rather than a class, since the view
needs to know where a run starts, not just whether a slot is hidden.
Tests carry a property check — no two page numbers may survive side by side
unless they are consecutive — with a guard that fails if every pair happens to
be separated by an ellipsis, which would make the property vacuous. It was, on
the first pass.
|
||
|
|
440e04b942 |
fix(insights): blur amounts in insight prose under privacy mode (#2865)
Insight titles and bodies are stored as finished prose with the amounts already interpolated (by the i18n template or the LLM writer), so the dashboard feed and insight cards rendered raw figures even when the hide-numbers toggle was active — only the right-aligned key figure was tagged privacy-sensitive. Add InsightsHelper#insight_privacy_text, which wraps each numeric fragment (currency amounts, percentages, bare counts, including suffix-currency and no-break-space locale formats) in a privacy-sensitive span at render time, and use it for the title and body in both the dashboard insights feed and the insight card. The sentence stays readable while privacy mode blurs the numbers. The helper splits the raw text before escaping and reassembles it with safe_join, so HTML in stored prose is still escaped and digit-bearing entities like ' are never mangled by the number regex. |
||
|
|
c40e7f4807 |
fix(insights): correct the budget card's figure, badge noise and toast a11y (#2799)
* fix(insights): correct the budget card's figure, badge noise and toast a11y
Four defects found while reviewing the insights surfaces for hierarchy.
**The budget_at_risk card's focal figure argued against its own headline.**
`insight_key_figure` returned `budget_spent_pct` for both budget cards, so
"2 categories need attention in your budget" displayed "14% / of budget" —
a reassuring number as the visual focus of a warning. It now leads with the
flagged count ("2 / need attention"); budget_on_track keeps the percentage,
where overall consumption genuinely is the subject.
**The "New" pill carried no information.** Visiting /insights marks every
insight read in one `update_all`, so at first paint the pill was on every
row. On the page it becomes a dot — same signal, without an uppercase
tracked chip stealing weight from the title beside it. In the dashboard
widget it goes entirely: the well's header already counts unread ("New · 3")
and, with three rows, the pill was usually on all of them.
**The undo toast was silent to screen readers.** A card leaves the page via
a Turbo `remove`, which announces nothing, and the toast that explains it
had no live region — unlike its neighbour `_sync_toast`, which sets
`role="status" aria-live="polite"`.
**The undo toast could only be closed with a mouse.** Its close affordance
was a bare `icon "x"` with a click action: not focusable, not named. Now a
real `DS::Button`, matching `_sync_toast`.
The controller test asserting a per-row badge is updated to assert the
header count that replaces it, and to lock in the pill's removal.
* feat(insights): acknowledge instead of dismiss, on both surfaces (#2800)
Two complaints about the insight feed: the close (×) control felt wrong,
and clearing an insight was only possible on /insights — not on the
dashboard widget, which is the surface people actually look at.
**The × was lying.** Dismissal has never been permanent. GenerateInsightsJob
resurfaces a row whose bucketed metadata changes materially "even if the user
had read or dismissed the stale version" (its own comment), and 6 of 8
generators scope dedup_key to a month token, so dismissing July's budget card
says nothing about August's. A destructive-looking control was performing a
non-destructive act. It is now "Got it", and the contract is statable:
acknowledgement covers the numbers you saw; new numbers are a new insight.
No migration. The DB value stays "dismissed" and dismissed_at keeps its name;
only the enum key and the vocabulary the code speaks change, so existing rows
stay hidden and become undoable under an honest label.
**The action pyramid was inverted.** The escape hatch was a chromed icon
button in the card's top-right — the strongest secondary scan position — while
the card's actual purpose ("View budget") was a borderless ghost link under
the body text. Both now sit in a footer strip: the subject action gets the
chrome, acknowledging is quiet labelled text beside it, and the key figure
gets the corner to itself instead of competing with a control.
**The widget can clear its own rows.** Each row gains an acknowledge control,
revealed on pointer hover, on keyboard focus, and shown unconditionally on
touch where there is no hover. No gesture, so the section's drag-to-reorder
handlers are untouched. The row becomes a stretched link plus a sibling
button, because button_to renders a <form> and a form cannot nest in an <a>.
The group is named (group/insight). The dashboard <section> is itself a
`.group` for its header controls, and a bare group-hover: matches any ancestor
group — hovering one row, or the section header, revealed every row's control.
Acknowledging re-renders the well rather than removing a row, so the next
insight is promoted into the freed slot; Insight::FEED_LIMIT is now shared
between the two controllers that render it so they cannot drift. Undo restores
the row on both surfaces, and carries autofocus so it is one keystroke away
after the acknowledged card leaves the DOM.
* fix(insights): guard unacknowledge! against non-acknowledged insights
CodeRabbit, Major: an arbitrary/stale PATCH /unacknowledge (e.g. an old
undo-toast link clicked after GenerateInsightsJob has since expired or
resurrected the insight) could force it back to :read regardless of
its actual current state — including pulling an :expired insight back
into visible view.
Guards the transition to only reverse an actual acknowledgement, per
CodeRabbit's suggested fix.
* test(insights): fix stale dismiss_insight_url route from main merge
main's preview-gate test used the pre-rename dismiss/undismiss route names;
this branch renamed those to acknowledge/unacknowledge earlier.
|
||
|
|
af3100c0a5 |
fix(insights): keep rolling period labels on month boundaries (#2880)
* fix(ci): skip scheduled preview cleanup on forks Only run the hourly Cloudflare preview cleanup on we-promise/sure, where the required secrets exist. * fix(insights): pin meta-line forward-window test mid-month Date.current..Date.current+30 is a full calendar month on the 1st of 31-day months (e.g. Aug 1..31), so insight_period_label prefers the month name over "Next 30 days" and flakes CI. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix rolling insight period labels on month boundaries --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: sure-admin <sure-admin@splashblot.com> |
||
|
|
1e800e2f93 | Include uncategorized spending in budget UI (#2877) | ||
|
|
51c93649da |
feat(snaptrade): replace device-flow OAuth with authorization-code + PKCE flow (#2747)
* feat(snaptrade): replace device-flow OAuth with authorization-code + PKCE flow Squashed from 16 commits on snaptrade-oauth-apps for a clean rebase onto current upstream/main ahead of opening a PR. * fix(snaptrade): address PR #2747 review feedback on OAuth PKCE flow - Remove unreachable dead-code guard in import_latest_snaptrade_data - Guard apply_oauth_tokens! against a malformed payload missing access_token - Wrap token endpoint network errors in ApiError and retry like data calls - Remove unused Provider::Snaptrade#revoke_token! instance method - Preserve return_to/accountable_type through the SnapTrade portal callback so the account-linking flow no longer drops users back to accounts_path - Show the real absolute OAuth callback URL in self-hosted setup instructions - Refresh brakeman.ignore fingerprint for the connect redirect after the return_to/accountable_type params were added Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y8SCCmKX6RphB5E73WSUQQ * fix(snaptrade): don't retry non-idempotent OAuth/API requests CodeRabbit flagged that Provider::Snaptrade retried OAuth token exchanges/refreshes and all API POST/DELETE calls (get_connection_url, delete_connection) after timeouts/connection failures. If the response is lost after SnapTrade already consumed a single-use auth code, rotated the refresh token, or applied a POST/DELETE, replaying the request either fails with invalid_grant on a token that actually succeeded, or risks duplicate side effects. Retries are now limited to GET requests; OAuth token requests and non-GET API calls translate a network failure straight into an ApiError without replay. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NrrGkgSBEqhjjBmmH1fcXL * fix(snaptrade): stop querying non-deterministically encrypted token via empty-string compare CodeRabbit flagged that the syncable scope's where.not(oauth_access_token: [nil, ""]) re-encrypts "" with a random IV on every query, so the "" comparison can never match a stored ciphertext and is a silent no-op. No code path ever persists oauth_access_token as "" (only nil or a real token via apply_oauth_tokens!), so the exclusion is unnecessary -- narrowed the scope to a plain NULL check, which encryption handles transparently since nil is never encrypted. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NrrGkgSBEqhjjBmmH1fcXL --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
7554463f93 |
fix(impersonation): guard Sidekiq jobs link (#2678)
* fix(impersonation): guard Sidekiq jobs link * fix(impersonation): address PR review feedback |
||
|
|
66cf9e7f0b |
feat(insights): proactive financial intelligence feed (#2550)
* feat(insights): proactive financial intelligence feed Adds a nightly job that analyzes each family's finances in pure Ruby and surfaces typed, stateful insights on the dashboard and a new /insights page. - Insight model (active/read/dismissed) with a per-family dedup_key unique index so nightly re-runs refresh rows instead of duplicating them - Seven generators (spending anomaly, cash-flow warning, net worth milestone, subscription audit, savings rate change, idle cash, budget health) built on IncomeStatement, BalanceSheet, RecurringTransaction, and BudgetCategory - LLM used as a writer, not a reasoner: Insight::BodyWriter narrates pre-computed facts via the configured provider, with an i18n template fallback so self-hosted installs without API keys work identically; bodies are only (re)written when an insight is new or its numbers changed - GenerateInsightsJob: cron fan-out per family, per-family advisory lock, metadata-diff upsert that preserves read/dismissed state for unchanged signals and reactivates on material change - Dashboard insights_feed section (top 3, collapsible/reorderable) and an /insights feed page with turbo-stream dismissal; viewing the feed marks insights read - Tests for the model, job upsert semantics, and controller flows Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6ZRA6cgCRM4UdFKct3wm4 * fix(insights): address Codex review — expiry, budget metadata, LLM usage - Expire visible insights whose condition cleared: generators declare the insight types they produce, the registry reports which generators ran to completion, and the job expires visible insights of those types whose dedup_key was not regenerated. A crashing generator can't wipe out its healthy insights, and an expired insight reactivates when its condition returns — unlike a user-dismissed one, which stays dismissed. - Include a bucketed budget-spent percent in budget_at_risk metadata so the body refreshes when overall usage moves >=10 points even if the same categories remain flagged. - Pass family to chat_response so LLM narration is recorded in llm_usages. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6ZRA6cgCRM4UdFKct3wm4 * fix(insights): address CodeRabbit review - Skip the mark-as-read write for Turbo hover-prefetch requests (X-Sec-Purpose) so unread badges don't clear before a real visit - Show the New pill on the dashboard feed (active = unread there; the feed never marks insights read) - Filter idle accounts in SQL instead of a per-account exists? loop - Eager-load merchants in the subscription audit query - Widen the advisory-lock key to the signed-bigint range and log when acquisition fails so a skipped nightly run is observable - Mirror the dedup_key unique index as a model validation - Assert the refresh action enqueues for the signed-in family Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6ZRA6cgCRM4UdFKct3wm4 * fix(insights): clear stale lifecycle timestamps on reactivation When an insight resurfaces, the row now leaves no contradictory state behind: the material-change path clears both read_at and dismissed_at, and the expired-recovery path clears read_at. Tests assert the contract, and the dashboard feed test now also locks in the unread "New" badge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6ZRA6cgCRM4UdFKct3wm4 * fix(insights): drop redundant standalone family_id index Every composite index on insights already leads with family_id, so the auto-created single-column index was pure write overhead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6ZRA6cgCRM4UdFKct3wm4 * fix(insights): stop nightly drift from resurrecting dismissed insights Three generators stored continuously drifting values (projected amounts, starting balance, current net worth) in metadata, and any metadata diff counts as a material change: the body is rewritten (an LLM call when a provider is configured) and read/dismissed state is cleared. Dismissing a cash-flow warning was undone at the next 6:00 run. Metadata now carries only the signal identity plus a coarse bucket, the same damping the budget generators already use; display values live in facts. Also orders the idle-cash pick by balance so the selected accounts don't flip between runs, and documents the dollar-scale threshold assumption on the relevant constants. * fix(ds): map DS::Button aria_label into the aria hash A bare aria_label: option reaches the tag helpers as a literal aria_label attribute (Rails only dasherizes the nested aria: hash), so the icon-only fallback overrode it and screen readers announced the insight dismiss button and the popover trigger as "X". * fix(insights): gate LLM narration behind AI consent The nightly job runs unprompted for every family, so narration now requires someone in the family to have AI enabled: consent to share financial facts with the provider, and a cost cap in managed mode. The template fallback keeps behavior identical otherwise. Narration failures are captured via DebugLogEntry so support can see them. Adds BodyWriter coverage, including a template-interpolation test for every generator template key. * feat(insights): rework the dashboard feed The feed rendered full insight cards inside the section shell — the only widget nesting card-on-card — and appended below the fold for every family with a saved section order. It now mirrors the outflows and balance-sheet list idiom (inset well with an uppercase mini-header, white row block, 28px sentiment-tinted icon circles via color-mix on the DS CSS variables, right-aligned key figures) and leads the dashboard for saved orders that predate it. Icon color comes from sentiment, not priority — a savings-rate improvement is high priority AND good news, and must not render red; red is reserved for a projected-negative balance. Rows have no hover wash (cursor plus a gentle icon scale, like the sibling widgets), links to /insights disable Turbo prefetch so the mark-as-read actually fires for mouse users, and the standard widget-size popover offers Half/Full. Full stays the default: the feed is far shorter than any other single-width widget, so a half default leaves a grid hole the masonry cannot backfill. * feat(insights): actionable cards, dismiss undo, live refresh Each row now persists its display facts (new jsonb column) alongside the change-detection metadata. Facts refresh every run without touching the body or user state, which is exactly why they are not part of the material-change comparison. The card gains what the stored data always supported: a type-and-period meta line replacing "x minutes ago", a right-aligned key figure (green only for good news), and a contextual link resolved from the subject ids in metadata — category, account, recurring transaction, budget month — omitted when the subject no longer exists. Dismiss is forgiving: a toast in the notification tray offers undo, and undismissing restores the row as read rather than re-badging it. Milestone insights can never regenerate once dismissed, so this closes a real loss path. Upsert failures are captured via DebugLogEntry. Manual refresh gets feedback: the button swaps to a disabled checking state, the page subscribes to a family-scoped stream, and the job broadcasts the refreshed list and the idle button when it finishes (also after a lock-skipped run, so the button cannot stay stuck). Savings copy is sign-aware: a negative rate reads "you spent more than you earned" with true minus signs. The empty state swaps Lucide sparkles for the brand assistant glyph (DS::EmptyState learns icon_custom:). * feat(insights): top-bar entry with unread count The dashboard feed hides at zero insights and nothing else linked to /insights, so the manual refresh (and the empty state) were unreachable for exactly the families who need them: fresh setups before the first 6:00 run. The sticky top bar travels to every screen and its right cluster had room, so insights get an icon entry there with a monochrome unread badge. Prefetch is disabled on the link for the same mark-as-read reason as the feed. --------- Signed-off-by: Juan José Mata <juanjo.mata@gmail.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Guillem Arias <accounts@gariasf.com> |
||
|
|
655895341d |
feat(imports): verify Sure NDJSON import readback (#1869)
* feat(imports): verify Sure NDJSON readback * fix(imports): tighten Sure readback verification * fix(imports): polish Sure verification review nits |
||
|
|
e59235fdc5 |
feat(statements): add account statement vault (#1753)
* feat(statements): add account statement vault Add web-only statement uploads, account linking, duplicate detection, and per-account coverage/reconciliation checks without mutating transactions. Extend ActiveStorage authorization and targeted tests for family/account scoping. * fix(statements): return deleted account statements to inbox Preserve linked statement records when an account is deleted by moving them back to the unmatched inbox, then expand coverage for upload validation, sanitized parser metadata, unavailable reconciliation, and missing-month coverage. * fix(statements): harden vault upload review flows Address review and security findings in the statement vault by preserving sanitized parser metadata, failing closed on orphaned statement blobs, avoiding account_id mass assignment permits, and adding regression coverage for link/delete edge cases. * fix(statements): harden vault upload and access controls * fix(statements): address vault hardening review * fix(statements): address vault review feedback Prioritize SHA-256 duplicate detection while preserving MD5 fallback for legacy rows. Remove free-form account notes from statement matching, document direct account-destroy unlinking, and add year-selectable historical coverage with muted out-of-range months. * fix(statements): harden vault review follow-ups Clarify legacy MD5 checksum use, whitelist statement balance helper dispatch, and preserve sanitized parser metadata. Hide statement management controls from read-only viewers while keeping server-side authorization unchanged. * fix(statements): repair settings system coverage Allow the changelog provider lookup in the self-hosting settings system test, include Statement Vault in settings navigation coverage, and align the feature title casing. Update the devcontainer so ActiveStorage and parallel system tests can run in the documented environment. * fix(statements): move vault beside accounts Place Statement Vault with account settings instead of between Imports and Exports. Keep settings footer ordering and system navigation coverage aligned, including the non-admin visibility guard. * fix(statements): address vault review cleanup Resolve CodeRabbit review feedback for statement upload validation, duplicate race handling, account statement matching semantics, metadata detection, ActiveStorage authorization tests, and small UI/style cleanups. * fix(statements): address vault cleanup review * fix(statements): deduplicate vault style helpers * fix(statements): close vault review follow-ups * fix(statements): refresh schema after upstream rebase * fix(statements): process vault uploads sequentially * fix(statements): close vault review follow-ups * fix(statements): scope vault index to accessible accounts * fix(statements): harden statement vault readiness Squash the statement vault migration hardening into the feature migration, tighten Active Storage authorization edge cases, bound CSV metadata detection, and add real PDF fixture coverage for stored statements. Validation: targeted statement/auth/controller/provider tests, full Rails suite, system tests, RuboCop, Biome, Brakeman, Zeitwerk, importmap audit, npm audit, ERB lint, CodeRabbit, and Codex Security all passed locally. * fix(statements): close vault review follow-ups Move statement unlinking to after account destroy commit, keep Kraken account creation on the shared crypto helper, and add statement metadata length limits with DB checks. Validation: fresh devcontainer with fresh DB via db:prepare, focused account/statement/Kraken/Binance tests, RuboCop, Brakeman, Zeitwerk, git diff --check, CodeRabbit, and Codex Security passed before commit. * fix(statements): address vault scan follow-ups Move statement tab data setup out of the ERB partial, harden reconciliation labels and coverage initialization, and tighten statement schema constraints. Validation: CodeRabbit and Codex Security reviewed the current PR diff; Rails focused tests, full Rails tests, system tests, RuboCop, Brakeman, Zeitwerk, ERB lint, npm lint, importmap audit, npm audit, and git diff --check passed. * fix(statements): defer vault tab loading --------- Signed-off-by: Juan José Mata <juanjo.mata@gmail.com> Co-authored-by: Juan José Mata <juanjo.mata@gmail.com> |
||
|
|
95f6451b39 |
feat(sync): add Brex provider connections (#1752)
* feat(sync): add Brex provider schema Adds Brex item and account tables with per-family credentials, scoped upstream account uniqueness, encrypted token storage, and sanitized provider payload columns. * feat(sync): add Brex provider core Adds Brex item/account models, provider client and adapter support, family connection helpers, and provider enum registration for read-only Brex cash and card data. * feat(sync): add Brex import pipeline Adds Brex account discovery, linked-account sync, cash/card balance processors, transaction import, sanitized metadata handling, and idempotent provider entry processing. * feat(sync): add Brex connection flows Adds Mercury-style Brex connection management, explicit item-scoped account selection and linking, settings provider UI, account index visibility, localized copy, and per-item cache handling. * test(sync): cover Brex provider workflows Adds targeted coverage for Brex provider requests, adapter config, item/account guards, importer behavior, entry processing, and Mercury-style controller flows. * fix(sync): align Brex API edge cases Tightens Brex account fetching against the official card-account response shape, sends transaction start filters as RFC3339 date-times, and keeps provider error bodies out of user-facing messages while expanding provider client guard coverage. * fix(sync): harden Brex provider integration Restrict Brex API base URLs to official hosts, tighten account-selection UI behavior, and add tests for invalid credentials, cache scoping, and provider setup edge cases. * test(sync): avoid Brex secret-shaped fixtures * refactor(sync): extract Brex account flows * fix(sync): address Brex provider review feedback * fix(sync): address Brex review follow-ups Move remaining Brex review cleanup into focused model behavior, tighten link/setup edge cases, localize summaries, and add regression coverage from CodeRabbit feedback. Also records the security-review pass as no-findings after diff-scoped inspection and Brakeman validation. * refactor(sync): split Brex account flow controllers Route Brex account selection and setup actions through small namespaced controllers while keeping existing URLs and helpers stable. Business flow remains in BrexItem::AccountFlow; the main Brex item controller now only handles connection CRUD, provider-panel rendering, destroy, and sync. * fix(sync): address Brex CodeRabbit review * fix(sync): address Brex follow-up review * fix(sync): address Brex review follow-ups * fix(sync): address Brex sync review findings * fix(sync): polish Brex review copy and errors * fix(sync): register Brex provider health * fix(sync): polish Brex bank sync presentation * fix(sync): address Brex review follow-ups * fix(sync): tighten Brex setup params * test(api): stabilize usage rate-limit window * fix(sync): polish Brex setup flow nits * fix(sync): harden Brex setup params * fix(sync): finalize Brex review cleanup --------- Signed-off-by: Juan José Mata <juanjo.mata@gmail.com> Co-authored-by: Juan José Mata <juanjo.mata@gmail.com> |
||
|
|
1646abb938 |
Fix SSO icon rendering for mixed-case provider icons (#1674)
* fix(sso): normalize icon names and fail gracefully * fix(sso): fall back to key icon --------- Co-authored-by: SureBot <sure-bot@we-promise.com> |
||
|
|
0a96bf199d |
SimpleFIN: setup UX + same-provider relink + card-replacement detection (#1493)
* SimpleFIN: setup UX + same-provider relink + card-replacement detection Fixes three bugs and adds auto-detection for credit-card fraud replacement. Bugs: - Importer: per-institution auth errors no longer flip the whole item to requires_update. Partial errors stay on sync_stats so other institutions keep syncing. - Setup page: new activity badges (recent / dormant / empty / likely-closed) via SimplefinAccount::ActivitySummary. Likely-closed (dormant + near-zero balance + prior history) defaults to "skip" in the type picker. - Relink: link_existing_account allows SimpleFIN to SimpleFIN swaps by atomically detaching the old AccountProvider inside a transaction. Adds "Change SimpleFIN account" menu item on linked-account dropdowns. Feature (credit-card scope only): - SimplefinItem::ReplacementDetector runs post-sync. Pairs a linked dormant zero-balance sfa with an unlinked active sfa at the same institution and account type. Persists suggestions on Sync#sync_stats. - Inline banner on the SimpleFIN item card prompts relink via CustomConfirm. Per-pair dismiss button scoped to the current sync (resurfaces on next sync if still applicable). Auto-suppresses once the relink has landed. Dev tooling: - bin/rails simplefin:seed_fraud_scenario[email] creates a realistic broken pair for manual QA; cleanup_fraud_scenario reverses it. * Address review feedback on #1493 - ReplacementDetector: symmetric one-to-one matching. Two dormant cards pointing at the same active card are now both skipped — previously the detector could emit two suggestions that would clobber each other if the user accepted both. - ReplacementDetector: require non-blank institution names on both sides before matching. Blank-vs-blank was accidentally treated as equal, risking cross-provider false matches when SimpleFIN omitted org_data. - ActivitySummary: fall back to "posted" when "transacted_at" is 0 (SimpleFIN's "unknown" sentinel). Integer 0 is truthy in Ruby, so the previous `|| fallback` short-circuited and ignored posted. - Controller: dismiss key is now the (dormant, active) pair so dismissing one candidate for a dormant card doesn't suppress others. - Helper test: freeze time around "6.hours.ago" and "5.days.ago" assertions so they don't flake when the suite runs before 06:00. * Address second review pass on #1493 - ReplacementDetector: canonicalize account_type in one place so filtering (supported_type?) and matching (type_matches?) agree on "credit card" vs "credit_card" variants. - ReplacementDetector: skip candidates with nil current_balance. nil is "unknown," not "zero" — previously fell back to 0 and passed the near- zero gate, allowing suggestions without balance evidence. |
||
|
|
aacbb5ef3b |
Budget page refactor: split into(All - Over Budget - On Track) (#1195)
* Optimize UI in budget * update locales * Optimize UI * optimize suggested_daily_spending * try over_budget and on_track * update locale * optimize * add budgets_helper.rb * fix * hide no buget and no expense sub-catogory * Optimize * Optimize button on phone * Fix Pipelock CI noise * using section to render both overbudget and onTrack * hide last ruler * fix * update test --------- Co-authored-by: Juan José Mata <juanjo.mata@gmail.com> |
||
|
|
60929cdee0 |
feat: add currency management for families with enabled currencies (#1419)
* feat: add currency management for families with enabled currencies * feat: update currency selection logic and improve accessibility * feat: update currency preferences to use group moniker in titles --------- Signed-off-by: Ang Wei Feng (Ted) <hello@tedawf.com> Signed-off-by: Juan José Mata <juanjo.mata@gmail.com> Co-authored-by: Juan José Mata <juanjo.mata@gmail.com> |
||
|
|
e8e100e1d8 |
Rework account views and addition flow (#1324)
* Move accountable partials * Split accountables into separate view partials * Fix test * Add form to permitted partials * Fix failing system tests * Update new account modal views * New sync algorithm implementation * Update account system test assertions to match new behavior * Fix off by 1 date error * Revert new balance sync algorithm * Add missing account overviews |
||
|
|
82c298307d |
Add formatting for EUR locales (#1231)
* Add formatting for EUR locales * Fix formatting assertion for EUR in english locales |
||
|
|
bbcd3881db |
Fix #910 (#917)
* Fix #910 * Unify helper for balance formatting in transactions and accounts views * Remove obsolete method * Rename helper method format_amount_by_curreny => totals_by_currency |
||
|
|
4099743107 | Addd application_helper_test.rb (#302) | ||
|
|
99de24ac70 | Initial commit |